@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.
- package/LICENSE +21 -21
- package/README.md +197 -55
- package/install.mjs +152 -101
- package/lib/args.mjs +276 -311
- package/lib/bootstrap/adapter.mjs +96 -95
- package/lib/bootstrap/apply.mjs +380 -379
- package/lib/bootstrap/flow.mjs +73 -70
- package/lib/bootstrap/record.mjs +154 -142
- package/lib/bootstrap/templates.mjs +148 -148
- package/lib/bootstrap/text.mjs +19 -19
- package/lib/catalog.mjs +213 -145
- package/lib/claude-plugins.mjs +64 -64
- package/lib/clis.mjs +102 -102
- package/lib/context.mjs +66 -65
- package/lib/deps.mjs +5 -7
- package/lib/design-md/catalog.json +454 -454
- package/lib/design-md/catalog.mjs +266 -268
- package/lib/design-md/flow.mjs +181 -166
- package/lib/design-md/open.mjs +51 -50
- package/lib/design-md/providers/awesome-design-md.mjs +117 -114
- package/lib/design-md/providers/index.mjs +9 -9
- package/lib/design-md/scan.mjs +14 -10
- package/lib/engine.mjs +48 -44
- package/lib/gitignore.mjs +18 -18
- package/lib/i18n/catalog/en.mjs +369 -0
- package/lib/i18n/catalog/ko.mjs +359 -0
- package/lib/i18n/detect.mjs +39 -0
- package/lib/i18n/index.mjs +93 -0
- package/lib/items/mcp.codebase-memory.mjs +6 -6
- package/lib/items/mcp.graphify.mjs +8 -0
- package/lib/items/mcp.headroom.mjs +8 -0
- package/lib/items/mcp.notion.mjs +6 -6
- package/lib/items/mcp.supabase.mjs +6 -6
- package/lib/items/mcp.vercel.mjs +6 -6
- package/lib/items/plugin.bkit.mjs +7 -7
- package/lib/items/plugin.ecc.mjs +11 -0
- package/lib/items/plugin.impeccable.mjs +14 -0
- package/lib/items/plugin.mattpocock-skills.mjs +2 -2
- package/lib/items/plugin.ponytail.mjs +104 -0
- package/lib/items/plugin.superpowers.mjs +7 -7
- package/lib/items/plugin.understand-anything.mjs +10 -0
- package/lib/items/skill.caveman.mjs +7 -0
- package/lib/items/skill.gsd.mjs +25 -24
- package/lib/items/skill.gstack.mjs +40 -39
- package/lib/items/skill.karpathy.mjs +7 -0
- package/lib/items/skill.taste.mjs +9 -0
- package/lib/jsonfile.mjs +40 -40
- package/lib/status.mjs +93 -92
- package/lib/tomlfile.mjs +60 -60
- package/lib/tui/render.mjs +41 -63
- package/lib/tui/rows.mjs +278 -206
- package/lib/tui/run.mjs +64 -28
- package/lib/update.mjs +85 -85
- package/lib/width.mjs +57 -0
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from './lib/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
})
|