@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/lib/args.mjs
CHANGED
|
@@ -1,311 +1,276 @@
|
|
|
1
|
-
// 인자 파싱만 한다 — 파일시스템도 콘솔도 프로세스도 모른다.
|
|
2
|
-
// install.mjs에서 분리한 이유: 진입점은 최상위에서 main()을 실행하므로
|
|
3
|
-
// import하는 순간 설치기가 돌아버려 순수 함수를 단위 테스트할 수 없었다.
|
|
4
|
-
//
|
|
5
|
-
// 오류는 전부 throw다. 종료 코드는 진입점 한 곳에서 정한다 —
|
|
6
|
-
// 예전에는 --set만 process.exit(2)로 빠져나가 같은 사용자 오류가
|
|
7
|
-
// 경로에 따라 1과 2로 갈렸다.
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
export function
|
|
140
|
-
const
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
let
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
set,
|
|
278
|
-
preview,
|
|
279
|
-
sync,
|
|
280
|
-
designDirs: collectValues(argv, '--design-dir'),
|
|
281
|
-
interactive: !list && set === null && preview === null && sync === null,
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// `bootstrap`에 허용되는 플래그: --dry-run, --adopt,
|
|
286
|
-
// --skill-mode(+값)/--skill-mode=값, -h/--help.
|
|
287
|
-
// 그 외 인자는 거부한다 — 조용히 삼켜지는 것을 막기 위해서다.
|
|
288
|
-
export function parseBootstrapArgs(argv) {
|
|
289
|
-
if (wantsHelp(argv)) return { dryRun: false, skillMode: 'auto', adopt: false, help: true }
|
|
290
|
-
assertKnownArgs(argv, BOOTSTRAP_SPEC, BOOTSTRAP_USAGE)
|
|
291
|
-
return {
|
|
292
|
-
dryRun: argv.includes('--dry-run'),
|
|
293
|
-
adopt: argv.includes('--adopt'),
|
|
294
|
-
skillMode: parseSkillMode(argv, BOOTSTRAP_USAGE),
|
|
295
|
-
help: false,
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// `update`에 허용되는 플래그: --dry-run, --force, -h/--help.
|
|
300
|
-
export function parseUpdateArgs(argv) {
|
|
301
|
-
if (wantsHelp(argv)) return { help: true, dryRun: false, force: false }
|
|
302
|
-
assertKnownArgs(argv, UPDATE_SPEC, UPDATE_USAGE)
|
|
303
|
-
return { help: false, dryRun: argv.includes('--dry-run'), force: argv.includes('--force') }
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// `status`에 허용되는 플래그: --json, -h/--help.
|
|
307
|
-
export function parseStatusArgs(argv) {
|
|
308
|
-
if (wantsHelp(argv)) return { help: true, json: false }
|
|
309
|
-
assertKnownArgs(argv, STATUS_SPEC, STATUS_USAGE)
|
|
310
|
-
return { help: false, json: argv.includes('--json') }
|
|
311
|
-
}
|
|
1
|
+
// 인자 파싱만 한다 — 파일시스템도 콘솔도 프로세스도 모른다.
|
|
2
|
+
// install.mjs에서 분리한 이유: 진입점은 최상위에서 main()을 실행하므로
|
|
3
|
+
// import하는 순간 설치기가 돌아버려 순수 함수를 단위 테스트할 수 없었다.
|
|
4
|
+
//
|
|
5
|
+
// 오류는 전부 throw다. 종료 코드는 진입점 한 곳에서 정한다 —
|
|
6
|
+
// 예전에는 --set만 process.exit(2)로 빠져나가 같은 사용자 오류가
|
|
7
|
+
// 경로에 따라 1과 2로 갈렸다.
|
|
8
|
+
|
|
9
|
+
import { LOCALES, LocalizedError, createT } from './i18n/index.mjs'
|
|
10
|
+
|
|
11
|
+
// 사용법은 모듈 로드 시점에 로케일을 모르므로 상수가 될 수 없다.
|
|
12
|
+
// `bootstrap` 서브커맨드 사용법. install.mjs와 두 런처(setup-agents.sh/.ps1) 모두에서 안내한다.
|
|
13
|
+
export const bootstrapUsage = (t) => t('usage.bootstrap')
|
|
14
|
+
// 서브커맨드 없는 최상위 사용법. `design`은 자기 사용법을 따로 갖는다.
|
|
15
|
+
export const rootUsage = (t) => t('usage.root')
|
|
16
|
+
export const designUsage = (t) => t('usage.design')
|
|
17
|
+
export const updateUsage = (t) => t('usage.update')
|
|
18
|
+
export const statusUsage = (t) => t('usage.status')
|
|
19
|
+
|
|
20
|
+
const SKILL_MODES = ['auto', 'link', 'copy']
|
|
21
|
+
|
|
22
|
+
// 인자 사양: 플래그 이름 → 'bool'(값 없음) | 'value'(값 하나를 소비).
|
|
23
|
+
// 미지 인자를 조용히 삼키지 않기 위한 것이다 — 오타(`--dryrun`)나 지원하지
|
|
24
|
+
// 않는 형식(`--set=a`)이 무시되면 사용자는 명령이 먹혔다고 믿지만 아무 일도
|
|
25
|
+
// 일어나지 않는다. bootstrap 파서만 지키던 규칙을 세 파서 모두로 넓힌다.
|
|
26
|
+
const COMMON_SPEC = { '-h': 'bool', '--help': 'bool', '--lang': 'value' }
|
|
27
|
+
const ROOT_SPEC = {
|
|
28
|
+
...COMMON_SPEC, '--dry-run': 'bool', '--list': 'bool',
|
|
29
|
+
'--set': 'value', '--skill-mode': 'value', '--design-dir': 'value',
|
|
30
|
+
}
|
|
31
|
+
const DESIGN_SPEC = {
|
|
32
|
+
...COMMON_SPEC, '--dry-run': 'bool', '--list': 'bool', '--set': 'value',
|
|
33
|
+
'--preview': 'value', '--sync': 'value', '--design-dir': 'value',
|
|
34
|
+
}
|
|
35
|
+
const BOOTSTRAP_SPEC = { ...COMMON_SPEC, '--dry-run': 'bool', '--adopt': 'bool', '--skill-mode': 'value' }
|
|
36
|
+
const UPDATE_SPEC = { ...COMMON_SPEC, '--dry-run': 'bool', '--force': 'bool' }
|
|
37
|
+
const STATUS_SPEC = { ...COMMON_SPEC, '--json': 'bool' }
|
|
38
|
+
|
|
39
|
+
export function assertKnownArgs(argv, spec, usage) {
|
|
40
|
+
for (let i = 0; i < argv.length; i++) {
|
|
41
|
+
const token = argv[i]
|
|
42
|
+
const eq = token.startsWith('--') ? token.indexOf('=') : -1
|
|
43
|
+
const name = eq > 0 ? token.slice(0, eq) : token
|
|
44
|
+
const kind = spec[name]
|
|
45
|
+
if (kind === undefined) throw new LocalizedError('error.unknownArg', { token, usage })
|
|
46
|
+
if (kind === 'bool' && eq > 0) throw new LocalizedError('error.flagTakesNoValue', { name, token, usage })
|
|
47
|
+
// `--플래그 값` 형태는 값 한 칸을 건너뛴다. `--플래그=값`은 이미 한 토큰이다.
|
|
48
|
+
if (kind === 'value' && eq < 0) i++
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 동작 플래그는 한 번에 하나만 뜻이 있다. 여러 개를 주면 실행 경로가 먼저 오는
|
|
53
|
+
// 하나를 골라 나머지를 조용히 버렸다 — `--list --set a`는 --set을, design의
|
|
54
|
+
// `--preview x --set y`는 --set을 무시했다. 알 수 없는 인자를 거부하는 것과
|
|
55
|
+
// 같은 이유(준 인자가 먹히지 않은 채 다른 일이 벌어진다)로 같이 거부한다.
|
|
56
|
+
export function assertSingleAction(argv, names, usage) {
|
|
57
|
+
const given = names.filter((name) => hasFlag(argv, name))
|
|
58
|
+
if (given.length > 1) {
|
|
59
|
+
// 조사를 붙이지 않는다 — 플래그 이름에 따라 은/는이 갈려 문장이 어색해진다.
|
|
60
|
+
throw new LocalizedError('error.singleAction', { given: given.join(', '), usage })
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function checkSkillMode(value, usage) {
|
|
65
|
+
if (!SKILL_MODES.includes(value)) {
|
|
66
|
+
throw new LocalizedError('error.badSkillMode', { list: SKILL_MODES.join(', '), value, usage })
|
|
67
|
+
}
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// `--skill-mode <값>`과 `--skill-mode=<값>`을 모두 받는다. 없으면 기본값 auto.
|
|
72
|
+
export function parseSkillMode(argv, usage) {
|
|
73
|
+
let mode = 'auto'
|
|
74
|
+
for (let i = 0; i < argv.length; i++) {
|
|
75
|
+
const a = argv[i]
|
|
76
|
+
if (a === '--skill-mode') {
|
|
77
|
+
const value = argv[i + 1]
|
|
78
|
+
if (value === undefined || value.startsWith('--')) {
|
|
79
|
+
throw new LocalizedError('error.skillModeNeedsValue', { list: SKILL_MODES.join(', '), usage })
|
|
80
|
+
}
|
|
81
|
+
mode = checkSkillMode(value, usage)
|
|
82
|
+
i++
|
|
83
|
+
} else if (a.startsWith('--skill-mode=')) {
|
|
84
|
+
mode = checkSkillMode(a.slice('--skill-mode='.length), usage)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return mode
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function checkLang(value, usage) {
|
|
91
|
+
if (!LOCALES.includes(value)) {
|
|
92
|
+
throw new LocalizedError('error.badLang', { list: LOCALES.join(', '), value, usage })
|
|
93
|
+
}
|
|
94
|
+
return value
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// `--lang <값>`과 `--lang=<값>`을 모두 받는다. 없으면 null —
|
|
98
|
+
// 이 자리에서 기본값을 정하지 않는다. 우선순위 판단은 resolveLocale의 몫이다.
|
|
99
|
+
export function parseLang(argv, usage) {
|
|
100
|
+
let lang = null
|
|
101
|
+
for (let i = 0; i < argv.length; i++) {
|
|
102
|
+
const a = argv[i]
|
|
103
|
+
if (a === '--lang') {
|
|
104
|
+
const value = argv[i + 1]
|
|
105
|
+
if (value === undefined || value.startsWith('--')) {
|
|
106
|
+
throw new LocalizedError('error.langNeedsValue', { list: LOCALES.join(', '), usage })
|
|
107
|
+
}
|
|
108
|
+
lang = checkLang(value, usage)
|
|
109
|
+
i++
|
|
110
|
+
} else if (a.startsWith('--lang=')) {
|
|
111
|
+
lang = checkLang(a.slice('--lang='.length), usage)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return lang
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 진입점이 로케일을 정하기 **전에** 부르는 관대한 사전 스캔이다.
|
|
118
|
+
// 여기서 던지면 인자 오류가 늘 영어로 나온다 — 정식 파서가 지역화된
|
|
119
|
+
// 오류를 내도록 미지원 값은 조용히 null로 넘긴다.
|
|
120
|
+
export function preScanLang(argv) {
|
|
121
|
+
for (let i = 0; i < argv.length; i++) {
|
|
122
|
+
const a = argv[i]
|
|
123
|
+
const value = a === '--lang' ? argv[i + 1] : a.startsWith('--lang=') ? a.slice('--lang='.length) : null
|
|
124
|
+
if (typeof value === 'string' && LOCALES.includes(value)) return value
|
|
125
|
+
}
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function wantsHelp(argv) {
|
|
130
|
+
return argv.includes('-h') || argv.includes('--help')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// `--flag` 또는 `--flag=값`이 있는가. 두 형식 중 하나만 인식하면 나머지
|
|
134
|
+
// 형식이 조용히 무시되어, 준 인자가 먹히지 않은 채 화면이 열린다.
|
|
135
|
+
export function hasFlag(argv, name) {
|
|
136
|
+
return argv.some((a) => a === name || a.startsWith(`${name}=`))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function requireValue(argv, name) {
|
|
140
|
+
const eq = argv.find((a) => a.startsWith(`${name}=`))
|
|
141
|
+
if (eq !== undefined) {
|
|
142
|
+
const value = eq.slice(name.length + 1)
|
|
143
|
+
if (!value) throw new LocalizedError('error.needsValue', { name })
|
|
144
|
+
return value
|
|
145
|
+
}
|
|
146
|
+
const value = argv[argv.indexOf(name) + 1]
|
|
147
|
+
if (value === undefined || value.startsWith('--')) {
|
|
148
|
+
throw new LocalizedError('error.needsValue', { name })
|
|
149
|
+
}
|
|
150
|
+
return value
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 반복 지정 가능한 플래그 값 수집: `--flag <값>` 과 `--flag=<값>` 둘 다 받는다.
|
|
154
|
+
export function collectValues(argv, name) {
|
|
155
|
+
const values = []
|
|
156
|
+
for (let i = 0; i < argv.length; i++) {
|
|
157
|
+
if (argv[i] === name) {
|
|
158
|
+
const value = argv[i + 1]
|
|
159
|
+
if (value === undefined || value.startsWith('--')) throw new LocalizedError('error.needsValue', { name })
|
|
160
|
+
values.push(value)
|
|
161
|
+
i++
|
|
162
|
+
} else if (argv[i].startsWith(`${name}=`)) {
|
|
163
|
+
const value = argv[i].slice(name.length + 1)
|
|
164
|
+
if (!value) throw new LocalizedError('error.needsValue', { name })
|
|
165
|
+
values.push(value)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return values
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 빈 문자열은 유효한 값이다(전체 제거). 없음(null)과 구별해야 한다.
|
|
172
|
+
// `--set <값>`과 `--set=<값>`을 모두 받는다 — 등호 형식만 인식되지 않으면
|
|
173
|
+
// 목표 집합을 지정했는데도 대화형 화면이 열린다. `--set=`은 빈 값이다.
|
|
174
|
+
export function parseSetArg(argv) {
|
|
175
|
+
const eq = argv.find((a) => a.startsWith('--set='))
|
|
176
|
+
if (eq !== undefined) return eq.slice('--set='.length)
|
|
177
|
+
if (!argv.includes('--set')) return null
|
|
178
|
+
const value = argv[argv.indexOf('--set') + 1]
|
|
179
|
+
if (value === undefined || value.startsWith('--')) {
|
|
180
|
+
throw new LocalizedError('error.setNeedsValue')
|
|
181
|
+
}
|
|
182
|
+
return value
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --help는 다른 인자 검증보다 앞선다 — 도움말을 보려는 사람에게 인자 오류를
|
|
186
|
+
// 먼저 내밀 이유가 없다. 아래 두 파서가 같은 규칙을 따른다.
|
|
187
|
+
|
|
188
|
+
// 서브커맨드 없는 최상위 인자.
|
|
189
|
+
export function parseRootArgs(argv, t = createT('en')) {
|
|
190
|
+
const usage = rootUsage(t)
|
|
191
|
+
if (wantsHelp(argv)) {
|
|
192
|
+
return { help: true, dryRun: false, listOnly: false, setArg: null, skillMode: 'auto', designDirs: [], lang: null, interactive: false }
|
|
193
|
+
}
|
|
194
|
+
assertKnownArgs(argv, ROOT_SPEC, usage)
|
|
195
|
+
assertSingleAction(argv, ['--list', '--set'], usage)
|
|
196
|
+
const setArg = parseSetArg(argv)
|
|
197
|
+
return {
|
|
198
|
+
help: false,
|
|
199
|
+
dryRun: argv.includes('--dry-run'),
|
|
200
|
+
listOnly: argv.includes('--list'),
|
|
201
|
+
setArg,
|
|
202
|
+
// 대화형 화면에서 실행하는 부트스트랩이 이 값을 쓴다. 예전에는 루트에서
|
|
203
|
+
// 읽지 않아, 런처가 넘긴 --skill-mode가 조용히 버려졌다.
|
|
204
|
+
skillMode: parseSkillMode(argv, usage),
|
|
205
|
+
designDirs: collectValues(argv, '--design-dir'),
|
|
206
|
+
lang: parseLang(argv, usage),
|
|
207
|
+
// 목록·집합 지정이 없으면 대화형 화면으로 간다. --lang은 동작 플래그가
|
|
208
|
+
// 아니므로 여기에 끼지 않는다.
|
|
209
|
+
interactive: !argv.includes('--list') && setArg === null,
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// `design` 서브커맨드 플래그.
|
|
214
|
+
export function parseDesignArgs(argv, t = createT('en')) {
|
|
215
|
+
const usage = designUsage(t)
|
|
216
|
+
if (wantsHelp(argv)) {
|
|
217
|
+
return { help: true, dryRun: false, list: false, set: null, preview: null, sync: null, designDirs: [], lang: null, interactive: false }
|
|
218
|
+
}
|
|
219
|
+
assertKnownArgs(argv, DESIGN_SPEC, usage)
|
|
220
|
+
assertSingleAction(argv, ['--list', '--set', '--preview', '--sync'], usage)
|
|
221
|
+
const list = argv.includes('--list')
|
|
222
|
+
const set = parseSetArg(argv)
|
|
223
|
+
const preview = hasFlag(argv, '--preview') ? requireValue(argv, '--preview') : null
|
|
224
|
+
|
|
225
|
+
let sync = null
|
|
226
|
+
const s = argv.find((a) => a === '--sync' || a.startsWith('--sync='))
|
|
227
|
+
if (s) {
|
|
228
|
+
const m = /^--sync=(installed|catalog|stale)$/.exec(s)
|
|
229
|
+
if (!m) throw new LocalizedError('error.badSync')
|
|
230
|
+
sync = m[1]
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
help: false,
|
|
235
|
+
dryRun: argv.includes('--dry-run'),
|
|
236
|
+
list,
|
|
237
|
+
set,
|
|
238
|
+
preview,
|
|
239
|
+
sync,
|
|
240
|
+
designDirs: collectValues(argv, '--design-dir'),
|
|
241
|
+
lang: parseLang(argv, usage),
|
|
242
|
+
interactive: !list && set === null && preview === null && sync === null,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// `bootstrap`에 허용되는 플래그: --dry-run, --adopt,
|
|
247
|
+
// --skill-mode(+값)/--skill-mode=값, --lang(+값)/--lang=값, -h/--help.
|
|
248
|
+
// 그 외 인자는 거부한다 — 조용히 삼켜지는 것을 막기 위해서다.
|
|
249
|
+
export function parseBootstrapArgs(argv, t = createT('en')) {
|
|
250
|
+
const usage = bootstrapUsage(t)
|
|
251
|
+
if (wantsHelp(argv)) return { dryRun: false, skillMode: 'auto', adopt: false, help: true, lang: null }
|
|
252
|
+
assertKnownArgs(argv, BOOTSTRAP_SPEC, usage)
|
|
253
|
+
return {
|
|
254
|
+
dryRun: argv.includes('--dry-run'),
|
|
255
|
+
adopt: argv.includes('--adopt'),
|
|
256
|
+
skillMode: parseSkillMode(argv, usage),
|
|
257
|
+
lang: parseLang(argv, usage),
|
|
258
|
+
help: false,
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// `update`에 허용되는 플래그: --dry-run, --force, --lang(+값)/--lang=값, -h/--help.
|
|
263
|
+
export function parseUpdateArgs(argv, t = createT('en')) {
|
|
264
|
+
const usage = updateUsage(t)
|
|
265
|
+
if (wantsHelp(argv)) return { help: true, dryRun: false, force: false, lang: null }
|
|
266
|
+
assertKnownArgs(argv, UPDATE_SPEC, usage)
|
|
267
|
+
return { help: false, dryRun: argv.includes('--dry-run'), force: argv.includes('--force'), lang: parseLang(argv, usage) }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// `status`에 허용되는 플래그: --json, --lang(+값)/--lang=값, -h/--help.
|
|
271
|
+
export function parseStatusArgs(argv, t = createT('en')) {
|
|
272
|
+
const usage = statusUsage(t)
|
|
273
|
+
if (wantsHelp(argv)) return { help: true, json: false, lang: null }
|
|
274
|
+
assertKnownArgs(argv, STATUS_SPEC, usage)
|
|
275
|
+
return { help: false, json: argv.includes('--json'), lang: parseLang(argv, usage) }
|
|
276
|
+
}
|