@rch4com/agent-setup 1.0.0 → 1.1.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/README.md +55 -49
- package/install.mjs +101 -83
- package/lib/args.mjs +311 -266
- package/lib/bootstrap/apply.mjs +118 -8
- package/lib/bootstrap/flow.mjs +28 -12
- package/lib/bootstrap/record.mjs +142 -0
- package/lib/bootstrap/text.mjs +19 -0
- package/lib/status.mjs +92 -0
- package/lib/update.mjs +85 -0
- package/package.json +1 -1
- package/lib/items/.omc/state/sessions/a859aff7-59d9-4ff6-afc6-8cafe4471dc4/pre-tool-advisory-throttle.json +0 -14
package/lib/args.mjs
CHANGED
|
@@ -1,266 +1,311 @@
|
|
|
1
|
-
// 인자 파싱만 한다 — 파일시스템도 콘솔도 프로세스도 모른다.
|
|
2
|
-
// install.mjs에서 분리한 이유: 진입점은 최상위에서 main()을 실행하므로
|
|
3
|
-
// import하는 순간 설치기가 돌아버려 순수 함수를 단위 테스트할 수 없었다.
|
|
4
|
-
//
|
|
5
|
-
// 오류는 전부 throw다. 종료 코드는 진입점 한 곳에서 정한다 —
|
|
6
|
-
// 예전에는 --set만 process.exit(2)로 빠져나가 같은 사용자 오류가
|
|
7
|
-
// 경로에 따라 1과 2로 갈렸다.
|
|
8
|
-
|
|
9
|
-
// `bootstrap` 서브커맨드 사용법. install.mjs와 두 런처(setup-agents.sh/.ps1) 모두에서 안내한다.
|
|
10
|
-
export const BOOTSTRAP_USAGE = `사용법: npx @rch4com/agent-setup bootstrap [--skill-mode auto|link|copy] [--dry-run]
|
|
11
|
-
node install.mjs bootstrap [옵션] # 저장소에 설치기를 두고 쓸 때
|
|
12
|
-
|
|
13
|
-
옵션:
|
|
14
|
-
--skill-mode auto|link|copy 스킬 연결 방식을 지정합니다. (기본값: auto)
|
|
15
|
-
auto: 심볼릭 링크를 먼저 시도하고, 실패하면 복사로 전환합니다.
|
|
16
|
-
link: 심볼릭 링크만 시도합니다. 실패하면 오류로 종료합니다.
|
|
17
|
-
copy: 항상 복제본을 만듭니다.
|
|
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
|
-
const
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
// 인자 파싱만 한다 — 파일시스템도 콘솔도 프로세스도 모른다.
|
|
2
|
+
// install.mjs에서 분리한 이유: 진입점은 최상위에서 main()을 실행하므로
|
|
3
|
+
// import하는 순간 설치기가 돌아버려 순수 함수를 단위 테스트할 수 없었다.
|
|
4
|
+
//
|
|
5
|
+
// 오류는 전부 throw다. 종료 코드는 진입점 한 곳에서 정한다 —
|
|
6
|
+
// 예전에는 --set만 process.exit(2)로 빠져나가 같은 사용자 오류가
|
|
7
|
+
// 경로에 따라 1과 2로 갈렸다.
|
|
8
|
+
|
|
9
|
+
// `bootstrap` 서브커맨드 사용법. install.mjs와 두 런처(setup-agents.sh/.ps1) 모두에서 안내한다.
|
|
10
|
+
export const BOOTSTRAP_USAGE = `사용법: npx @rch4com/agent-setup bootstrap [--skill-mode auto|link|copy] [--dry-run]
|
|
11
|
+
node install.mjs bootstrap [옵션] # 저장소에 설치기를 두고 쓸 때
|
|
12
|
+
|
|
13
|
+
옵션:
|
|
14
|
+
--skill-mode auto|link|copy 스킬 연결 방식을 지정합니다. (기본값: auto)
|
|
15
|
+
auto: 심볼릭 링크를 먼저 시도하고, 실패하면 복사로 전환합니다.
|
|
16
|
+
link: 심볼릭 링크만 시도합니다. 실패하면 오류로 종료합니다.
|
|
17
|
+
copy: 항상 복제본을 만듭니다.
|
|
18
|
+
--adopt 파일을 만들지 않고, 이미 있는 파일 중 이 버전의
|
|
19
|
+
템플릿과 같은 것만 관리 대상으로 기록합니다.
|
|
20
|
+
--dry-run 아무것도 바꾸지 않고 예정된 동작만 출력합니다.
|
|
21
|
+
-h, --help 이 도움말을 출력하고 종료합니다.
|
|
22
|
+
|
|
23
|
+
런처로도 사용할 수 있습니다:
|
|
24
|
+
./setup-agents.sh [옵션]
|
|
25
|
+
pwsh -File ./setup-agents.ps1 [옵션]
|
|
26
|
+
--tui (또는 -Tui)를 주면 의존성을 설치하고 대화형 화면을 엽니다.
|
|
27
|
+
거기서 부트스트랩·에이전트·design.md를 한 목록에서 검색하고 고릅니다.`
|
|
28
|
+
|
|
29
|
+
// 서브커맨드 없는 최상위 사용법. `design`은 자기 사용법을 따로 갖는다.
|
|
30
|
+
export const ROOT_USAGE = `사용법: npx @rch4com/agent-setup [옵션]
|
|
31
|
+
npx @rch4com/agent-setup bootstrap [옵션]
|
|
32
|
+
npx @rch4com/agent-setup update [옵션]
|
|
33
|
+
npx @rch4com/agent-setup status [옵션]
|
|
34
|
+
npx @rch4com/agent-setup design [옵션]
|
|
35
|
+
|
|
36
|
+
저장소에 설치기를 두고 쓸 때는 npx @rch4com/agent-setup 자리에
|
|
37
|
+
node install.mjs 를 넣으면 됩니다.
|
|
38
|
+
|
|
39
|
+
옵션이 없으면 대화형 화면을 엽니다 — 부트스트랩·에이전트·design.md를
|
|
40
|
+
한 목록에서 검색하고 고릅니다.
|
|
41
|
+
|
|
42
|
+
옵션:
|
|
43
|
+
--list 현재 설치 상태만 출력하고 종료합니다.
|
|
44
|
+
--set <목록> 쉼표로 구분한 항목을 목표 상태로 맞춥니다.
|
|
45
|
+
전체 제거는 --set "" 로 명시하세요.
|
|
46
|
+
--skill-mode auto|link|copy 대화형 화면에서 실행하는 부트스트랩의 스킬
|
|
47
|
+
연결 방식입니다. (기본값: auto)
|
|
48
|
+
--design-dir <경로> design.md 소스를 더합니다. <소스id>=<경로>
|
|
49
|
+
형식도 받으며 반복 지정할 수 있습니다.
|
|
50
|
+
--dry-run 아무것도 바꾸지 않고 예정된 동작만 출력합니다.
|
|
51
|
+
-h, --help 이 도움말을 출력하고 종료합니다.
|
|
52
|
+
|
|
53
|
+
--list와 --set은 동작을 고르는 플래그라 함께 쓸 수 없습니다.
|
|
54
|
+
|
|
55
|
+
서브커맨드 도움말: npx @rch4com/agent-setup bootstrap --help
|
|
56
|
+
npx @rch4com/agent-setup update --help
|
|
57
|
+
npx @rch4com/agent-setup status --help
|
|
58
|
+
npx @rch4com/agent-setup design --help`
|
|
59
|
+
|
|
60
|
+
export const DESIGN_USAGE = `사용법: npx @rch4com/agent-setup design [옵션]
|
|
61
|
+
node install.mjs design [옵션] # 저장소에 설치기를 두고 쓸 때
|
|
62
|
+
|
|
63
|
+
옵션이 없으면 대화형 화면을 엽니다 — DESIGN.MD 섹션이 그 안에 있습니다.
|
|
64
|
+
|
|
65
|
+
옵션:
|
|
66
|
+
--list 카탈로그와 설치 상태를 출력합니다.
|
|
67
|
+
--set <목록> 쉼표로 구분한 항목을 목표 상태로 맞춥니다.
|
|
68
|
+
토큰은 <이름> 또는 <제공자>/<이름> 입니다.
|
|
69
|
+
전체 제거는 --set "" 로 명시하세요.
|
|
70
|
+
--preview <목록> 미리보기를 엽니다.
|
|
71
|
+
--sync=installed 설치본을 원본 최신으로 다시 받습니다.
|
|
72
|
+
--sync=catalog 사용 가능 목록·카테고리를 소스에서 다시 만듭니다.
|
|
73
|
+
--sync=stale 설치본을 원본과 해시 비교합니다.
|
|
74
|
+
--design-dir <경로> design.md 소스를 더합니다. <소스id>=<경로>
|
|
75
|
+
형식도 받으며 반복 지정할 수 있습니다.
|
|
76
|
+
--dry-run 아무것도 바꾸지 않고 예정된 동작만 출력합니다.
|
|
77
|
+
-h, --help 이 도움말을 출력하고 종료합니다.
|
|
78
|
+
|
|
79
|
+
--list, --set, --preview, --sync는 동작을 고르는 플래그라
|
|
80
|
+
한 번에 하나만 지정할 수 있습니다.`
|
|
81
|
+
|
|
82
|
+
export const UPDATE_USAGE = `사용법: npx @rch4com/agent-setup update [옵션]
|
|
83
|
+
|
|
84
|
+
설치 기록에 남은 해시와 대조해, 우리가 쓴 그대로인 관리 파일만
|
|
85
|
+
최신 템플릿으로 갱신합니다. 사용자가 고친 파일은 건드리지 않고
|
|
86
|
+
드리프트로 보고합니다.
|
|
87
|
+
|
|
88
|
+
옵션:
|
|
89
|
+
--force 드리프트 파일까지 덮어씁니다. 워킹트리가 깨끗해야 합니다
|
|
90
|
+
(git이 유일한 되돌리기 수단이므로).
|
|
91
|
+
--dry-run 아무것도 바꾸지 않고 예정된 동작만 출력합니다.
|
|
92
|
+
-h, --help 이 도움말을 출력하고 종료합니다.`
|
|
93
|
+
|
|
94
|
+
export const STATUS_USAGE = `사용법: npx @rch4com/agent-setup status [옵션]
|
|
95
|
+
|
|
96
|
+
설치 기록(의도) / 실제 저장소 상태(스캔) / 실행 중 도구 버전을
|
|
97
|
+
나란히 보여줍니다. 아무것도 바꾸지 않습니다.
|
|
98
|
+
|
|
99
|
+
옵션:
|
|
100
|
+
--json 기계가 읽을 형태로 출력합니다 (CI 판정용).
|
|
101
|
+
-h, --help 이 도움말을 출력하고 종료합니다.`
|
|
102
|
+
|
|
103
|
+
const SKILL_MODES = ['auto', 'link', 'copy']
|
|
104
|
+
|
|
105
|
+
// 인자 사양: 플래그 이름 → 'bool'(값 없음) | 'value'(값 하나를 소비).
|
|
106
|
+
// 미지 인자를 조용히 삼키지 않기 위한 것이다 — 오타(`--dryrun`)나 지원하지
|
|
107
|
+
// 않는 형식(`--set=a`)이 무시되면 사용자는 명령이 먹혔다고 믿지만 아무 일도
|
|
108
|
+
// 일어나지 않는다. bootstrap 파서만 지키던 규칙을 세 파서 모두로 넓힌다.
|
|
109
|
+
const HELP_SPEC = { '-h': 'bool', '--help': 'bool' }
|
|
110
|
+
const ROOT_SPEC = {
|
|
111
|
+
...HELP_SPEC, '--dry-run': 'bool', '--list': 'bool',
|
|
112
|
+
'--set': 'value', '--skill-mode': 'value', '--design-dir': 'value',
|
|
113
|
+
}
|
|
114
|
+
const DESIGN_SPEC = {
|
|
115
|
+
...HELP_SPEC, '--dry-run': 'bool', '--list': 'bool', '--set': 'value',
|
|
116
|
+
'--preview': 'value', '--sync': 'value', '--design-dir': 'value',
|
|
117
|
+
}
|
|
118
|
+
const BOOTSTRAP_SPEC = { ...HELP_SPEC, '--dry-run': 'bool', '--adopt': 'bool', '--skill-mode': 'value' }
|
|
119
|
+
const UPDATE_SPEC = { ...HELP_SPEC, '--dry-run': 'bool', '--force': 'bool' }
|
|
120
|
+
const STATUS_SPEC = { ...HELP_SPEC, '--json': 'bool' }
|
|
121
|
+
|
|
122
|
+
export function assertKnownArgs(argv, spec, usage) {
|
|
123
|
+
for (let i = 0; i < argv.length; i++) {
|
|
124
|
+
const token = argv[i]
|
|
125
|
+
const eq = token.startsWith('--') ? token.indexOf('=') : -1
|
|
126
|
+
const name = eq > 0 ? token.slice(0, eq) : token
|
|
127
|
+
const kind = spec[name]
|
|
128
|
+
if (kind === undefined) throw new Error(`알 수 없는 인자입니다: ${token}\n\n${usage}`)
|
|
129
|
+
if (kind === 'bool' && eq > 0) throw new Error(`${name}에는 값을 줄 수 없습니다: ${token}\n\n${usage}`)
|
|
130
|
+
// `--플래그 값` 형태는 값 한 칸을 건너뛴다. `--플래그=값`은 이미 한 토큰이다.
|
|
131
|
+
if (kind === 'value' && eq < 0) i++
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 동작 플래그는 한 번에 하나만 뜻이 있다. 여러 개를 주면 실행 경로가 먼저 오는
|
|
136
|
+
// 하나를 골라 나머지를 조용히 버렸다 — `--list --set a`는 --set을, design의
|
|
137
|
+
// `--preview x --set y`는 --set을 무시했다. 알 수 없는 인자를 거부하는 것과
|
|
138
|
+
// 같은 이유(준 인자가 먹히지 않은 채 다른 일이 벌어진다)로 같이 거부한다.
|
|
139
|
+
export function assertSingleAction(argv, names, usage) {
|
|
140
|
+
const given = names.filter((name) => hasFlag(argv, name))
|
|
141
|
+
if (given.length > 1) {
|
|
142
|
+
// 조사를 붙이지 않는다 — 플래그 이름에 따라 은/는이 갈려 문장이 어색해진다.
|
|
143
|
+
throw new Error(`함께 쓸 수 없습니다: ${given.join(', ')} — 동작 플래그는 한 번에 하나만 지정하세요.\n\n${usage}`)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function checkSkillMode(value, usage) {
|
|
148
|
+
if (!SKILL_MODES.includes(value)) {
|
|
149
|
+
throw new Error(`--skill-mode는 ${SKILL_MODES.join(', ')} 중 하나여야 합니다: ${value}\n\n${usage}`)
|
|
150
|
+
}
|
|
151
|
+
return value
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// `--skill-mode <값>`과 `--skill-mode=<값>`을 모두 받는다. 없으면 기본값 auto.
|
|
155
|
+
export function parseSkillMode(argv, usage) {
|
|
156
|
+
let mode = 'auto'
|
|
157
|
+
for (let i = 0; i < argv.length; i++) {
|
|
158
|
+
const a = argv[i]
|
|
159
|
+
if (a === '--skill-mode') {
|
|
160
|
+
const value = argv[i + 1]
|
|
161
|
+
if (value === undefined || value.startsWith('--')) {
|
|
162
|
+
throw new Error(`--skill-mode 뒤에 값이 필요합니다 (${SKILL_MODES.join(', ')} 중 하나).\n\n${usage}`)
|
|
163
|
+
}
|
|
164
|
+
mode = checkSkillMode(value, usage)
|
|
165
|
+
i++
|
|
166
|
+
} else if (a.startsWith('--skill-mode=')) {
|
|
167
|
+
mode = checkSkillMode(a.slice('--skill-mode='.length), usage)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return mode
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function wantsHelp(argv) {
|
|
174
|
+
return argv.includes('-h') || argv.includes('--help')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// `--flag` 또는 `--flag=값`이 있는가. 두 형식 중 하나만 인식하면 나머지
|
|
178
|
+
// 형식이 조용히 무시되어, 준 인자가 먹히지 않은 채 화면이 열린다.
|
|
179
|
+
export function hasFlag(argv, name) {
|
|
180
|
+
return argv.some((a) => a === name || a.startsWith(`${name}=`))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function requireValue(argv, name) {
|
|
184
|
+
const eq = argv.find((a) => a.startsWith(`${name}=`))
|
|
185
|
+
if (eq !== undefined) {
|
|
186
|
+
const value = eq.slice(name.length + 1)
|
|
187
|
+
if (!value) throw new Error(`${name} 뒤에 값이 필요합니다.`)
|
|
188
|
+
return value
|
|
189
|
+
}
|
|
190
|
+
const value = argv[argv.indexOf(name) + 1]
|
|
191
|
+
if (value === undefined || value.startsWith('--')) {
|
|
192
|
+
throw new Error(`${name} 뒤에 값이 필요합니다.`)
|
|
193
|
+
}
|
|
194
|
+
return value
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 반복 지정 가능한 플래그 값 수집: `--flag <값>` 과 `--flag=<값>` 둘 다 받는다.
|
|
198
|
+
export function collectValues(argv, name) {
|
|
199
|
+
const values = []
|
|
200
|
+
for (let i = 0; i < argv.length; i++) {
|
|
201
|
+
if (argv[i] === name) {
|
|
202
|
+
const value = argv[i + 1]
|
|
203
|
+
if (value === undefined || value.startsWith('--')) throw new Error(`${name} 뒤에 값이 필요합니다.`)
|
|
204
|
+
values.push(value)
|
|
205
|
+
i++
|
|
206
|
+
} else if (argv[i].startsWith(`${name}=`)) {
|
|
207
|
+
const value = argv[i].slice(name.length + 1)
|
|
208
|
+
if (!value) throw new Error(`${name} 뒤에 값이 필요합니다.`)
|
|
209
|
+
values.push(value)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return values
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 빈 문자열은 유효한 값이다(전체 제거). 없음(null)과 구별해야 한다.
|
|
216
|
+
// `--set <값>`과 `--set=<값>`을 모두 받는다 — 등호 형식만 인식되지 않으면
|
|
217
|
+
// 목표 집합을 지정했는데도 대화형 화면이 열린다. `--set=`은 빈 값이다.
|
|
218
|
+
export function parseSetArg(argv) {
|
|
219
|
+
const eq = argv.find((a) => a.startsWith('--set='))
|
|
220
|
+
if (eq !== undefined) return eq.slice('--set='.length)
|
|
221
|
+
if (!argv.includes('--set')) return null
|
|
222
|
+
const value = argv[argv.indexOf('--set') + 1]
|
|
223
|
+
if (value === undefined || value.startsWith('--')) {
|
|
224
|
+
throw new Error('--set 뒤에 항목 목록이 필요합니다. 전체 제거는 --set "" 로 명시하세요.')
|
|
225
|
+
}
|
|
226
|
+
return value
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// --help는 다른 인자 검증보다 앞선다 — 도움말을 보려는 사람에게 인자 오류를
|
|
230
|
+
// 먼저 내밀 이유가 없다. 아래 두 파서가 같은 규칙을 따른다.
|
|
231
|
+
|
|
232
|
+
// 서브커맨드 없는 최상위 인자.
|
|
233
|
+
export function parseRootArgs(argv) {
|
|
234
|
+
if (wantsHelp(argv)) {
|
|
235
|
+
return { help: true, dryRun: false, listOnly: false, setArg: null, skillMode: 'auto', designDirs: [], interactive: false }
|
|
236
|
+
}
|
|
237
|
+
assertKnownArgs(argv, ROOT_SPEC, ROOT_USAGE)
|
|
238
|
+
assertSingleAction(argv, ['--list', '--set'], ROOT_USAGE)
|
|
239
|
+
const setArg = parseSetArg(argv)
|
|
240
|
+
return {
|
|
241
|
+
help: false,
|
|
242
|
+
dryRun: argv.includes('--dry-run'),
|
|
243
|
+
listOnly: argv.includes('--list'),
|
|
244
|
+
setArg,
|
|
245
|
+
// 대화형 화면에서 실행하는 부트스트랩이 이 값을 쓴다. 예전에는 루트에서
|
|
246
|
+
// 읽지 않아, 런처가 넘긴 --skill-mode가 조용히 버려졌다.
|
|
247
|
+
skillMode: parseSkillMode(argv, ROOT_USAGE),
|
|
248
|
+
designDirs: collectValues(argv, '--design-dir'),
|
|
249
|
+
// 목록·집합 지정이 없으면 대화형 화면으로 간다.
|
|
250
|
+
interactive: !argv.includes('--list') && setArg === null,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// `design` 서브커맨드 플래그.
|
|
255
|
+
export function parseDesignArgs(argv) {
|
|
256
|
+
if (wantsHelp(argv)) {
|
|
257
|
+
return { help: true, dryRun: false, list: false, set: null, preview: null, sync: null, designDirs: [], interactive: false }
|
|
258
|
+
}
|
|
259
|
+
assertKnownArgs(argv, DESIGN_SPEC, DESIGN_USAGE)
|
|
260
|
+
assertSingleAction(argv, ['--list', '--set', '--preview', '--sync'], DESIGN_USAGE)
|
|
261
|
+
const list = argv.includes('--list')
|
|
262
|
+
const set = parseSetArg(argv)
|
|
263
|
+
const preview = hasFlag(argv, '--preview') ? requireValue(argv, '--preview') : null
|
|
264
|
+
|
|
265
|
+
let sync = null
|
|
266
|
+
const s = argv.find((a) => a === '--sync' || a.startsWith('--sync='))
|
|
267
|
+
if (s) {
|
|
268
|
+
const m = /^--sync=(installed|catalog|stale)$/.exec(s)
|
|
269
|
+
if (!m) throw new Error('--sync=installed|catalog|stale 형식으로 지정하세요.')
|
|
270
|
+
sync = m[1]
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
help: false,
|
|
275
|
+
dryRun: argv.includes('--dry-run'),
|
|
276
|
+
list,
|
|
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
|
+
}
|