@rch4com/agent-setup 1.0.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 +151 -3
- package/install.mjs +102 -33
- package/lib/args.mjs +104 -94
- package/lib/bootstrap/adapter.mjs +96 -95
- package/lib/bootstrap/apply.mjs +380 -269
- package/lib/bootstrap/flow.mjs +73 -54
- package/lib/bootstrap/record.mjs +154 -0
- package/lib/bootstrap/templates.mjs +148 -148
- package/lib/bootstrap/text.mjs +19 -0
- 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 -0
- 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 -0
- package/lib/width.mjs +57 -0
- package/package.json +36 -36
- package/lib/items/.omc/state/sessions/a859aff7-59d9-4ff6-afc6-8cafe4471dc4/pre-tool-advisory-throttle.json +0 -14
package/lib/design-md/flow.mjs
CHANGED
|
@@ -1,166 +1,181 @@
|
|
|
1
|
-
import { scan, planChanges, apply } from '../engine.mjs'
|
|
2
|
-
import { loadCatalog, saveCatalog, buildItems, allEntries, sha256, resolveTokens, netFetch, CATALOG_PATH } from './catalog.mjs'
|
|
3
|
-
import { PROVIDERS } from './providers/index.mjs'
|
|
4
|
-
import { discoverSources, extraDirsFromEnv } from './scan.mjs'
|
|
5
|
-
import { makeOpener, openPreview } from './open.mjs'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
for (const
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export async function
|
|
97
|
-
const installed = await installedItems(root, items)
|
|
98
|
-
|
|
99
|
-
for (const item of installed) {
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
1
|
+
import { scan, planChanges, apply } from '../engine.mjs'
|
|
2
|
+
import { loadCatalog, saveCatalog, buildItems, allEntries, sha256, resolveTokens, netFetch, CATALOG_PATH } from './catalog.mjs'
|
|
3
|
+
import { PROVIDERS } from './providers/index.mjs'
|
|
4
|
+
import { discoverSources, extraDirsFromEnv } from './scan.mjs'
|
|
5
|
+
import { makeOpener, openPreview } from './open.mjs'
|
|
6
|
+
import { createT, toText } from '../i18n/index.mjs'
|
|
7
|
+
import { labelWidth, pad } from '../width.mjs'
|
|
8
|
+
|
|
9
|
+
// design 항목의 detect는 installed/absent만 돌려준다(catalog.mjs) — partial은
|
|
10
|
+
// 열을 넓히기만 하고 실제로 나오지 않는다.
|
|
11
|
+
const DESIGN_STATUS_KEYS = ['status.installed', 'status.absent']
|
|
12
|
+
|
|
13
|
+
// 카탈로그의 카테고리는 공급자가 준 영어 데이터라 번역하지 않는다.
|
|
14
|
+
// 우리가 만든 catch-all(__other·__local)만 번역 대상이다.
|
|
15
|
+
export function categoryLabel(t, id) {
|
|
16
|
+
return id.startsWith('__') ? t(`category.${id.slice(2)}`) : id
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function report(results, log, t) {
|
|
20
|
+
for (const r of results) {
|
|
21
|
+
const message = toText(t, r.message)
|
|
22
|
+
log(` ${r.ok ? '✔' : '✖'} ${t(`change.${r.action}`)} ${r.item.label}${message ? ` — ${message}` : ''}`)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── 비대화형/공용 코어 ─────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
function printList(states, log, t) {
|
|
29
|
+
const byProvider = new Map()
|
|
30
|
+
for (const s of states) {
|
|
31
|
+
if (!byProvider.has(s.item.providerId)) byProvider.set(s.item.providerId, [])
|
|
32
|
+
byProvider.get(s.item.providerId).push(s)
|
|
33
|
+
}
|
|
34
|
+
// 폭은 그 로케일의 실제 라벨에서 뽑는다. padEnd는 코드 유닛을 세어 한글이
|
|
35
|
+
// 밀리고, 상수로 박으면 영어('Not installed' 13칸)가 넘쳐 이름 열이 어긋난다.
|
|
36
|
+
const statusWidth = labelWidth(t, DESIGN_STATUS_KEYS)
|
|
37
|
+
for (const [pid, group] of byProvider) {
|
|
38
|
+
log(`[${pid}${group[0].item.local ? ` · ${t('design.localTag')}` : ''}]`)
|
|
39
|
+
for (const s of group) {
|
|
40
|
+
log(` ${pad(t(`status.${s.status}`), statusWidth)} ${s.item.name} — ${s.item.label} [${categoryLabel(t, s.item.designCategory)}]`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 미리보기는 관대하게 해석한다: 알 수 없거나 중복된 토큰은 예외 없이 안내하고 건너뛴다.
|
|
46
|
+
function openPreviews(items, tokensStr, opener, log, t) {
|
|
47
|
+
for (const tok of tokensStr.split(',').map((s) => s.trim()).filter(Boolean)) {
|
|
48
|
+
const slash = tok.indexOf('/')
|
|
49
|
+
const matches = slash >= 0
|
|
50
|
+
? items.filter((i) => i.providerId === tok.slice(0, slash) && i.name === tok.slice(slash + 1))
|
|
51
|
+
: items.filter((i) => i.name === tok)
|
|
52
|
+
if (matches.length === 0) { log(t('design.unknownItem', { token: tok })); continue }
|
|
53
|
+
if (matches.length > 1) {
|
|
54
|
+
log(t('design.ambiguous', { token: tok, options: matches.map((m) => `${m.providerId}/${m.name}`).join(', ') }))
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
openPreview(opener, matches[0], log, t)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 보이는 집합(visibleStates) 안에서만 diff/적용한다.
|
|
62
|
+
async function applyVisible(root, visibleStates, selectedIds, { dryRun, log, t }) {
|
|
63
|
+
const changes = planChanges(visibleStates, selectedIds)
|
|
64
|
+
if (changes.length === 0) { log(t('apply.noChanges')); return [] }
|
|
65
|
+
const results = await apply(root, changes, { dryRun, log, t })
|
|
66
|
+
report(results, log, t)
|
|
67
|
+
return results
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function installedItems(root, items) {
|
|
71
|
+
const out = []
|
|
72
|
+
for (const item of items) {
|
|
73
|
+
if ((await item.detect({ root })).status === 'installed') out.push(item)
|
|
74
|
+
}
|
|
75
|
+
return out
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function refreshCatalog({ dryRun, fetchImpl, log, catalogFile = CATALOG_PATH, t = createT('en') }) {
|
|
79
|
+
const next = { updatedAt: new Date().toISOString(), providers: {} }
|
|
80
|
+
for (const provider of PROVIDERS) {
|
|
81
|
+
try {
|
|
82
|
+
const entries = await provider.fetchCatalog(fetchImpl)
|
|
83
|
+
next.providers[provider.id] = { label: provider.label, entries }
|
|
84
|
+
log(t('design.provider.count', { label: provider.label, count: entries.length }))
|
|
85
|
+
} catch (err) {
|
|
86
|
+
log(t('design.provider.failed', { label: provider.label, message: err.message }))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const total = allEntries(next).length
|
|
90
|
+
if (total === 0) { log(t('design.catalog.empty')); return { total: 0 } }
|
|
91
|
+
if (!dryRun) saveCatalog(next, catalogFile)
|
|
92
|
+
log(dryRun ? t('design.catalog.planned', { total }) : t('design.catalog.updated', { total }))
|
|
93
|
+
return { total }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function updateInstalled(root, items, { dryRun, log, t = createT('en') }) {
|
|
97
|
+
const installed = await installedItems(root, items)
|
|
98
|
+
if (installed.length === 0) { log(t('design.none')); return }
|
|
99
|
+
for (const item of installed) {
|
|
100
|
+
try {
|
|
101
|
+
await item.install({ root, dryRun, fresh: true })
|
|
102
|
+
log(t('design.updated', { label: item.label }))
|
|
103
|
+
} catch (err) {
|
|
104
|
+
log(t('design.updateFailed', { label: item.label, message: err.message }))
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function findStale(root, items, { log, t = createT('en') }) {
|
|
110
|
+
const installed = await installedItems(root, items)
|
|
111
|
+
const stale = []
|
|
112
|
+
for (const item of installed) {
|
|
113
|
+
const local = item.localText({ root })
|
|
114
|
+
if (local == null) continue
|
|
115
|
+
let remote
|
|
116
|
+
try { remote = await item.remoteText() } catch { remote = null }
|
|
117
|
+
if (remote == null) { log(t('design.sourceCheckFailed', { label: item.label })); continue }
|
|
118
|
+
if (sha256(local) !== sha256(remote)) stale.push(item)
|
|
119
|
+
}
|
|
120
|
+
return stale
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── 진입점 ────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
export async function runDesign(root, opts = {}) {
|
|
126
|
+
const {
|
|
127
|
+
dryRun = false,
|
|
128
|
+
list = false,
|
|
129
|
+
set = null,
|
|
130
|
+
sync = null,
|
|
131
|
+
preview = null,
|
|
132
|
+
fetchImpl = netFetch,
|
|
133
|
+
log = console.log,
|
|
134
|
+
designDirs = [],
|
|
135
|
+
env = process.env,
|
|
136
|
+
catalogFile = CATALOG_PATH,
|
|
137
|
+
t = createT('en'),
|
|
138
|
+
} = opts
|
|
139
|
+
const opener = opts.opener ?? makeOpener(dryRun, log)
|
|
140
|
+
|
|
141
|
+
const catalog = loadCatalog(catalogFile)
|
|
142
|
+
// 디렉터리 스캔이 먼저다 — 프로바이더 등록 없이도 사내 정의가 목록에 오른다.
|
|
143
|
+
const sources = opts.sources ?? discoverSources({
|
|
144
|
+
...(opts.bundleDir ? { bundleDir: opts.bundleDir } : {}),
|
|
145
|
+
extraDirs: [...extraDirsFromEnv(env), ...designDirs],
|
|
146
|
+
log,
|
|
147
|
+
t,
|
|
148
|
+
})
|
|
149
|
+
const items = buildItems(catalog, { fetchImpl, sources })
|
|
150
|
+
|
|
151
|
+
if (list) {
|
|
152
|
+
printList(await scan(root, items), log, t)
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
if (preview != null) {
|
|
156
|
+
openPreviews(items, preview, opener, log, t)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
if (sync != null) {
|
|
160
|
+
await runSync(root, items, { op: sync, dryRun, fetchImpl, log, catalogFile, t })
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (set != null) {
|
|
164
|
+
const selected = new Set(resolveTokens(items, set).map((i) => i.id))
|
|
165
|
+
await applyVisible(root, await scan(root, items), selected, { dryRun, log, t })
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
printList(await scan(root, items), log, t)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function runSync(root, items, { op: chosen, dryRun, fetchImpl, log, catalogFile, t }) {
|
|
172
|
+
if (chosen === 'catalog') { await refreshCatalog({ dryRun, fetchImpl, log, catalogFile, t }); return }
|
|
173
|
+
if (chosen === 'installed') { await updateInstalled(root, items, { dryRun, log, t }); return }
|
|
174
|
+
if (chosen === 'stale') {
|
|
175
|
+
const stale = await findStale(root, items, { log, t })
|
|
176
|
+
if (stale.length === 0) { log(t('design.allCurrent')); return }
|
|
177
|
+
log(t('design.staleList', { count: stale.length, names: stale.map((i) => i.name).join(', ') }))
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
log(t('design.unknownSync', { action: chosen }))
|
|
181
|
+
}
|
package/lib/design-md/open.mjs
CHANGED
|
@@ -1,50 +1,51 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process'
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
process.platform === '
|
|
30
|
-
['
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { createT, msg } from '../i18n/index.mjs'
|
|
4
|
+
|
|
5
|
+
// 미리보기 대상은 신뢰 경계 밖이다 — 이름이 원격 README 파싱이나 디렉터리
|
|
6
|
+
// 스캔에서 온다. http(s) URL이거나 실제로 존재하는 로컬 경로일 때만 OS에
|
|
7
|
+
// 넘긴다. `javascript:`처럼 스킴만 바꾼 대상이 기본 앱으로 열리지 않게 한다.
|
|
8
|
+
export function isOpenableTarget(target) {
|
|
9
|
+
const text = String(target ?? '')
|
|
10
|
+
// eslint-disable-next-line no-control-regex
|
|
11
|
+
if (!text || /[\x00-\x1f\x7f]/.test(text)) return false
|
|
12
|
+
if (/^https?:\/\//i.test(text)) return true
|
|
13
|
+
return existsSync(text)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// OS 기본 브라우저/앱으로 target(URL 또는 파일)을 연다. 주입 가능(테스트 대체).
|
|
17
|
+
export function makeOpener(dryRun, log = console.log) {
|
|
18
|
+
return (target) => {
|
|
19
|
+
if (dryRun) {
|
|
20
|
+
log(` [dry-run] open ${target}`)
|
|
21
|
+
return { ok: true }
|
|
22
|
+
}
|
|
23
|
+
if (!isOpenableTarget(target)) return { ok: false, output: msg('design.notOpenable') }
|
|
24
|
+
// Windows에서 `cmd /c start`를 쓰지 않는다 — Node는 셸 없이 cmd를 부를 때
|
|
25
|
+
// 공백 없는 인자를 quote하지 않고, cmd는 `&`·`|`를 명령 구분자로 읽는다.
|
|
26
|
+
// 그러면 이름에 섞인 `&`가 곧바로 명령 실행이 된다. rundll32는 셸을 거치지
|
|
27
|
+
// 않아 인자가 그대로 전달되고, URL과 로컬 파일을 모두 기본 앱으로 연다.
|
|
28
|
+
const [cmd, args] =
|
|
29
|
+
process.platform === 'win32' ? ['rundll32', ['url.dll,FileProtocolHandler', target]] :
|
|
30
|
+
process.platform === 'darwin' ? ['open', [target]] :
|
|
31
|
+
['xdg-open', [target]]
|
|
32
|
+
try {
|
|
33
|
+
execFileSync(cmd, args, { stdio: 'ignore' })
|
|
34
|
+
return { ok: true }
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return { ok: false, output: String(err.message) }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 항목의 미리보기를 연다: 웹 URL이 있으면 그 페이지, 로컬 항목은 원본 파일을 기본 앱으로.
|
|
42
|
+
export function openPreview(opener, item, log = console.log, t = createT('en')) {
|
|
43
|
+
const target = item.webUrl ?? item.previewPath ?? null
|
|
44
|
+
if (!target) {
|
|
45
|
+
log(t('design.noPreviewUrl', { label: item.label }))
|
|
46
|
+
return { ok: false }
|
|
47
|
+
}
|
|
48
|
+
const r = opener(target)
|
|
49
|
+
if (!r.ok) log(t('design.openFailed', { label: item.label, target }))
|
|
50
|
+
return r
|
|
51
|
+
}
|