@rch4com/agent-setup 1.0.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 -0
- package/README.md +49 -0
- package/install.mjs +83 -0
- package/lib/args.mjs +266 -0
- package/lib/bootstrap/adapter.mjs +95 -0
- package/lib/bootstrap/apply.mjs +269 -0
- package/lib/bootstrap/flow.mjs +54 -0
- package/lib/bootstrap/manifest.mjs +74 -0
- package/lib/bootstrap/templates.mjs +148 -0
- package/lib/catalog.mjs +145 -0
- package/lib/claude-plugins.mjs +64 -0
- package/lib/clis.mjs +102 -0
- package/lib/context.mjs +65 -0
- package/lib/deps.mjs +19 -0
- package/lib/design-md/cache/awesome-design-md/airbnb/DESIGN.md +545 -0
- package/lib/design-md/cache/awesome-design-md/airtable/DESIGN.md +554 -0
- package/lib/design-md/cache/awesome-design-md/apple/DESIGN.md +562 -0
- package/lib/design-md/cache/awesome-design-md/binance/DESIGN.md +634 -0
- package/lib/design-md/cache/awesome-design-md/bmw/DESIGN.md +544 -0
- package/lib/design-md/cache/awesome-design-md/bmw-m/DESIGN.md +503 -0
- package/lib/design-md/cache/awesome-design-md/bugatti/DESIGN.md +454 -0
- package/lib/design-md/cache/awesome-design-md/cal/DESIGN.md +542 -0
- package/lib/design-md/cache/awesome-design-md/claude/DESIGN.md +589 -0
- package/lib/design-md/cache/awesome-design-md/clay/DESIGN.md +541 -0
- package/lib/design-md/cache/awesome-design-md/clickhouse/DESIGN.md +544 -0
- package/lib/design-md/cache/awesome-design-md/cohere/DESIGN.md +451 -0
- package/lib/design-md/cache/awesome-design-md/coinbase/DESIGN.md +570 -0
- package/lib/design-md/cache/awesome-design-md/composio/DESIGN.md +506 -0
- package/lib/design-md/cache/awesome-design-md/cursor/DESIGN.md +537 -0
- package/lib/design-md/cache/awesome-design-md/dell-1996/DESIGN.md +632 -0
- package/lib/design-md/cache/awesome-design-md/elevenlabs/DESIGN.md +504 -0
- package/lib/design-md/cache/awesome-design-md/expo/DESIGN.md +526 -0
- package/lib/design-md/cache/awesome-design-md/ferrari/DESIGN.md +531 -0
- package/lib/design-md/cache/awesome-design-md/figma/DESIGN.md +578 -0
- package/lib/design-md/cache/awesome-design-md/framer/DESIGN.md +544 -0
- package/lib/design-md/cache/awesome-design-md/hashicorp/DESIGN.md +575 -0
- package/lib/design-md/cache/awesome-design-md/hp/DESIGN.md +670 -0
- package/lib/design-md/cache/awesome-design-md/ibm/DESIGN.md +550 -0
- package/lib/design-md/cache/awesome-design-md/intercom/DESIGN.md +546 -0
- package/lib/design-md/cache/awesome-design-md/kraken/DESIGN.md +125 -0
- package/lib/design-md/cache/awesome-design-md/lamborghini/DESIGN.md +288 -0
- package/lib/design-md/cache/awesome-design-md/linear.app/DESIGN.md +548 -0
- package/lib/design-md/cache/awesome-design-md/lovable/DESIGN.md +298 -0
- package/lib/design-md/cache/awesome-design-md/mastercard/DESIGN.md +365 -0
- package/lib/design-md/cache/awesome-design-md/meta/DESIGN.md +683 -0
- package/lib/design-md/cache/awesome-design-md/minimax/DESIGN.md +746 -0
- package/lib/design-md/cache/awesome-design-md/mintlify/DESIGN.md +852 -0
- package/lib/design-md/cache/awesome-design-md/miro/DESIGN.md +825 -0
- package/lib/design-md/cache/awesome-design-md/mistral.ai/DESIGN.md +773 -0
- package/lib/design-md/cache/awesome-design-md/mongodb/DESIGN.md +767 -0
- package/lib/design-md/cache/awesome-design-md/nike/DESIGN.md +575 -0
- package/lib/design-md/cache/awesome-design-md/nintendo-2001/DESIGN.md +649 -0
- package/lib/design-md/cache/awesome-design-md/notion/DESIGN.md +821 -0
- package/lib/design-md/cache/awesome-design-md/nvidia/DESIGN.md +640 -0
- package/lib/design-md/cache/awesome-design-md/ollama/DESIGN.md +539 -0
- package/lib/design-md/cache/awesome-design-md/opencode.ai/DESIGN.md +521 -0
- package/lib/design-md/cache/awesome-design-md/pinterest/DESIGN.md +597 -0
- package/lib/design-md/cache/awesome-design-md/playstation/DESIGN.md +661 -0
- package/lib/design-md/cache/awesome-design-md/posthog/DESIGN.md +690 -0
- package/lib/design-md/cache/awesome-design-md/raycast/DESIGN.md +669 -0
- package/lib/design-md/cache/awesome-design-md/renault/DESIGN.md +589 -0
- package/lib/design-md/cache/awesome-design-md/replicate/DESIGN.md +616 -0
- package/lib/design-md/cache/awesome-design-md/resend/DESIGN.md +585 -0
- package/lib/design-md/cache/awesome-design-md/revolut/DESIGN.md +636 -0
- package/lib/design-md/cache/awesome-design-md/runwayml/DESIGN.md +244 -0
- package/lib/design-md/cache/awesome-design-md/sanity/DESIGN.md +357 -0
- package/lib/design-md/cache/awesome-design-md/sentry/DESIGN.md +551 -0
- package/lib/design-md/cache/awesome-design-md/shopify/DESIGN.md +516 -0
- package/lib/design-md/cache/awesome-design-md/slack/DESIGN.md +482 -0
- package/lib/design-md/cache/awesome-design-md/spacex/DESIGN.md +363 -0
- package/lib/design-md/cache/awesome-design-md/spotify/DESIGN.md +246 -0
- package/lib/design-md/cache/awesome-design-md/starbucks/DESIGN.md +580 -0
- package/lib/design-md/cache/awesome-design-md/stripe/DESIGN.md +487 -0
- package/lib/design-md/cache/awesome-design-md/supabase/DESIGN.md +462 -0
- package/lib/design-md/cache/awesome-design-md/superhuman/DESIGN.md +448 -0
- package/lib/design-md/cache/awesome-design-md/tesla/DESIGN.md +286 -0
- package/lib/design-md/cache/awesome-design-md/theverge/DESIGN.md +339 -0
- package/lib/design-md/cache/awesome-design-md/together.ai/DESIGN.md +633 -0
- package/lib/design-md/cache/awesome-design-md/uber/DESIGN.md +636 -0
- package/lib/design-md/cache/awesome-design-md/vercel/DESIGN.md +736 -0
- package/lib/design-md/cache/awesome-design-md/vodafone/DESIGN.md +538 -0
- package/lib/design-md/cache/awesome-design-md/voltagent/DESIGN.md +521 -0
- package/lib/design-md/cache/awesome-design-md/warp/DESIGN.md +526 -0
- package/lib/design-md/cache/awesome-design-md/webflow/DESIGN.md +588 -0
- package/lib/design-md/cache/awesome-design-md/wired/DESIGN.md +497 -0
- package/lib/design-md/cache/awesome-design-md/wise/DESIGN.md +544 -0
- package/lib/design-md/cache/awesome-design-md/x.ai/DESIGN.md +465 -0
- package/lib/design-md/cache/awesome-design-md/zapier/DESIGN.md +537 -0
- package/lib/design-md/cache/feelanet/faux/design.md +288 -0
- package/lib/design-md/cache/feelanet/faux-teams/design.md +382 -0
- package/lib/design-md/catalog.json +454 -0
- package/lib/design-md/catalog.mjs +268 -0
- package/lib/design-md/flow.mjs +166 -0
- package/lib/design-md/open.mjs +50 -0
- package/lib/design-md/providers/awesome-design-md.mjs +114 -0
- package/lib/design-md/providers/index.mjs +9 -0
- package/lib/design-md/scan.mjs +291 -0
- package/lib/engine.mjs +44 -0
- package/lib/gitignore.mjs +18 -0
- package/lib/items/.omc/state/sessions/a859aff7-59d9-4ff6-afc6-8cafe4471dc4/pre-tool-advisory-throttle.json +14 -0
- package/lib/items/mcp.codebase-memory.mjs +6 -0
- package/lib/items/mcp.notion.mjs +6 -0
- package/lib/items/mcp.supabase.mjs +6 -0
- package/lib/items/mcp.vercel.mjs +6 -0
- package/lib/items/plugin.bkit.mjs +7 -0
- package/lib/items/plugin.mattpocock-skills.mjs +8 -0
- package/lib/items/plugin.superpowers.mjs +7 -0
- package/lib/items/skill.gsd.mjs +24 -0
- package/lib/items/skill.gstack.mjs +39 -0
- package/lib/jsonfile.mjs +40 -0
- package/lib/tomlfile.mjs +60 -0
- package/lib/tui/render.mjs +192 -0
- package/lib/tui/rows.mjs +206 -0
- package/lib/tui/run.mjs +257 -0
- package/lib/tui/state.mjs +188 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rch4com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# agent-setup
|
|
2
|
+
|
|
3
|
+
Claude Code, Codex, Gemini CLI, OpenCode, Kilo Code, Kiro, Kimi Code,
|
|
4
|
+
Grok Build, Antigravity, GitHub Copilot CLI, VS Code Copilot을 **한 저장소에서
|
|
5
|
+
함께** 쓰기 위한 저장소 범위 부트스트랩과 선택 항목 설치기입니다.
|
|
6
|
+
|
|
7
|
+
공통 지침은 루트 `AGENTS.md` 하나, 공통 스킬은 `.agents/skills` 하나로 두고,
|
|
8
|
+
도구별 설정 파일만 각 도구가 읽는 위치에 만듭니다.
|
|
9
|
+
|
|
10
|
+
## 사용법
|
|
11
|
+
|
|
12
|
+
Git 저장소 안에서 실행합니다.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 배선 — 공통 지침·스킬·도구별 설정 파일을 만든다
|
|
16
|
+
npx @rch4com/agent-setup bootstrap
|
|
17
|
+
|
|
18
|
+
# 무엇이 만들어질지만 확인한다
|
|
19
|
+
npx @rch4com/agent-setup bootstrap --dry-run
|
|
20
|
+
|
|
21
|
+
# 플러그인·MCP·스킬·DESIGN.md를 골라 설치하는 대화형 화면
|
|
22
|
+
npx @rch4com/agent-setup
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
설치하면 실행 명령은 `agent-setup`으로 짧아집니다. 이름에 스코프가 붙은 것은
|
|
26
|
+
스코프 없는 `agent-setup`이 npm의 유사 이름 제한에 걸리기 때문입니다.
|
|
27
|
+
|
|
28
|
+
## 안전 원칙
|
|
29
|
+
|
|
30
|
+
- 반드시 Git 저장소 안에서만 실행되며, 저장소 루트 밖에는 쓰지 않습니다.
|
|
31
|
+
- 홈 디렉터리의 글로벌 설정을 읽거나 수정하지 않습니다.
|
|
32
|
+
- 기존 설정 파일을 덮어쓰지 않습니다.
|
|
33
|
+
- 반복 실행할 수 있습니다.
|
|
34
|
+
|
|
35
|
+
## 요구 사항
|
|
36
|
+
|
|
37
|
+
Node.js 20 이상.
|
|
38
|
+
|
|
39
|
+
## 문서
|
|
40
|
+
|
|
41
|
+
생성되는 구조, 도구별 연결 방식, 설치 가능한 항목, DESIGN.md 라이브러리 등
|
|
42
|
+
상세 문서는 GitHub에 있습니다.
|
|
43
|
+
|
|
44
|
+
- [사용법과 생성 구조](https://github.com/rch4com/Agent-Setup/blob/main/AgentSetup-README.md)
|
|
45
|
+
- [저장소](https://github.com/rch4com/Agent-Setup)
|
|
46
|
+
|
|
47
|
+
## 라이선스
|
|
48
|
+
|
|
49
|
+
MIT
|
package/install.mjs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { findRepoRoot } from './lib/context.mjs'
|
|
3
|
+
import { runBootstrap } from './lib/bootstrap/flow.mjs'
|
|
4
|
+
import { withDeps } from './lib/deps.mjs'
|
|
5
|
+
import {
|
|
6
|
+
BOOTSTRAP_USAGE, DESIGN_USAGE, ROOT_USAGE,
|
|
7
|
+
parseBootstrapArgs, parseDesignArgs, parseRootArgs,
|
|
8
|
+
} from './lib/args.mjs'
|
|
9
|
+
|
|
10
|
+
// 위 정적 import는 전부 의존성 없는 모듈이다 — 부트스트랩은 npm install 없이 돌아야 하고,
|
|
11
|
+
// bootstrap.isolation.test.mjs가 이 불변식을 지킨다. lib/ 나머지는 동적 import로만 닿는다.
|
|
12
|
+
|
|
13
|
+
const STATUS_LABEL = { installed: '설치됨', partial: '일부 설치됨', absent: '미설치' }
|
|
14
|
+
const ACTION_LABEL = { install: '설치', complete: '보완 설치', uninstall: '제거' }
|
|
15
|
+
|
|
16
|
+
// 대화형 화면은 TUI가 맡는다.
|
|
17
|
+
async function openTui(root, { dryRun, skillMode, designDirs }) {
|
|
18
|
+
const { runTui } = await withDeps(() => import('./lib/tui/run.mjs'))
|
|
19
|
+
// skillMode는 화면 안의 '부트스트랩 실행' 작업이 쓴다. design 경로에서는
|
|
20
|
+
// 없으므로 undefined가 넘어가고 runTui의 기본값(auto)이 적용된다.
|
|
21
|
+
await runTui(root, { dryRun, skillMode, designDirs })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// --list / --set 전용 비대화형 경로.
|
|
25
|
+
async function runClassic(root, { dryRun, listOnly, setArg }) {
|
|
26
|
+
const { loadItems } = await withDeps(() => import('./lib/catalog.mjs'))
|
|
27
|
+
const { scan, planChanges, apply } = await withDeps(() => import('./lib/engine.mjs'))
|
|
28
|
+
const items = await loadItems()
|
|
29
|
+
const states = await scan(root, items)
|
|
30
|
+
|
|
31
|
+
if (listOnly) {
|
|
32
|
+
for (const s of states) console.log(`${STATUS_LABEL[s.status].padEnd(7)} ${s.item.id} — ${s.item.label}${s.detail ? ` (${s.detail})` : ''}`)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const selectedIds = new Set(setArg.split(',').map((s) => s.trim()).filter(Boolean))
|
|
37
|
+
const known = new Set(items.map((i) => i.id))
|
|
38
|
+
for (const id of selectedIds) if (!known.has(id)) throw new Error(`알 수 없는 항목: ${id}`)
|
|
39
|
+
|
|
40
|
+
const changes = planChanges(states, selectedIds)
|
|
41
|
+
if (changes.length === 0) { console.log('변경할 항목이 없습니다.'); return }
|
|
42
|
+
|
|
43
|
+
const results = await apply(root, changes, { dryRun })
|
|
44
|
+
for (const r of results) {
|
|
45
|
+
console.log(`${r.ok ? '✔' : '✖'} ${ACTION_LABEL[r.action]} ${r.item.label}${r.message ? ` — ${r.message}` : ''}`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const after = await scan(root, items)
|
|
49
|
+
console.log('\n최종 상태:')
|
|
50
|
+
for (const s of after) console.log(` ${STATUS_LABEL[s.status].padEnd(7)} ${s.item.label}`)
|
|
51
|
+
console.log('\n설정 파일 변경 내용은 git diff로 확인할 수 있습니다.')
|
|
52
|
+
if (results.some((r) => !r.ok)) process.exitCode = 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function main() {
|
|
56
|
+
const argv = process.argv.slice(2)
|
|
57
|
+
const root = findRepoRoot()
|
|
58
|
+
|
|
59
|
+
if (argv[0] === 'bootstrap') {
|
|
60
|
+
const opts = parseBootstrapArgs(argv.slice(1))
|
|
61
|
+
if (opts.help) { console.log(BOOTSTRAP_USAGE); return }
|
|
62
|
+
const { failed } = runBootstrap(root, opts)
|
|
63
|
+
if (failed.length > 0) process.exitCode = 1
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (argv[0] === 'design') {
|
|
68
|
+
const opts = parseDesignArgs(argv.slice(1))
|
|
69
|
+
if (opts.help) { console.log(DESIGN_USAGE); return }
|
|
70
|
+
// 플래그 없는 `design`은 통합 화면을 연다 — DESIGN.MD 섹션이 그 안에 있다.
|
|
71
|
+
if (opts.interactive) return openTui(root, opts)
|
|
72
|
+
const { runDesign } = await withDeps(() => import('./lib/design-md/flow.mjs'))
|
|
73
|
+
await runDesign(root, opts)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const opts = parseRootArgs(argv)
|
|
78
|
+
if (opts.help) { console.log(ROOT_USAGE); return }
|
|
79
|
+
if (opts.interactive) return openTui(root, opts)
|
|
80
|
+
await runClassic(root, opts)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main().catch((err) => { console.error(err.message); process.exit(1) })
|
package/lib/args.mjs
ADDED
|
@@ -0,0 +1,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
|
+
--dry-run 아무것도 바꾸지 않고 예정된 동작만 출력합니다.
|
|
19
|
+
-h, --help 이 도움말을 출력하고 종료합니다.
|
|
20
|
+
|
|
21
|
+
런처로도 사용할 수 있습니다:
|
|
22
|
+
./setup-agents.sh [옵션]
|
|
23
|
+
pwsh -File ./setup-agents.ps1 [옵션]
|
|
24
|
+
--tui (또는 -Tui)를 주면 의존성을 설치하고 대화형 화면을 엽니다.
|
|
25
|
+
거기서 부트스트랩·에이전트·design.md를 한 목록에서 검색하고 고릅니다.`
|
|
26
|
+
|
|
27
|
+
// 서브커맨드 없는 최상위 사용법. `design`은 자기 사용법을 따로 갖는다.
|
|
28
|
+
export const ROOT_USAGE = `사용법: npx @rch4com/agent-setup [옵션]
|
|
29
|
+
npx @rch4com/agent-setup bootstrap [옵션]
|
|
30
|
+
npx @rch4com/agent-setup design [옵션]
|
|
31
|
+
|
|
32
|
+
저장소에 설치기를 두고 쓸 때는 npx @rch4com/agent-setup 자리에
|
|
33
|
+
node install.mjs 를 넣으면 됩니다.
|
|
34
|
+
|
|
35
|
+
옵션이 없으면 대화형 화면을 엽니다 — 부트스트랩·에이전트·design.md를
|
|
36
|
+
한 목록에서 검색하고 고릅니다.
|
|
37
|
+
|
|
38
|
+
옵션:
|
|
39
|
+
--list 현재 설치 상태만 출력하고 종료합니다.
|
|
40
|
+
--set <목록> 쉼표로 구분한 항목을 목표 상태로 맞춥니다.
|
|
41
|
+
전체 제거는 --set "" 로 명시하세요.
|
|
42
|
+
--skill-mode auto|link|copy 대화형 화면에서 실행하는 부트스트랩의 스킬
|
|
43
|
+
연결 방식입니다. (기본값: auto)
|
|
44
|
+
--design-dir <경로> design.md 소스를 더합니다. <소스id>=<경로>
|
|
45
|
+
형식도 받으며 반복 지정할 수 있습니다.
|
|
46
|
+
--dry-run 아무것도 바꾸지 않고 예정된 동작만 출력합니다.
|
|
47
|
+
-h, --help 이 도움말을 출력하고 종료합니다.
|
|
48
|
+
|
|
49
|
+
--list와 --set은 동작을 고르는 플래그라 함께 쓸 수 없습니다.
|
|
50
|
+
|
|
51
|
+
서브커맨드 도움말: npx @rch4com/agent-setup bootstrap --help
|
|
52
|
+
npx @rch4com/agent-setup design --help`
|
|
53
|
+
|
|
54
|
+
export const DESIGN_USAGE = `사용법: npx @rch4com/agent-setup design [옵션]
|
|
55
|
+
node install.mjs design [옵션] # 저장소에 설치기를 두고 쓸 때
|
|
56
|
+
|
|
57
|
+
옵션이 없으면 대화형 화면을 엽니다 — DESIGN.MD 섹션이 그 안에 있습니다.
|
|
58
|
+
|
|
59
|
+
옵션:
|
|
60
|
+
--list 카탈로그와 설치 상태를 출력합니다.
|
|
61
|
+
--set <목록> 쉼표로 구분한 항목을 목표 상태로 맞춥니다.
|
|
62
|
+
토큰은 <이름> 또는 <제공자>/<이름> 입니다.
|
|
63
|
+
전체 제거는 --set "" 로 명시하세요.
|
|
64
|
+
--preview <목록> 미리보기를 엽니다.
|
|
65
|
+
--sync=installed 설치본을 원본 최신으로 다시 받습니다.
|
|
66
|
+
--sync=catalog 사용 가능 목록·카테고리를 소스에서 다시 만듭니다.
|
|
67
|
+
--sync=stale 설치본을 원본과 해시 비교합니다.
|
|
68
|
+
--design-dir <경로> design.md 소스를 더합니다. <소스id>=<경로>
|
|
69
|
+
형식도 받으며 반복 지정할 수 있습니다.
|
|
70
|
+
--dry-run 아무것도 바꾸지 않고 예정된 동작만 출력합니다.
|
|
71
|
+
-h, --help 이 도움말을 출력하고 종료합니다.
|
|
72
|
+
|
|
73
|
+
--list, --set, --preview, --sync는 동작을 고르는 플래그라
|
|
74
|
+
한 번에 하나만 지정할 수 있습니다.`
|
|
75
|
+
|
|
76
|
+
const SKILL_MODES = ['auto', 'link', 'copy']
|
|
77
|
+
|
|
78
|
+
// 인자 사양: 플래그 이름 → 'bool'(값 없음) | 'value'(값 하나를 소비).
|
|
79
|
+
// 미지 인자를 조용히 삼키지 않기 위한 것이다 — 오타(`--dryrun`)나 지원하지
|
|
80
|
+
// 않는 형식(`--set=a`)이 무시되면 사용자는 명령이 먹혔다고 믿지만 아무 일도
|
|
81
|
+
// 일어나지 않는다. bootstrap 파서만 지키던 규칙을 세 파서 모두로 넓힌다.
|
|
82
|
+
const HELP_SPEC = { '-h': 'bool', '--help': 'bool' }
|
|
83
|
+
const ROOT_SPEC = {
|
|
84
|
+
...HELP_SPEC, '--dry-run': 'bool', '--list': 'bool',
|
|
85
|
+
'--set': 'value', '--skill-mode': 'value', '--design-dir': 'value',
|
|
86
|
+
}
|
|
87
|
+
const DESIGN_SPEC = {
|
|
88
|
+
...HELP_SPEC, '--dry-run': 'bool', '--list': 'bool', '--set': 'value',
|
|
89
|
+
'--preview': 'value', '--sync': 'value', '--design-dir': 'value',
|
|
90
|
+
}
|
|
91
|
+
const BOOTSTRAP_SPEC = { ...HELP_SPEC, '--dry-run': 'bool', '--skill-mode': 'value' }
|
|
92
|
+
|
|
93
|
+
export function assertKnownArgs(argv, spec, usage) {
|
|
94
|
+
for (let i = 0; i < argv.length; i++) {
|
|
95
|
+
const token = argv[i]
|
|
96
|
+
const eq = token.startsWith('--') ? token.indexOf('=') : -1
|
|
97
|
+
const name = eq > 0 ? token.slice(0, eq) : token
|
|
98
|
+
const kind = spec[name]
|
|
99
|
+
if (kind === undefined) throw new Error(`알 수 없는 인자입니다: ${token}\n\n${usage}`)
|
|
100
|
+
if (kind === 'bool' && eq > 0) throw new Error(`${name}에는 값을 줄 수 없습니다: ${token}\n\n${usage}`)
|
|
101
|
+
// `--플래그 값` 형태는 값 한 칸을 건너뛴다. `--플래그=값`은 이미 한 토큰이다.
|
|
102
|
+
if (kind === 'value' && eq < 0) i++
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 동작 플래그는 한 번에 하나만 뜻이 있다. 여러 개를 주면 실행 경로가 먼저 오는
|
|
107
|
+
// 하나를 골라 나머지를 조용히 버렸다 — `--list --set a`는 --set을, design의
|
|
108
|
+
// `--preview x --set y`는 --set을 무시했다. 알 수 없는 인자를 거부하는 것과
|
|
109
|
+
// 같은 이유(준 인자가 먹히지 않은 채 다른 일이 벌어진다)로 같이 거부한다.
|
|
110
|
+
export function assertSingleAction(argv, names, usage) {
|
|
111
|
+
const given = names.filter((name) => hasFlag(argv, name))
|
|
112
|
+
if (given.length > 1) {
|
|
113
|
+
// 조사를 붙이지 않는다 — 플래그 이름에 따라 은/는이 갈려 문장이 어색해진다.
|
|
114
|
+
throw new Error(`함께 쓸 수 없습니다: ${given.join(', ')} — 동작 플래그는 한 번에 하나만 지정하세요.\n\n${usage}`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function checkSkillMode(value, usage) {
|
|
119
|
+
if (!SKILL_MODES.includes(value)) {
|
|
120
|
+
throw new Error(`--skill-mode는 ${SKILL_MODES.join(', ')} 중 하나여야 합니다: ${value}\n\n${usage}`)
|
|
121
|
+
}
|
|
122
|
+
return value
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// `--skill-mode <값>`과 `--skill-mode=<값>`을 모두 받는다. 없으면 기본값 auto.
|
|
126
|
+
export function parseSkillMode(argv, usage) {
|
|
127
|
+
let mode = 'auto'
|
|
128
|
+
for (let i = 0; i < argv.length; i++) {
|
|
129
|
+
const a = argv[i]
|
|
130
|
+
if (a === '--skill-mode') {
|
|
131
|
+
const value = argv[i + 1]
|
|
132
|
+
if (value === undefined || value.startsWith('--')) {
|
|
133
|
+
throw new Error(`--skill-mode 뒤에 값이 필요합니다 (${SKILL_MODES.join(', ')} 중 하나).\n\n${usage}`)
|
|
134
|
+
}
|
|
135
|
+
mode = checkSkillMode(value, usage)
|
|
136
|
+
i++
|
|
137
|
+
} else if (a.startsWith('--skill-mode=')) {
|
|
138
|
+
mode = checkSkillMode(a.slice('--skill-mode='.length), usage)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return mode
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function wantsHelp(argv) {
|
|
145
|
+
return argv.includes('-h') || argv.includes('--help')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// `--flag` 또는 `--flag=값`이 있는가. 두 형식 중 하나만 인식하면 나머지
|
|
149
|
+
// 형식이 조용히 무시되어, 준 인자가 먹히지 않은 채 화면이 열린다.
|
|
150
|
+
export function hasFlag(argv, name) {
|
|
151
|
+
return argv.some((a) => a === name || a.startsWith(`${name}=`))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function requireValue(argv, name) {
|
|
155
|
+
const eq = argv.find((a) => a.startsWith(`${name}=`))
|
|
156
|
+
if (eq !== undefined) {
|
|
157
|
+
const value = eq.slice(name.length + 1)
|
|
158
|
+
if (!value) throw new Error(`${name} 뒤에 값이 필요합니다.`)
|
|
159
|
+
return value
|
|
160
|
+
}
|
|
161
|
+
const value = argv[argv.indexOf(name) + 1]
|
|
162
|
+
if (value === undefined || value.startsWith('--')) {
|
|
163
|
+
throw new Error(`${name} 뒤에 값이 필요합니다.`)
|
|
164
|
+
}
|
|
165
|
+
return value
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 반복 지정 가능한 플래그 값 수집: `--flag <값>` 과 `--flag=<값>` 둘 다 받는다.
|
|
169
|
+
export function collectValues(argv, name) {
|
|
170
|
+
const values = []
|
|
171
|
+
for (let i = 0; i < argv.length; i++) {
|
|
172
|
+
if (argv[i] === name) {
|
|
173
|
+
const value = argv[i + 1]
|
|
174
|
+
if (value === undefined || value.startsWith('--')) throw new Error(`${name} 뒤에 값이 필요합니다.`)
|
|
175
|
+
values.push(value)
|
|
176
|
+
i++
|
|
177
|
+
} else if (argv[i].startsWith(`${name}=`)) {
|
|
178
|
+
const value = argv[i].slice(name.length + 1)
|
|
179
|
+
if (!value) throw new Error(`${name} 뒤에 값이 필요합니다.`)
|
|
180
|
+
values.push(value)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return values
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 빈 문자열은 유효한 값이다(전체 제거). 없음(null)과 구별해야 한다.
|
|
187
|
+
// `--set <값>`과 `--set=<값>`을 모두 받는다 — 등호 형식만 인식되지 않으면
|
|
188
|
+
// 목표 집합을 지정했는데도 대화형 화면이 열린다. `--set=`은 빈 값이다.
|
|
189
|
+
export function parseSetArg(argv) {
|
|
190
|
+
const eq = argv.find((a) => a.startsWith('--set='))
|
|
191
|
+
if (eq !== undefined) return eq.slice('--set='.length)
|
|
192
|
+
if (!argv.includes('--set')) return null
|
|
193
|
+
const value = argv[argv.indexOf('--set') + 1]
|
|
194
|
+
if (value === undefined || value.startsWith('--')) {
|
|
195
|
+
throw new Error('--set 뒤에 항목 목록이 필요합니다. 전체 제거는 --set "" 로 명시하세요.')
|
|
196
|
+
}
|
|
197
|
+
return value
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// --help는 다른 인자 검증보다 앞선다 — 도움말을 보려는 사람에게 인자 오류를
|
|
201
|
+
// 먼저 내밀 이유가 없다. 아래 두 파서가 같은 규칙을 따른다.
|
|
202
|
+
|
|
203
|
+
// 서브커맨드 없는 최상위 인자.
|
|
204
|
+
export function parseRootArgs(argv) {
|
|
205
|
+
if (wantsHelp(argv)) {
|
|
206
|
+
return { help: true, dryRun: false, listOnly: false, setArg: null, skillMode: 'auto', designDirs: [], interactive: false }
|
|
207
|
+
}
|
|
208
|
+
assertKnownArgs(argv, ROOT_SPEC, ROOT_USAGE)
|
|
209
|
+
assertSingleAction(argv, ['--list', '--set'], ROOT_USAGE)
|
|
210
|
+
const setArg = parseSetArg(argv)
|
|
211
|
+
return {
|
|
212
|
+
help: false,
|
|
213
|
+
dryRun: argv.includes('--dry-run'),
|
|
214
|
+
listOnly: argv.includes('--list'),
|
|
215
|
+
setArg,
|
|
216
|
+
// 대화형 화면에서 실행하는 부트스트랩이 이 값을 쓴다. 예전에는 루트에서
|
|
217
|
+
// 읽지 않아, 런처가 넘긴 --skill-mode가 조용히 버려졌다.
|
|
218
|
+
skillMode: parseSkillMode(argv, ROOT_USAGE),
|
|
219
|
+
designDirs: collectValues(argv, '--design-dir'),
|
|
220
|
+
// 목록·집합 지정이 없으면 대화형 화면으로 간다.
|
|
221
|
+
interactive: !argv.includes('--list') && setArg === null,
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// `design` 서브커맨드 플래그.
|
|
226
|
+
export function parseDesignArgs(argv) {
|
|
227
|
+
if (wantsHelp(argv)) {
|
|
228
|
+
return { help: true, dryRun: false, list: false, set: null, preview: null, sync: null, designDirs: [], interactive: false }
|
|
229
|
+
}
|
|
230
|
+
assertKnownArgs(argv, DESIGN_SPEC, DESIGN_USAGE)
|
|
231
|
+
assertSingleAction(argv, ['--list', '--set', '--preview', '--sync'], DESIGN_USAGE)
|
|
232
|
+
const list = argv.includes('--list')
|
|
233
|
+
const set = parseSetArg(argv)
|
|
234
|
+
const preview = hasFlag(argv, '--preview') ? requireValue(argv, '--preview') : null
|
|
235
|
+
|
|
236
|
+
let sync = null
|
|
237
|
+
const s = argv.find((a) => a === '--sync' || a.startsWith('--sync='))
|
|
238
|
+
if (s) {
|
|
239
|
+
const m = /^--sync=(installed|catalog|stale)$/.exec(s)
|
|
240
|
+
if (!m) throw new Error('--sync=installed|catalog|stale 형식으로 지정하세요.')
|
|
241
|
+
sync = m[1]
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
help: false,
|
|
246
|
+
dryRun: argv.includes('--dry-run'),
|
|
247
|
+
list,
|
|
248
|
+
set,
|
|
249
|
+
preview,
|
|
250
|
+
sync,
|
|
251
|
+
designDirs: collectValues(argv, '--design-dir'),
|
|
252
|
+
interactive: !list && set === null && preview === null && sync === null,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// `bootstrap`에 허용되는 플래그: --dry-run, --skill-mode(+값)/--skill-mode=값, -h/--help.
|
|
257
|
+
// 그 외 인자는 거부한다 — 조용히 삼켜지는 것을 막기 위해서다.
|
|
258
|
+
export function parseBootstrapArgs(argv) {
|
|
259
|
+
if (wantsHelp(argv)) return { dryRun: false, skillMode: 'auto', help: true }
|
|
260
|
+
assertKnownArgs(argv, BOOTSTRAP_SPEC, BOOTSTRAP_USAGE)
|
|
261
|
+
return {
|
|
262
|
+
dryRun: argv.includes('--dry-run'),
|
|
263
|
+
skillMode: parseSkillMode(argv, BOOTSTRAP_USAGE),
|
|
264
|
+
help: false,
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// 스킬 어댑터 — .agents/skills를 도구별 경로에서 보이게 한다.
|
|
2
|
+
// Windows는 Junction을 쓴다. 관리자 권한이 필요 없고, MSYS(Git Bash)의 ln -s가
|
|
3
|
+
// 링크 대신 복사를 만드는 문제도 우회한다.
|
|
4
|
+
import { cpSync, lstatSync, mkdirSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
|
5
|
+
import { dirname, join, relative, resolve } from 'node:path'
|
|
6
|
+
import { repoPath, repoPathStrict } from '../context.mjs'
|
|
7
|
+
import { pathExists } from './apply.mjs'
|
|
8
|
+
|
|
9
|
+
const MARKER = '.agent-kit-managed-copy'
|
|
10
|
+
const SOURCE_REL = '.agents/skills'
|
|
11
|
+
|
|
12
|
+
// 심볼릭 링크/Junction이면 가리키는 절대 경로를, 아니면 null을 돌려준다.
|
|
13
|
+
function linkTarget(target) {
|
|
14
|
+
try {
|
|
15
|
+
if (!lstatSync(target).isSymbolicLink()) return null
|
|
16
|
+
return resolve(dirname(target), readlinkSync(target))
|
|
17
|
+
} catch {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createLink(source, target) {
|
|
23
|
+
if (process.platform === 'win32') {
|
|
24
|
+
// Junction은 절대 경로를 요구한다.
|
|
25
|
+
symlinkSync(source, target, 'junction')
|
|
26
|
+
} else {
|
|
27
|
+
symlinkSync(relative(dirname(target), source), target)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createCopy(source, target) {
|
|
32
|
+
mkdirSync(target, { recursive: true })
|
|
33
|
+
cpSync(source, target, { recursive: true })
|
|
34
|
+
writeFileSync(join(target, MARKER), '', { encoding: 'utf8' })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function configureAdapter(root, { tool, path: rel }, { dryRun = false, skillMode = 'auto', log }) {
|
|
38
|
+
// 기존 상태를 살펴보는 데에는 어휘적 경로를 쓴다. target이 저장소 밖을 가리키는
|
|
39
|
+
// 링크일 수 있는데, 그 경우도 "보존 + 경고"가 정답이라 지켜야 할 쓰기가 없다.
|
|
40
|
+
// repoPathStrict를 여기서 쓰면 이탈 링크에서 예외가 던져져 경고 대신 죽는다.
|
|
41
|
+
const target = repoPath(root, rel)
|
|
42
|
+
|
|
43
|
+
const linked = linkTarget(target)
|
|
44
|
+
if (linked !== null) {
|
|
45
|
+
if (linked === repoPath(root, SOURCE_REL)) {
|
|
46
|
+
log(`${tool} 스킬 링크 확인: ${rel}`)
|
|
47
|
+
return { ok: true, action: 'skip', path: rel }
|
|
48
|
+
}
|
|
49
|
+
log(`경고: ${rel} 경로가 다른 위치를 가리키는 링크입니다. 변경하지 않습니다.`)
|
|
50
|
+
return { ok: true, action: 'warn', path: rel, message: '다른 위치를 가리키는 링크' }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const managedCopy = pathExists(target) && pathExists(join(target, MARKER))
|
|
54
|
+
if (pathExists(target) && !managedCopy) {
|
|
55
|
+
log(`경고: ${rel} 경로가 이미 존재하며 agent-kit 관리 대상이 아닙니다. 변경하지 않습니다.`)
|
|
56
|
+
return { ok: true, action: 'warn', path: rel, message: '관리 대상이 아닌 기존 항목' }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 여기부터는 실제로 링크/복사본을 만드는 경로다. 엄격 검사는 dry-run 여부와
|
|
60
|
+
// 무관하게 항상 하고(dry-run에서도 이탈 경로를 걸러내려면), 실제 파일시스템
|
|
61
|
+
// 쓰기만 !dryRun으로 막는다 — apply.mjs의 ensureDirs와 같은 형태.
|
|
62
|
+
// source(.agents/skills)는 실제로 읽어 복사하는 대상이므로 여기서 함께 검사한다.
|
|
63
|
+
const source = repoPathStrict(root, SOURCE_REL)
|
|
64
|
+
const strictTarget = repoPathStrict(root, rel)
|
|
65
|
+
|
|
66
|
+
if (dryRun) {
|
|
67
|
+
log(`${tool} 스킬 어댑터 생성 예정: ${rel} (${skillMode})`)
|
|
68
|
+
return { ok: true, action: 'skip', path: rel }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (managedCopy) {
|
|
72
|
+
log(`${tool} 스킬 복제본 동기화: ${rel}`)
|
|
73
|
+
// 마커가 확인된 복사본만 지운다.
|
|
74
|
+
rmSync(strictTarget, { recursive: true, force: true })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
mkdirSync(dirname(strictTarget), { recursive: true })
|
|
78
|
+
|
|
79
|
+
if (skillMode !== 'copy') {
|
|
80
|
+
try {
|
|
81
|
+
createLink(source, strictTarget)
|
|
82
|
+
log(`${tool} 스킬 링크 생성: ${rel} -> ${SOURCE_REL}`)
|
|
83
|
+
return { ok: true, action: 'link', path: rel }
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (skillMode === 'link') {
|
|
86
|
+
return { ok: false, action: 'link', path: rel, message: `링크 생성 실패: ${err.message}` }
|
|
87
|
+
}
|
|
88
|
+
log(`경고: ${tool} 링크 생성에 실패하여 복사 방식으로 전환합니다.`)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
createCopy(source, strictTarget)
|
|
93
|
+
log(`${tool} 스킬 복제본 생성: ${rel}`)
|
|
94
|
+
return { ok: true, action: 'copy', path: rel }
|
|
95
|
+
}
|