@rch4com/agent-setup 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/install.mjs CHANGED
@@ -54,14 +54,21 @@ async function runClassic(root, { dryRun, listOnly, setArg, t }) {
54
54
  const { loadItems } = await withDeps(() => import('./lib/catalog.mjs'), t)
55
55
  const { scan, planChanges, apply, assertExclusive } = await withDeps(() => import('./lib/engine.mjs'), t)
56
56
  const { plainLine } = await withDeps(() => import('./lib/tui/progress.mjs'), t)
57
+ const { coverageText } = await withDeps(() => import('./lib/tui/rows.mjs'), t)
57
58
  const items = await loadItems()
58
59
  const states = await scan(root, items)
59
60
  const statusWidth = labelWidth(t, STATUS_KEYS)
60
61
 
61
62
  if (listOnly) {
63
+ // CI가 읽는 경로다 — 화면의 힌트와 같은 정보(커버리지·대상 파일·설치 위치)를
64
+ // 싣는다. 상태·이름만으로는 "내 CLI에서 되나"를 여기서 알 수 없었다.
62
65
  for (const s of states) {
63
66
  const detail = toText(t, s.detail)
64
- console.log(`${pad(t(`status.${s.status}`), statusWidth)} ${s.item.id} — ${scopedLabel(s.item, t)}${detail ? ` (${detail})` : ''}`)
67
+ const extras = [coverageText(s.item, s, t)]
68
+ if (s.item.target) extras.push(t('item.target', { path: s.item.target }))
69
+ if (s.item.scope === 'user') extras.push(t('item.location.user'))
70
+ const tail = extras.filter(Boolean).map((x) => ` · ${x}`).join('')
71
+ console.log(`${pad(t(`status.${s.status}`), statusWidth)} ${s.item.id} — ${scopedLabel(s.item, t)}${tail}${detail ? ` (${detail})` : ''}`)
65
72
  }
66
73
  return
67
74
  }
@@ -118,7 +125,11 @@ async function main() {
118
125
  // 살아남는다. 조용히 되돌아가는 대신 화면이 그 사실을 알리게 한다.
119
126
  const localeForced = isLocaleForced({ flag, env: process.env })
120
127
 
121
- if (root === null) throw new LocalizedError('error.notGitRepo')
128
+ // 도움말은 저장소가 필요 없다. 이 검사를 먼저 하면 `--help`가 Git 저장소
129
+ // 밖에서 "Run this inside a Git repository"로 죽는다 — 사용법을 보려는
130
+ // 사람에게 그 오류를 내밀 이유가 없다.
131
+ const wantsHelp = argv.includes('-h') || argv.includes('--help')
132
+ if (root === null && !wantsHelp) throw new LocalizedError('error.notGitRepo')
122
133
 
123
134
  if (argv[0] === 'bootstrap') {
124
135
  const opts = parseBootstrapArgs(argv.slice(1), t)
@@ -133,7 +144,8 @@ async function main() {
133
144
  if (opts.help) { console.log(updateUsage(t)); return }
134
145
  // 항목 수렴을 위해 스캔이 필요해 의존성 있는 모듈에 닿는다.
135
146
  const { runUpdate } = await withDeps(() => import('./lib/update.mjs'), t)
136
- await runUpdate(root, { ...opts, t })
147
+ const { failed } = await runUpdate(root, { ...opts, t })
148
+ if (failed.length > 0) process.exitCode = 1
137
149
  return
138
150
  }
139
151
 
package/lib/catalog.mjs CHANGED
@@ -3,7 +3,7 @@ import { join, dirname } from 'node:path'
3
3
  import { fileURLToPath, pathToFileURL } from 'node:url'
4
4
  import { execFile } from 'node:child_process'
5
5
  import { promisify } from 'node:util'
6
- import { CLIS, CLI_IDS } from './clis.mjs'
6
+ import { CLIS, CLI_IDS, MCP_CLI_IDS } from './clis.mjs'
7
7
  import { isPluginEnabled, enablePlugin, disablePlugin } from './claude-plugins.mjs'
8
8
  import { repoPath, repoPathStrict } from './context.mjs'
9
9
  import { LocalizedError, msg } from './i18n/index.mjs'
@@ -61,10 +61,22 @@ export function shellQuote(text, platform = process.platform) {
61
61
 
62
62
  const execFileAsync = promisify(execFile)
63
63
 
64
+ // 외부 명령 하나에 주는 시간. npx·git clone이 네트워크에서 멈추면 예전에는
65
+ // 영원히 기다렸다 — Ctrl+C도 항목 경계에서만 보므로 그 항목이 끝나야 멈췄다.
66
+ // 10분이면 gsd(3,500파일)·ECC 같은 큰 설치도 끝나고, 멈춘 명령은 결국 실패로
67
+ // 보고된다. 호출부가 opts.timeout으로 바꿀 수 있다.
68
+ export const EXEC_TIMEOUT_MS = 10 * 60 * 1000
69
+
70
+ // 자식 출력의 상한. execFile 기본값(1MB)을 넘기면 Node가 자식을 **죽이고**
71
+ // "maxBuffer length exceeded"로 실패시킨다 — gsd(3,500파일)·hallmark
72
+ // (references 100개 이상)처럼 파일마다 한 줄씩 찍는 설치가 반쯤 깔린 채
73
+ // 실패로 보고됐다. 64MB면 어떤 설치 로그도 넉넉히 담긴다.
74
+ export const EXEC_MAX_BUFFER = 64 * 1024 * 1024
75
+
64
76
  // 비동기다. 동기 실행은 이벤트 루프를 통째로 막아, npx가 도는 수십 초 동안
65
77
  // 진행 화면을 한 번도 다시 그릴 수 없었다. 반환 형태({ ok, output })는
66
78
  // 그대로라 호출부는 await만 더하면 된다.
67
- export function makeExec(dryRun, log = console.log) {
79
+ export function makeExec(dryRun, log = console.log, t = null) {
68
80
  return async (cmd, args, opts = {}) => {
69
81
  if (dryRun) {
70
82
  log(` [dry-run] ${cmd} ${args.join(' ')}`)
@@ -84,6 +96,8 @@ export function makeExec(dryRun, log = console.log) {
84
96
  // 복원하려면 스폰 직후 우리가 직접 stdin을 닫아야 한다.
85
97
  const p = execFileAsync(file, fileArgs, {
86
98
  encoding: 'utf8',
99
+ timeout: EXEC_TIMEOUT_MS,
100
+ maxBuffer: EXEC_MAX_BUFFER,
87
101
  ...opts,
88
102
  shell,
89
103
  })
@@ -92,6 +106,13 @@ export function makeExec(dryRun, log = console.log) {
92
106
  const { stdout } = await p
93
107
  return { ok: true, output: stdout }
94
108
  } catch (err) {
109
+ // 시간 초과로 죽였으면 그 사실을 말한다 — stderr는 대개 비어 있어
110
+ // 아래 폴백만으로는 "왜 실패했는지"가 사라진다.
111
+ if (err.killed && err.signal) {
112
+ const seconds = Math.round((opts.timeout ?? EXEC_TIMEOUT_MS) / 1000)
113
+ const command = [cmd, ...args].join(' ')
114
+ return { ok: false, output: t ? t('error.commandTimeout', { seconds, command }) : `timed out after ${seconds}s: ${command}` }
115
+ }
95
116
  // spawn 자체가 실패하면(ENOENT 등) err.stderr가 undefined가 아니라
96
117
  // 빈 문자열로 온다 — ??는 빈 문자열을 "값 있음"으로 보고 통과시켜
97
118
  // err.message(바이너리 이름이 담긴 진단 텍스트)를 삼켜 버린다. ||로
@@ -101,11 +122,24 @@ export function makeExec(dryRun, log = console.log) {
101
122
  }
102
123
  }
103
124
 
104
- export function defineMcp({ id, label, server, supports = [...CLI_IDS], unsupported = {}, note, group = null }) {
125
+ // supports 기본값은 프로젝트 MCP 파일이 있는 CLI(MCP_CLI_IDS)다. Antigravity는
126
+ // 그 파일이 없어 빠지고, 사유가 기본으로 붙는다.
127
+ //
128
+ // stdio 서버도 copilot에 그대로 배선한다. Copilot CLI는 루트 .mcp.json(Claude
129
+ // 파일)을 우선 읽고 거기엔 `type: "stdio"`가 쓰이는데, 한때 README는 그 형식이
130
+ // 붙지 않는다고 적었다. 2026-09-05에 Copilot CLI 1.0.82의 app.js를 읽어 확인한
131
+ // 바로는 로컬 서버 판정이 `type === undefined || type === "local" || type ===
132
+ // "stdio"`이고 스키마도 `enum(["local", "stdio"])`라 stdio를 받는다 — 두 파일에
133
+ // 각자 형식으로 써도 어느 쪽이 이기든 붙는다.
134
+ export function defineMcp({ id, label, server, supports = [...MCP_CLI_IDS], unsupported: reasons = {}, note, group = null, verified = null }) {
135
+ const unsupported = { ...reasons }
136
+ for (const cli of CLI_IDS) {
137
+ if (!supports.includes(cli) && !unsupported[cli] && !MCP_CLI_IDS.includes(cli)) unsupported[cli] = msg('item.unsupported.noProjectMcp')
138
+ }
105
139
  assertReasons(id, supports, unsupported)
106
140
  const name = id.replace(/^mcp\./, '')
107
141
  return {
108
- id, category: 'mcp', label, scope: 'project', supports, unsupported, note, group,
142
+ id, category: 'mcp', label, scope: 'project', supports, unsupported, note, group, verified,
109
143
  async detect({ root }) {
110
144
  const present = supports.filter((cli) => CLIS[cli].has(root, name))
111
145
  if (present.length === 0) return { status: 'absent' }
@@ -140,12 +174,12 @@ export function defineMcp({ id, label, server, supports = [...CLI_IDS], unsuppor
140
174
  // 배선은 Claude 한 곳이라도 미배선 "사유"는 CLI마다 다를 수 있다 — 상류가
141
175
  // 자체 경로로 지원하는 CLI에 일괄 "Claude 전용" 사유를 붙이면 거짓 정보가 된다.
142
176
  // 항목이 사유를 넘기면 그 CLI만 덮고, 나머지는 기본 사유로 채운다.
143
- export function definePlugin({ id, label, installId, detectIds, marketplace, note, group = null, exclusive = null, unsupported: reasons = {} }) {
177
+ export function definePlugin({ id, label, installId, detectIds, marketplace, note, group = null, exclusive = null, unsupported: reasons = {}, verified = null }) {
144
178
  const unsupported = Object.fromEntries(
145
179
  CLI_IDS.filter((c) => c !== 'claude').map((c) => [c, reasons[c] ?? msg('item.unsupported.claudePlugin')]),
146
180
  )
147
181
  return {
148
- id, category: 'plugin', label, scope: 'project', supports: ['claude'], unsupported, note, group, exclusive,
182
+ id, category: 'plugin', label, scope: 'project', supports: ['claude'], unsupported, note, group, exclusive, verified,
149
183
  async detect({ root }) {
150
184
  return { status: isPluginEnabled(root, detectIds) ? 'installed' : 'absent' }
151
185
  },
@@ -170,11 +204,11 @@ export function definePlugin({ id, label, installId, detectIds, marketplace, not
170
204
  // 대부분의 스킬 상류가 Claude 경로만 주기 때문이고, 상류가 런타임별 설치를
171
205
  // 주는 항목(skill.gsd)은 목록을 직접 넘긴다 — 넘긴 CLI는 미배선 사유 대상에서
172
206
  // 빠진다.
173
- export function defineSkill({ id, label, scope, detect, install, uninstall, note, group = null, supports = ['claude'], unsupported: reasons = {} }) {
207
+ export function defineSkill({ id, label, scope, detect, install, uninstall, note, group = null, supports = ['claude'], unsupported: reasons = {}, verified = null }) {
174
208
  const unsupported = Object.fromEntries(
175
209
  CLI_IDS.filter((c) => !supports.includes(c)).map((c) => [c, reasons[c] ?? msg('item.unsupported.claudeSkill')]),
176
210
  )
177
- return { id, category: 'skill', label, scope, supports: [...supports], unsupported, note, group, detect, install, uninstall }
211
+ return { id, category: 'skill', label, scope, supports: [...supports], unsupported, note, group, verified, detect, install, uninstall }
178
212
  }
179
213
 
180
214
  // 이 저장소가 공유 스킬 자리로 쓰는 디렉터리. vercel-labs/skills 레지스트리의
@@ -269,11 +303,11 @@ function pruneLock(root, names) {
269
303
  //
270
304
  // exclusive는 같은 상류를 플러그인으로도 넣을 수 있을 때 쓴다. 두 경로가 겹치면
271
305
  // 같은 스킬이 두 번 등록되므로 하나만 고르게 막는다.
272
- export function defineRegistrySkill({ id, label, source, skill, anchor = null, note, group = null, exclusive = null }) {
306
+ export function defineRegistrySkill({ id, label, source, skill, anchor = null, note, group = null, exclusive = null, verified = null }) {
273
307
  const probe = anchor ?? skill
274
308
  if (skill === '*' && !anchor) throw new LocalizedError('error.registrySkillAnchor', { id })
275
309
  return {
276
- id, category: 'skill', label, scope: 'project', supports: [...CLI_IDS], unsupported: {}, note, group, exclusive,
310
+ id, category: 'skill', label, scope: 'project', supports: [...CLI_IDS], unsupported: {}, note, group, exclusive, verified,
277
311
  async detect({ root }) {
278
312
  return { status: findSkillDir(root, probe) ? 'installed' : 'absent' }
279
313
  },
package/lib/clis.mjs CHANGED
@@ -106,6 +106,17 @@ export const CLIS = {
106
106
  ? { type: 'http', url: s.url }
107
107
  : { type: 'stdio', command: s.command, args: s.args }),
108
108
  },
109
+ // Antigravity는 프로젝트 스코프 MCP 파일이 없다 — MCP는 홈 글로벌
110
+ // (~/.gemini/config/mcp_config.json)에서만 설정한다(bootstrap/manifest.mjs
111
+ // 주석·README). 그래서 어댑터(file·has·add·remove)가 없다. 그래도 목록에
112
+ // 두는 이유는 부트스트랩이 지원하는 11개 도구와 화면의 CLI 수가 어긋나면
113
+ // 안 되기 때문이다 — 공유 .agents/skills를 네이티브로 읽으므로 레지스트리
114
+ // 스킬은 실제로 닿고, MCP 항목은 "왜 안 닿는지"를 사유로 밝힌다.
115
+ antigravity: {
116
+ label: 'Antigravity',
117
+ },
109
118
  }
110
119
 
111
120
  export const CLI_IDS = Object.keys(CLIS)
121
+ // 프로젝트 설정 파일에 MCP를 쓸 수 있는 CLI — 어댑터가 있는 것만.
122
+ export const MCP_CLI_IDS = CLI_IDS.filter((id) => typeof CLIS[id].add === 'function')
package/lib/engine.mjs CHANGED
@@ -6,7 +6,9 @@ export async function scan(root, items) {
6
6
  for (const item of items) {
7
7
  try {
8
8
  const r = await item.detect({ root })
9
- states.push({ item, status: r.status, detail: r.detail })
9
+ // excluded는 전역 항목이 "이 머신에 없는 CLI"를 알리는 자리다 — 행 힌트가
10
+ // supports를 나열할 때 그 CLI를 빼고 따로 표시한다.
11
+ states.push({ item, status: r.status, detail: r.detail, excluded: r.excluded ?? [] })
10
12
  } catch (err) {
11
13
  states.push({ item, status: 'absent', detail: msg('item.scanFailed', { message: err.message }) })
12
14
  }
@@ -41,7 +43,7 @@ export function planChanges(states, selectedIds) {
41
43
  }
42
44
 
43
45
  export async function apply(root, changes, { dryRun = false, log = console.log, t = createT('en'), onProgress = null, shouldStop = null } = {}) {
44
- const baseExec = makeExec(dryRun, log)
46
+ const baseExec = makeExec(dryRun, log, t)
45
47
  const results = []
46
48
  const total = changes.length
47
49
 
@@ -12,6 +12,13 @@
12
12
  // copilot ~/.copilot/config.json의 installedPlugins[] (주석 있는 JSONC).
13
13
  // gemini ~/.gemini/extensions/<이름>/ (공식 extensions 문서).
14
14
  // opencode 전역 opencode.json의 plugin 배열 (XDG_CONFIG_HOME 존중).
15
+ // grok ~/.grok/installed-plugins/registry.json의 repos[*].plugins 키
16
+ // (2026-09-05 grok 1.0.5로 설치·제거 왕복 실측). 설치는
17
+ // `grok plugin install <소스> --trust`가 registry와 디렉터리를 만들고
18
+ // config.toml [plugins].enabled에도 이름을 넣는다. 제거
19
+ // (`grok plugin uninstall <이름> --confirm`)는 디렉터리·registry만
20
+ // 지우고 enabled 항목은 남기므로, 감지의 근거는 registry여야 한다 —
21
+ // enabled를 보면 제거한 뒤에도 영원히 설치됨으로 읽힌다.
15
22
  import { existsSync, readFileSync } from 'node:fs'
16
23
  import { homedir } from 'node:os'
17
24
  import { delimiter, join } from 'node:path'
@@ -67,13 +74,14 @@ function opencodePlugins(file) {
67
74
  // installId 마켓플레이스 설치 id (<이름>@<마켓>)
68
75
  // gemini gemini extensions install에 줄 git URL, 없으면 null
69
76
  // opencode 전역 opencode.json plugin 배열에 넣을 항목, 없으면 null
70
- // supports는 어댑터 유무로 정해진다 — codex·copilot은 항상, gemini·opencode는
77
+ // grok grok plugin install에 줄 소스(마켓 id 또는 owner/repo), 없으면 null
78
+ // supports는 어댑터 유무로 정해진다 — codex·copilot은 항상, gemini·opencode·grok은
71
79
  // 좌표가 있을 때만.
72
80
  export function defineGlobalPlugin(
73
- { id, label, group, note, exclusive = null, unsupported = {}, pluginName, marketplaceRepo, installId, gemini = null, opencode = null },
81
+ { id, label, group, note, exclusive = null, unsupported = {}, pluginName, marketplaceRepo, installId, gemini = null, opencode = null, grok = null, verified = null },
74
82
  { home = null, env = process.env, hasBinary = defaultHasBinary } = {},
75
83
  ) {
76
- const supports = ['codex', ...(gemini ? ['gemini'] : []), ...(opencode ? ['opencode'] : []), 'copilot']
84
+ const supports = ['codex', ...(gemini ? ['gemini'] : []), ...(opencode ? ['opencode'] : []), 'copilot', ...(grok ? ['grok'] : [])]
77
85
 
78
86
  const files = () => {
79
87
  const base = home ?? homedir()
@@ -82,19 +90,29 @@ export function defineGlobalPlugin(
82
90
  copilot: join(base, '.copilot', 'config.json'),
83
91
  gemini: join(base, '.gemini', 'extensions', pluginName),
84
92
  opencode: join(env.XDG_CONFIG_HOME || join(base, '.config'), 'opencode', 'opencode.json'),
93
+ grok: join(base, '.grok', 'installed-plugins', 'registry.json'),
85
94
  }
86
95
  }
87
96
 
97
+ // registry.json: { repos: { "<이름>-<해시>": { plugins: { "<이름>": {...} } } } }.
98
+ // 이름은 repos 키(해시가 붙는다)가 아니라 plugins 키에서 본다.
99
+ const grokInstalled = (file) => {
100
+ const repos = readJson(file)?.repos
101
+ if (!repos || typeof repos !== 'object') return false
102
+ return Object.values(repos).some((r) => Object.hasOwn(r?.plugins ?? {}, pluginName))
103
+ }
104
+
88
105
  const isOurEntry = (p) => String(p).startsWith(`${pluginName}@`)
89
106
  const wired = {
90
107
  codex: (f) => codexPluginIds(f.codex, pluginName).length > 0,
91
108
  copilot: (f) => copilotInstalled(f.copilot, pluginName),
92
109
  gemini: (f) => existsSync(f.gemini),
93
110
  opencode: (f) => (opencodePlugins(f.opencode) ?? []).some(isOurEntry),
111
+ grok: (f) => grokInstalled(f.grok),
94
112
  }
95
113
 
96
114
  return {
97
- id, category: 'plugin', label, scope: 'user', group, exclusive,
115
+ id, category: 'plugin', label, scope: 'user', group, exclusive, verified,
98
116
  supports: [...supports],
99
117
  unsupported,
100
118
  note,
@@ -108,11 +126,14 @@ export function defineGlobalPlugin(
108
126
  const noCli = supports.filter((c) => !machine.includes(c))
109
127
  const missing = machine.filter((c) => !present.includes(c))
110
128
  const detail = noCli.length > 0 ? msg('item.global.noCli', { list: noCli.join(', ') }) : undefined
111
- if (present.length === 0) return { status: 'absent', detail }
112
- if (missing.length === 0) return { status: 'installed', detail }
129
+ // excluded는 detail과 별도로 구조체로 넘긴다 — 행 힌트가 CLI 이름을
130
+ // 나열할 때 이 머신에 없는 것을 빼고 따로 적으려면 문장이 아니라 목록이 필요하다.
131
+ if (present.length === 0) return { status: 'absent', detail, excluded: noCli }
132
+ if (missing.length === 0) return { status: 'installed', detail, excluded: noCli }
113
133
  return {
114
134
  status: 'partial',
115
135
  detail: msg('item.plugin.partial', { present: present.join(', '), missing: missing.join(', ') }),
136
+ excluded: noCli,
116
137
  }
117
138
  },
118
139
 
@@ -148,6 +169,11 @@ export function defineGlobalPlugin(
148
169
  } else if (cli === 'gemini') {
149
170
  const r = await exec('gemini', ['extensions', 'install', gemini])
150
171
  if (!r.ok) fail(cli, r.output)
172
+ } else if (cli === 'grok') {
173
+ // --trust는 확인 프롬프트를 건너뛴다 — stdin이 닫힌 채 도는 실행이라
174
+ // 프롬프트가 뜨면 영원히 멈춘다.
175
+ const r = await exec('grok', ['plugin', 'install', grok, '--trust'])
176
+ if (!r.ok) fail(cli, r.output)
151
177
  } else {
152
178
  if (dryRun) {
153
179
  log(t('log.plugin.add', { cli: CLIS.opencode.label, name: opencode }))
@@ -195,6 +221,12 @@ export function defineGlobalPlugin(
195
221
  let r = await exec('copilot', ['plugin', 'uninstall', pluginName])
196
222
  if (!r.ok) r = await exec('copilot', ['plugin', 'uninstall', pluginName])
197
223
  if (!r.ok) fail(cli, r.output)
224
+ } else if (cli === 'grok') {
225
+ // --confirm은 한 저장소에 플러그인이 여럿일 때의 확인을 건너뛴다.
226
+ // config.toml [plugins].enabled의 이름은 상류가 남긴다 — 우리가
227
+ // 등록한 줄이라는 보장이 없어 건드리지 않는다(note에 적는다).
228
+ const r = await exec('grok', ['plugin', 'uninstall', pluginName, '--confirm'])
229
+ if (!r.ok) fail(cli, r.output)
198
230
  } else {
199
231
  const r = await exec('gemini', ['extensions', 'uninstall', pluginName])
200
232
  if (!r.ok) fail(cli, r.output)
@@ -150,6 +150,7 @@ export default {
150
150
  'log.record.write': 'install record written: {path}',
151
151
  'error.recordUnreadable': 'Cannot read {path} ({code})',
152
152
  'error.recordNotJson': 'Cannot read {path} — not JSON ({message})',
153
+ 'error.jsonInvalid': 'Cannot read {path} — invalid JSON: {message}. Fix the file by hand; nothing was written.',
153
154
  'error.recordVersion': '{path} has format version {found}. This tool uses {expected} — upgrade the tool or recreate the record.',
154
155
 
155
156
  'log.repoRoot': 'repository root: {path}',
@@ -213,6 +214,8 @@ export default {
213
214
  'status.row.items': 'items installed {list}',
214
215
  'status.row.recordOnly': ' record only {list}',
215
216
  'status.row.repoOnly': ' repo only {list}',
217
+ 'status.row.stale': ' stale check {list}',
218
+ 'status.hint.stale': ' → verified more than {days} days ago. Re-check upstream and update verified',
216
219
  'status.none': '(none)',
217
220
  'update.versionMove': 'pinned {pinned} → running {running}',
218
221
  'update.summary': '{updated} updated · {created} created · {drift} drifted',
@@ -232,13 +235,13 @@ export default {
232
235
  'item.unsupported.eccScale': 'upstream also supports OpenCode, Gemini and Kimi project-locally, but its skills/ holds 284 entries that would swamp the shared directory — only the plugin route is wired',
233
236
  'item.unsupported.uaUser': 'upstream install.sh clones into ~/.understand-anything and links from the home directory (user scope); the copilot plugin install offers no scope choice either',
234
237
  'item.unsupported.mattpocockRegistry': 'upstream also documents npx skills add (the shared .agents/skills), but this item wires the Claude Code plugin only',
238
+ 'item.unsupported.unmeasuredUpstream': 'upstream has an install command, but the detect/remove path has not been verified, so it is not wired yet — you can install it by hand with the command in the upstream README',
239
+ 'item.unsupported.noProjectMcp': 'no project-scoped MCP file — MCP is configured only in the home global (~/.gemini/config/mcp_config.json), so register it there by hand if needed',
235
240
  'item.unsupported.upstreamNone': 'no official install path upstream',
236
241
  'item.unsupported.superpowersSeparate': 'upstream supports it via a separate per-harness install — this item wires the Claude Code plugin only',
237
242
  'item.unsupported.superpowersGlobalClaude': 'covered by the repo-scope item superpowers (repo)',
238
- 'item.unsupported.superpowersGlobalGrok': 'upstream 6.3.0 ships grok plugin install superpowers@xai-official, but the detect/remove paths are unverified — not wired yet',
239
243
  'item.unsupported.superpowersGlobalKimi': 'upstream installs only via the interactive /plugins command — no headless path',
240
244
  'item.unsupported.globalProjectItem': 'covered by the repo-scope item {item}',
241
- 'item.unsupported.ponytailGlobalGrok': 'upstream ships grok plugin install DietrichGebert/ponytail --trust, but the detect/remove paths are unverified — not wired yet',
242
245
  'item.unsupported.impeccableJunction': 'upstream supports it, but npx impeccable install breaks the shared .agents/skills link, so only the plugin route is wired',
243
246
  'item.unsupported.gstackHost': 'upstream supports it via ./setup --host — this item installs for Claude Code only',
244
247
  'item.unsupported.gstackShared': 'the upstream --host route is user-scoped, so it is not used. In a bootstrapped repository the shared .agents/skills junction reaches this CLI instead, and it scans recursively, so it sees the individual skills too (measured 2026-08-15)',
@@ -264,8 +267,8 @@ export default {
264
267
  'item.mcp.graphify.note': 'needs the graphify-mcp binary on PATH. Install: uv tool install "graphifyy[mcp]". Reads graphify-out/graph.json — build it first with /graphify .',
265
268
  'item.mcp.headroom.note': 'needs the headroom binary on PATH. Install: uv tool install --python 3.13 "headroom-ai[proxy,mcp]". The proxy (headroom wrap) is a way of launching, not repo config',
266
269
  'item.plugin.superpowers.note': 'official marketplace plugin. Upstream supports 14 harnesses (Codex, Grok Build, …) via per-harness installs',
267
- 'item.global.superpowers.note': 'installs superpowers user-globally with each harness\'s official command — marketplace for codex/copilot, extensions for gemini, the global opencode.json for OpenCode. CLIs missing from this machine are skipped. With skill.superpowers also on, overlapping CLIs see the same skills twice',
268
- 'item.global.ponytail.note': 'installs Ponytail user-globally with each harness\'s official command — the DietrichGebert/ponytail marketplace for codex/copilot, extensions for gemini. After the codex install, check hook registration once via /hooks. Upstream recommends scripts/uninstall.js to clean ~/.config/ponytail before removal — this item removes the plugin only',
270
+ 'item.global.superpowers.note': 'installs superpowers user-globally with each harness\'s official command — marketplace for codex/copilot, extensions for gemini, the global opencode.json for OpenCode, grok plugin install superpowers@xai-official for grok. CLIs missing from this machine are skipped. Removing from grok leaves the name in ~/.grok/config.toml [plugins].enabled (upstream behavior). With skill.superpowers also on, overlapping CLIs see the same skills twice',
271
+ 'item.global.ponytail.note': 'installs Ponytail user-globally with each harness\'s official command — the DietrichGebert/ponytail marketplace for codex/copilot, extensions for gemini, grok plugin install DietrichGebert/ponytail for grok. After the codex install, check hook registration once via /hooks. Upstream recommends scripts/uninstall.js to clean ~/.config/ponytail before removal — this item removes the plugin only',
269
272
  'item.plugin.mattpocock-skills.note': '35 engineering and productivity skills (code-review, diagnosing-bugs, domain-modeling, …)',
270
273
  'item.plugin.ponytail.note': 'lean-code ruleset. Wires the Claude Code plugin and the OpenCode plugin entry, both repo-scoped',
271
274
  'item.plugin.ecc.note': 'skills, instincts, memory and security for agent harnesses. Installed as a plugin — its skills/ holds 284 entries, too many to copy into the shared skills directory',
@@ -273,15 +276,15 @@ export default {
273
276
  'item.plugin.understand-anything.note': 'turns the codebase into an explorable knowledge graph. The script for other CLIs clones into ~/.understand-anything (user scope), so only the plugin is wired',
274
277
  'item.skill.caveman.note': 'cuts output tokens by answering in caveman-speak; code and errors stay byte-exact. Toggle with /caveman',
275
278
  'item.skill.taste.note': 'anti-slop frontend skill for landing pages, portfolios and redesigns (skill name: design-taste-frontend)',
276
- 'item.skill.hallmark.note': 'anti-AI-slop design skill with four verbs (build, audit, redesign, study). 21 themes and 57 slop-test gates. Upstream also documents Cursor and Codex paths; the shared skills directory covers all 10 CLIs at once',
279
+ 'item.skill.hallmark.note': 'anti-AI-slop design skill with four verbs (build, audit, redesign, study). 21 themes and 57 slop-test gates. Upstream also documents Cursor and Codex paths; the shared skills directory covers all 11 CLIs at once',
277
280
  'item.skill.diagram-design.note': 'draws 27 diagram types in HTML/SVG using your brand colors and fonts; also redraws Mermaid and draw.io sources. The default fonts (Instrument Serif, Geist) carry no CJK glyphs — swap them in style-guide.md — and PNG export needs Playwright and Chromium separately. The three upstream slash commands are left out, but the references/ they delegate to come along',
278
- 'item.skill.superpowers.note': 'copies 14 skills into the shared .agents/skills so all 10 CLIs see them. Only one of this and the plugin edition can be chosen — this route has no session-start hook, so nothing is injected at the start of a conversation and skills are invoked on demand',
279
- 'item.skill.agent-browser.note': 'a skill that drives the browser-automation CLI. Requires the agent-browser binary on PATH — npm i -g agent-browser, then agent-browser install once (downloads Chrome for Testing). The SKILL.md is a discovery stub; the real workflow guidance is served by the installed CLI. The body is harness-neutral, so all 10 CLIs share it (only the allowed-tools line is Claude syntax)',
280
- 'item.skill.find-skills.note': 'a skill that searches the public skills registry and guides installs. It only instructs npx skills commands — harness-neutral, shared by all 10 CLIs',
281
- 'item.skill.mcp-builder.note': 'a guide for building MCP servers (Python FastMCP, Node MCP SDK). Its four reference/ documents and the Apache-2.0 LICENSE.txt are copied along inside the skill directory. Harness-neutral content shared by all 10 CLIs',
282
- 'item.skill.mattpocock-skills.note': 'copies 35 skills into the shared .agents/skills so all 10 CLIs see them. Upstream documents this route itself, and with no hooks involved nothing is lost against the plugin edition. Only one of the two can be chosen',
281
+ 'item.skill.superpowers.note': 'copies 14 skills into the shared .agents/skills so all 11 CLIs see them. Only one of this and the plugin edition can be chosen — this route has no session-start hook, so nothing is injected at the start of a conversation and skills are invoked on demand',
282
+ 'item.skill.agent-browser.note': 'a skill that drives the browser-automation CLI. Requires the agent-browser binary on PATH — npm i -g agent-browser, then agent-browser install once (downloads Chrome for Testing). The SKILL.md is a discovery stub; the real workflow guidance is served by the installed CLI. The body is harness-neutral, so all 11 CLIs share it (only the allowed-tools line is Claude syntax)',
283
+ 'item.skill.find-skills.note': 'a skill that searches the public skills registry and guides installs. It only instructs npx skills commands — harness-neutral, shared by all 11 CLIs',
284
+ 'item.skill.mcp-builder.note': 'a guide for building MCP servers (Python FastMCP, Node MCP SDK). Its four reference/ documents and the Apache-2.0 LICENSE.txt are copied along inside the skill directory. Harness-neutral content shared by all 11 CLIs',
285
+ 'item.skill.mattpocock-skills.note': 'copies 35 skills into the shared .agents/skills so all 11 CLIs see them. Upstream documents this route itself, and with no hooks involved nothing is lost against the plugin edition. Only one of the two can be chosen',
283
286
  'item.skill.karpathy.note': 'behavioral guidelines that curb overbuilding and vague success criteria',
284
- 'item.skill.prompt-master.note': 'a skill that writes and refines prompts for AI tools. Activates only when explicitly asked to write or fix a prompt. Harness-neutral body shared by all 10 CLIs',
287
+ 'item.skill.prompt-master.note': 'a skill that writes and refines prompts for AI tools. Activates only when explicitly asked to write or fix a prompt. Harness-neutral body shared by all 11 CLIs',
285
288
  'item.skill.strix.note': 'copies 9 security-testing skills (pentesting, OWASP Top 10, vulnerability discovery and fix flows) into the shared .agents/skills. The skills drive the Strix agent, so the strix binary is required on PATH — self-hosted needs pipx install strix-agent plus Docker and STRIX_LLM/LLM_API_KEY, managed needs strix cloud login. Use only against targets you are authorized to test',
286
289
  'item.skill.gstack.note': 'repo-local clone + setup (needs bash; Git Bash on Windows). Runtime state (~/.gstack) may be created globally. In a bootstrapped repository .claude/skills is a junction, so the files land in the shared .agents/skills — codex and opencode scan recursively and see the individual skills too',
287
290
  'item.skill.gsd.note': 'npx @opengsd/gsd-core, installed per project — wires five runtimes in one run: claude, codex, opencode, copilot, kilo (.claude/commands, .codex/skills, .opencode/skills, .github/skills, .kilo/skills). Each runtime writes roughly 700 files. gemini was dropped upstream and kimi still blocks project installs',
@@ -307,6 +310,7 @@ export default {
307
310
  'error.itemFieldMissing': '{file}: {field} missing',
308
311
  'error.itemReasonMissing': '{id}: needs a reason for unsupported CLI \'{cli}\' (unsupported.{cli})',
309
312
  'error.shellQuote': 'The argument contains a double quote that cannot be passed to the shell: {value}',
313
+ 'error.commandTimeout': 'stopped after {seconds}s without finishing: {command}',
310
314
 
311
315
  // TUI 탭 이름. rows.mjs의 SECTION_ORDER id(action·plugin·mcp·skill·design)를
312
316
  // 그대로 따른다 — 탭 순서·검색 스코프는 id로 갈리고, 여기서는 표시만 맡는다.
@@ -321,12 +325,16 @@ export default {
321
325
  // buildActions가 쓴다. action.*.label과 section.*는 LABEL_WIDTH(24) 안에
322
326
  // 들어야 한다 — i18n.test.mjs가 검사한다.
323
327
  'tui.counts': 'selected {picked} / {total}',
328
+ // 머리글의 변경 예정 건수 — Enter 전에 무슨 일이 일어날지 알린다.
329
+ 'tui.pending': '{count} pending change(s){marks}',
330
+ // 0건이 아닐 때 붙는 내역. 행의 [+]·[-] 표식과 같은 글자다.
331
+ 'tui.pendingMarks': ' (+{add} -{remove})',
324
332
  'tui.search.prefix': 'Search › ',
325
333
  'tui.search.placeholder': 'type to search · ↓ for the list',
326
334
  'tui.empty.filtered': ' Nothing matches in this tab. Press Tab to try another.',
327
335
  'tui.empty.none': ' Nothing here.',
328
- 'tui.hint.search': 'type to search (spaces included) ↓ to list Tab tab Esc clear Ctrl+F CLI Ctrl+D detail',
329
- 'tui.hint.list': 'Space select ↑↓ move (↑ at top = search) Tab tab Enter run/submit Ctrl+A all Ctrl+O preview Ctrl+F CLI Ctrl+D detail',
336
+ 'tui.hint.search': 'type to search (spaces included) ↓ to list Tab tab Esc clear Ctrl+F CLI Ctrl+D detail F1 help',
337
+ 'tui.hint.list': 'Space select ↑↓ move Tab tab Enter run/submit Ctrl+A all Ctrl+O preview Ctrl+F CLI Ctrl+D detail F1 help',
330
338
  // 힌트 줄 오른쪽 끝의 고정 자리. 나머지 힌트가 다 잘려 나가도 이 자리는 남는다.
331
339
  'tui.hint.quit': 'Ctrl+Q quit',
332
340
  'tui.review.title': 'Review — {count} change(s)',
@@ -347,6 +355,25 @@ export default {
347
355
  'tui.pressAnyKey': 'Press any key to continue…',
348
356
  'tui.confirmSuffix': ' [y/N] ',
349
357
 
358
+ // 도움말 화면(F1). 키마다 한 줄 — 바닥글에 담기지 않는 뜻까지 적는다.
359
+ 'help.title': 'Keys',
360
+ 'help.lines': [
361
+ ' any character · / move up to the search box (Space is part of the query there)',
362
+ ' ↓ · Enter (search) go down to the list',
363
+ ' ↑↓ · PgUp PgDn move the cursor · ↑ at the top returns to the search box',
364
+ ' Home · End first · last row',
365
+ ' Space select/deselect — [+] to install · [-] to remove · [×] installed',
366
+ ' Tab · Shift+Tab · ←→ switch tabs (selections accumulate across tabs)',
367
+ ' Enter run a [▶] action row; otherwise submit (review → apply all)',
368
+ ' Ctrl+A select/deselect every visible item in this tab',
369
+ ' Ctrl+O preview the item under the cursor (design.md opens a browser)',
370
+ ' Ctrl+F · Ctrl+B cycle the CLI filter forward / backward (All → one CLI)',
371
+ ' Ctrl+D expand/collapse the detail panel to full screen',
372
+ ' Esc clear the query → to the list → press again to quit',
373
+ ' Ctrl+Q quit (from any screen, in one press)',
374
+ ],
375
+ 'help.close': 'Press any key to go back',
376
+
350
377
  // 진행 화면(tui/progress.mjs) 문구. Task 9의 이벤트(start·command·done)를
351
378
  // 사람이 읽는 줄로 바꾼다. plain*은 비TTY(CI·파이프)용 한 줄 출력이다.
352
379
  'progress.title': 'Applying — {count} change(s){suffix}',
@@ -381,6 +408,8 @@ export default {
381
408
  'item.target': 'file: {path}',
382
409
  // 커버리지는 전부 지원할 때도 찍는다 — 표시가 없는 것과 "10/10"은 뜻이 다르다.
383
410
  'item.cliCoverage': 'CLI {covered}/{total}',
411
+ // 전역 항목의 짧은 힌트 — 이 머신에 없는 CLI를 나열에서 빼고 따로 적는다.
412
+ 'item.cliMissing': '{list} missing',
384
413
  'item.unsupportedList': 'not wired for {count}: {groups}',
385
414
  'item.unsupportedGroup': '{clis} — {why}',
386
415
  'detail.wired': 'wired',
@@ -144,6 +144,7 @@ export default {
144
144
  'log.record.write': '설치 기록 기록: {path}',
145
145
  'error.recordUnreadable': '{path}을 읽을 수 없습니다 ({code})',
146
146
  'error.recordNotJson': '{path}을 읽을 수 없습니다 — JSON이 아닙니다 ({message})',
147
+ 'error.jsonInvalid': '{path}을 읽을 수 없습니다 — JSON이 깨져 있습니다: {message}. 파일을 손으로 고치세요. 아무것도 쓰지 않았습니다.',
147
148
  'error.recordVersion': '{path}의 형식 버전이 {found}입니다. 이 도구는 {expected}을 씁니다 — 도구를 올리거나 기록을 다시 만드세요.',
148
149
 
149
150
  'log.repoRoot': '저장소 루트: {path}',
@@ -208,6 +209,8 @@ export default {
208
209
  'status.row.items': '항목 설치됨 {list}',
209
210
  'status.row.recordOnly': ' 기록에만 {list}',
210
211
  'status.row.repoOnly': ' 저장소에만 {list}',
212
+ 'status.row.stale': ' 실측 오래됨 {list}',
213
+ 'status.hint.stale': ' → {days}일 넘은 실측 사유입니다. 상류를 다시 확인하고 verified를 갱신하세요',
211
214
  'status.none': '(없음)',
212
215
  'update.versionMove': '고정 {pinned} → 실행 중 {running}',
213
216
  'update.summary': '갱신 {updated}건 · 신규 {created}건 · 드리프트 {drift}건',
@@ -226,12 +229,12 @@ export default {
226
229
  'item.unsupported.uaUser': '상류 install.sh가 ~/.understand-anything에 클론하고 홈에 링크를 겁니다(사용자 스코프). copilot 플러그인 설치도 스코프 선택이 없습니다',
227
230
  'item.unsupported.mattpocockRegistry': '상류가 npx skills add(공유 .agents/skills)도 안내하지만 이 항목은 Claude Code 플러그인만 배선합니다',
228
231
  'item.unsupported.upstreamNone': '상류에 공식 설치 경로가 없습니다',
232
+ 'item.unsupported.unmeasuredUpstream': '상류에 설치 명령이 있지만 감지·제거 경로를 실측하지 못해 아직 배선하지 않습니다 — 상류 README의 명령으로 직접 설치할 수 있습니다',
233
+ 'item.unsupported.noProjectMcp': '프로젝트 스코프 MCP 파일이 없습니다 — 홈 글로벌(~/.gemini/config/mcp_config.json)에서만 설정하므로 필요하면 그 파일에 직접 등록하세요',
229
234
  'item.unsupported.superpowersSeparate': '상류가 하니스별 별도 설치로 지원합니다 — 이 항목은 Claude Code 플러그인만 배선합니다',
230
235
  'item.unsupported.superpowersGlobalClaude': '저장소 범위 항목 superpowers (저장소)가 담당합니다',
231
- 'item.unsupported.superpowersGlobalGrok': '상류 6.3.0에 grok plugin install superpowers@xai-official 명령이 있지만 감지·제거 경로를 실측하지 못해 아직 배선하지 않습니다',
232
236
  'item.unsupported.superpowersGlobalKimi': '상류 설치가 대화형 /plugins 명령뿐이라 헤드리스 경로가 없습니다',
233
237
  'item.unsupported.globalProjectItem': '저장소 범위 항목 {item}이 담당합니다',
234
- 'item.unsupported.ponytailGlobalGrok': '상류 명령(grok plugin install DietrichGebert/ponytail --trust)이 있지만 감지·제거 경로 미실측이라 배선하지 않습니다',
235
238
  'item.unsupported.impeccableJunction': '상류는 지원하지만 npx impeccable install이 공유 .agents/skills 연결을 끊어 플러그인 경로만 배선합니다',
236
239
  'item.unsupported.gstackHost': '상류는 ./setup --host로 지원합니다 — 이 항목은 Claude Code에만 설치합니다',
237
240
  'item.unsupported.gstackShared': '상류 --host 경로는 사용자 스코프라 쓰지 않습니다. 대신 부트스트랩 저장소에서는 공유 .agents/skills Junction으로 닿고, 이 CLI는 재귀 스캔이라 개별 스킬까지 봅니다(2026-08-15 실측)',
@@ -257,8 +260,8 @@ export default {
257
260
  'item.mcp.graphify.note': 'PATH에 graphify-mcp 바이너리 필요. 설치: uv tool install "graphifyy[mcp]". graphify-out/graph.json을 읽으므로 /graphify .로 그래프를 먼저 만드세요',
258
261
  'item.mcp.headroom.note': 'PATH에 headroom 바이너리 필요. 설치: uv tool install --python 3.13 "headroom-ai[proxy,mcp]". 프록시(headroom wrap)는 실행 방식이라 저장소 설정 대상이 아닙니다',
259
262
  'item.plugin.superpowers.note': '공식 마켓플레이스 플러그인. 상류는 Codex·Grok Build 등 14개 하니스를 하니스별 설치로 지원합니다',
260
- 'item.global.superpowers.note': 'superpowers를 하니스별 공식 명령으로 사용자 전역에 설치합니다 — codex·copilot은 마켓플레이스, gemini는 extensions, opencode는 전역 opencode.json. 이 머신에 없는 CLI는 건너뜁니다. skill.superpowers와 함께 켜면 겹치는 CLI는 같은 스킬을 두 곳에서 봅니다',
261
- 'item.global.ponytail.note': 'Ponytail을 하니스별 공식 명령으로 사용자 전역에 설치합니다 — codex·copilot은 마켓플레이스(DietrichGebert/ponytail), gemini는 extensions. codex는 설치 후 /hooks에서 훅 등록을 한 번 확인해야 합니다. 상류는 제거 전 scripts/uninstall.js로 ~/.config/ponytail 정리를 안내합니다 — 이 항목은 플러그인만 제거합니다',
263
+ 'item.global.superpowers.note': 'superpowers를 하니스별 공식 명령으로 사용자 전역에 설치합니다 — codex·copilot은 마켓플레이스, gemini는 extensions, opencode는 전역 opencode.json, grok은 grok plugin install superpowers@xai-official. 이 머신에 없는 CLI는 건너뜁니다. grok 제거는 ~/.grok/config.toml [plugins].enabled의 이름을 남깁니다(상류 동작). skill.superpowers와 함께 켜면 겹치는 CLI는 같은 스킬을 두 곳에서 봅니다',
264
+ 'item.global.ponytail.note': 'Ponytail을 하니스별 공식 명령으로 사용자 전역에 설치합니다 — codex·copilot은 마켓플레이스(DietrichGebert/ponytail), gemini는 extensions, grok은 grok plugin install DietrichGebert/ponytail. codex는 설치 후 /hooks에서 훅 등록을 한 번 확인해야 합니다. 상류는 제거 전 scripts/uninstall.js로 ~/.config/ponytail 정리를 안내합니다 — 이 항목은 플러그인만 제거합니다',
262
265
  'item.plugin.mattpocock-skills.note': '엔지니어링·생산성 스킬 35종 (code-review, diagnosing-bugs, domain-modeling 등)',
263
266
  'item.plugin.ponytail.note': '군더더기 없는 코드 규칙 묶음. Claude Code 플러그인과 OpenCode plugin 항목을 저장소 범위로 배선합니다',
264
267
  'item.plugin.ecc.note': '에이전트 하네스용 스킬·본능·메모리·보안 묶음. skills/에 284개가 들어 있어 공유 스킬 디렉터리에 복사하지 않고 플러그인으로 설치합니다',
@@ -266,15 +269,15 @@ export default {
266
269
  'item.plugin.understand-anything.note': '코드베이스를 탐색 가능한 지식 그래프로 만듭니다. 다른 CLI용 스크립트는 ~/.understand-anything에 클론하는 사용자 스코프라 플러그인만 배선합니다',
267
270
  'item.skill.caveman.note': '원시인 말투로 답해 출력 토큰을 줄입니다. 코드와 오류는 그대로 둡니다. /caveman으로 켜고 끕니다',
268
271
  'item.skill.taste.note': '랜딩 페이지·포트폴리오·리디자인용 안티슬롭 프런트엔드 스킬 (스킬 이름: design-taste-frontend)',
269
- 'item.skill.hallmark.note': '네 가지 동사(신규 제작·audit·redesign·study)를 갖춘 안티 AI슬롭 디자인 스킬. 테마 21종과 슬롭 테스트 57종. 상류는 Cursor·Codex 경로도 안내하지만 공유 스킬 디렉터리 하나로 10개 CLI가 함께 봅니다',
272
+ 'item.skill.hallmark.note': '네 가지 동사(신규 제작·audit·redesign·study)를 갖춘 안티 AI슬롭 디자인 스킬. 테마 21종과 슬롭 테스트 57종. 상류는 Cursor·Codex 경로도 안내하지만 공유 스킬 디렉터리 하나로 11개 CLI가 함께 봅니다',
270
273
  'item.skill.diagram-design.note': '브랜드 색·폰트로 다이어그램 27종을 그리는 HTML/SVG 스킬. Mermaid·draw.io 원본도 다시 그립니다. 기본 폰트(Instrument Serif·Geist)에 한글 글리프가 없어 style-guide.md에서 바꿔야 하고, PNG 내보내기는 Playwright·Chromium이 따로 필요합니다. 상류 플러그인의 명령 3개는 빠지지만 그 본문인 references/는 그대로 옵니다',
271
- 'item.skill.superpowers.note': '공유 .agents/skills에 스킬 14종을 복사해 10개 CLI가 함께 봅니다. 플러그인 판과 하나만 고를 수 있습니다 — 이쪽은 session-start 훅이 없어 대화 시작 시 자동 주입이 되지 않고, 스킬을 불러서 씁니다',
272
- 'item.skill.agent-browser.note': '브라우저 자동화 CLI를 쓰게 하는 스킬. PATH에 agent-browser 바이너리 필요 — npm i -g agent-browser 후 최초 1회 agent-browser install(Chrome for Testing 다운로드). SKILL.md는 안내 스텁이라 실제 지침은 설치된 CLI가 제공합니다. 본문이 하네스 중립이라 10개 CLI가 함께 씁니다(allowed-tools 줄만 Claude 문법)',
273
- 'item.skill.find-skills.note': '공개 스킬 레지스트리를 검색해 설치를 안내하는 스킬. npx skills 명령만 지시하는 하네스 중립 문서라 10개 CLI가 함께 씁니다',
274
- 'item.skill.mcp-builder.note': 'MCP 서버 제작 가이드(Python FastMCP·Node MCP SDK). reference/ 문서 4종과 Apache-2.0 LICENSE.txt가 스킬 디렉터리에 함께 복사됩니다. 내용이 하네스 중립이라 10개 CLI가 함께 씁니다',
275
- 'item.skill.mattpocock-skills.note': '공유 .agents/skills에 스킬 35종을 복사해 10개 CLI가 함께 봅니다. 상류가 직접 안내하는 경로이며 훅이 없어 플러그인 판에 견줘 잃는 것이 없습니다. 둘 중 하나만 고를 수 있습니다',
274
+ 'item.skill.superpowers.note': '공유 .agents/skills에 스킬 14종을 복사해 11개 CLI가 함께 봅니다. 플러그인 판과 하나만 고를 수 있습니다 — 이쪽은 session-start 훅이 없어 대화 시작 시 자동 주입이 되지 않고, 스킬을 불러서 씁니다',
275
+ 'item.skill.agent-browser.note': '브라우저 자동화 CLI를 쓰게 하는 스킬. PATH에 agent-browser 바이너리 필요 — npm i -g agent-browser 후 최초 1회 agent-browser install(Chrome for Testing 다운로드). SKILL.md는 안내 스텁이라 실제 지침은 설치된 CLI가 제공합니다. 본문이 하네스 중립이라 11개 CLI가 함께 씁니다(allowed-tools 줄만 Claude 문법)',
276
+ 'item.skill.find-skills.note': '공개 스킬 레지스트리를 검색해 설치를 안내하는 스킬. npx skills 명령만 지시하는 하네스 중립 문서라 11개 CLI가 함께 씁니다',
277
+ 'item.skill.mcp-builder.note': 'MCP 서버 제작 가이드(Python FastMCP·Node MCP SDK). reference/ 문서 4종과 Apache-2.0 LICENSE.txt가 스킬 디렉터리에 함께 복사됩니다. 내용이 하네스 중립이라 11개 CLI가 함께 씁니다',
278
+ 'item.skill.mattpocock-skills.note': '공유 .agents/skills에 스킬 35종을 복사해 11개 CLI가 함께 봅니다. 상류가 직접 안내하는 경로이며 훅이 없어 플러그인 판에 견줘 잃는 것이 없습니다. 둘 중 하나만 고를 수 있습니다',
276
279
  'item.skill.karpathy.note': '과설계와 모호한 완료 기준을 줄이는 행동 지침',
277
- 'item.skill.prompt-master.note': 'AI 도구용 프롬프트를 만들고 다듬는 스킬. 프롬프트 작성·수정을 명시적으로 요청할 때만 활성화됩니다. 본문이 하네스 중립이라 10개 CLI가 함께 씁니다',
280
+ 'item.skill.prompt-master.note': 'AI 도구용 프롬프트를 만들고 다듬는 스킬. 프롬프트 작성·수정을 명시적으로 요청할 때만 활성화됩니다. 본문이 하네스 중립이라 11개 CLI가 함께 씁니다',
278
281
  'item.skill.strix.note': '보안 테스트 스킬 9종(침투 테스트·OWASP Top 10·취약점 탐지와 수정 흐름)을 공유 .agents/skills에 복사합니다. 스킬이 Strix 에이전트를 구동하므로 PATH에 strix 바이너리가 필요합니다 — 자체 호스팅은 pipx install strix-agent에 Docker와 STRIX_LLM·LLM_API_KEY, 관리형은 strix cloud login. 승인된 대상에만 쓰세요',
279
282
  'item.skill.gstack.note': '저장소 로컬 clone + setup (bash 필요, Windows는 Git Bash). 런타임 상태(~/.gstack)는 전역에 생길 수 있음. 부트스트랩 저장소에서는 .claude/skills가 Junction이라 실물이 공유 .agents/skills에 앉습니다 — codex·opencode는 재귀 스캔이라 개별 스킬까지 함께 봅니다',
280
283
  'item.skill.gsd.note': 'npx @opengsd/gsd-core 프로젝트 로컬 설치 — claude·codex·opencode·copilot·kilo 다섯 런타임을 한 번에 배선합니다(.claude/commands, .codex/skills, .opencode/skills, .github/skills, .kilo/skills). 런타임마다 700개 안팎의 파일이 생깁니다. gemini는 상류가 제거했고 kimi는 프로젝트 설치를 아직 막습니다',
@@ -299,6 +302,7 @@ export default {
299
302
  'error.itemFieldMissing': '{file}: {field} 누락',
300
303
  'error.itemReasonMissing': '{id}: 미지원 CLI \'{cli}\'의 사유(unsupported.{cli})가 필요합니다',
301
304
  'error.shellQuote': '셸에 넘길 수 없는 큰따옴표가 인자에 있습니다: {value}',
305
+ 'error.commandTimeout': '{seconds}초 안에 끝나지 않아 중단했습니다: {command}',
302
306
 
303
307
  // TUI 탭 이름. en.mjs 쪽 주석 참고. PLUGIN·MCP·SKILL·DESIGN.MD는 형식·브랜드
304
308
  // 이름이라 번역하지 않는다 — 두 로케일에서 값이 같은 게 맞다.
@@ -311,12 +315,16 @@ export default {
311
315
 
312
316
  // TUI 목록·검토 화면 문구. en.mjs 쪽 주석 참고.
313
317
  'tui.counts': '선택 {picked} / 전체 {total}',
318
+ // 머리글의 변경 예정 건수 — Enter 전에 무슨 일이 일어날지 알린다.
319
+ 'tui.pending': '변경 예정 {count}건{marks}',
320
+ // 0건이 아닐 때 붙는 내역. 행의 [+]·[-] 표식과 같은 글자다.
321
+ 'tui.pendingMarks': ' (+{add} -{remove})',
314
322
  'tui.search.prefix': '검색 › ',
315
323
  'tui.search.placeholder': '타이핑하면 검색 · ↓ 로 목록',
316
324
  'tui.empty.filtered': ' 이 탭에는 일치하는 항목이 없습니다. Tab으로 다른 탭을 보세요.',
317
325
  'tui.empty.none': ' 항목이 없습니다.',
318
- 'tui.hint.search': '입력=검색어 ↓ 목록으로 Tab 탭이동 Esc 검색해제 Ctrl+F CLI Ctrl+D 상세',
319
- 'tui.hint.list': 'Space 선택 ↑↓ 이동 Tab 탭 Enter 실행/제출 Ctrl+A 전체 Ctrl+O 미리보기 Ctrl+F CLI Ctrl+D 상세',
326
+ 'tui.hint.search': '입력=검색어 ↓ 목록으로 Tab 탭이동 Esc 검색해제 Ctrl+F CLI Ctrl+D 상세 F1 도움말',
327
+ 'tui.hint.list': 'Space 선택 ↑↓ 이동 Tab 탭 Enter 실행/제출 Ctrl+A 전체 Ctrl+O 미리보기 Ctrl+F CLI Ctrl+D 상세 F1 도움말',
320
328
  // 힌트 줄 오른쪽 끝의 고정 자리. en.mjs 쪽 주석 참고.
321
329
  'tui.hint.quit': 'Ctrl+Q 종료',
322
330
  'tui.review.title': '제출 검토 — 변경 {count}건',
@@ -337,6 +345,25 @@ export default {
337
345
  'tui.pressAnyKey': '계속하려면 아무 키나 누르세요…',
338
346
  'tui.confirmSuffix': ' [y/N] ',
339
347
 
348
+ // 도움말 화면(F1). 키마다 한 줄 — 바닥글에 담기지 않는 뜻까지 적는다.
349
+ 'help.title': '키 안내',
350
+ 'help.lines': [
351
+ ' 아무 글자 · / 검색칸으로 올라가 검색 (검색칸에서는 Space도 검색어)',
352
+ ' ↓ · Enter (검색칸) 목록으로 내려간다',
353
+ ' ↑↓ · PgUp PgDn 커서 이동 · 맨 위에서 ↑는 검색칸으로',
354
+ ' Home · End 목록 처음 · 끝',
355
+ ' Space 선택/해제 — [+] 설치 예정 · [-] 제거 예정 · [×] 설치됨',
356
+ ' Tab · Shift+Tab · ←→ 탭 이동 (선택은 탭을 넘나들며 누적)',
357
+ ' Enter [▶] 작업 행은 실행, 그 밖에는 제출(검토 → 일괄 적용)',
358
+ ' Ctrl+A 지금 탭에서 보이는 항목 전체 선택/해제',
359
+ ' Ctrl+O 커서 항목 미리보기 (design.md는 브라우저)',
360
+ ' Ctrl+F · Ctrl+B CLI 필터 앞 / 뒤로 (전체 → CLI 하나씩)',
361
+ ' Ctrl+D 상세 패널 전체 화면 펼치기/접기',
362
+ ' Esc 검색어 지우기 → 목록으로 → 한 번 더 누르면 종료',
363
+ ' Ctrl+Q 종료 (어느 화면에서나 한 번에)',
364
+ ],
365
+ 'help.close': '아무 키나 누르면 돌아갑니다',
366
+
340
367
  // 진행 화면(tui/progress.mjs) 문구. en.mjs 쪽 주석 참고.
341
368
  'progress.title': '적용 중 — 변경 {count}건{suffix}',
342
369
  'progress.counter': '{done}/{total} {percent}%',
@@ -368,6 +395,8 @@ export default {
368
395
  'item.target': '대상 파일: {path}',
369
396
  // en.mjs 쪽 주석 참고.
370
397
  'item.cliCoverage': 'CLI {covered}/{total}',
398
+ // 전역 항목의 짧은 힌트 — 이 머신에 없는 CLI를 나열에서 빼고 따로 적는다.
399
+ 'item.cliMissing': '{list} 없음',
371
400
  'item.unsupportedList': '미배선 {count}곳: {groups}',
372
401
  'item.unsupportedGroup': '{clis} — {why}',
373
402
  'detail.wired': '배선',