@rch4com/agent-setup 1.1.0 → 1.3.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 (55) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +197 -55
  3. package/install.mjs +152 -101
  4. package/lib/args.mjs +276 -311
  5. package/lib/bootstrap/adapter.mjs +96 -95
  6. package/lib/bootstrap/apply.mjs +380 -379
  7. package/lib/bootstrap/flow.mjs +73 -70
  8. package/lib/bootstrap/record.mjs +154 -142
  9. package/lib/bootstrap/templates.mjs +148 -148
  10. package/lib/bootstrap/text.mjs +19 -19
  11. package/lib/catalog.mjs +213 -145
  12. package/lib/claude-plugins.mjs +64 -64
  13. package/lib/clis.mjs +102 -102
  14. package/lib/context.mjs +66 -65
  15. package/lib/deps.mjs +5 -7
  16. package/lib/design-md/catalog.json +454 -454
  17. package/lib/design-md/catalog.mjs +266 -268
  18. package/lib/design-md/flow.mjs +181 -166
  19. package/lib/design-md/open.mjs +51 -50
  20. package/lib/design-md/providers/awesome-design-md.mjs +117 -114
  21. package/lib/design-md/providers/index.mjs +9 -9
  22. package/lib/design-md/scan.mjs +14 -10
  23. package/lib/engine.mjs +48 -44
  24. package/lib/gitignore.mjs +18 -18
  25. package/lib/i18n/catalog/en.mjs +369 -0
  26. package/lib/i18n/catalog/ko.mjs +359 -0
  27. package/lib/i18n/detect.mjs +39 -0
  28. package/lib/i18n/index.mjs +93 -0
  29. package/lib/items/mcp.codebase-memory.mjs +6 -6
  30. package/lib/items/mcp.graphify.mjs +8 -0
  31. package/lib/items/mcp.headroom.mjs +8 -0
  32. package/lib/items/mcp.notion.mjs +6 -6
  33. package/lib/items/mcp.supabase.mjs +6 -6
  34. package/lib/items/mcp.vercel.mjs +6 -6
  35. package/lib/items/plugin.bkit.mjs +7 -7
  36. package/lib/items/plugin.ecc.mjs +11 -0
  37. package/lib/items/plugin.impeccable.mjs +14 -0
  38. package/lib/items/plugin.mattpocock-skills.mjs +2 -2
  39. package/lib/items/plugin.ponytail.mjs +104 -0
  40. package/lib/items/plugin.superpowers.mjs +7 -7
  41. package/lib/items/plugin.understand-anything.mjs +10 -0
  42. package/lib/items/skill.caveman.mjs +7 -0
  43. package/lib/items/skill.gsd.mjs +25 -24
  44. package/lib/items/skill.gstack.mjs +40 -39
  45. package/lib/items/skill.karpathy.mjs +7 -0
  46. package/lib/items/skill.taste.mjs +9 -0
  47. package/lib/jsonfile.mjs +40 -40
  48. package/lib/status.mjs +93 -92
  49. package/lib/tomlfile.mjs +60 -60
  50. package/lib/tui/render.mjs +41 -63
  51. package/lib/tui/rows.mjs +278 -206
  52. package/lib/tui/run.mjs +64 -28
  53. package/lib/update.mjs +85 -85
  54. package/lib/width.mjs +57 -0
  55. package/package.json +36 -36
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 rch4com
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rch4com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,55 +1,197 @@
1
- # agent-setup
2
-
3
- Claude Code, Codex, Gemini CLI, OpenCode, Kilo Code, Kiro, Kimi Code,
4
- Grok Build, Antigravity, GitHub Copilot CLI, VS Code Copilot을 **한 저장소에서
5
- 함께** 쓰기 위한 저장소 범위 부트스트랩과 선택 항목 설치기입니다.
6
-
7
- 공통 지침은 루트 `AGENTS.md` 하나, 공통 스킬은 `.agents/skills` 하나로 두고,
8
- 도구별 설정 파일만 각 도구가 읽는 위치에 만듭니다.
9
-
10
- ## 사용법
11
-
12
- Git 저장소 안에서 실행합니다.
13
-
14
- ```bash
15
- # 배선 — 공통 지침·스킬·도구별 설정 파일을 만든다
16
- npx @rch4com/agent-setup bootstrap
17
-
18
- # 무엇이 만들어질지만 확인한다
19
- npx @rch4com/agent-setup bootstrap --dry-run
20
-
21
- # 플러그인·MCP·스킬·DESIGN.md를 골라 설치하는 대화형 화면
22
- npx @rch4com/agent-setup
23
-
24
- # 관리 파일을 최신 템플릿으로 갱신한다 (사용자가 고친 파일은 건드리지 않는다)
25
- npx @rch4com/agent-setup@latest update
26
-
27
- # 의도 / 실제 / 버전을 비교한다
28
- npx @rch4com/agent-setup status
29
- ```
30
-
31
- 설치하면 실행 명령은 `agent-setup`으로 짧아집니다. 이름에 스코프가 붙은 것은
32
- 스코프 없는 `agent-setup`이 npm의 유사 이름 제한에 걸리기 때문입니다.
33
-
34
- ## 안전 원칙
35
-
36
- - 반드시 Git 저장소 안에서만 실행되며, 저장소 루트 밖에는 쓰지 않습니다.
37
- - 홈 디렉터리의 글로벌 설정을 읽거나 수정하지 않습니다.
38
- - 기존 설정 파일을 덮어쓰지 않습니다.
39
- - 반복 실행할 수 있습니다.
40
-
41
- ## 요구 사항
42
-
43
- Node.js 20 이상.
44
-
45
- ## 문서
46
-
47
- 생성되는 구조, 도구별 연결 방식, 설치 가능한 항목, DESIGN.md 라이브러리 등
48
- 상세 문서는 GitHub에 있습니다.
49
-
50
- - [사용법과 생성 구조](https://github.com/rch4com/Agent-Setup/blob/main/AgentSetup-README.md)
51
- - [저장소](https://github.com/rch4com/Agent-Setup)
52
-
53
- ## 라이선스
54
-
55
- MIT
1
+ # agent-setup
2
+
3
+ Repository-scoped bootstrap for using many coding agents in one repo — plus an
4
+ optional picker for plugins, MCP servers, skills, and DESIGN.md documents.
5
+
6
+ Supports **Claude Code, Codex, Gemini CLI, OpenCode, Kilo Code, Kiro, Kimi Code,
7
+ Grok Build, Antigravity, GitHub Copilot CLI, and VS Code Copilot.**
8
+
9
+ One shared instruction file (`AGENTS.md`) and one shared skills directory
10
+ (`.agents/skills`) serve every tool. Only the per-tool config files are written,
11
+ each in the location that tool actually reads.
12
+
13
+ [한국어 문서는 아래에 있습니다 ↓](#한국어)
14
+
15
+ ## Usage
16
+
17
+ Run inside a Git repository. Nothing needs to be copied into your repo.
18
+
19
+ ```bash
20
+ # Wire up shared instructions, skills, and per-tool config files
21
+ npx @rch4com/agent-setup bootstrap
22
+
23
+ # Show what would be created, without writing anything
24
+ npx @rch4com/agent-setup bootstrap --dry-run
25
+
26
+ # Interactive picker for plugins, MCP servers, skills, and DESIGN.md
27
+ npx @rch4com/agent-setup
28
+ ```
29
+
30
+ The package name is scoped, but the installed command is just `agent-setup` —
31
+ the long form only appears when invoking it through `npx`.
32
+
33
+ The screen speaks your OS language by default, falling back to English. Pass
34
+ `--lang en|ko` (or set `AGENT_SETUP_LANG`) to pick one for this run. The
35
+ interactive screen's first row also switches it, and the choice is remembered
36
+ in `.agent-kit/agent-setup.json`.
37
+
38
+ ## Keeping in sync
39
+
40
+ ```bash
41
+ npx @rch4com/agent-setup@latest update # pull in improved templates
42
+ npx @rch4com/agent-setup@latest update --dry-run # preview the changes
43
+ npx @rch4com/agent-setup status # intent / reality / version
44
+ ```
45
+
46
+ `update` only rewrites files that are **still exactly as this tool wrote them**.
47
+ The decision is made per file, using hashes stored in the install record
48
+ (`.agent-kit/agent-setup.json`, which is committed).
49
+
50
+ | Situation | What happens |
51
+ |---|---|
52
+ | Hash matches the record | Replaced with the new template |
53
+ | Hash differs | You edited it → **left alone**, reported as drift |
54
+ | File missing | Created (this is how support for new tools arrives) |
55
+ | No hash in the record | Origin unknown → left alone |
56
+
57
+ The managed blocks in `CLAUDE.md` and `GEMINI.md` are replaced **between the
58
+ markers only**, so anything you wrote around them survives.
59
+
60
+ Hashes are computed after normalizing line endings to LF, so a CRLF checkout on
61
+ Windows is not mistaken for drift.
62
+
63
+ Use `update --force` to overwrite drifted files too. It refuses to run unless the
64
+ working tree is clean, because git is the only way to undo it.
65
+
66
+ ### Adopting a repository that already uses this
67
+
68
+ Repositories that vendored the installer have no record yet.
69
+
70
+ ```bash
71
+ npx @rch4com/agent-setup bootstrap --adopt
72
+ ```
73
+
74
+ This writes no files — it only creates the record. Files are adopted **only if
75
+ they match this version's template**, because stamping a hash onto a file you had
76
+ already customized would let the next `update` erase your changes. Unadopted files
77
+ show up in `status` and can be pulled in later with `update --force`.
78
+
79
+ ## Safety
80
+
81
+ - Runs only inside a Git repository, and never writes outside the repository root.
82
+ - Never reads or modifies global configuration in your home directory.
83
+ - Never overwrites existing configuration files.
84
+ - Safe to run repeatedly.
85
+
86
+ ## Requirements
87
+
88
+ Node.js 20 or later.
89
+
90
+ ## Documentation
91
+
92
+ The generated layout, how each tool is wired, the installable items, and the
93
+ DESIGN.md library are documented on GitHub.
94
+
95
+ - [Usage and generated structure](https://github.com/rch4com/Agent-Setup/blob/main/AgentSetup-README.md)
96
+ - [Repository](https://github.com/rch4com/Agent-Setup)
97
+
98
+ ## License
99
+
100
+ MIT
101
+
102
+ ---
103
+
104
+ # 한국어
105
+
106
+ Claude Code, Codex, Gemini CLI, OpenCode, Kilo Code, Kiro, Kimi Code,
107
+ Grok Build, Antigravity, GitHub Copilot CLI, VS Code Copilot을 **한 저장소에서
108
+ 함께** 쓰기 위한 저장소 범위 부트스트랩과 선택 항목 설치기입니다.
109
+
110
+ 공통 지침은 루트 `AGENTS.md` 하나, 공통 스킬은 `.agents/skills` 하나로 두고,
111
+ 도구별 설정 파일만 각 도구가 읽는 위치에 만듭니다.
112
+
113
+ ## 사용법
114
+
115
+ Git 저장소 안에서 실행합니다. 저장소에 복사해 둘 파일이 없습니다.
116
+
117
+ ```bash
118
+ # 배선 — 공통 지침·스킬·도구별 설정 파일을 만든다
119
+ npx @rch4com/agent-setup bootstrap
120
+
121
+ # 무엇이 만들어질지만 확인한다
122
+ npx @rch4com/agent-setup bootstrap --dry-run
123
+
124
+ # 플러그인·MCP·스킬·DESIGN.md를 골라 설치하는 대화형 화면
125
+ npx @rch4com/agent-setup
126
+ ```
127
+
128
+ 패키지 이름에는 스코프가 붙지만 설치 후 실행 명령은 `agent-setup`입니다 —
129
+ 길어지는 것은 `npx`로 직접 부를 때뿐입니다.
130
+
131
+ 화면 언어는 기본으로 OS 언어를 따르고, 지원하지 않으면 영어로 갑니다.
132
+ 이번 실행만 다른 언어로 보려면 `--lang en|ko`를 주거나 `AGENT_SETUP_LANG`을
133
+ 설정하세요. 대화형 화면의 첫 행에서도 바꿀 수 있고, 고른 언어는
134
+ `.agent-kit/agent-setup.json`에 저장되어 다음 실행에도 이어집니다.
135
+
136
+ ## 최신으로 갱신하기
137
+
138
+ ```bash
139
+ npx @rch4com/agent-setup@latest update # 개선된 템플릿을 받아온다
140
+ npx @rch4com/agent-setup@latest update --dry-run # 무엇이 바뀔지만 확인
141
+ npx @rch4com/agent-setup status # 의도 / 실제 / 버전 비교
142
+ ```
143
+
144
+ `update`는 **우리가 쓴 그대로인 파일만** 다시 씁니다. 판정은 파일마다 따로 하며,
145
+ 근거는 커밋되는 설치 기록(`.agent-kit/agent-setup.json`)에 남은 해시입니다.
146
+
147
+ | 상황 | 처리 |
148
+ |---|---|
149
+ | 기록된 해시와 일치 | 새 템플릿으로 교체 |
150
+ | 해시가 다름 | 사용자가 고쳤음 → **건드리지 않고** 드리프트로 보고 |
151
+ | 파일이 없음 | 새로 생성 (새 도구 지원이 이 경로로 들어옵니다) |
152
+ | 기록에 해시가 없음 | 출처 불명 → 건드리지 않음 |
153
+
154
+ `CLAUDE.md`·`GEMINI.md`의 관리 블록은 **마커 사이만** 교체하므로 주변에 쓴
155
+ 내용은 그대로 남습니다.
156
+
157
+ 해시는 줄바꿈을 LF로 정규화한 뒤 계산하므로, Windows에서 CRLF로 체크아웃해도
158
+ 드리프트로 오판하지 않습니다.
159
+
160
+ 드리프트 파일까지 반영하려면 `update --force`를 씁니다. git이 유일한 되돌리기
161
+ 수단이므로 워킹트리가 깨끗할 때만 동작합니다.
162
+
163
+ ### 이미 쓰던 저장소 끌어오기
164
+
165
+ 설치기를 복사해 쓰던 저장소에는 기록이 없습니다.
166
+
167
+ ```bash
168
+ npx @rch4com/agent-setup bootstrap --adopt
169
+ ```
170
+
171
+ 파일을 만들지 않고 기록만 만듭니다. **이 버전의 템플릿과 같은 파일만** 채택하는데,
172
+ 이미 고쳐 둔 파일에 해시를 박으면 다음 `update`가 그 수정을 지워버리기
173
+ 때문입니다. 채택되지 않은 파일은 `status`에 나오고, 원할 때 `update --force`로
174
+ 들여올 수 있습니다.
175
+
176
+ ## 안전 원칙
177
+
178
+ - 반드시 Git 저장소 안에서만 실행되며, 저장소 루트 밖에는 쓰지 않습니다.
179
+ - 홈 디렉터리의 글로벌 설정을 읽거나 수정하지 않습니다.
180
+ - 기존 설정 파일을 덮어쓰지 않습니다.
181
+ - 반복 실행할 수 있습니다.
182
+
183
+ ## 요구 사항
184
+
185
+ Node.js 20 이상.
186
+
187
+ ## 문서
188
+
189
+ 생성되는 구조, 도구별 연결 방식, 설치 가능한 항목, DESIGN.md 라이브러리 등
190
+ 상세 문서는 GitHub에 있습니다.
191
+
192
+ - [사용법과 생성 구조](https://github.com/rch4com/Agent-Setup/blob/main/AgentSetup-README.md)
193
+ - [저장소](https://github.com/rch4com/Agent-Setup)
194
+
195
+ ## 라이선스
196
+
197
+ MIT
package/install.mjs CHANGED
@@ -1,101 +1,152 @@
1
- #!/usr/bin/env node
2
- import { findRepoRoot } from './lib/context.mjs'
3
- import { runBootstrap } from './lib/bootstrap/flow.mjs'
4
- import { withDeps } from './lib/deps.mjs'
5
- import {
6
- BOOTSTRAP_USAGE, DESIGN_USAGE, ROOT_USAGE, STATUS_USAGE, UPDATE_USAGE,
7
- parseBootstrapArgs, parseDesignArgs, parseRootArgs, parseStatusArgs, parseUpdateArgs,
8
- } from './lib/args.mjs'
9
-
10
- // 위 정적 import는 전부 의존성 없는 모듈이다 — 부트스트랩은 npm install 없이 돌아야 하고,
11
- // bootstrap.isolation.test.mjs가 이 불변식을 지킨다. lib/ 나머지는 동적 import로만 닿는다.
12
-
13
- const STATUS_LABEL = { installed: '설치됨', partial: '일부 설치됨', absent: '미설치' }
14
- const ACTION_LABEL = { install: '설치', complete: '보완 설치', uninstall: '제거' }
15
-
16
- // 대화형 화면은 TUI가 맡는다.
17
- async function openTui(root, { dryRun, skillMode, designDirs }) {
18
- const { runTui } = await withDeps(() => import('./lib/tui/run.mjs'))
19
- // skillMode는 화면 안의 '부트스트랩 실행' 작업이 쓴다. design 경로에서는
20
- // 없으므로 undefined가 넘어가고 runTui의 기본값(auto)이 적용된다.
21
- await runTui(root, { dryRun, skillMode, designDirs })
22
- }
23
-
24
- // --list / --set 전용 비대화형 경로.
25
- async function runClassic(root, { dryRun, listOnly, setArg }) {
26
- const { loadItems } = await withDeps(() => import('./lib/catalog.mjs'))
27
- const { scan, planChanges, apply } = await withDeps(() => import('./lib/engine.mjs'))
28
- const items = await loadItems()
29
- const states = await scan(root, items)
30
-
31
- if (listOnly) {
32
- for (const s of states) console.log(`${STATUS_LABEL[s.status].padEnd(7)} ${s.item.id} — ${s.item.label}${s.detail ? ` (${s.detail})` : ''}`)
33
- return
34
- }
35
-
36
- const selectedIds = new Set(setArg.split(',').map((s) => s.trim()).filter(Boolean))
37
- const known = new Set(items.map((i) => i.id))
38
- for (const id of selectedIds) if (!known.has(id)) throw new Error(`알 수 없는 항목: ${id}`)
39
-
40
- const changes = planChanges(states, selectedIds)
41
- if (changes.length === 0) { console.log('변경할 항목이 없습니다.'); return }
42
-
43
- const results = await apply(root, changes, { dryRun })
44
- for (const r of results) {
45
- console.log(`${r.ok ? '✔' : '✖'} ${ACTION_LABEL[r.action]} ${r.item.label}${r.message ? ` — ${r.message}` : ''}`)
46
- }
47
-
48
- const after = await scan(root, items)
49
- console.log('\n최종 상태:')
50
- for (const s of after) console.log(` ${STATUS_LABEL[s.status].padEnd(7)} ${s.item.label}`)
51
- console.log('\n설정 파일 변경 내용은 git diff로 확인할 수 있습니다.')
52
- if (results.some((r) => !r.ok)) process.exitCode = 1
53
- }
54
-
55
- async function main() {
56
- const argv = process.argv.slice(2)
57
- const root = findRepoRoot()
58
-
59
- if (argv[0] === 'bootstrap') {
60
- const opts = parseBootstrapArgs(argv.slice(1))
61
- if (opts.help) { console.log(BOOTSTRAP_USAGE); return }
62
- const { failed } = runBootstrap(root, opts)
63
- if (failed.length > 0) process.exitCode = 1
64
- return
65
- }
66
-
67
- if (argv[0] === 'update') {
68
- const opts = parseUpdateArgs(argv.slice(1))
69
- if (opts.help) { console.log(UPDATE_USAGE); return }
70
- // 항목 수렴을 위해 스캔이 필요해 의존성 있는 모듈에 닿는다.
71
- const { runUpdate } = await withDeps(() => import('./lib/update.mjs'))
72
- await runUpdate(root, opts)
73
- return
74
- }
75
-
76
- if (argv[0] === 'status') {
77
- const opts = parseStatusArgs(argv.slice(1))
78
- if (opts.help) { console.log(STATUS_USAGE); return }
79
- // 항목 스캔이 필요해 의존성 있는 모듈에 닿는다.
80
- const { runStatus } = await withDeps(() => import('./lib/status.mjs'))
81
- await runStatus(root, opts)
82
- return
83
- }
84
-
85
- if (argv[0] === 'design') {
86
- const opts = parseDesignArgs(argv.slice(1))
87
- if (opts.help) { console.log(DESIGN_USAGE); return }
88
- // 플래그 없는 `design`은 통합 화면을 연다 — DESIGN.MD 섹션이 그 안에 있다.
89
- if (opts.interactive) return openTui(root, opts)
90
- const { runDesign } = await withDeps(() => import('./lib/design-md/flow.mjs'))
91
- await runDesign(root, opts)
92
- return
93
- }
94
-
95
- const opts = parseRootArgs(argv)
96
- if (opts.help) { console.log(ROOT_USAGE); return }
97
- if (opts.interactive) return openTui(root, opts)
98
- await runClassic(root, opts)
99
- }
100
-
101
- main().catch((err) => { console.error(err.message); process.exit(1) })
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 } = await withDeps(() => import('./lib/engine.mjs'), t)
55
+ const items = await loadItems()
56
+ const states = await scan(root, items)
57
+ const statusWidth = labelWidth(t, STATUS_KEYS)
58
+
59
+ if (listOnly) {
60
+ for (const s of states) {
61
+ const detail = toText(t, s.detail)
62
+ console.log(`${pad(t(`status.${s.status}`), statusWidth)} ${s.item.id} — ${s.item.label}${detail ? ` (${detail})` : ''}`)
63
+ }
64
+ return
65
+ }
66
+
67
+ const selectedIds = new Set(setArg.split(',').map((s) => s.trim()).filter(Boolean))
68
+ const known = new Set(items.map((i) => i.id))
69
+ for (const id of selectedIds) if (!known.has(id)) throw new LocalizedError('error.unknownItem', { id })
70
+
71
+ const changes = planChanges(states, selectedIds)
72
+ if (changes.length === 0) { console.log(t('apply.noChanges')); return }
73
+
74
+ const results = await apply(root, changes, { dryRun, t })
75
+ for (const r of results) {
76
+ console.log(`${r.ok ? '✔' : '✖'} ${t(`change.${r.action}`)} ${r.item.label}${r.message ? ` — ${toText(t, r.message)}` : ''}`)
77
+ }
78
+
79
+ const after = await scan(root, items)
80
+ console.log(`\n${t('apply.finalState')}`)
81
+ for (const s of after) console.log(` ${pad(t(`status.${s.status}`), statusWidth)} ${s.item.label}`)
82
+ console.log(`\n${t('apply.seeGitDiff')}`)
83
+ if (results.some((r) => !r.ok)) process.exitCode = 1
84
+ }
85
+
86
+ async function main() {
87
+ const root = tryFindRepoRoot()
88
+ const record = root === null ? null : tryReadRecord(root)
89
+ const locale = resolveLocale({ flag, env: process.env, record, detected: detectLocale(process.env) })
90
+ const t = createT(locale)
91
+ sessionT = t
92
+ // 플래그·환경변수가 로케일을 못박으면 TUI에서 고른 언어는 이번 실행에만
93
+ // 살아남는다. 조용히 되돌아가는 대신 화면이 그 사실을 알리게 한다.
94
+ const localeForced = isLocaleForced({ flag, env: process.env })
95
+
96
+ if (root === null) throw new LocalizedError('error.notGitRepo')
97
+
98
+ if (argv[0] === 'bootstrap') {
99
+ const opts = parseBootstrapArgs(argv.slice(1), t)
100
+ if (opts.help) { console.log(bootstrapUsage(t)); return }
101
+ const { failed } = runBootstrap(root, { ...opts, t })
102
+ if (failed.length > 0) process.exitCode = 1
103
+ return
104
+ }
105
+
106
+ if (argv[0] === 'update') {
107
+ const opts = parseUpdateArgs(argv.slice(1), t)
108
+ if (opts.help) { console.log(updateUsage(t)); return }
109
+ // 항목 수렴을 위해 스캔이 필요해 의존성 있는 모듈에 닿는다.
110
+ const { runUpdate } = await withDeps(() => import('./lib/update.mjs'), t)
111
+ await runUpdate(root, { ...opts, t })
112
+ return
113
+ }
114
+
115
+ if (argv[0] === 'status') {
116
+ const opts = parseStatusArgs(argv.slice(1), t)
117
+ if (opts.help) { console.log(statusUsage(t)); return }
118
+ // 항목 스캔이 필요해 의존성 있는 모듈에 닿는다.
119
+ const { runStatus } = await withDeps(() => import('./lib/status.mjs'), t)
120
+ await runStatus(root, { ...opts, t })
121
+ return
122
+ }
123
+
124
+ if (argv[0] === 'design') {
125
+ const opts = parseDesignArgs(argv.slice(1), t)
126
+ if (opts.help) { console.log(designUsage(t)); return }
127
+ // 플래그 없는 `design`은 통합 화면을 연다 — DESIGN.MD 섹션이 그 안에 있다.
128
+ if (opts.interactive) return openTui(root, { ...opts, t, localeForced })
129
+ const { runDesign } = await withDeps(() => import('./lib/design-md/flow.mjs'), t)
130
+ await runDesign(root, { ...opts, t })
131
+ return
132
+ }
133
+
134
+ const opts = parseRootArgs(argv, t)
135
+ if (opts.help) { console.log(rootUsage(t)); return }
136
+ if (opts.interactive) return openTui(root, { ...opts, t, localeForced })
137
+ await runClassic(root, { ...opts, t })
138
+ }
139
+
140
+ // 오류는 한 곳에서 지역화한다. LocalizedError의 .message는 영어라
141
+ // 스택 트레이스가 읽히고, 여기서 활성 로케일로 다시 렌더한다.
142
+ //
143
+ // 기록 파일은 다시 읽지 않는다 — 오류 경로에서 그 기록을 실제로 쓴 명령이
144
+ // 실패했을 수도 있는 파일을 재차 읽을 이유가 없다. 대신 main()이 이미 정한
145
+ // t를 그대로 쓴다. 재차 해석하면 record가 빠져, TUI에서 한국어를 고른 팀이
146
+ // 성공은 한국어로 오류는 영어로 받는다. sessionT가 비어 있는 경우는 로케일
147
+ // 확정 전에 던진 때뿐이라 플래그·환경변수·OS만으로 정한다.
148
+ main().catch((err) => {
149
+ const t = sessionT ?? createT(resolveLocale({ flag, env: process.env, detected: detectLocale(process.env) }))
150
+ console.error(err.key ? t(err.key, err.params) : err.message)
151
+ process.exit(1)
152
+ })