@rch4com/agent-setup 1.10.0 → 1.10.1
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 +199 -199
- package/install.mjs +163 -163
- package/lib/args.mjs +276 -276
- package/lib/bootstrap/adapter.mjs +96 -96
- package/lib/bootstrap/apply.mjs +380 -380
- package/lib/bootstrap/flow.mjs +140 -140
- package/lib/bootstrap/record.mjs +154 -154
- package/lib/bootstrap/templates.mjs +148 -148
- package/lib/bootstrap/text.mjs +19 -19
- package/lib/catalog.mjs +234 -234
- package/lib/claude-plugins.mjs +64 -64
- package/lib/clis.mjs +111 -111
- package/lib/context.mjs +66 -66
- package/lib/deps.mjs +17 -17
- package/lib/design-md/catalog.json +454 -454
- package/lib/design-md/catalog.mjs +266 -266
- package/lib/design-md/flow.mjs +192 -192
- package/lib/design-md/open.mjs +51 -51
- package/lib/design-md/providers/awesome-design-md.mjs +124 -124
- package/lib/design-md/providers/index.mjs +9 -9
- package/lib/design-md/scan.mjs +295 -295
- package/lib/engine.mjs +115 -115
- package/lib/gitignore.mjs +18 -18
- package/lib/i18n/catalog/en.mjs +426 -426
- package/lib/i18n/catalog/ko.mjs +411 -411
- package/lib/i18n/detect.mjs +39 -39
- package/lib/i18n/index.mjs +93 -93
- package/lib/items/mcp.codebase-memory.mjs +6 -6
- 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.mattpocock-skills.mjs +8 -8
- package/lib/items/plugin.ponytail.mjs +104 -104
- package/lib/items/skill.gsd.mjs +44 -44
- package/lib/items/skill.gstack.mjs +54 -54
- package/lib/jsonfile.mjs +40 -40
- package/lib/status.mjs +93 -93
- package/lib/tomlfile.mjs +60 -60
- package/lib/tui/detail.mjs +127 -127
- package/lib/tui/progress.mjs +160 -160
- package/lib/tui/render.mjs +268 -268
- package/lib/tui/rows.mjs +317 -317
- package/lib/tui/run.mjs +396 -396
- package/lib/tui/state.mjs +249 -249
- package/lib/update.mjs +85 -85
- package/lib/width.mjs +115 -115
- 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
|
+
})
|