Profile img

Hi, I'm who's behind Fedify, Hollo, BotKit, and this website, Hackers' Pub! My main account is at @hongminhee洪 民憙 (Hong Minhee) :nonbinary:.

Fedify, Hollo, BotKit, 그리고 보고 계신 이 사이트 Hackers' Pub을 만들고 있습니다. 제 메인 계정은: @hongminhee洪 民憙 (Hong Minhee) :nonbinary:.

FedifyHolloBotKit、そしてこのサイト、Hackers' Pubを作っています。私のメインアカウントは「@hongminhee洪 民憙 (Hong Minhee) :nonbinary:」に。

Website
hongminhee.org
GitHub
@dahlia
Hollo
@hongminhee@hollo.social
DEV
@hongminhee
velog
@hongminhee
Qiita
@hongminhee
Zenn
@hongminhee
Matrix
@hongminhee:matrix.org
X
@hongminhee
1
1

합주실 다음 플랜:

  • CO2, VOC 미터 설치: 밀폐공간이라 좀 쓰다보면 이산화탄소 수치가 올라가는데 정작 이용자들은 잘 모르고 머리가 띵한 느낌만 받기 쉬움. 수치를 실시간으로 화면에 띄워주거나 1000ppm 이상 올라가면 경고 표시하도록
  • 위 수치 보면서 공기청정기 갖다놓을지 좀 고민해볼듯.
2
2

I've been using this exact distinction for a while now. Since Korean, my native language, has distinct terms for the system (런타임), the point in time (實行時(실행 시)), and the duration (實行時間(실행 시간)), using a single spelling for all three in English always felt a bit blurry to me. This spelling convention helps bridge that gap and makes technical writing much more precise.

Personally, I find the meaning as indicated by Google's style guide the most clear, combined with an explicit case for hyphenation:

Runtime: use the system meaning. E.g. “the runtime was updated last week,” or “I'm using version 21 of the Java runtime.”

Run-time: use the moment meaning, but only when used in the adjective position. E.g. “run-time instrumentation is useful for finding bugs.”

Run time: use the duration meaning. E.g. “the run time was reduced by 5%,” or “a run time of five minutes is unacceptable.” In addition, when you want to use the moment meaning, but not as an adjective, this form should also be used. E.g. “typechecking happens at run time in our implementation.”

My Opinion on Run Time vs. Run-time vs. Runtime (by Bob Rubbens)

0

회사에선 AI를 쓰기가 왜 이렇게 싫은가? 곰곰이 생각해 봤는데 내 머릿속에 짜야 할 코드가 80%쯤 그려져 있는 상태에서 나 대충 이런 거 만들 건데 키보드 두드리기 귀찮으니까 네가 좀 짜줘 하는 거랑 아직 내 머릿속에도 코드가 30%쯤밖에 없는데 내가 뭘 해야 할지 나도 잘 모르겠지만 일단 네가 시작해봐 하는 거랑은 체감이 다른듯. 플러터도 몇 년 써서 익숙해지고 나면 아 귀찮아 AI가 대신 두드려주면 좋겠어 하게 될까.

2

회사에선 AI를 쓰기가 왜 이렇게 싫은가? 곰곰이 생각해 봤는데 내 머릿속에 짜야 할 코드가 80%쯤 그려져 있는 상태에서 나 대충 이런 거 만들 건데 키보드 두드리기 귀찮으니까 네가 좀 짜줘 하는 거랑 아직 내 머릿속에도 코드가 30%쯤밖에 없는데 내가 뭘 해야 할지 나도 잘 모르겠지만 일단 네가 시작해봐 하는 거랑은 체감이 다른듯. 플러터도 몇 년 써서 익숙해지고 나면 아 귀찮아 AI가 대신 두드려주면 좋겠어 하게 될까.

4

(わかる人にはわかると思うけど)最近は、機能追加や修正において、コードを直接いじるよりも、修正計画をしっかり書いてからコードエージェントに任せることが増えている。

手戻りを減らすためにも、修正計画はできるだけ具体的に書くことを意識している。テスト対応まで含めて指示することも多い。

もちろん、最終的なコードの検証とテストは自分で行う。

2
5

iPadOS와 macOS 26 한동안 써봤는데 Liquid Glass는 그냥 실패작이 맞는 것 같다.

  • 👍 신기하긴 하다.
  • 👎 가독성 재앙. 결국 애플도 이걸 인정했는지 버전업할수록 효과를 죽이는 선택지를 넣기 시작함
  • 👎 그래서인지 어느정도 쓰다보면 딱히 신경쓰이지도 않음. 결국 GPU 연산력만 낭비함
  • 👎 릴리즈된지 한잠 지났지만 도입된 어플리케이션도 손에 꼽을 정도로 적음. 특히 내 macOS 환경에서는 기본앱 말고 도입한 앱은 단 하나도 못 본듯
  • 👎 본질적으로 이게 사용자 경험에 무슨 가치를 더하는지 모르겠음. 그냥 전형적인 form over function.
1

iPadOS와 macOS 26 한동안 써봤는데 Liquid Glass는 그냥 실패작이 맞는 것 같다.

  • 👍 신기하긴 하다.
  • 👎 가독성 재앙. 결국 애플도 이걸 인정했는지 버전업할수록 효과를 죽이는 선택지를 넣기 시작함
  • 👎 그래서인지 어느정도 쓰다보면 딱히 신경쓰이지도 않음. 결국 GPU 연산력만 낭비함
  • 👎 릴리즈된지 한잠 지났지만 도입된 어플리케이션도 손에 꼽을 정도로 적음. 특히 내 macOS 환경에서는 기본앱 말고 도입한 앱은 단 하나도 못 본듯
  • 👎 본질적으로 이게 사용자 경험에 무슨 가치를 더하는지 모르겠음. 그냥 전형적인 form over function.
2
1
2
6
0
0

I've spent a long time asking myself why open source matters so much to me, why I keep coming back to it. I once joined a company purely because they promised I could do open source full-time (it didn't turn out well). Before that, I was doing open source inside and outside of regular jobs. And now, in the age of LLMs, when the value of code itself seems to be declining, I'm still here, still doing this.

Recently it clicked. I do open source because it's social work—in the sense that it lets me participate in society.

Everyone wants to belong to some community, to connect with others. But I was never good at the usual ways of doing that. Social activities that came naturally to others were difficult for me. In school, I had few friends. After class, I'd stay home assembling Lego or reading books alone. Then I discovered coding.

Coding was a wonderful hobby for me, especially because I encountered it at the dawn of the internet era. The first programming languages I properly learned were Perl, PHP, and JavaScript—all languages of the internet age. The synergy was something else.

Gradually I fell into the world of open source. And there, even someone like me—awkward at conventional social interaction—could be social. My code helped people. I could collaborate by exchanging code. I could have conversations, mediated by code. IRC, mailing lists, forums—these became my social media. Over time, “the group I wanted recognition from” became the people in the open source world. I didn't care much about being recognized by classmates, but I wanted to be recognized by these people I'd never met face to face.

That mindset still shapes me now, approaching forty. I still care more about recognition from open source programmers than from colleagues. The social activity that happens in open source communities is, after my family, the most important social activity in my life.

The specific things I build, the technical details—those matter less than I used to think. I just want to do the kind of social activity that suits me, and open source happens to be the way I do it.

That's all, really.

6
8
0
1
1
2

Hongdown 0.2.0 is out! Hongdown is an opinionated formatter written in , and this release brings support, so you can now use it as a library in .js, , , and browsers.

New features:

  • Smart heading sentence case conversion with ~450 built-in proper nouns
  • SmartyPants-style typographic punctuation ("straight"“curly”)
  • External code formatter integration for code blocks
  • Directory argument support for batch formatting

Try it in the browser: https://dahlia.github.io/hongdown/

Release notes: https://github.com/dahlia/hongdown/discussions/10

1
8
2
3

@jnkrtech That's a fair characterization. The factory function in derive() is essentially a monadic bind—it takes the parsed dependency value and returns a new parser. We tried to keep the API feeling applicative on the surface (you still compose parsers declaratively), but the underlying mechanism is indeed monadic when dependencies are involved.

1

jnkrtech replied to the below article:

Your CLI's completion should know what options you've already typed

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

Consider Git's -C option:

git -C /path/to/repo checkout <TAB>

When you hit Tab, Git completes branch names from /path/to/repo, not your current directory. The completion is context-aware—it depends on the value of another option.

Most CLI parsers can't do this. They treat each option in isolation, so completion for --branch has no way of knowing the --repo value. You end up with two unpleasant choices: either show completions for all possible branches across all repositories (useless), or give up on completion entirely for these options.

Optique 0.10.0 introduces a dependency system that solves this problem while preserving full type safety.

Static dependencies with or()

Optique already handles certain kinds of dependent options via the or() combinator:

import { flag, object, option, or, string } from "@optique/core";

const outputOptions = or(
  object({
    json: flag("--json"),
    pretty: flag("--pretty"),
  }),
  object({
    csv: flag("--csv"),
    delimiter: option("--delimiter", string()),
  }),
);

TypeScript knows that if json is true, you'll have a pretty field, and if csv is true, you'll have a delimiter field. The parser enforces this at runtime, and shell completion will suggest --pretty only when --json is present.

This works well when the valid combinations are known at definition time. But it can't handle cases where valid values depend on runtime input—like branch names that vary by repository.

Runtime dependencies

Common scenarios include:

  • A deployment CLI where --environment affects which services are available
  • A database tool where --connection affects which tables can be completed
  • A cloud CLI where --project affects which resources are shown

In each case, you can't know the valid values until you know what the user typed for the dependency option. Optique 0.10.0 introduces dependency() and derive() to handle exactly this.

The dependency system

The core idea is simple: mark one option as a dependency source, then create derived parsers that use its value.

import {
  choice,
  dependency,
  message,
  object,
  option,
  string,
} from "@optique/core";

function getRefsFromRepo(repoPath: string): string[] {
  // In real code, this would read from the Git repository
  return ["main", "develop", "feature/login"];
}

// Mark as a dependency source
const repoParser = dependency(string());

// Create a derived parser
const refParser = repoParser.derive({
  metavar: "REF",
  factory: (repoPath) => {
    const refs = getRefsFromRepo(repoPath);
    return choice(refs);
  },
  defaultValue: () => ".",
});

const parser = object({
  repo: option("--repo", repoParser, {
    description: message`Path to the repository`,
  }),
  ref: option("--ref", refParser, {
    description: message`Git reference`,
  }),
});

The factory function is where the dependency gets resolved. It receives the actual value the user provided for --repo and returns a parser that validates against refs from that specific repository.

Under the hood, Optique uses a three-phase parsing strategy:

  1. Parse all options in a first pass, collecting dependency values
  2. Call factory functions with the collected values to create concrete parsers
  3. Re-parse derived options using those dynamically created parsers

This means both validation and completion work correctly—if the user has already typed --repo /some/path, the --ref completion will show refs from that path.

Repository-aware completion with @optique/git

The @optique/git package provides async value parsers that read from Git repositories. Combined with the dependency system, you can build CLIs with repository-aware completion:

import {
  command,
  dependency,
  message,
  object,
  option,
  string,
} from "@optique/core";
import { gitBranch } from "@optique/git";

const repoParser = dependency(string());

const branchParser = repoParser.deriveAsync({
  metavar: "BRANCH",
  factory: (repoPath) => gitBranch({ dir: repoPath }),
  defaultValue: () => ".",
});

const checkout = command(
  "checkout",
  object({
    repo: option("--repo", repoParser, {
      description: message`Path to the repository`,
    }),
    branch: option("--branch", branchParser, {
      description: message`Branch to checkout`,
    }),
  }),
);

Now when you type my-cli checkout --repo /path/to/project --branch <TAB>, the completion will show branches from /path/to/project. The defaultValue of "." means that if --repo isn't specified, it falls back to the current directory.

Multiple dependencies

Sometimes a parser needs values from multiple options. The deriveFrom() function handles this:

import {
  choice,
  dependency,
  deriveFrom,
  message,
  object,
  option,
} from "@optique/core";

function getAvailableServices(env: string, region: string): string[] {
  return [`${env}-api-${region}`, `${env}-web-${region}`];
}

const envParser = dependency(choice(["dev", "staging", "prod"] as const));
const regionParser = dependency(choice(["us-east", "eu-west"] as const));

const serviceParser = deriveFrom({
  dependencies: [envParser, regionParser] as const,
  metavar: "SERVICE",
  factory: (env, region) => {
    const services = getAvailableServices(env, region);
    return choice(services);
  },
  defaultValues: () => ["dev", "us-east"] as const,
});

const parser = object({
  env: option("--env", envParser, {
    description: message`Deployment environment`,
  }),
  region: option("--region", regionParser, {
    description: message`Cloud region`,
  }),
  service: option("--service", serviceParser, {
    description: message`Service to deploy`,
  }),
});

The factory receives values in the same order as the dependency array. If some dependencies aren't provided, Optique uses the defaultValues.

Async support

Real-world dependency resolution often involves I/O—reading from Git repositories, querying APIs, accessing databases. Optique provides async variants for these cases:

import { dependency, string } from "@optique/core";
import { gitBranch } from "@optique/git";

const repoParser = dependency(string());

const branchParser = repoParser.deriveAsync({
  metavar: "BRANCH",
  factory: (repoPath) => gitBranch({ dir: repoPath }),
  defaultValue: () => ".",
});

The @optique/git package uses isomorphic-git under the hood, so gitBranch(), gitTag(), and gitRef() all work in both Node.js and Deno.

There's also deriveSync() for when you need to be explicit about synchronous behavior, and deriveFromAsync() for multiple async dependencies.

Wrapping up

The dependency system lets you build CLIs where options are aware of each other—not just for validation, but for shell completion too. You get type safety throughout: TypeScript knows the relationship between your dependency sources and derived parsers, and invalid combinations are caught at compile time.

This is particularly useful for tools that interact with external systems where the set of valid values isn't known until runtime. Git repositories, cloud providers, databases, container registries—anywhere the completion choices depend on context the user has already provided.

This feature will be available in Optique 0.10.0. To try the pre-release:

deno add jsr:@optique/core@0.10.0-dev.311

Or with npm:

npm install @optique/core@0.10.0-dev.311

See the documentation for more details.

Read more →
5
1

Your CLI's completion should know what options you've already typed

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

Consider Git's -C option:

git -C /path/to/repo checkout <TAB>

When you hit Tab, Git completes branch names from /path/to/repo, not your current directory. The completion is context-aware—it depends on the value of another option.

Most CLI parsers can't do this. They treat each option in isolation, so completion for --branch has no way of knowing the --repo value. You end up with two unpleasant choices: either show completions for all possible branches across all repositories (useless), or give up on completion entirely for these options.

Optique 0.10.0 introduces a dependency system that solves this problem while preserving full type safety.

Static dependencies with or()

Optique already handles certain kinds of dependent options via the or() combinator:

import { flag, object, option, or, string } from "@optique/core";

const outputOptions = or(
  object({
    json: flag("--json"),
    pretty: flag("--pretty"),
  }),
  object({
    csv: flag("--csv"),
    delimiter: option("--delimiter", string()),
  }),
);

TypeScript knows that if json is true, you'll have a pretty field, and if csv is true, you'll have a delimiter field. The parser enforces this at runtime, and shell completion will suggest --pretty only when --json is present.

This works well when the valid combinations are known at definition time. But it can't handle cases where valid values depend on runtime input—like branch names that vary by repository.

Runtime dependencies

Common scenarios include:

  • A deployment CLI where --environment affects which services are available
  • A database tool where --connection affects which tables can be completed
  • A cloud CLI where --project affects which resources are shown

In each case, you can't know the valid values until you know what the user typed for the dependency option. Optique 0.10.0 introduces dependency() and derive() to handle exactly this.

The dependency system

The core idea is simple: mark one option as a dependency source, then create derived parsers that use its value.

import {
  choice,
  dependency,
  message,
  object,
  option,
  string,
} from "@optique/core";

function getRefsFromRepo(repoPath: string): string[] {
  // In real code, this would read from the Git repository
  return ["main", "develop", "feature/login"];
}

// Mark as a dependency source
const repoParser = dependency(string());

// Create a derived parser
const refParser = repoParser.derive({
  metavar: "REF",
  factory: (repoPath) => {
    const refs = getRefsFromRepo(repoPath);
    return choice(refs);
  },
  defaultValue: () => ".",
});

const parser = object({
  repo: option("--repo", repoParser, {
    description: message`Path to the repository`,
  }),
  ref: option("--ref", refParser, {
    description: message`Git reference`,
  }),
});

The factory function is where the dependency gets resolved. It receives the actual value the user provided for --repo and returns a parser that validates against refs from that specific repository.

Under the hood, Optique uses a three-phase parsing strategy:

  1. Parse all options in a first pass, collecting dependency values
  2. Call factory functions with the collected values to create concrete parsers
  3. Re-parse derived options using those dynamically created parsers

This means both validation and completion work correctly—if the user has already typed --repo /some/path, the --ref completion will show refs from that path.

Repository-aware completion with @optique/git

The @optique/git package provides async value parsers that read from Git repositories. Combined with the dependency system, you can build CLIs with repository-aware completion:

import {
  command,
  dependency,
  message,
  object,
  option,
  string,
} from "@optique/core";
import { gitBranch } from "@optique/git";

const repoParser = dependency(string());

const branchParser = repoParser.deriveAsync({
  metavar: "BRANCH",
  factory: (repoPath) => gitBranch({ dir: repoPath }),
  defaultValue: () => ".",
});

const checkout = command(
  "checkout",
  object({
    repo: option("--repo", repoParser, {
      description: message`Path to the repository`,
    }),
    branch: option("--branch", branchParser, {
      description: message`Branch to checkout`,
    }),
  }),
);

Now when you type my-cli checkout --repo /path/to/project --branch <TAB>, the completion will show branches from /path/to/project. The defaultValue of "." means that if --repo isn't specified, it falls back to the current directory.

Multiple dependencies

Sometimes a parser needs values from multiple options. The deriveFrom() function handles this:

import {
  choice,
  dependency,
  deriveFrom,
  message,
  object,
  option,
} from "@optique/core";

function getAvailableServices(env: string, region: string): string[] {
  return [`${env}-api-${region}`, `${env}-web-${region}`];
}

const envParser = dependency(choice(["dev", "staging", "prod"] as const));
const regionParser = dependency(choice(["us-east", "eu-west"] as const));

const serviceParser = deriveFrom({
  dependencies: [envParser, regionParser] as const,
  metavar: "SERVICE",
  factory: (env, region) => {
    const services = getAvailableServices(env, region);
    return choice(services);
  },
  defaultValues: () => ["dev", "us-east"] as const,
});

const parser = object({
  env: option("--env", envParser, {
    description: message`Deployment environment`,
  }),
  region: option("--region", regionParser, {
    description: message`Cloud region`,
  }),
  service: option("--service", serviceParser, {
    description: message`Service to deploy`,
  }),
});

The factory receives values in the same order as the dependency array. If some dependencies aren't provided, Optique uses the defaultValues.

Async support

Real-world dependency resolution often involves I/O—reading from Git repositories, querying APIs, accessing databases. Optique provides async variants for these cases:

import { dependency, string } from "@optique/core";
import { gitBranch } from "@optique/git";

const repoParser = dependency(string());

const branchParser = repoParser.deriveAsync({
  metavar: "BRANCH",
  factory: (repoPath) => gitBranch({ dir: repoPath }),
  defaultValue: () => ".",
});

The @optique/git package uses isomorphic-git under the hood, so gitBranch(), gitTag(), and gitRef() all work in both Node.js and Deno.

There's also deriveSync() for when you need to be explicit about synchronous behavior, and deriveFromAsync() for multiple async dependencies.

Wrapping up

The dependency system lets you build CLIs where options are aware of each other—not just for validation, but for shell completion too. You get type safety throughout: TypeScript knows the relationship between your dependency sources and derived parsers, and invalid combinations are caught at compile time.

This is particularly useful for tools that interact with external systems where the set of valid values isn't known until runtime. Git repositories, cloud providers, databases, container registries—anywhere the completion choices depend on context the user has already provided.

This feature will be available in Optique 0.10.0. To try the pre-release:

deno add jsr:@optique/core@0.10.0-dev.311

Or with npm:

npm install @optique/core@0.10.0-dev.311

See the documentation for more details.

Read more →
5

mise가 cargo backend도 지원하므로 아래 같이 릴리스 전에 미리 설치할 수 있다

# mise use cargo:https://github.com/dahlia/hongdown@rev:<sha>
mise use cargo:https://github.com/dahlia/hongdown@rev:c79718132e7fdfe667f602ce4123e3bb6a80e6b6
2

클로드 코드 스킬 잘쓰고싶은데 정말 모르겠다 ㅠㅠ…

  1. 클로드 코드 스킬이라는게 사실 agent랑 한 끗 차이라고 생각하기는 하는데 agent는 실행되면 눈에 보이는것과 달리 보이지도 않는다.
  2. 잘 발동도 안하는것 같길래, 무슨 히어로물에서 기술명 외치듯이 쓰다가...
  3. 이 방법 비슷하게도 사용해보고 있는데 잘안되는것 같기도하다.
  4. 클로드 코드 공식 문서에서 알게된 사실인데 커맨드 처럼 사용할 수 있기도해서 클로드 코드도 헷갈려하는 느낌이기도…

일단 심기 일전해서 description을 다시 재정비해봐야겠음.

2

클로드 코드 스킬 잘쓰고싶은데 정말 모르겠다 ㅠㅠ…

  1. 클로드 코드 스킬이라는게 사실 agent랑 한 끗 차이라고 생각하기는 하는데 agent는 실행되면 눈에 보이는것과 달리 보이지도 않는다.
  2. 잘 발동도 안하는것 같길래, 무슨 히어로물에서 기술명 외치듯이 쓰다가...
  3. 이 방법 비슷하게도 사용해보고 있는데 잘안되는것 같기도하다.
  4. 클로드 코드 공식 문서에서 알게된 사실인데 커맨드 처럼 사용할 수 있기도해서 클로드 코드도 헷갈려하는 느낌이기도…

일단 심기 일전해서 description을 다시 재정비해봐야겠음.

2

I've been working on a tricky problem in Optique (my CLI parser library): how do you make one option's value affect another option's validation and shell completion?

Think git -C <path> branch --delete <TAB>—the branch completions should come from the repo at <path>, not the current directory.

I think I've found a solution that fits naturally with Optique's architecture: declare dependencies between value parsers, then topologically sort them at parse time.

const cwdString = dependency(string());

const parser = object({
  cwd: optional(option("-C", cwdString)),
  branches: multiple(argument(
    cwdString.derive({
      metavar: "BRANCH",
      factory: dir => gitBranch({ dir }),
      defaultValue: () => process.cwd(),
    })
  )),
});

Details in the issue:

https://github.com/dahlia/optique/issues/74#issuecomment-3738381049

0

이번 주말+오늘 했던 약간의 야크셰이빙 공유

  1. vscode용 GUI git 확장을 구현하고 있다. (하는중)
  • Claude Code를 모든 팀 멤버가 사용하기로 결정하면서 기획문서도 일단은 git으로 관리하고 있는데 꽤나 재밌게 일하고 있다. 그런데 프로그래머가 아닌 멤버에게 vscode를 설치해서 마크다운 작성과 Claude Code 클라이언트를 사용 유도했던 것은 괜찮은 접근일 수 있었으나, 결국 좋은 git GUI 플러그인들은 돈주고 쓰긴해야해서 고민이 되었다.
  • 요즘 Remote desktop에 연결해서 주로 일을 하고 있는데, git kraken 같은 기존 강자(?)들도 remote에 ssh로 접속해서 하는등의 기능을 제공하지 않고 있다. workaround로 sshfs를 쓸 수 있으나 그 경우 git worktree를 사용하지 못하게됨.
  • 건너편 자리 동료가 Intelij에선 다되고 GUI로 하는게 CLI보다 빠르면서 실수도 적지 않느냐라는 얘기를 하면서 놀리는데, 어느정도는 맞는 얘기라고 생각하기도 한다.
  • 그래서 만들고 있다(!) 일단 맨날 쓰는 커맨드 위주로 만들고 있고 가장 중요한건 interactive rebase나 interactive add, split commit 같이 GUI에서 더 잘할 수 있는 일들까지 만드는게 목표.
  1. vscode로 kotlin +Spring 프로젝트 돌리다가, Kotlin 2.3.0 지원이 안되서 Language Runtime Server에 지원하도록 했다. (PR은 안 만들듯..)
  • https://github.com/fwcd/kotlin-language-server 은 꽤 오래부터 있던 라이브러리인데, 매번 vscode에서 이거 사용해가지고 kotlin + spring 서버 돌리려니까 실패를 했었다.
  • 오늘 Claude Code랑 같이 도전했더니 거의 성공했는데, kotlin-language-server가 kotlin 2.1.0을 지원하고있고, 우리 서비스는 2.3.0이라서 문제가 생긴다는 것을 발견했다.
  • 그래서 그냥 간단하게 2.3.0만 지원하도록 하려고했는데, java 버전도 25로 올라갔으므로 기존 19버전에서 25버전으로 같이 올렸다.
  • 별 패치는 없었지만 일단 잘 돌아간다.
  • 너무 큰 버전업이라서 올리기 어려운것도 있지만, JetBrain에서 드디어 공식 라이브러리를 만들고 있는 중이므로 잠깐 버티는 용도로만 써야겠다. https://github.com/Kotlin/kotlin-lsp
  • 참고로 우리 서비스는 gradle 멀티모듈을 사용하는데 이와 관련한 기능이 kotlin-lsp에서 지원되지 않기 때문에 사용할 수 가 없었다.
1
2
2

나는 CLI툴이 MCP보다 LLM에게 나은 도구라고 생각하는데, CLI 툴은 bash로 조합이 되기 때문이다. 즉 코딩이 가능하다. 디렉토리의 파일들의 각 첫 50줄을 읽는 작업은 ls, head, xargs를 조합해 한반의 호출로 가능하다. 그에 반해 MCP의 Read 툴 같은건호출을 파일 갯수만큼 해야한다.

이는 bash가 충분히 좋은 프로그래밍 언어라던가 MCP에 조합성을 추가할수가 없다는 얘기는 물론 아니다.

나는 CLI툴이 MCP보다 LLM에게 나은 도구라고 생각하는데, CLI 툴은 bash로 조합이 되기 때문이다. 즉 코딩이 가능하다. 디렉토리의 파일들의 각 첫 50줄을 읽는 작업은 ls, head, xargs를 조합해 한반의 호출로 가능하다. 그에 반해 MCP의 Read 툴 같은건호출을 파일 갯수만큼 해야한다.

이는 bash가 충분히 좋은 프로그래밍 언어라던가 MCP에 조합성을 추가할수가 없다는 얘기는 물론 아니다.

1
1
0

지난 회사에서 담당했던 작업 중 하나가 웹뷰와 호스트 앱 (Android, iOS) 간 구조화된 메시징 레이어를 만드는 일이었는데, (그 위에서 RPC를 구현하는 것도 가능) 이걸 오픈 소스로 재구현하는 것도 해볼만할 것 같다.

0
2
2

최근에 Manim-community 들어갔다가 GitHub에 있던 저장소가 Codeberg로 가게 된 걸 알게 되었는데 (지금은 또 GitHub에 돌아와있다) 그때 홈페이지에 How my GitHub Pages got Hacked 라는 글과 함께 해당 사실을 알리고 있었다[1]. 오늘 아마 이와 같은 내용으로 도메인을 뺏기는(?) 현상을 주변에서 봐서 verified domain들을 등록해놓았다.


  1. 당시 아카이브: https://web.archive.org/web/20251229233759/https://www.manim.community/ ↩︎

4

주말동안 Nix Flake의 워크스페이스 flakespace를 만들었는데, 구현이 너무 Hacky해서 솔직히 내가 만들어놓고도 쓸 맘이 안든다. 잡버그 고치느라 시간을 많이 버릴 걱정이든다. 일단 트라이는 해보겠다만.. Nix Flake가 자체적으로 워크스페이스 기능을 제공했으면 좋겠다.

3
9
7

이건 글 올라오기도 전에 그냥 결과물만 봐도 질려 버릴 정도였다. 도대체 본업 따로 있는 개인이 어떤 막강한 힘으로 이런 것을 완성 단계까지 만들어낸 것인지

  • https://dream-house.kr/ 내막을 전혀 모르고 그냥 이용 안내만 읽어도 대충 하는 집이 절대 아니라는 것이 느껴짐
  • https://github.com/segfault87/tasi653b-rs 음량계를 샀는데 윈도용 드라이버밖에 없길래 리눅스 시스템에 통합하기 위해 USB 드라이버 직접 짰다고 함. 야크가 남아나질 않음
  • https://github.com/segfault87/dxe
    • 그냥 디렉토리 트리 구조만 보고 있어도 흉내낼 엄두도 안 남. 전 세계에 과연 이런 합주실 시스템이 존재할까?
    • 하다못해 합주실 이용자들 주차 할인까지 자동화했다고? 여러 명이 하나의 예약으로 한꺼번에 방문한 경우까지 대응되어 있다고?
  • https://x.com/segfault87/status/1998019060592959756 ← ???

그야말로 전형적인 "생각은 해 볼 수 있는데 실현할 엄두는 나지 않는 것들"을 그냥 다 해 버린 케이스가 이렇게 가까운 곳에 있는 것이다.

20년 넘게 리눅스 삽질하던 사람이 작심하고 엔지니어링의 본때를 보여주겠다 하면 이렇게 되는 것인가. 이런 사람들과 같은 세계에 살며 뱁새가 황새 따라가는 것 이것 참 힘듭니다.

4

마스토돈 스타일의 새로운 커뮤 플랫폼, 커뮹! 모바일 앱이 출시되었어요! 베타 테스트에 참여해주신 여러분, 커뮹!에서 활동해 주시는 여러분 모두 응원해주셔서 감사합니다 🐓📲🥰

iOS: apps.apple.com/us/app/commung/
Android: play.google.com/store/apps/det

2
0
1