@rch4com/agent-setup 1.8.0 → 1.10.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.
Files changed (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +199 -197
  3. package/install.mjs +163 -163
  4. package/lib/args.mjs +276 -276
  5. package/lib/bootstrap/adapter.mjs +96 -96
  6. package/lib/bootstrap/apply.mjs +380 -380
  7. package/lib/bootstrap/flow.mjs +140 -140
  8. package/lib/bootstrap/record.mjs +154 -154
  9. package/lib/bootstrap/templates.mjs +148 -148
  10. package/lib/bootstrap/text.mjs +19 -19
  11. package/lib/catalog.mjs +234 -234
  12. package/lib/claude-plugins.mjs +64 -64
  13. package/lib/clis.mjs +111 -111
  14. package/lib/context.mjs +66 -66
  15. package/lib/deps.mjs +17 -17
  16. package/lib/design-md/catalog.json +454 -454
  17. package/lib/design-md/catalog.mjs +266 -266
  18. package/lib/design-md/flow.mjs +192 -192
  19. package/lib/design-md/open.mjs +51 -51
  20. package/lib/design-md/providers/awesome-design-md.mjs +124 -124
  21. package/lib/design-md/providers/index.mjs +9 -9
  22. package/lib/design-md/scan.mjs +295 -295
  23. package/lib/engine.mjs +115 -115
  24. package/lib/gitignore.mjs +18 -18
  25. package/lib/i18n/catalog/en.mjs +426 -425
  26. package/lib/i18n/catalog/ko.mjs +411 -410
  27. package/lib/i18n/detect.mjs +39 -39
  28. package/lib/i18n/index.mjs +93 -93
  29. package/lib/items/mcp.codebase-memory.mjs +6 -6
  30. package/lib/items/mcp.notion.mjs +6 -6
  31. package/lib/items/mcp.supabase.mjs +6 -6
  32. package/lib/items/mcp.vercel.mjs +6 -6
  33. package/lib/items/plugin.mattpocock-skills.mjs +8 -8
  34. package/lib/items/plugin.ponytail.mjs +104 -104
  35. package/lib/items/skill.gsd.mjs +44 -44
  36. package/lib/items/skill.gstack.mjs +54 -54
  37. package/lib/items/skill.hallmark.mjs +10 -0
  38. package/lib/jsonfile.mjs +40 -40
  39. package/lib/status.mjs +93 -93
  40. package/lib/tomlfile.mjs +60 -60
  41. package/lib/tui/detail.mjs +127 -127
  42. package/lib/tui/progress.mjs +160 -160
  43. package/lib/tui/render.mjs +268 -268
  44. package/lib/tui/rows.mjs +317 -317
  45. package/lib/tui/run.mjs +396 -396
  46. package/lib/tui/state.mjs +249 -249
  47. package/lib/update.mjs +85 -85
  48. package/lib/width.mjs +115 -115
  49. package/package.json +36 -36
package/install.mjs CHANGED
@@ -1,163 +1,163 @@
1
- #!/usr/bin/env node
2
- import { findRepoRoot } from './lib/context.mjs'
3
- import { runBootstrap } from './lib/bootstrap/flow.mjs'
4
- import { readRecord } from './lib/bootstrap/record.mjs'
5
- import { withDeps } from './lib/deps.mjs'
6
- import { LocalizedError, createT, isLocaleForced, resolveLocale, toText } from './lib/i18n/index.mjs'
7
- import { detectLocale } from './lib/i18n/detect.mjs'
8
- import { labelWidth, pad } from './lib/width.mjs'
9
- import {
10
- bootstrapUsage, designUsage, rootUsage, statusUsage, updateUsage,
11
- parseBootstrapArgs, parseDesignArgs, parseRootArgs, parseStatusArgs, parseUpdateArgs, preScanLang,
12
- } from './lib/args.mjs'
13
-
14
- // 위 정적 import는 전부 의존성 없는 모듈이다 — 부트스트랩은 npm install 없이 돌아야 하고,
15
- // bootstrap.isolation.test.mjs가 이 불변식을 지킨다. lib/ 나머지는 동적 import로만 닿는다.
16
-
17
- // 저장소 탐지와 기록 읽기는 "있으면 쓴다"로만 한다. 여기서 던지면 언어를
18
- // 정하기도 전에 죽어, 한국어를 모르는 사람이 한국어 오류를 보게 된다.
19
- // 삼킨 오류는 사라지지 않는다 — 기록을 실제로 쓰는 명령이 readRecord를
20
- // 정식으로 다시 불러 그때 지역화된 오류로 던진다.
21
- function tryFindRepoRoot() {
22
- try { return findRepoRoot() } catch { return null }
23
- }
24
-
25
- function tryReadRecord(root) {
26
- try { return readRecord(root) } catch { return null }
27
- }
28
-
29
- // main() 안의 지역변수로 두면 아래 catch에서 안 보인다. main()이 던지는
30
- // 모든 오류가 이 catch를 거치므로, --lang만 주고 AGENT_SETUP_LANG은 안 준
31
- // 사용자의 오류 메시지가 flag를 반영하지 못해 요청한 언어와 어긋난다.
32
- const argv = process.argv.slice(2)
33
- const flag = preScanLang(argv)
34
-
35
- // main()이 로케일을 확정하는 즉시 여기에 담고, 아래 catch가 이 값을 쓴다.
36
- // main() 안의 지역변수로만 두면 catch에서 보이지 않아, 오류 메시지가 성공
37
- // 경로와 다른 언어로 나온다.
38
- let sessionT = null
39
-
40
- // 상태 라벨은 로케일마다 길이가 다르다 — 폭을 상수로 박으면 한쪽이 어긋난다.
41
- const STATUS_KEYS = ['status.installed', 'status.partial', 'status.absent']
42
-
43
- // 대화형 화면은 TUI가 맡는다.
44
- async function openTui(root, { dryRun, skillMode, designDirs, t, localeForced }) {
45
- const { runTui } = await withDeps(() => import('./lib/tui/run.mjs'), t)
46
- // skillMode는 화면 안의 '부트스트랩 실행' 작업이 쓴다. design 경로에서는
47
- // 없으므로 undefined가 넘어가고 runTui의 기본값(auto)이 적용된다.
48
- await runTui(root, { dryRun, skillMode, designDirs, t, localeForced })
49
- }
50
-
51
- // --list / --set 전용 비대화형 경로.
52
- async function runClassic(root, { dryRun, listOnly, setArg, t }) {
53
- const { loadItems } = await withDeps(() => import('./lib/catalog.mjs'), t)
54
- const { scan, planChanges, apply, assertExclusive } = await withDeps(() => import('./lib/engine.mjs'), t)
55
- const { plainLine } = await withDeps(() => import('./lib/tui/progress.mjs'), t)
56
- const items = await loadItems()
57
- const states = await scan(root, items)
58
- const statusWidth = labelWidth(t, STATUS_KEYS)
59
-
60
- if (listOnly) {
61
- for (const s of states) {
62
- const detail = toText(t, s.detail)
63
- console.log(`${pad(t(`status.${s.status}`), statusWidth)} ${s.item.id} — ${s.item.label}${detail ? ` (${detail})` : ''}`)
64
- }
65
- return
66
- }
67
-
68
- const selectedIds = new Set(setArg.split(',').map((s) => s.trim()).filter(Boolean))
69
- const known = new Set(items.map((i) => i.id))
70
- for (const id of selectedIds) if (!known.has(id)) throw new LocalizedError('error.unknownItem', { id })
71
- assertExclusive(items, selectedIds)
72
-
73
- const changes = planChanges(states, selectedIds)
74
- if (changes.length === 0) { console.log(t('apply.noChanges')); return }
75
-
76
- // 비대화형 경로다 — 바를 그리지 않고 평문 한 줄씩 흘린다.
77
- // ANSI 제어문자로 CI 로그를 더럽히지 않기 위해서다.
78
- const results = await apply(root, changes, {
79
- dryRun,
80
- t,
81
- onProgress: (event) => {
82
- const line = plainLine(event, t)
83
- if (line) console.log(line)
84
- },
85
- })
86
- for (const r of results) {
87
- console.log(`${r.ok ? '✔' : '✖'} ${t(`change.${r.action}`)} ${r.item.label}${r.message ? ` — ${toText(t, r.message)}` : ''}`)
88
- }
89
-
90
- const after = await scan(root, items)
91
- console.log(`\n${t('apply.finalState')}`)
92
- for (const s of after) console.log(` ${pad(t(`status.${s.status}`), statusWidth)} ${s.item.label}`)
93
- console.log(`\n${t('apply.seeGitDiff')}`)
94
- if (results.some((r) => !r.ok)) process.exitCode = 1
95
- }
96
-
97
- async function main() {
98
- const root = tryFindRepoRoot()
99
- const record = root === null ? null : tryReadRecord(root)
100
- const locale = resolveLocale({ flag, env: process.env, record, detected: detectLocale(process.env) })
101
- const t = createT(locale)
102
- sessionT = t
103
- // 플래그·환경변수가 로케일을 못박으면 TUI에서 고른 언어는 이번 실행에만
104
- // 살아남는다. 조용히 되돌아가는 대신 화면이 그 사실을 알리게 한다.
105
- const localeForced = isLocaleForced({ flag, env: process.env })
106
-
107
- if (root === null) throw new LocalizedError('error.notGitRepo')
108
-
109
- if (argv[0] === 'bootstrap') {
110
- const opts = parseBootstrapArgs(argv.slice(1), t)
111
- if (opts.help) { console.log(bootstrapUsage(t)); return }
112
- const { failed } = runBootstrap(root, { ...opts, t })
113
- if (failed.length > 0) process.exitCode = 1
114
- return
115
- }
116
-
117
- if (argv[0] === 'update') {
118
- const opts = parseUpdateArgs(argv.slice(1), t)
119
- if (opts.help) { console.log(updateUsage(t)); return }
120
- // 항목 수렴을 위해 스캔이 필요해 의존성 있는 모듈에 닿는다.
121
- const { runUpdate } = await withDeps(() => import('./lib/update.mjs'), t)
122
- await runUpdate(root, { ...opts, t })
123
- return
124
- }
125
-
126
- if (argv[0] === 'status') {
127
- const opts = parseStatusArgs(argv.slice(1), t)
128
- if (opts.help) { console.log(statusUsage(t)); return }
129
- // 항목 스캔이 필요해 의존성 있는 모듈에 닿는다.
130
- const { runStatus } = await withDeps(() => import('./lib/status.mjs'), t)
131
- await runStatus(root, { ...opts, t })
132
- return
133
- }
134
-
135
- if (argv[0] === 'design') {
136
- const opts = parseDesignArgs(argv.slice(1), t)
137
- if (opts.help) { console.log(designUsage(t)); return }
138
- // 플래그 없는 `design`은 통합 화면을 연다 — DESIGN.MD 섹션이 그 안에 있다.
139
- if (opts.interactive) return openTui(root, { ...opts, t, localeForced })
140
- const { runDesign } = await withDeps(() => import('./lib/design-md/flow.mjs'), t)
141
- await runDesign(root, { ...opts, t })
142
- return
143
- }
144
-
145
- const opts = parseRootArgs(argv, t)
146
- if (opts.help) { console.log(rootUsage(t)); return }
147
- if (opts.interactive) return openTui(root, { ...opts, t, localeForced })
148
- await runClassic(root, { ...opts, t })
149
- }
150
-
151
- // 오류는 한 곳에서 지역화한다. LocalizedError의 .message는 영어라
152
- // 스택 트레이스가 읽히고, 여기서 활성 로케일로 다시 렌더한다.
153
- //
154
- // 기록 파일은 다시 읽지 않는다 — 오류 경로에서 그 기록을 실제로 쓴 명령이
155
- // 실패했을 수도 있는 파일을 재차 읽을 이유가 없다. 대신 main()이 이미 정한
156
- // t를 그대로 쓴다. 재차 해석하면 record가 빠져, TUI에서 한국어를 고른 팀이
157
- // 성공은 한국어로 오류는 영어로 받는다. sessionT가 비어 있는 경우는 로케일
158
- // 확정 전에 던진 때뿐이라 플래그·환경변수·OS만으로 정한다.
159
- main().catch((err) => {
160
- const t = sessionT ?? createT(resolveLocale({ flag, env: process.env, detected: detectLocale(process.env) }))
161
- console.error(err.key ? t(err.key, err.params) : err.message)
162
- process.exit(1)
163
- })
1
+ #!/usr/bin/env node
2
+ import { findRepoRoot } from './lib/context.mjs'
3
+ import { runBootstrap } from './lib/bootstrap/flow.mjs'
4
+ import { readRecord } from './lib/bootstrap/record.mjs'
5
+ import { withDeps } from './lib/deps.mjs'
6
+ import { LocalizedError, createT, isLocaleForced, resolveLocale, toText } from './lib/i18n/index.mjs'
7
+ import { detectLocale } from './lib/i18n/detect.mjs'
8
+ import { labelWidth, pad } from './lib/width.mjs'
9
+ import {
10
+ bootstrapUsage, designUsage, rootUsage, statusUsage, updateUsage,
11
+ parseBootstrapArgs, parseDesignArgs, parseRootArgs, parseStatusArgs, parseUpdateArgs, preScanLang,
12
+ } from './lib/args.mjs'
13
+
14
+ // 위 정적 import는 전부 의존성 없는 모듈이다 — 부트스트랩은 npm install 없이 돌아야 하고,
15
+ // bootstrap.isolation.test.mjs가 이 불변식을 지킨다. lib/ 나머지는 동적 import로만 닿는다.
16
+
17
+ // 저장소 탐지와 기록 읽기는 "있으면 쓴다"로만 한다. 여기서 던지면 언어를
18
+ // 정하기도 전에 죽어, 한국어를 모르는 사람이 한국어 오류를 보게 된다.
19
+ // 삼킨 오류는 사라지지 않는다 — 기록을 실제로 쓰는 명령이 readRecord를
20
+ // 정식으로 다시 불러 그때 지역화된 오류로 던진다.
21
+ function tryFindRepoRoot() {
22
+ try { return findRepoRoot() } catch { return null }
23
+ }
24
+
25
+ function tryReadRecord(root) {
26
+ try { return readRecord(root) } catch { return null }
27
+ }
28
+
29
+ // main() 안의 지역변수로 두면 아래 catch에서 안 보인다. main()이 던지는
30
+ // 모든 오류가 이 catch를 거치므로, --lang만 주고 AGENT_SETUP_LANG은 안 준
31
+ // 사용자의 오류 메시지가 flag를 반영하지 못해 요청한 언어와 어긋난다.
32
+ const argv = process.argv.slice(2)
33
+ const flag = preScanLang(argv)
34
+
35
+ // main()이 로케일을 확정하는 즉시 여기에 담고, 아래 catch가 이 값을 쓴다.
36
+ // main() 안의 지역변수로만 두면 catch에서 보이지 않아, 오류 메시지가 성공
37
+ // 경로와 다른 언어로 나온다.
38
+ let sessionT = null
39
+
40
+ // 상태 라벨은 로케일마다 길이가 다르다 — 폭을 상수로 박으면 한쪽이 어긋난다.
41
+ const STATUS_KEYS = ['status.installed', 'status.partial', 'status.absent']
42
+
43
+ // 대화형 화면은 TUI가 맡는다.
44
+ async function openTui(root, { dryRun, skillMode, designDirs, t, localeForced }) {
45
+ const { runTui } = await withDeps(() => import('./lib/tui/run.mjs'), t)
46
+ // skillMode는 화면 안의 '부트스트랩 실행' 작업이 쓴다. design 경로에서는
47
+ // 없으므로 undefined가 넘어가고 runTui의 기본값(auto)이 적용된다.
48
+ await runTui(root, { dryRun, skillMode, designDirs, t, localeForced })
49
+ }
50
+
51
+ // --list / --set 전용 비대화형 경로.
52
+ async function runClassic(root, { dryRun, listOnly, setArg, t }) {
53
+ const { loadItems } = await withDeps(() => import('./lib/catalog.mjs'), t)
54
+ const { scan, planChanges, apply, assertExclusive } = await withDeps(() => import('./lib/engine.mjs'), t)
55
+ const { plainLine } = await withDeps(() => import('./lib/tui/progress.mjs'), t)
56
+ const items = await loadItems()
57
+ const states = await scan(root, items)
58
+ const statusWidth = labelWidth(t, STATUS_KEYS)
59
+
60
+ if (listOnly) {
61
+ for (const s of states) {
62
+ const detail = toText(t, s.detail)
63
+ console.log(`${pad(t(`status.${s.status}`), statusWidth)} ${s.item.id} — ${s.item.label}${detail ? ` (${detail})` : ''}`)
64
+ }
65
+ return
66
+ }
67
+
68
+ const selectedIds = new Set(setArg.split(',').map((s) => s.trim()).filter(Boolean))
69
+ const known = new Set(items.map((i) => i.id))
70
+ for (const id of selectedIds) if (!known.has(id)) throw new LocalizedError('error.unknownItem', { id })
71
+ assertExclusive(items, selectedIds)
72
+
73
+ const changes = planChanges(states, selectedIds)
74
+ if (changes.length === 0) { console.log(t('apply.noChanges')); return }
75
+
76
+ // 비대화형 경로다 — 바를 그리지 않고 평문 한 줄씩 흘린다.
77
+ // ANSI 제어문자로 CI 로그를 더럽히지 않기 위해서다.
78
+ const results = await apply(root, changes, {
79
+ dryRun,
80
+ t,
81
+ onProgress: (event) => {
82
+ const line = plainLine(event, t)
83
+ if (line) console.log(line)
84
+ },
85
+ })
86
+ for (const r of results) {
87
+ console.log(`${r.ok ? '✔' : '✖'} ${t(`change.${r.action}`)} ${r.item.label}${r.message ? ` — ${toText(t, r.message)}` : ''}`)
88
+ }
89
+
90
+ const after = await scan(root, items)
91
+ console.log(`\n${t('apply.finalState')}`)
92
+ for (const s of after) console.log(` ${pad(t(`status.${s.status}`), statusWidth)} ${s.item.label}`)
93
+ console.log(`\n${t('apply.seeGitDiff')}`)
94
+ if (results.some((r) => !r.ok)) process.exitCode = 1
95
+ }
96
+
97
+ async function main() {
98
+ const root = tryFindRepoRoot()
99
+ const record = root === null ? null : tryReadRecord(root)
100
+ const locale = resolveLocale({ flag, env: process.env, record, detected: detectLocale(process.env) })
101
+ const t = createT(locale)
102
+ sessionT = t
103
+ // 플래그·환경변수가 로케일을 못박으면 TUI에서 고른 언어는 이번 실행에만
104
+ // 살아남는다. 조용히 되돌아가는 대신 화면이 그 사실을 알리게 한다.
105
+ const localeForced = isLocaleForced({ flag, env: process.env })
106
+
107
+ if (root === null) throw new LocalizedError('error.notGitRepo')
108
+
109
+ if (argv[0] === 'bootstrap') {
110
+ const opts = parseBootstrapArgs(argv.slice(1), t)
111
+ if (opts.help) { console.log(bootstrapUsage(t)); return }
112
+ const { failed } = runBootstrap(root, { ...opts, t })
113
+ if (failed.length > 0) process.exitCode = 1
114
+ return
115
+ }
116
+
117
+ if (argv[0] === 'update') {
118
+ const opts = parseUpdateArgs(argv.slice(1), t)
119
+ if (opts.help) { console.log(updateUsage(t)); return }
120
+ // 항목 수렴을 위해 스캔이 필요해 의존성 있는 모듈에 닿는다.
121
+ const { runUpdate } = await withDeps(() => import('./lib/update.mjs'), t)
122
+ await runUpdate(root, { ...opts, t })
123
+ return
124
+ }
125
+
126
+ if (argv[0] === 'status') {
127
+ const opts = parseStatusArgs(argv.slice(1), t)
128
+ if (opts.help) { console.log(statusUsage(t)); return }
129
+ // 항목 스캔이 필요해 의존성 있는 모듈에 닿는다.
130
+ const { runStatus } = await withDeps(() => import('./lib/status.mjs'), t)
131
+ await runStatus(root, { ...opts, t })
132
+ return
133
+ }
134
+
135
+ if (argv[0] === 'design') {
136
+ const opts = parseDesignArgs(argv.slice(1), t)
137
+ if (opts.help) { console.log(designUsage(t)); return }
138
+ // 플래그 없는 `design`은 통합 화면을 연다 — DESIGN.MD 섹션이 그 안에 있다.
139
+ if (opts.interactive) return openTui(root, { ...opts, t, localeForced })
140
+ const { runDesign } = await withDeps(() => import('./lib/design-md/flow.mjs'), t)
141
+ await runDesign(root, { ...opts, t })
142
+ return
143
+ }
144
+
145
+ const opts = parseRootArgs(argv, t)
146
+ if (opts.help) { console.log(rootUsage(t)); return }
147
+ if (opts.interactive) return openTui(root, { ...opts, t, localeForced })
148
+ await runClassic(root, { ...opts, t })
149
+ }
150
+
151
+ // 오류는 한 곳에서 지역화한다. LocalizedError의 .message는 영어라
152
+ // 스택 트레이스가 읽히고, 여기서 활성 로케일로 다시 렌더한다.
153
+ //
154
+ // 기록 파일은 다시 읽지 않는다 — 오류 경로에서 그 기록을 실제로 쓴 명령이
155
+ // 실패했을 수도 있는 파일을 재차 읽을 이유가 없다. 대신 main()이 이미 정한
156
+ // t를 그대로 쓴다. 재차 해석하면 record가 빠져, TUI에서 한국어를 고른 팀이
157
+ // 성공은 한국어로 오류는 영어로 받는다. sessionT가 비어 있는 경우는 로케일
158
+ // 확정 전에 던진 때뿐이라 플래그·환경변수·OS만으로 정한다.
159
+ main().catch((err) => {
160
+ const t = sessionT ?? createT(resolveLocale({ flag, env: process.env, detected: detectLocale(process.env) }))
161
+ console.error(err.key ? t(err.key, err.params) : err.message)
162
+ process.exit(1)
163
+ })