@rch4com/agent-setup 1.8.0 → 1.10.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 (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +199 -197
  3. package/install.mjs +163 -163
  4. package/lib/args.mjs +276 -276
  5. package/lib/bootstrap/adapter.mjs +96 -96
  6. package/lib/bootstrap/apply.mjs +380 -380
  7. package/lib/bootstrap/flow.mjs +140 -140
  8. package/lib/bootstrap/record.mjs +154 -154
  9. package/lib/bootstrap/templates.mjs +148 -148
  10. package/lib/bootstrap/text.mjs +19 -19
  11. package/lib/catalog.mjs +234 -234
  12. package/lib/claude-plugins.mjs +64 -64
  13. package/lib/clis.mjs +111 -111
  14. package/lib/context.mjs +66 -66
  15. package/lib/deps.mjs +17 -17
  16. package/lib/design-md/catalog.json +454 -454
  17. package/lib/design-md/catalog.mjs +266 -266
  18. package/lib/design-md/flow.mjs +192 -192
  19. package/lib/design-md/open.mjs +51 -51
  20. package/lib/design-md/providers/awesome-design-md.mjs +124 -124
  21. package/lib/design-md/providers/index.mjs +9 -9
  22. package/lib/design-md/scan.mjs +295 -295
  23. package/lib/engine.mjs +115 -115
  24. package/lib/gitignore.mjs +18 -18
  25. package/lib/i18n/catalog/en.mjs +426 -425
  26. package/lib/i18n/catalog/ko.mjs +411 -410
  27. package/lib/i18n/detect.mjs +39 -39
  28. package/lib/i18n/index.mjs +93 -93
  29. package/lib/items/mcp.codebase-memory.mjs +6 -6
  30. package/lib/items/mcp.notion.mjs +6 -6
  31. package/lib/items/mcp.supabase.mjs +6 -6
  32. package/lib/items/mcp.vercel.mjs +6 -6
  33. package/lib/items/plugin.mattpocock-skills.mjs +8 -8
  34. package/lib/items/plugin.ponytail.mjs +104 -104
  35. package/lib/items/skill.gsd.mjs +44 -44
  36. package/lib/items/skill.gstack.mjs +54 -54
  37. package/lib/items/skill.hallmark.mjs +10 -0
  38. package/lib/jsonfile.mjs +40 -40
  39. package/lib/status.mjs +93 -93
  40. package/lib/tomlfile.mjs +60 -60
  41. package/lib/tui/detail.mjs +127 -127
  42. package/lib/tui/progress.mjs +160 -160
  43. package/lib/tui/render.mjs +268 -268
  44. package/lib/tui/rows.mjs +317 -317
  45. package/lib/tui/run.mjs +396 -396
  46. package/lib/tui/state.mjs +249 -249
  47. package/lib/update.mjs +85 -85
  48. package/lib/width.mjs +115 -115
  49. package/package.json +36 -36
package/lib/clis.mjs CHANGED
@@ -1,111 +1,111 @@
1
- import { repoPath, repoPathStrict } from './context.mjs'
2
- import { readJson, setKey, removeKey, getIn } from './jsonfile.mjs'
3
- import { hasSection, appendSection, removeSection } from './tomlfile.mjs'
4
-
5
- // 읽기(has)는 어휘적 경로면 충분하고, 실제로 만들거나 고치는 경로(add·remove)만
6
- // 링크를 통한 저장소 이탈까지 검사한다 — bootstrap의 apply.mjs, design-md의
7
- // designPaths와 같은 규칙이다. 저장소 안 `.codex`·`.claude`가 홈을 가리키는
8
- // Junction일 때 MCP 등록 한 번으로 글로벌 설정이 생성·수정되는 것을 막는다.
9
- function jsonAdapter(relFile, topKey, toEntry) {
10
- return {
11
- // 경로를 밖으로 낸다 — 상세 패널이 "이 항목이 어디에 쓰이나"를 보여 준다.
12
- // 어댑터가 실제로 쓰는 값과 같은 변수라 두 곳이 갈릴 수 없다.
13
- file: relFile,
14
- has(root, name) {
15
- const data = readJson(repoPath(root, relFile))
16
- return getIn(data, [topKey, name]) !== undefined
17
- },
18
- add(root, name, server) {
19
- setKey(repoPathStrict(root, relFile), [topKey, name], toEntry(server))
20
- },
21
- remove(root, name) {
22
- removeKey(repoPathStrict(root, relFile), [topKey, name])
23
- },
24
- }
25
- }
26
-
27
- function tomlLines(server) {
28
- if (server.kind === 'http') return [`url = ${JSON.stringify(server.url)}`]
29
- return [
30
- `command = ${JSON.stringify(server.command)}`,
31
- `args = [${server.args.map((a) => JSON.stringify(a)).join(', ')}]`,
32
- ]
33
- }
34
-
35
- // codex·grok은 TOML을 쓴다. jsonAdapter와 같은 이유로 경로를 한 번만 적는다.
36
- function tomlAdapter(file) {
37
- return {
38
- file,
39
- has: (root, name) => hasSection(repoPath(root, file), name),
40
- add: (root, name, s) => appendSection(repoPathStrict(root, file), name, tomlLines(s)),
41
- remove: (root, name) => removeSection(repoPathStrict(root, file), name),
42
- }
43
- }
44
-
45
- export const CLIS = {
46
- claude: {
47
- label: 'Claude Code',
48
- ...jsonAdapter('.mcp.json', 'mcpServers', (s) =>
49
- s.kind === 'http'
50
- ? { type: 'http', url: s.url }
51
- : { type: 'stdio', command: s.command, args: s.args }),
52
- },
53
- codex: {
54
- label: 'Codex',
55
- ...tomlAdapter('.codex/config.toml'),
56
- },
57
- gemini: {
58
- label: 'Gemini CLI',
59
- ...jsonAdapter('.gemini/settings.json', 'mcpServers', (s) =>
60
- s.kind === 'http' ? { httpUrl: s.url } : { command: s.command, args: s.args }),
61
- },
62
- opencode: {
63
- label: 'OpenCode',
64
- ...jsonAdapter('opencode.jsonc', 'mcp', (s) =>
65
- s.kind === 'http'
66
- ? { type: 'remote', url: s.url, enabled: true }
67
- : { type: 'local', command: [s.command, ...s.args], enabled: true }),
68
- },
69
- kilo: {
70
- label: 'Kilo Code',
71
- ...jsonAdapter('.kilocode/mcp.json', 'mcpServers', (s) =>
72
- s.kind === 'http'
73
- ? { type: 'streamable-http', url: s.url, disabled: false }
74
- : { command: s.command, args: s.args, disabled: false }),
75
- },
76
- kiro: {
77
- label: 'Kiro',
78
- ...jsonAdapter('.kiro/settings/mcp.json', 'mcpServers', (s) =>
79
- s.kind === 'http'
80
- ? { url: s.url, disabled: false, autoApprove: [] }
81
- : { command: s.command, args: s.args, disabled: false, autoApprove: [] }),
82
- },
83
- kimi: {
84
- label: 'Kimi Code',
85
- ...jsonAdapter('.kimi-code/mcp.json', 'mcpServers', (s) =>
86
- s.kind === 'http' ? { url: s.url } : { command: s.command, args: s.args }),
87
- },
88
- grok: {
89
- label: 'Grok Build',
90
- ...tomlAdapter('.grok/config.toml'),
91
- },
92
- copilot: {
93
- label: 'GitHub Copilot CLI',
94
- // Copilot CLI는 루트 .mcp.json도 읽지만(같은 이름이면 그쪽이 우선),
95
- // 도구마다 자기 파일을 갖는 이 저장소의 패턴을 따라 .github/mcp.json에 쓴다.
96
- ...jsonAdapter('.github/mcp.json', 'mcpServers', (s) =>
97
- s.kind === 'http'
98
- ? { type: 'http', url: s.url }
99
- : { type: 'local', command: s.command, args: s.args }),
100
- },
101
- vscode: {
102
- label: 'VS Code Copilot',
103
- // VS Code는 최상위 키가 servers이고 로컬 서버 타입이 stdio다.
104
- ...jsonAdapter('.vscode/mcp.json', 'servers', (s) =>
105
- s.kind === 'http'
106
- ? { type: 'http', url: s.url }
107
- : { type: 'stdio', command: s.command, args: s.args }),
108
- },
109
- }
110
-
111
- export const CLI_IDS = Object.keys(CLIS)
1
+ import { repoPath, repoPathStrict } from './context.mjs'
2
+ import { readJson, setKey, removeKey, getIn } from './jsonfile.mjs'
3
+ import { hasSection, appendSection, removeSection } from './tomlfile.mjs'
4
+
5
+ // 읽기(has)는 어휘적 경로면 충분하고, 실제로 만들거나 고치는 경로(add·remove)만
6
+ // 링크를 통한 저장소 이탈까지 검사한다 — bootstrap의 apply.mjs, design-md의
7
+ // designPaths와 같은 규칙이다. 저장소 안 `.codex`·`.claude`가 홈을 가리키는
8
+ // Junction일 때 MCP 등록 한 번으로 글로벌 설정이 생성·수정되는 것을 막는다.
9
+ function jsonAdapter(relFile, topKey, toEntry) {
10
+ return {
11
+ // 경로를 밖으로 낸다 — 상세 패널이 "이 항목이 어디에 쓰이나"를 보여 준다.
12
+ // 어댑터가 실제로 쓰는 값과 같은 변수라 두 곳이 갈릴 수 없다.
13
+ file: relFile,
14
+ has(root, name) {
15
+ const data = readJson(repoPath(root, relFile))
16
+ return getIn(data, [topKey, name]) !== undefined
17
+ },
18
+ add(root, name, server) {
19
+ setKey(repoPathStrict(root, relFile), [topKey, name], toEntry(server))
20
+ },
21
+ remove(root, name) {
22
+ removeKey(repoPathStrict(root, relFile), [topKey, name])
23
+ },
24
+ }
25
+ }
26
+
27
+ function tomlLines(server) {
28
+ if (server.kind === 'http') return [`url = ${JSON.stringify(server.url)}`]
29
+ return [
30
+ `command = ${JSON.stringify(server.command)}`,
31
+ `args = [${server.args.map((a) => JSON.stringify(a)).join(', ')}]`,
32
+ ]
33
+ }
34
+
35
+ // codex·grok은 TOML을 쓴다. jsonAdapter와 같은 이유로 경로를 한 번만 적는다.
36
+ function tomlAdapter(file) {
37
+ return {
38
+ file,
39
+ has: (root, name) => hasSection(repoPath(root, file), name),
40
+ add: (root, name, s) => appendSection(repoPathStrict(root, file), name, tomlLines(s)),
41
+ remove: (root, name) => removeSection(repoPathStrict(root, file), name),
42
+ }
43
+ }
44
+
45
+ export const CLIS = {
46
+ claude: {
47
+ label: 'Claude Code',
48
+ ...jsonAdapter('.mcp.json', 'mcpServers', (s) =>
49
+ s.kind === 'http'
50
+ ? { type: 'http', url: s.url }
51
+ : { type: 'stdio', command: s.command, args: s.args }),
52
+ },
53
+ codex: {
54
+ label: 'Codex',
55
+ ...tomlAdapter('.codex/config.toml'),
56
+ },
57
+ gemini: {
58
+ label: 'Gemini CLI',
59
+ ...jsonAdapter('.gemini/settings.json', 'mcpServers', (s) =>
60
+ s.kind === 'http' ? { httpUrl: s.url } : { command: s.command, args: s.args }),
61
+ },
62
+ opencode: {
63
+ label: 'OpenCode',
64
+ ...jsonAdapter('opencode.jsonc', 'mcp', (s) =>
65
+ s.kind === 'http'
66
+ ? { type: 'remote', url: s.url, enabled: true }
67
+ : { type: 'local', command: [s.command, ...s.args], enabled: true }),
68
+ },
69
+ kilo: {
70
+ label: 'Kilo Code',
71
+ ...jsonAdapter('.kilocode/mcp.json', 'mcpServers', (s) =>
72
+ s.kind === 'http'
73
+ ? { type: 'streamable-http', url: s.url, disabled: false }
74
+ : { command: s.command, args: s.args, disabled: false }),
75
+ },
76
+ kiro: {
77
+ label: 'Kiro',
78
+ ...jsonAdapter('.kiro/settings/mcp.json', 'mcpServers', (s) =>
79
+ s.kind === 'http'
80
+ ? { url: s.url, disabled: false, autoApprove: [] }
81
+ : { command: s.command, args: s.args, disabled: false, autoApprove: [] }),
82
+ },
83
+ kimi: {
84
+ label: 'Kimi Code',
85
+ ...jsonAdapter('.kimi-code/mcp.json', 'mcpServers', (s) =>
86
+ s.kind === 'http' ? { url: s.url } : { command: s.command, args: s.args }),
87
+ },
88
+ grok: {
89
+ label: 'Grok Build',
90
+ ...tomlAdapter('.grok/config.toml'),
91
+ },
92
+ copilot: {
93
+ label: 'GitHub Copilot CLI',
94
+ // Copilot CLI는 루트 .mcp.json도 읽지만(같은 이름이면 그쪽이 우선),
95
+ // 도구마다 자기 파일을 갖는 이 저장소의 패턴을 따라 .github/mcp.json에 쓴다.
96
+ ...jsonAdapter('.github/mcp.json', 'mcpServers', (s) =>
97
+ s.kind === 'http'
98
+ ? { type: 'http', url: s.url }
99
+ : { type: 'local', command: s.command, args: s.args }),
100
+ },
101
+ vscode: {
102
+ label: 'VS Code Copilot',
103
+ // VS Code는 최상위 키가 servers이고 로컬 서버 타입이 stdio다.
104
+ ...jsonAdapter('.vscode/mcp.json', 'servers', (s) =>
105
+ s.kind === 'http'
106
+ ? { type: 'http', url: s.url }
107
+ : { type: 'stdio', command: s.command, args: s.args }),
108
+ },
109
+ }
110
+
111
+ export const CLI_IDS = Object.keys(CLIS)
package/lib/context.mjs CHANGED
@@ -1,66 +1,66 @@
1
- import { execFileSync } from 'node:child_process'
2
- import { lstatSync, realpathSync } from 'node:fs'
3
- import { dirname, resolve, sep } from 'node:path'
4
- import { LocalizedError } from './i18n/index.mjs'
5
-
6
- export function findRepoRoot(cwd = process.cwd()) {
7
- let out
8
- try {
9
- out = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd, encoding: 'utf8' })
10
- } catch {
11
- throw new LocalizedError('error.notGitRepo')
12
- }
13
- return resolve(out.trim())
14
- }
15
-
16
- export function repoPath(root, rel) {
17
- const abs = resolve(root, rel)
18
- const normRoot = resolve(root)
19
- if (abs !== normRoot && !abs.startsWith(normRoot + sep)) {
20
- throw new LocalizedError('error.pathOutsideRepo', { path: abs })
21
- }
22
- return abs
23
- }
24
-
25
- function pathExists(target) {
26
- try {
27
- lstatSync(target)
28
- return true
29
- } catch {
30
- return false
31
- }
32
- }
33
-
34
- // repoPath의 어휘적 검사에 더해, 가장 가까운 존재하는 조상의 realpath가
35
- // 저장소 안인지 확인한다. 어휘적 검사만으로는 심볼릭 링크/Junction을 통한
36
- // 이탈을 막지 못한다. 부트스트랩의 모든 쓰기 경로가 이 함수를 쓴다.
37
- export function repoPathStrict(root, rel) {
38
- const abs = repoPath(root, rel)
39
-
40
- let probe = abs
41
- while (!pathExists(probe)) {
42
- const parent = dirname(probe)
43
- if (parent === probe) break
44
- probe = parent
45
- }
46
-
47
- let realProbe
48
- try {
49
- realProbe = realpathSync(probe)
50
- } catch (err) {
51
- // 경합으로 사라졌거나(ENOENT) 링크가 순환하는(ELOOP) 경우다.
52
- // 원인을 알 수 없는 raw 예외 대신 진단 가능한 메시지로 바꾼다.
53
- throw new LocalizedError('error.pathUnresolvable', { path: probe, code: err.code ?? err.message })
54
- }
55
-
56
- let realRoot
57
- try {
58
- realRoot = realpathSync(root)
59
- } catch (err) {
60
- throw new LocalizedError('error.pathUnresolvable', { path: root, code: err.code ?? err.message })
61
- }
62
- if (realProbe !== realRoot && !realProbe.startsWith(realRoot + sep)) {
63
- throw new LocalizedError('error.pathEscapesViaLink', { path: probe, real: realProbe })
64
- }
65
- return abs
66
- }
1
+ import { execFileSync } from 'node:child_process'
2
+ import { lstatSync, realpathSync } from 'node:fs'
3
+ import { dirname, resolve, sep } from 'node:path'
4
+ import { LocalizedError } from './i18n/index.mjs'
5
+
6
+ export function findRepoRoot(cwd = process.cwd()) {
7
+ let out
8
+ try {
9
+ out = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd, encoding: 'utf8' })
10
+ } catch {
11
+ throw new LocalizedError('error.notGitRepo')
12
+ }
13
+ return resolve(out.trim())
14
+ }
15
+
16
+ export function repoPath(root, rel) {
17
+ const abs = resolve(root, rel)
18
+ const normRoot = resolve(root)
19
+ if (abs !== normRoot && !abs.startsWith(normRoot + sep)) {
20
+ throw new LocalizedError('error.pathOutsideRepo', { path: abs })
21
+ }
22
+ return abs
23
+ }
24
+
25
+ function pathExists(target) {
26
+ try {
27
+ lstatSync(target)
28
+ return true
29
+ } catch {
30
+ return false
31
+ }
32
+ }
33
+
34
+ // repoPath의 어휘적 검사에 더해, 가장 가까운 존재하는 조상의 realpath가
35
+ // 저장소 안인지 확인한다. 어휘적 검사만으로는 심볼릭 링크/Junction을 통한
36
+ // 이탈을 막지 못한다. 부트스트랩의 모든 쓰기 경로가 이 함수를 쓴다.
37
+ export function repoPathStrict(root, rel) {
38
+ const abs = repoPath(root, rel)
39
+
40
+ let probe = abs
41
+ while (!pathExists(probe)) {
42
+ const parent = dirname(probe)
43
+ if (parent === probe) break
44
+ probe = parent
45
+ }
46
+
47
+ let realProbe
48
+ try {
49
+ realProbe = realpathSync(probe)
50
+ } catch (err) {
51
+ // 경합으로 사라졌거나(ENOENT) 링크가 순환하는(ELOOP) 경우다.
52
+ // 원인을 알 수 없는 raw 예외 대신 진단 가능한 메시지로 바꾼다.
53
+ throw new LocalizedError('error.pathUnresolvable', { path: probe, code: err.code ?? err.message })
54
+ }
55
+
56
+ let realRoot
57
+ try {
58
+ realRoot = realpathSync(root)
59
+ } catch (err) {
60
+ throw new LocalizedError('error.pathUnresolvable', { path: root, code: err.code ?? err.message })
61
+ }
62
+ if (realProbe !== realRoot && !realProbe.startsWith(realRoot + sep)) {
63
+ throw new LocalizedError('error.pathEscapesViaLink', { path: probe, real: realProbe })
64
+ }
65
+ return abs
66
+ }
package/lib/deps.mjs CHANGED
@@ -1,17 +1,17 @@
1
- // 선택적 의존성 로딩. 부트스트랩은 npm install 없이 돌아야 하므로
2
- // jsonc-parser·smol-toml에 닿는 모듈은 전부 동적 import로 가져오고,
3
- // 없을 때는 Node의 원시 메시지 대신 설치 방법을 안내한다.
4
- import { LocalizedError } from './i18n/index.mjs'
5
-
6
- // t는 현재 안내 문구가 쓰지 않지만 시그니처에 남겨 둔다 — 호출부가 이미
7
- // 넘기고 있고, 나중에 지역화된 안내가 늘어날 자리다.
8
- export async function withDeps(load, t) {
9
- try {
10
- return await load()
11
- } catch (err) {
12
- // 모듈이 정말 없을 때만 안내로 바꾼다. 로드된 모듈 안에서 난 오류는
13
- // 그대로 올려보내야 한다 — 삼키면 진짜 버그가 "의존성 없음"으로 둔갑한다.
14
- if (err.code !== 'ERR_MODULE_NOT_FOUND') throw err
15
- throw new LocalizedError('error.depsMissing')
16
- }
17
- }
1
+ // 선택적 의존성 로딩. 부트스트랩은 npm install 없이 돌아야 하므로
2
+ // jsonc-parser·smol-toml에 닿는 모듈은 전부 동적 import로 가져오고,
3
+ // 없을 때는 Node의 원시 메시지 대신 설치 방법을 안내한다.
4
+ import { LocalizedError } from './i18n/index.mjs'
5
+
6
+ // t는 현재 안내 문구가 쓰지 않지만 시그니처에 남겨 둔다 — 호출부가 이미
7
+ // 넘기고 있고, 나중에 지역화된 안내가 늘어날 자리다.
8
+ export async function withDeps(load, t) {
9
+ try {
10
+ return await load()
11
+ } catch (err) {
12
+ // 모듈이 정말 없을 때만 안내로 바꾼다. 로드된 모듈 안에서 난 오류는
13
+ // 그대로 올려보내야 한다 — 삼키면 진짜 버그가 "의존성 없음"으로 둔갑한다.
14
+ if (err.code !== 'ERR_MODULE_NOT_FOUND') throw err
15
+ throw new LocalizedError('error.depsMissing')
16
+ }
17
+ }