@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/clis.mjs
CHANGED
|
@@ -1,102 +1,102 @@
|
|
|
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
|
-
has(root, name) {
|
|
12
|
-
const data = readJson(repoPath(root, relFile))
|
|
13
|
-
return getIn(data, [topKey, name]) !== undefined
|
|
14
|
-
},
|
|
15
|
-
add(root, name, server) {
|
|
16
|
-
setKey(repoPathStrict(root, relFile), [topKey, name], toEntry(server))
|
|
17
|
-
},
|
|
18
|
-
remove(root, name) {
|
|
19
|
-
removeKey(repoPathStrict(root, relFile), [topKey, name])
|
|
20
|
-
},
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function tomlLines(server) {
|
|
25
|
-
if (server.kind === 'http') return [`url = ${JSON.stringify(server.url)}`]
|
|
26
|
-
return [
|
|
27
|
-
`command = ${JSON.stringify(server.command)}`,
|
|
28
|
-
`args = [${server.args.map((a) => JSON.stringify(a)).join(', ')}]`,
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const CLIS = {
|
|
33
|
-
claude: {
|
|
34
|
-
label: 'Claude Code',
|
|
35
|
-
...jsonAdapter('.mcp.json', 'mcpServers', (s) =>
|
|
36
|
-
s.kind === 'http'
|
|
37
|
-
? { type: 'http', url: s.url }
|
|
38
|
-
: { type: 'stdio', command: s.command, args: s.args }),
|
|
39
|
-
},
|
|
40
|
-
codex: {
|
|
41
|
-
label: 'Codex',
|
|
42
|
-
has: (root, name) => hasSection(repoPath(root, '.codex/config.toml'), name),
|
|
43
|
-
add: (root, name, s) => appendSection(repoPathStrict(root, '.codex/config.toml'), name, tomlLines(s)),
|
|
44
|
-
remove: (root, name) => removeSection(repoPathStrict(root, '.codex/config.toml'), name),
|
|
45
|
-
},
|
|
46
|
-
gemini: {
|
|
47
|
-
label: 'Gemini CLI',
|
|
48
|
-
...jsonAdapter('.gemini/settings.json', 'mcpServers', (s) =>
|
|
49
|
-
s.kind === 'http' ? { httpUrl: s.url } : { command: s.command, args: s.args }),
|
|
50
|
-
},
|
|
51
|
-
opencode: {
|
|
52
|
-
label: 'OpenCode',
|
|
53
|
-
...jsonAdapter('opencode.jsonc', 'mcp', (s) =>
|
|
54
|
-
s.kind === 'http'
|
|
55
|
-
? { type: 'remote', url: s.url, enabled: true }
|
|
56
|
-
: { type: 'local', command: [s.command, ...s.args], enabled: true }),
|
|
57
|
-
},
|
|
58
|
-
kilo: {
|
|
59
|
-
label: 'Kilo Code',
|
|
60
|
-
...jsonAdapter('.kilocode/mcp.json', 'mcpServers', (s) =>
|
|
61
|
-
s.kind === 'http'
|
|
62
|
-
? { type: 'streamable-http', url: s.url, disabled: false }
|
|
63
|
-
: { command: s.command, args: s.args, disabled: false }),
|
|
64
|
-
},
|
|
65
|
-
kiro: {
|
|
66
|
-
label: 'Kiro',
|
|
67
|
-
...jsonAdapter('.kiro/settings/mcp.json', 'mcpServers', (s) =>
|
|
68
|
-
s.kind === 'http'
|
|
69
|
-
? { url: s.url, disabled: false, autoApprove: [] }
|
|
70
|
-
: { command: s.command, args: s.args, disabled: false, autoApprove: [] }),
|
|
71
|
-
},
|
|
72
|
-
kimi: {
|
|
73
|
-
label: 'Kimi Code',
|
|
74
|
-
...jsonAdapter('.kimi-code/mcp.json', 'mcpServers', (s) =>
|
|
75
|
-
s.kind === 'http' ? { url: s.url } : { command: s.command, args: s.args }),
|
|
76
|
-
},
|
|
77
|
-
grok: {
|
|
78
|
-
label: 'Grok Build',
|
|
79
|
-
has: (root, name) => hasSection(repoPath(root, '.grok/config.toml'), name),
|
|
80
|
-
add: (root, name, s) => appendSection(repoPathStrict(root, '.grok/config.toml'), name, tomlLines(s)),
|
|
81
|
-
remove: (root, name) => removeSection(repoPathStrict(root, '.grok/config.toml'), name),
|
|
82
|
-
},
|
|
83
|
-
copilot: {
|
|
84
|
-
label: 'GitHub Copilot CLI',
|
|
85
|
-
// Copilot CLI는 루트 .mcp.json도 읽지만(같은 이름이면 그쪽이 우선),
|
|
86
|
-
// 도구마다 자기 파일을 갖는 이 저장소의 패턴을 따라 .github/mcp.json에 쓴다.
|
|
87
|
-
...jsonAdapter('.github/mcp.json', 'mcpServers', (s) =>
|
|
88
|
-
s.kind === 'http'
|
|
89
|
-
? { type: 'http', url: s.url }
|
|
90
|
-
: { type: 'local', command: s.command, args: s.args }),
|
|
91
|
-
},
|
|
92
|
-
vscode: {
|
|
93
|
-
label: 'VS Code Copilot',
|
|
94
|
-
// VS Code는 최상위 키가 servers이고 로컬 서버 타입이 stdio다.
|
|
95
|
-
...jsonAdapter('.vscode/mcp.json', 'servers', (s) =>
|
|
96
|
-
s.kind === 'http'
|
|
97
|
-
? { type: 'http', url: s.url }
|
|
98
|
-
: { type: 'stdio', command: s.command, args: s.args }),
|
|
99
|
-
},
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
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
|
+
has(root, name) {
|
|
12
|
+
const data = readJson(repoPath(root, relFile))
|
|
13
|
+
return getIn(data, [topKey, name]) !== undefined
|
|
14
|
+
},
|
|
15
|
+
add(root, name, server) {
|
|
16
|
+
setKey(repoPathStrict(root, relFile), [topKey, name], toEntry(server))
|
|
17
|
+
},
|
|
18
|
+
remove(root, name) {
|
|
19
|
+
removeKey(repoPathStrict(root, relFile), [topKey, name])
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function tomlLines(server) {
|
|
25
|
+
if (server.kind === 'http') return [`url = ${JSON.stringify(server.url)}`]
|
|
26
|
+
return [
|
|
27
|
+
`command = ${JSON.stringify(server.command)}`,
|
|
28
|
+
`args = [${server.args.map((a) => JSON.stringify(a)).join(', ')}]`,
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const CLIS = {
|
|
33
|
+
claude: {
|
|
34
|
+
label: 'Claude Code',
|
|
35
|
+
...jsonAdapter('.mcp.json', 'mcpServers', (s) =>
|
|
36
|
+
s.kind === 'http'
|
|
37
|
+
? { type: 'http', url: s.url }
|
|
38
|
+
: { type: 'stdio', command: s.command, args: s.args }),
|
|
39
|
+
},
|
|
40
|
+
codex: {
|
|
41
|
+
label: 'Codex',
|
|
42
|
+
has: (root, name) => hasSection(repoPath(root, '.codex/config.toml'), name),
|
|
43
|
+
add: (root, name, s) => appendSection(repoPathStrict(root, '.codex/config.toml'), name, tomlLines(s)),
|
|
44
|
+
remove: (root, name) => removeSection(repoPathStrict(root, '.codex/config.toml'), name),
|
|
45
|
+
},
|
|
46
|
+
gemini: {
|
|
47
|
+
label: 'Gemini CLI',
|
|
48
|
+
...jsonAdapter('.gemini/settings.json', 'mcpServers', (s) =>
|
|
49
|
+
s.kind === 'http' ? { httpUrl: s.url } : { command: s.command, args: s.args }),
|
|
50
|
+
},
|
|
51
|
+
opencode: {
|
|
52
|
+
label: 'OpenCode',
|
|
53
|
+
...jsonAdapter('opencode.jsonc', 'mcp', (s) =>
|
|
54
|
+
s.kind === 'http'
|
|
55
|
+
? { type: 'remote', url: s.url, enabled: true }
|
|
56
|
+
: { type: 'local', command: [s.command, ...s.args], enabled: true }),
|
|
57
|
+
},
|
|
58
|
+
kilo: {
|
|
59
|
+
label: 'Kilo Code',
|
|
60
|
+
...jsonAdapter('.kilocode/mcp.json', 'mcpServers', (s) =>
|
|
61
|
+
s.kind === 'http'
|
|
62
|
+
? { type: 'streamable-http', url: s.url, disabled: false }
|
|
63
|
+
: { command: s.command, args: s.args, disabled: false }),
|
|
64
|
+
},
|
|
65
|
+
kiro: {
|
|
66
|
+
label: 'Kiro',
|
|
67
|
+
...jsonAdapter('.kiro/settings/mcp.json', 'mcpServers', (s) =>
|
|
68
|
+
s.kind === 'http'
|
|
69
|
+
? { url: s.url, disabled: false, autoApprove: [] }
|
|
70
|
+
: { command: s.command, args: s.args, disabled: false, autoApprove: [] }),
|
|
71
|
+
},
|
|
72
|
+
kimi: {
|
|
73
|
+
label: 'Kimi Code',
|
|
74
|
+
...jsonAdapter('.kimi-code/mcp.json', 'mcpServers', (s) =>
|
|
75
|
+
s.kind === 'http' ? { url: s.url } : { command: s.command, args: s.args }),
|
|
76
|
+
},
|
|
77
|
+
grok: {
|
|
78
|
+
label: 'Grok Build',
|
|
79
|
+
has: (root, name) => hasSection(repoPath(root, '.grok/config.toml'), name),
|
|
80
|
+
add: (root, name, s) => appendSection(repoPathStrict(root, '.grok/config.toml'), name, tomlLines(s)),
|
|
81
|
+
remove: (root, name) => removeSection(repoPathStrict(root, '.grok/config.toml'), name),
|
|
82
|
+
},
|
|
83
|
+
copilot: {
|
|
84
|
+
label: 'GitHub Copilot CLI',
|
|
85
|
+
// Copilot CLI는 루트 .mcp.json도 읽지만(같은 이름이면 그쪽이 우선),
|
|
86
|
+
// 도구마다 자기 파일을 갖는 이 저장소의 패턴을 따라 .github/mcp.json에 쓴다.
|
|
87
|
+
...jsonAdapter('.github/mcp.json', 'mcpServers', (s) =>
|
|
88
|
+
s.kind === 'http'
|
|
89
|
+
? { type: 'http', url: s.url }
|
|
90
|
+
: { type: 'local', command: s.command, args: s.args }),
|
|
91
|
+
},
|
|
92
|
+
vscode: {
|
|
93
|
+
label: 'VS Code Copilot',
|
|
94
|
+
// VS Code는 최상위 키가 servers이고 로컬 서버 타입이 stdio다.
|
|
95
|
+
...jsonAdapter('.vscode/mcp.json', 'servers', (s) =>
|
|
96
|
+
s.kind === 'http'
|
|
97
|
+
? { type: 'http', url: s.url }
|
|
98
|
+
: { type: 'stdio', command: s.command, args: s.args }),
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const CLI_IDS = Object.keys(CLIS)
|
package/lib/context.mjs
CHANGED
|
@@ -1,65 +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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
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
|
-
probe
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,19 +1,17 @@
|
|
|
1
1
|
// 선택적 의존성 로딩. 부트스트랩은 npm install 없이 돌아야 하므로
|
|
2
2
|
// jsonc-parser·smol-toml에 닿는 모듈은 전부 동적 import로 가져오고,
|
|
3
3
|
// 없을 때는 Node의 원시 메시지 대신 설치 방법을 안내한다.
|
|
4
|
+
import { LocalizedError } from './i18n/index.mjs'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
' npm install --prefix agent-installer'
|
|
9
|
-
|
|
10
|
-
export async function withDeps(load) {
|
|
6
|
+
// t는 현재 안내 문구가 쓰지 않지만 시그니처에 남겨 둔다 — 호출부가 이미
|
|
7
|
+
// 넘기고 있고, 나중에 지역화된 안내가 늘어날 자리다.
|
|
8
|
+
export async function withDeps(load, t) {
|
|
11
9
|
try {
|
|
12
10
|
return await load()
|
|
13
11
|
} catch (err) {
|
|
14
12
|
// 모듈이 정말 없을 때만 안내로 바꾼다. 로드된 모듈 안에서 난 오류는
|
|
15
13
|
// 그대로 올려보내야 한다 — 삼키면 진짜 버그가 "의존성 없음"으로 둔갑한다.
|
|
16
14
|
if (err.code !== 'ERR_MODULE_NOT_FOUND') throw err
|
|
17
|
-
throw new
|
|
15
|
+
throw new LocalizedError('error.depsMissing')
|
|
18
16
|
}
|
|
19
17
|
}
|