@rch4com/agent-setup 1.11.1 → 1.14.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/install.mjs +14 -1
- package/lib/bootstrap/manifest.mjs +14 -2
- package/lib/catalog.mjs +77 -14
- package/lib/global-plugin.mjs +207 -0
- package/lib/i18n/catalog/en.mjs +18 -3
- package/lib/i18n/catalog/ko.mjs +18 -3
- package/lib/items/global.ponytail.mjs +52 -0
- package/lib/items/global.superpowers.mjs +52 -0
- package/lib/items/plugin.impeccable.mjs +6 -3
- package/lib/items/plugin.mattpocock-skills.mjs +2 -1
- package/lib/items/plugin.superpowers.mjs +2 -1
- package/lib/items/skill.mattpocock-skills.mjs +16 -0
- package/lib/items/skill.superpowers.mjs +19 -0
- package/package.json +16 -5
package/install.mjs
CHANGED
|
@@ -70,7 +70,20 @@ async function runClassic(root, { dryRun, listOnly, setArg, t }) {
|
|
|
70
70
|
for (const id of selectedIds) if (!known.has(id)) throw new LocalizedError('error.unknownItem', { id })
|
|
71
71
|
assertExclusive(items, selectedIds)
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
// 전역(scope: 'user') 항목은 --set 생략만으로 지우지 않는다. --set은 "저장소가
|
|
74
|
+
// 가질 상태"의 선언이라 목록 밖 항목을 제거하는데, 전역 항목까지 그 규칙에
|
|
75
|
+
// 태우면 무관한 --set 실행 하나가 머신 전체 설치를 걷어 간다 — 스크래치
|
|
76
|
+
// 저장소에서 스킬 둘을 --set 하다가 copilot의 전역 superpowers가 함께
|
|
77
|
+
// 제거되는 것을 실측했다(2026-08-15). 전역 항목의 제거는 TUI에서 명시적으로
|
|
78
|
+
// 해제해 검토 화면을 거친다.
|
|
79
|
+
const changes = []
|
|
80
|
+
for (const c of planChanges(states, selectedIds)) {
|
|
81
|
+
if (c.action === 'uninstall' && c.item.scope === 'user') {
|
|
82
|
+
console.log(t('run.keepUserScope', { id: c.item.id }))
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
changes.push(c)
|
|
86
|
+
}
|
|
74
87
|
if (changes.length === 0) { console.log(t('apply.noChanges')); return }
|
|
75
88
|
|
|
76
89
|
// 비대화형 경로다 — 바를 그리지 않고 평문 한 줄씩 흘린다.
|
|
@@ -52,8 +52,20 @@ export const MANIFEST = {
|
|
|
52
52
|
{ path: '.vscode/settings.json', key: 'chat.useAgentsMdFile', value: true },
|
|
53
53
|
],
|
|
54
54
|
|
|
55
|
-
// .agents/skills 를 가리키는 도구별
|
|
56
|
-
//
|
|
55
|
+
// .agents/skills 를 가리키는 도구별 어댑터.
|
|
56
|
+
//
|
|
57
|
+
// 여기에 없는 도구는 그 경로를 네이티브로 읽는다 — 2026-08-15에 도구별로
|
|
58
|
+
// 실측했다. Codex는 `codex debug prompt-input`이 프로젝트 SKILL.md 경로를
|
|
59
|
+
// 그대로 찍고, OpenCode·Kilo Code는 `debug skill`이, Copilot CLI는
|
|
60
|
+
// `skill list`가 같은 것을 보여 준다. Kimi Code는 배포본에
|
|
61
|
+
// `PROJECT_GENERIC_DIRS = [".agents/skills"]`가 있고, VS Code Copilot 확장은
|
|
62
|
+
// 프로젝트 경로 표에 `.agents/skills/<name>/`를 싣는다. Antigravity 바이너리도
|
|
63
|
+
// `{workspace}/.agents/skills/{skill_name}/SKILL.md`를 갖고 있다.
|
|
64
|
+
//
|
|
65
|
+
// 네이티브로 읽는 도구에 어댑터를 더하면 같은 스킬이 두 경로에서 잡혀 중복
|
|
66
|
+
// 등록된다 — 목록을 늘리기 전에 반드시 그 도구가 못 읽는 것을 먼저 확인한다.
|
|
67
|
+
// Kiro가 그 증거다: 확장이 `~/.kiro/skills/`와 `<workspace>/.kiro/skills/`만
|
|
68
|
+
// 훑고 `.agents/skills`는 한 번도 참조하지 않아, 이 어댑터가 있어야만 닿는다.
|
|
57
69
|
adapters: [
|
|
58
70
|
{ tool: 'Claude Code', path: '.claude/skills' },
|
|
59
71
|
{ tool: 'Kiro', path: '.kiro/skills' },
|
package/lib/catalog.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { join, dirname } from 'node:path'
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
4
|
import { execFile } from 'node:child_process'
|
|
@@ -140,12 +140,12 @@ export function defineMcp({ id, label, server, supports = [...CLI_IDS], unsuppor
|
|
|
140
140
|
// 배선은 Claude 한 곳이라도 미배선 "사유"는 CLI마다 다를 수 있다 — 상류가
|
|
141
141
|
// 자체 경로로 지원하는 CLI에 일괄 "Claude 전용" 사유를 붙이면 거짓 정보가 된다.
|
|
142
142
|
// 항목이 사유를 넘기면 그 CLI만 덮고, 나머지는 기본 사유로 채운다.
|
|
143
|
-
export function definePlugin({ id, label, installId, detectIds, marketplace, note, group = null, unsupported: reasons = {} }) {
|
|
143
|
+
export function definePlugin({ id, label, installId, detectIds, marketplace, note, group = null, exclusive = null, unsupported: reasons = {} }) {
|
|
144
144
|
const unsupported = Object.fromEntries(
|
|
145
145
|
CLI_IDS.filter((c) => c !== 'claude').map((c) => [c, reasons[c] ?? msg('item.unsupported.claudePlugin')]),
|
|
146
146
|
)
|
|
147
147
|
return {
|
|
148
|
-
id, category: 'plugin', label, scope: 'project', supports: ['claude'], unsupported, note, group,
|
|
148
|
+
id, category: 'plugin', label, scope: 'project', supports: ['claude'], unsupported, note, group, exclusive,
|
|
149
149
|
async detect({ root }) {
|
|
150
150
|
return { status: isPluginEnabled(root, detectIds) ? 'installed' : 'absent' }
|
|
151
151
|
},
|
|
@@ -207,28 +207,91 @@ function findSkillDir(root, skill) {
|
|
|
207
207
|
return null
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
// 레지스트리가 저장소 루트에 남기는 잠금 파일. 스킬 이름마다 어느 출처에서
|
|
211
|
+
// 왔는지를 적어 두므로, 한 상류가 여러 스킬을 깔았을 때 "우리가 넣은 것"만
|
|
212
|
+
// 골라 지울 수 있는 유일한 근거다.
|
|
213
|
+
const SKILLS_LOCK = 'skills-lock.json'
|
|
214
|
+
|
|
215
|
+
// 잠금 파일의 source는 `owner/repo`로 정규화돼 있고 항목은 URL을 넘긴다.
|
|
216
|
+
function sourceKey(source) {
|
|
217
|
+
return String(source).replace(/^https?:\/\/github\.com\//, '').replace(/\.git$/, '').replace(/\/+$/, '')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function readLock(root) {
|
|
221
|
+
const file = repoPath(root, SKILLS_LOCK)
|
|
222
|
+
if (!existsSync(file)) return null
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
225
|
+
} catch {
|
|
226
|
+
// 잠금 파일이 깨졌다고 제거를 막지는 않는다 — 디렉터리 스캔 폴백이 있다.
|
|
227
|
+
return null
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function lockedSkills(root, source) {
|
|
232
|
+
const want = sourceKey(source)
|
|
233
|
+
return Object.entries(readLock(root)?.skills ?? {})
|
|
234
|
+
.filter(([, v]) => sourceKey(v?.source ?? '') === want)
|
|
235
|
+
.map(([name]) => name)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 레지스트리의 remove는 디렉터리도, 잠금 파일의 항목도 남긴다(실측). 디렉터리는
|
|
239
|
+
// 아래에서 우리가 지우는데 잠금 항목을 그대로 두면 파일이 "설치돼 있다"고
|
|
240
|
+
// 거짓말한다 — 커밋해서 팀과 나누는 파일이라, 동료가 experimental_install로
|
|
241
|
+
// 복원하면 지운 스킬이 되살아난다.
|
|
242
|
+
function pruneLock(root, names) {
|
|
243
|
+
const parsed = readLock(root)
|
|
244
|
+
const skills = parsed?.skills
|
|
245
|
+
if (!skills) return
|
|
246
|
+
const gone = names.filter((n) => Object.hasOwn(skills, n))
|
|
247
|
+
if (gone.length === 0) return
|
|
248
|
+
for (const n of gone) delete skills[n]
|
|
249
|
+
const file = repoPathStrict(root, SKILLS_LOCK)
|
|
250
|
+
// 남은 스킬이 없으면 파일째 지운다. 이 파일은 레지스트리가 만든 산출물이라
|
|
251
|
+
// 빈 껍데기를 남기면 우리가 만든 흔적이 저장소에 계속 남는다.
|
|
252
|
+
if (Object.keys(skills).length === 0) rmSync(file, { force: true })
|
|
253
|
+
else writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`)
|
|
254
|
+
}
|
|
255
|
+
|
|
210
256
|
// vercel-labs/skills 레지스트리로 설치하는 스킬. `--agent universal`의 프로젝트
|
|
211
257
|
// 경로가 .agents/skills라, 한 번 설치하면 10개 CLI가 함께 본다 — Claude·Kiro·
|
|
212
|
-
// Grok은 부트스트랩이 만든 Junction으로, 나머지는 그 경로를 네이티브로
|
|
258
|
+
// Grok은 부트스트랩이 만든 Junction으로, 나머지는 그 경로를 네이티브로 읽는다
|
|
259
|
+
// (도구별 실측 근거는 bootstrap/manifest.mjs의 adapters 주석에 있다).
|
|
213
260
|
// `--copy`로 실물을 남긴다: 커밋해서 팀과 나누는 자리라 링크는 클론 뒤 깨진다.
|
|
214
|
-
|
|
261
|
+
//
|
|
262
|
+
// skill에 `*`를 주면 그 상류의 스킬을 전부 넣는다. 그때 detect가 볼 대표
|
|
263
|
+
// 스킬을 anchor로 지정해야 한다 — 디렉터리가 여럿이라 "무엇이 있으면 설치된
|
|
264
|
+
// 것인가"를 이름 하나로 정해 두지 않으면 판정이 흔들린다.
|
|
265
|
+
//
|
|
266
|
+
// exclusive는 같은 상류를 플러그인으로도 넣을 수 있을 때 쓴다. 두 경로가 겹치면
|
|
267
|
+
// 같은 스킬이 두 번 등록되므로 하나만 고르게 막는다.
|
|
268
|
+
export function defineRegistrySkill({ id, label, source, skill, anchor = null, note, group = null, exclusive = null }) {
|
|
269
|
+
const probe = anchor ?? skill
|
|
270
|
+
if (skill === '*' && !anchor) throw new LocalizedError('error.registrySkillAnchor', { id })
|
|
215
271
|
return {
|
|
216
|
-
id, category: 'skill', label, scope: 'project', supports: [...CLI_IDS], unsupported: {}, note, group,
|
|
272
|
+
id, category: 'skill', label, scope: 'project', supports: [...CLI_IDS], unsupported: {}, note, group, exclusive,
|
|
217
273
|
async detect({ root }) {
|
|
218
|
-
return { status: findSkillDir(root,
|
|
274
|
+
return { status: findSkillDir(root, probe) ? 'installed' : 'absent' }
|
|
219
275
|
},
|
|
220
276
|
async install({ root, exec }) {
|
|
221
277
|
const r = await exec('npx', ['-y', 'skills@latest', 'add', source, '--skill', skill, '--agent', 'universal', '--yes', '--copy'], { cwd: root })
|
|
222
278
|
if (!r.ok) throw new LocalizedError('error.registrySkillInstall', { skill, output: r.output })
|
|
223
279
|
},
|
|
224
280
|
async uninstall({ root, dryRun, exec }) {
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
281
|
+
// 지울 이름을 먼저 정한다. 여러 스킬을 깐 항목은 잠금 파일이 알려 주고,
|
|
282
|
+
// 잠금 파일이 없거나 깨졌으면 대표 스킬 하나라도 치운다.
|
|
283
|
+
const names = skill === '*' ? lockedSkills(root, source) : [skill]
|
|
284
|
+
const targets = names.length > 0 ? names : [probe]
|
|
285
|
+
for (const name of targets) {
|
|
286
|
+
// 레지스트리의 remove는 --agent universal에서 "Done!"을 찍고도 디렉터리를
|
|
287
|
+
// 남긴다(실측). 그래서 성공/실패와 무관하게 남은 디렉터리를 우리가 지운다 —
|
|
288
|
+
// 남겨 두면 detect가 계속 installed로 읽어 제거가 안 된 채 성공으로 보인다.
|
|
289
|
+
await exec('npx', ['-y', 'skills@latest', 'remove', name, '--agent', 'universal', '--yes'], { cwd: root })
|
|
290
|
+
if (dryRun) continue
|
|
291
|
+
const dir = findSkillDir(root, name)
|
|
292
|
+
if (dir) rmSync(repoPathStrict(root, `${SHARED_SKILLS}/${dir}`), { recursive: true, force: true })
|
|
293
|
+
}
|
|
294
|
+
if (!dryRun) pruneLock(root, targets)
|
|
232
295
|
},
|
|
233
296
|
}
|
|
234
297
|
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// 사용자 전역(scope: 'user') 플러그인 항목의 공통 기구. 상류가 하니스별 전역
|
|
2
|
+
// 설치만 지원하는 도구(superpowers, ponytail)가 공유한다 — 항목 파일
|
|
3
|
+
// (lib/items/global.*.mjs)은 상류별 좌표와 미배선 사유만 든다.
|
|
4
|
+
//
|
|
5
|
+
// 전역 파일을 만지므로 repoPath 계열 가드를 일부러 안 쓴다 — 저장소 이탈이
|
|
6
|
+
// 목적인 기구다. 항목 파일에 두지 않는 이유는 gitmessage.mjs와 같다:
|
|
7
|
+
// loadItems가 items/의 모든 .mjs를 항목으로 읽는다.
|
|
8
|
+
//
|
|
9
|
+
// 하니스별 기록 위치(2026-08-15 실측, 상세는 global.superpowers.mjs):
|
|
10
|
+
// codex $CODEX_HOME/config.toml의 [plugins."<이름>@…"] — CODEX_HOME
|
|
11
|
+
// 재지정을 실제로 쓰는 환경(Orca)이 있어 환경변수를 따라간다.
|
|
12
|
+
// copilot ~/.copilot/config.json의 installedPlugins[] (주석 있는 JSONC).
|
|
13
|
+
// gemini ~/.gemini/extensions/<이름>/ (공식 extensions 문서).
|
|
14
|
+
// opencode 전역 opencode.json의 plugin 배열 (XDG_CONFIG_HOME 존중).
|
|
15
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
16
|
+
import { homedir } from 'node:os'
|
|
17
|
+
import { delimiter, join } from 'node:path'
|
|
18
|
+
import { parse as parseToml } from 'smol-toml'
|
|
19
|
+
import { CLIS } from './clis.mjs'
|
|
20
|
+
import { readJson, setKey, removeKey } from './jsonfile.mjs'
|
|
21
|
+
import { msg, LocalizedError } from './i18n/index.mjs'
|
|
22
|
+
|
|
23
|
+
// 실행은 cmd 셸을 거치므로(catalog.mjs makeExec) win32에서는 PATHEXT 확장자가
|
|
24
|
+
// 있어야 실제로 부를 수 있다 — copilot.ps1만 있는 PATH는 "없음"이 맞다.
|
|
25
|
+
function defaultHasBinary(name, env = process.env) {
|
|
26
|
+
const dirs = (env.PATH ?? '').split(delimiter).filter(Boolean)
|
|
27
|
+
const exts = process.platform === 'win32'
|
|
28
|
+
? (env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean)
|
|
29
|
+
: ['']
|
|
30
|
+
return dirs.some((dir) => exts.some((ext) => existsSync(join(dir, name + ext))))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// superpowers@openai-curated처럼 다른 마켓플레이스로 설치된 판도 잡아야
|
|
34
|
+
// 하므로(실측: 이런 머신이 실제로 있다) 이름이 아니라 접두사로 고른다.
|
|
35
|
+
// 제거도 여기서 찾은 id를 그대로 쓴다 — 감지가 "있다"고 말한 판을 지워야
|
|
36
|
+
// 상태가 맞아떨어진다.
|
|
37
|
+
function codexPluginIds(file, pluginName) {
|
|
38
|
+
if (!existsSync(file)) return []
|
|
39
|
+
try {
|
|
40
|
+
const data = parseToml(readFileSync(file, 'utf8'))
|
|
41
|
+
return Object.keys(data.plugins ?? {}).filter((id) => id.startsWith(`${pluginName}@`))
|
|
42
|
+
} catch {
|
|
43
|
+
// 깨진 config.toml은 미설치로 둔다 — 설치를 시도하면 codex가 스스로
|
|
44
|
+
// 진단하고, "설치됨"으로 오판해 제거를 그리로 보내는 것보다 낫다.
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function copilotInstalled(file, pluginName) {
|
|
50
|
+
const list = readJson(file)?.installedPlugins
|
|
51
|
+
return Array.isArray(list) && list.some((p) => p?.name === pluginName)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ponytail(items/plugin.ponytail.mjs)과 같은 규칙: plugin 키가 배열이 아니면
|
|
55
|
+
// 손대지 않는다.
|
|
56
|
+
function opencodePlugins(file) {
|
|
57
|
+
const value = readJson(file)?.plugin
|
|
58
|
+
if (value === undefined) return []
|
|
59
|
+
if (!Array.isArray(value)) return null
|
|
60
|
+
return value
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// config:
|
|
64
|
+
// pluginName 감지·제거가 쓰는 상류 이름 (codex 접두사, copilot name,
|
|
65
|
+
// gemini 확장 디렉터리 이름)
|
|
66
|
+
// marketplaceRepo codex·copilot 마켓플레이스 (owner/repo)
|
|
67
|
+
// installId 마켓플레이스 설치 id (<이름>@<마켓>)
|
|
68
|
+
// gemini gemini extensions install에 줄 git URL, 없으면 null
|
|
69
|
+
// opencode 전역 opencode.json plugin 배열에 넣을 항목, 없으면 null
|
|
70
|
+
// supports는 어댑터 유무로 정해진다 — codex·copilot은 항상, gemini·opencode는
|
|
71
|
+
// 좌표가 있을 때만.
|
|
72
|
+
export function defineGlobalPlugin(
|
|
73
|
+
{ id, label, group, note, exclusive = null, unsupported = {}, pluginName, marketplaceRepo, installId, gemini = null, opencode = null },
|
|
74
|
+
{ home = null, env = process.env, hasBinary = defaultHasBinary } = {},
|
|
75
|
+
) {
|
|
76
|
+
const supports = ['codex', ...(gemini ? ['gemini'] : []), ...(opencode ? ['opencode'] : []), 'copilot']
|
|
77
|
+
|
|
78
|
+
const files = () => {
|
|
79
|
+
const base = home ?? homedir()
|
|
80
|
+
return {
|
|
81
|
+
codex: join(env.CODEX_HOME || join(base, '.codex'), 'config.toml'),
|
|
82
|
+
copilot: join(base, '.copilot', 'config.json'),
|
|
83
|
+
gemini: join(base, '.gemini', 'extensions', pluginName),
|
|
84
|
+
opencode: join(env.XDG_CONFIG_HOME || join(base, '.config'), 'opencode', 'opencode.json'),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const isOurEntry = (p) => String(p).startsWith(`${pluginName}@`)
|
|
89
|
+
const wired = {
|
|
90
|
+
codex: (f) => codexPluginIds(f.codex, pluginName).length > 0,
|
|
91
|
+
copilot: (f) => copilotInstalled(f.copilot, pluginName),
|
|
92
|
+
gemini: (f) => existsSync(f.gemini),
|
|
93
|
+
opencode: (f) => (opencodePlugins(f.opencode) ?? []).some(isOurEntry),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
id, category: 'plugin', label, scope: 'user', group, exclusive,
|
|
98
|
+
supports: [...supports],
|
|
99
|
+
unsupported,
|
|
100
|
+
note,
|
|
101
|
+
|
|
102
|
+
// 상태는 "이 머신에 있는 하니스" 기준이다. CLI가 없는 하니스까지 누락으로
|
|
103
|
+
// 세면 gemini 없는 머신은 영원히 partial이라 apply가 매번 헛돈다.
|
|
104
|
+
async detect() {
|
|
105
|
+
const f = files()
|
|
106
|
+
const present = supports.filter((c) => wired[c](f))
|
|
107
|
+
const machine = supports.filter((c) => hasBinary(c))
|
|
108
|
+
const noCli = supports.filter((c) => !machine.includes(c))
|
|
109
|
+
const missing = machine.filter((c) => !present.includes(c))
|
|
110
|
+
const detail = noCli.length > 0 ? msg('item.global.noCli', { list: noCli.join(', ') }) : undefined
|
|
111
|
+
if (present.length === 0) return { status: 'absent', detail }
|
|
112
|
+
if (missing.length === 0) return { status: 'installed', detail }
|
|
113
|
+
return {
|
|
114
|
+
status: 'partial',
|
|
115
|
+
detail: msg('item.plugin.partial', { present: present.join(', '), missing: missing.join(', ') }),
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
async install({ dryRun, exec, log = () => {}, t }) {
|
|
120
|
+
const f = files()
|
|
121
|
+
// opencode 설정이 손댈 수 없는 꼴이면 아무것도 만지기 전에 멈춘다 —
|
|
122
|
+
// 절반만 설치된 채 실패하는 것보다 통째로 거절이 낫다.
|
|
123
|
+
const needsOpencode = opencode && !wired.opencode(f) && hasBinary('opencode')
|
|
124
|
+
if (needsOpencode && !dryRun && opencodePlugins(f.opencode) === null) {
|
|
125
|
+
throw new LocalizedError('error.globalOpencodePlugin', { file: f.opencode, entry: opencode })
|
|
126
|
+
}
|
|
127
|
+
const skipped = []
|
|
128
|
+
const failures = []
|
|
129
|
+
const fail = (cli, output) => failures.push(`${cli}: ${String(output).trim().split('\n')[0]}`)
|
|
130
|
+
for (const cli of supports) {
|
|
131
|
+
if (wired[cli](f)) continue
|
|
132
|
+
if (!hasBinary(cli)) {
|
|
133
|
+
skipped.push(cli)
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
if (cli === 'codex') {
|
|
137
|
+
// 마켓 등록은 이미 있어도 해가 없다 — 성패는 add가 판정한다.
|
|
138
|
+
await exec('codex', ['plugin', 'marketplace', 'add', marketplaceRepo])
|
|
139
|
+
const r = await exec('codex', ['plugin', 'add', installId])
|
|
140
|
+
if (!r.ok) fail(cli, r.output)
|
|
141
|
+
} else if (cli === 'copilot') {
|
|
142
|
+
await exec('copilot', ['plugin', 'marketplace', 'add', marketplaceRepo])
|
|
143
|
+
// 실측(2026-08-15): 다른 copilot 세션이 떠 있으면 os error 5로 한 번
|
|
144
|
+
// 실패하고 재시도에 성공한다. 한 번만 다시 시도한다.
|
|
145
|
+
let r = await exec('copilot', ['plugin', 'install', installId])
|
|
146
|
+
if (!r.ok) r = await exec('copilot', ['plugin', 'install', installId])
|
|
147
|
+
if (!r.ok) fail(cli, r.output)
|
|
148
|
+
} else if (cli === 'gemini') {
|
|
149
|
+
const r = await exec('gemini', ['extensions', 'install', gemini])
|
|
150
|
+
if (!r.ok) fail(cli, r.output)
|
|
151
|
+
} else {
|
|
152
|
+
if (dryRun) {
|
|
153
|
+
log(t('log.plugin.add', { cli: CLIS.opencode.label, name: opencode }))
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
const list = opencodePlugins(f.opencode) ?? []
|
|
157
|
+
if (list.length === 0) setKey(f.opencode, ['plugin'], [opencode])
|
|
158
|
+
else setKey(f.opencode, ['plugin', list.length], opencode)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (failures.length > 0) throw new LocalizedError('error.globalExec', { detail: failures.join(' / ') })
|
|
162
|
+
if (skipped.length > 0) return { message: msg('item.global.skipped', { list: skipped.join(', ') }) }
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
async uninstall({ dryRun, exec, log = () => {}, t }) {
|
|
166
|
+
const f = files()
|
|
167
|
+
const skipped = []
|
|
168
|
+
const failures = []
|
|
169
|
+
const fail = (cli, output) => failures.push(`${cli}: ${String(output).trim().split('\n')[0]}`)
|
|
170
|
+
for (const cli of supports) {
|
|
171
|
+
if (!wired[cli](f)) continue
|
|
172
|
+
if (cli === 'opencode') {
|
|
173
|
+
if (dryRun) {
|
|
174
|
+
log(t('log.plugin.remove', { cli: CLIS.opencode.label, name: opencode }))
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
const kept = (opencodePlugins(f.opencode) ?? []).filter((p) => !isOurEntry(p))
|
|
178
|
+
// 우리 항목만 빼고 비면 키째 지운다 — 빈 배열은 우리가 남긴 흔적이다.
|
|
179
|
+
if (kept.length === 0) removeKey(f.opencode, ['plugin'])
|
|
180
|
+
else setKey(f.opencode, ['plugin'], kept)
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
183
|
+
if (!hasBinary(cli)) {
|
|
184
|
+
skipped.push(cli)
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
if (cli === 'codex') {
|
|
188
|
+
for (const pid of codexPluginIds(f.codex, pluginName)) {
|
|
189
|
+
const r = await exec('codex', ['plugin', 'remove', pid])
|
|
190
|
+
if (!r.ok) fail(cli, r.output)
|
|
191
|
+
}
|
|
192
|
+
// 마켓플레이스 등록은 남긴다 — 우리가 등록했다는 보장이 없고,
|
|
193
|
+
// 같은 마켓의 다른 플러그인이 쓰고 있을 수 있다.
|
|
194
|
+
} else if (cli === 'copilot') {
|
|
195
|
+
let r = await exec('copilot', ['plugin', 'uninstall', pluginName])
|
|
196
|
+
if (!r.ok) r = await exec('copilot', ['plugin', 'uninstall', pluginName])
|
|
197
|
+
if (!r.ok) fail(cli, r.output)
|
|
198
|
+
} else {
|
|
199
|
+
const r = await exec('gemini', ['extensions', 'uninstall', pluginName])
|
|
200
|
+
if (!r.ok) fail(cli, r.output)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (failures.length > 0) throw new LocalizedError('error.globalExec', { detail: failures.join(' / ') })
|
|
204
|
+
if (skipped.length > 0) return { message: msg('item.global.skipped', { list: skipped.join(', ') }) }
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
}
|
package/lib/i18n/catalog/en.mjs
CHANGED
|
@@ -234,6 +234,11 @@ export default {
|
|
|
234
234
|
'item.unsupported.mattpocockRegistry': 'upstream also documents npx skills add (the shared .agents/skills), but this item wires the Claude Code plugin only',
|
|
235
235
|
'item.unsupported.upstreamNone': 'no official install path upstream',
|
|
236
236
|
'item.unsupported.superpowersSeparate': 'upstream supports it via a separate per-harness install — this item wires the Claude Code plugin only',
|
|
237
|
+
'item.unsupported.superpowersGlobalClaude': 'covered by the project-scope item superpowers (plugin)',
|
|
238
|
+
'item.unsupported.superpowersGlobalGrok': 'upstream 6.3.0 ships grok plugin install superpowers@xai-official, but the detect/remove paths are unverified — not wired yet',
|
|
239
|
+
'item.unsupported.superpowersGlobalKimi': 'upstream installs only via the interactive /plugins command — no headless path',
|
|
240
|
+
'item.unsupported.globalProjectItem': 'covered by the project-scope item {item}',
|
|
241
|
+
'item.unsupported.ponytailGlobalGrok': 'upstream ships grok plugin install DietrichGebert/ponytail --trust, but the detect/remove paths are unverified — not wired yet',
|
|
237
242
|
'item.unsupported.bkitPort': 'upstream ships separate ports (bkit-codex / bkit-gemini) installed apart from this plugin; the Gemini port is still a Gemini CLI extension and has had no update since the 2026-06-18 sunset',
|
|
238
243
|
'item.unsupported.impeccableJunction': 'upstream supports it, but npx impeccable install breaks the shared .agents/skills link, so only the plugin route is wired',
|
|
239
244
|
'item.unsupported.gstackHost': 'upstream supports it via ./setup --host — this item installs for Claude Code only',
|
|
@@ -241,6 +246,9 @@ export default {
|
|
|
241
246
|
'item.unsupported.gsdGemini': 'upstream dropped Gemini CLI (sunset 2026-06) — Antigravity succeeds it via --antigravity',
|
|
242
247
|
'item.mcp.partial': 'registered: {present} / missing: {missing}',
|
|
243
248
|
'item.plugin.partial': 'wired: {present} / missing: {missing}',
|
|
249
|
+
'item.global.noCli': 'CLI not on this machine: {list} — excluded from the target set',
|
|
250
|
+
'run.keepUserScope': '· {id} is a user-global item and is not removed by omission from --set — uncheck it in the TUI to remove it',
|
|
251
|
+
'item.global.skipped': 'skipped (no CLI): {list}',
|
|
244
252
|
'item.plugin.deferred': 'recorded in config — it downloads on the next Claude Code run',
|
|
245
253
|
'item.scanFailed': 'detection failed: {message}',
|
|
246
254
|
'log.mcp.add': ' [dry-run] register {name} in the {cli} config',
|
|
@@ -254,7 +262,9 @@ export default {
|
|
|
254
262
|
'item.mcp.codebase-memory.note': 'needs the codebase-memory-mcp binary on PATH. Install: https://github.com/DeusData/codebase-memory-mcp (install.sh / install.ps1)',
|
|
255
263
|
'item.mcp.graphify.note': 'needs the graphify-mcp binary on PATH. Install: uv tool install "graphifyy[mcp]". Reads graphify-out/graph.json — build it first with /graphify .',
|
|
256
264
|
'item.mcp.headroom.note': 'needs the headroom binary on PATH. Install: uv tool install --python 3.13 "headroom-ai[proxy,mcp]". The proxy (headroom wrap) is a way of launching, not repo config',
|
|
257
|
-
'item.plugin.superpowers.note': 'official marketplace plugin. Upstream supports
|
|
265
|
+
'item.plugin.superpowers.note': 'official marketplace plugin. Upstream supports 14 harnesses (Codex, Grok Build, …) via per-harness installs',
|
|
266
|
+
'item.global.superpowers.note': 'installs superpowers user-globally with each harness\'s official command — marketplace for codex/copilot, extensions for gemini, the global opencode.json for OpenCode. CLIs missing from this machine are skipped. With skill.superpowers also on, overlapping CLIs see the same skills twice',
|
|
267
|
+
'item.global.ponytail.note': 'installs Ponytail user-globally with each harness\'s official command — the DietrichGebert/ponytail marketplace for codex/copilot, extensions for gemini. After the codex install, check hook registration once via /hooks. Upstream recommends scripts/uninstall.js to clean ~/.config/ponytail before removal — this item removes the plugin only',
|
|
258
268
|
'item.plugin.mattpocock-skills.note': '35 engineering and productivity skills (code-review, diagnosing-bugs, domain-modeling, …)',
|
|
259
269
|
'item.plugin.ponytail.note': 'lean-code ruleset. Wires the Claude Code plugin and the OpenCode plugin entry, both repo-scoped',
|
|
260
270
|
'item.plugin.ecc.note': 'skills, instincts, memory and security for agent harnesses. Installed as a plugin — its skills/ holds 284 entries, too many to copy into the shared skills directory',
|
|
@@ -264,6 +274,8 @@ export default {
|
|
|
264
274
|
'item.skill.taste.note': 'anti-slop frontend skill for landing pages, portfolios and redesigns (skill name: design-taste-frontend)',
|
|
265
275
|
'item.skill.hallmark.note': 'anti-AI-slop design skill with four verbs (build, audit, redesign, study). 21 themes and 57 slop-test gates. Upstream also documents Cursor and Codex paths; the shared skills directory covers all 10 CLIs at once',
|
|
266
276
|
'item.skill.diagram-design.note': 'draws 27 diagram types in HTML/SVG using your brand colors and fonts; also redraws Mermaid and draw.io sources. The default fonts (Instrument Serif, Geist) carry no CJK glyphs — swap them in style-guide.md — and PNG export needs Playwright and Chromium separately. The three upstream slash commands are left out, but the references/ they delegate to come along',
|
|
277
|
+
'item.skill.superpowers.note': 'copies 14 skills into the shared .agents/skills so all 10 CLIs see them. Only one of this and the plugin edition can be chosen — this route has no session-start hook, so nothing is injected at the start of a conversation and skills are invoked on demand',
|
|
278
|
+
'item.skill.mattpocock-skills.note': 'copies 35 skills into the shared .agents/skills so all 10 CLIs see them. Upstream documents this route itself, and with no hooks involved nothing is lost against the plugin edition. Only one of the two can be chosen',
|
|
267
279
|
'item.skill.karpathy.note': 'behavioral guidelines that curb overbuilding and vague success criteria',
|
|
268
280
|
'item.skill.gstack.note': 'repo-local clone + setup (needs bash; Git Bash on Windows). Runtime state (~/.gstack) may be created globally.',
|
|
269
281
|
'item.skill.gsd.note': 'npx @opengsd/gsd-core, installed per project. Upstream supports 18 runtimes (Codex, Antigravity, …); this item wires --claude only',
|
|
@@ -276,13 +288,16 @@ export default {
|
|
|
276
288
|
'log.gitmessage.remove': ' [dry-run] remove {path}',
|
|
277
289
|
'error.gitmessageForeign': '{path} already exists and was not written by this tool. A hand-written template is never overwritten — move or delete it first.',
|
|
278
290
|
'error.gitmessageConfig': 'git config commit.template failed: {output}',
|
|
279
|
-
'error.exclusiveItems': 'These items
|
|
291
|
+
'error.exclusiveItems': 'These items compete for the same slot, so only one can be chosen: {ids}',
|
|
280
292
|
'error.gstackClone': 'gstack clone failed: {output}',
|
|
281
293
|
'error.gstackSetup': 'gstack setup failed: {output}',
|
|
282
294
|
'error.gsdInstall': 'GSD install failed: {output}',
|
|
283
295
|
'error.gsdUninstall': 'GSD uninstall failed: {output}',
|
|
284
296
|
'error.ponytailOpencodePlugin': '{file}: the plugin key is not an array. Leaving it alone — add "@dietrichgebert/ponytail" by hand.',
|
|
297
|
+
'error.globalExec': 'a harness command failed — {detail}',
|
|
298
|
+
'error.globalOpencodePlugin': '{file}: the plugin key is not an array. Leaving it alone — add "{entry}" by hand.',
|
|
285
299
|
'error.registrySkillInstall': 'skill registry install failed ({skill}): {output}',
|
|
300
|
+
'error.registrySkillAnchor': "{id}: when skill is '*', an anchor skill must be named for detect to look at",
|
|
286
301
|
'error.itemFieldMissing': '{file}: {field} missing',
|
|
287
302
|
'error.itemReasonMissing': '{id}: needs a reason for unsupported CLI \'{cli}\' (unsupported.{cli})',
|
|
288
303
|
'error.shellQuote': 'The argument contains a double quote that cannot be passed to the shell: {value}',
|
|
@@ -319,7 +334,7 @@ export default {
|
|
|
319
334
|
'tui.toggledAll': 'Selected every visible item in the {tab} tab.',
|
|
320
335
|
'tui.toggledNone': 'Cleared every visible item in the {tab} tab.',
|
|
321
336
|
// 배타 묶음에서 형제가 함께 꺼졌을 때. 조용히 뒤집히면 오조작으로 읽힌다.
|
|
322
|
-
'tui.exclusiveSwitched': 'Switched to {kept} — they
|
|
337
|
+
'tui.exclusiveSwitched': 'Switched to {kept} — they compete for the same slot, so {dropped} was cleared.',
|
|
323
338
|
'tui.nonInteractive': 'The interactive screen only opens in a terminal. --list and --set work too.',
|
|
324
339
|
'tui.pressAnyKey': 'Press any key to continue…',
|
|
325
340
|
'tui.confirmSuffix': ' [y/N] ',
|
package/lib/i18n/catalog/ko.mjs
CHANGED
|
@@ -227,6 +227,11 @@ export default {
|
|
|
227
227
|
'item.unsupported.mattpocockRegistry': '상류가 npx skills add(공유 .agents/skills)도 안내하지만 이 항목은 Claude Code 플러그인만 배선합니다',
|
|
228
228
|
'item.unsupported.upstreamNone': '상류에 공식 설치 경로가 없습니다',
|
|
229
229
|
'item.unsupported.superpowersSeparate': '상류가 하니스별 별도 설치로 지원합니다 — 이 항목은 Claude Code 플러그인만 배선합니다',
|
|
230
|
+
'item.unsupported.superpowersGlobalClaude': '프로젝트 스코프 항목 superpowers (plugin)이 담당합니다',
|
|
231
|
+
'item.unsupported.superpowersGlobalGrok': '상류 6.3.0에 grok plugin install superpowers@xai-official 명령이 있지만 감지·제거 경로를 실측하지 못해 아직 배선하지 않습니다',
|
|
232
|
+
'item.unsupported.superpowersGlobalKimi': '상류 설치가 대화형 /plugins 명령뿐이라 헤드리스 경로가 없습니다',
|
|
233
|
+
'item.unsupported.globalProjectItem': '프로젝트 스코프 항목 {item}이 담당합니다',
|
|
234
|
+
'item.unsupported.ponytailGlobalGrok': '상류 명령(grok plugin install DietrichGebert/ponytail --trust)이 있지만 감지·제거 경로 미실측이라 배선하지 않습니다',
|
|
230
235
|
'item.unsupported.bkitPort': '상류가 별도 배포판(bkit-codex·bkit-gemini)으로 지원합니다 — 이 플러그인과 별개 설치입니다. Gemini판은 Gemini CLI 확장 그대로이고 2026-06-18 종료 이후 갱신이 없습니다',
|
|
231
236
|
'item.unsupported.impeccableJunction': '상류는 지원하지만 npx impeccable install이 공유 .agents/skills 연결을 끊어 플러그인 경로만 배선합니다',
|
|
232
237
|
'item.unsupported.gstackHost': '상류는 ./setup --host로 지원합니다 — 이 항목은 Claude Code에만 설치합니다',
|
|
@@ -234,6 +239,9 @@ export default {
|
|
|
234
239
|
'item.unsupported.gsdGemini': '상류가 Gemini CLI 지원을 제거했습니다(2026-06 종료) — Antigravity가 --antigravity로 승계합니다',
|
|
235
240
|
'item.mcp.partial': '등록됨: {present} / 누락: {missing}',
|
|
236
241
|
'item.plugin.partial': '배선됨: {present} / 누락: {missing}',
|
|
242
|
+
'item.global.noCli': 'CLI 없음: {list} — 이 머신 대상에서 제외',
|
|
243
|
+
'run.keepUserScope': '· {id}는 사용자 전역 항목이라 --set 생략으로는 제거하지 않습니다 — 제거하려면 TUI에서 해제하세요',
|
|
244
|
+
'item.global.skipped': 'CLI가 없어 건너뜀: {list}',
|
|
237
245
|
'item.plugin.deferred': '설정 기록됨 — 다음 Claude Code 실행 시 다운로드됩니다',
|
|
238
246
|
'item.scanFailed': '감지 실패: {message}',
|
|
239
247
|
'log.mcp.add': ' [dry-run] {cli} 설정에 {name} 등록',
|
|
@@ -247,7 +255,9 @@ export default {
|
|
|
247
255
|
'item.mcp.codebase-memory.note': 'PATH에 codebase-memory-mcp 바이너리 필요. 설치: https://github.com/DeusData/codebase-memory-mcp (install.sh / install.ps1)',
|
|
248
256
|
'item.mcp.graphify.note': 'PATH에 graphify-mcp 바이너리 필요. 설치: uv tool install "graphifyy[mcp]". graphify-out/graph.json을 읽으므로 /graphify .로 그래프를 먼저 만드세요',
|
|
249
257
|
'item.mcp.headroom.note': 'PATH에 headroom 바이너리 필요. 설치: uv tool install --python 3.13 "headroom-ai[proxy,mcp]". 프록시(headroom wrap)는 실행 방식이라 저장소 설정 대상이 아닙니다',
|
|
250
|
-
'item.plugin.superpowers.note': '공식 마켓플레이스 플러그인. 상류는 Codex·
|
|
258
|
+
'item.plugin.superpowers.note': '공식 마켓플레이스 플러그인. 상류는 Codex·Grok Build 등 14개 하니스를 하니스별 설치로 지원합니다',
|
|
259
|
+
'item.global.superpowers.note': 'superpowers를 하니스별 공식 명령으로 사용자 전역에 설치합니다 — codex·copilot은 마켓플레이스, gemini는 extensions, opencode는 전역 opencode.json. 이 머신에 없는 CLI는 건너뜁니다. skill.superpowers와 함께 켜면 겹치는 CLI는 같은 스킬을 두 곳에서 봅니다',
|
|
260
|
+
'item.global.ponytail.note': 'Ponytail을 하니스별 공식 명령으로 사용자 전역에 설치합니다 — codex·copilot은 마켓플레이스(DietrichGebert/ponytail), gemini는 extensions. codex는 설치 후 /hooks에서 훅 등록을 한 번 확인해야 합니다. 상류는 제거 전 scripts/uninstall.js로 ~/.config/ponytail 정리를 안내합니다 — 이 항목은 플러그인만 제거합니다',
|
|
251
261
|
'item.plugin.mattpocock-skills.note': '엔지니어링·생산성 스킬 35종 (code-review, diagnosing-bugs, domain-modeling 등)',
|
|
252
262
|
'item.plugin.ponytail.note': '군더더기 없는 코드 규칙 묶음. Claude Code 플러그인과 OpenCode plugin 항목을 저장소 범위로 배선합니다',
|
|
253
263
|
'item.plugin.ecc.note': '에이전트 하네스용 스킬·본능·메모리·보안 묶음. skills/에 284개가 들어 있어 공유 스킬 디렉터리에 복사하지 않고 플러그인으로 설치합니다',
|
|
@@ -257,6 +267,8 @@ export default {
|
|
|
257
267
|
'item.skill.taste.note': '랜딩 페이지·포트폴리오·리디자인용 안티슬롭 프런트엔드 스킬 (스킬 이름: design-taste-frontend)',
|
|
258
268
|
'item.skill.hallmark.note': '네 가지 동사(신규 제작·audit·redesign·study)를 갖춘 안티 AI슬롭 디자인 스킬. 테마 21종과 슬롭 테스트 57종. 상류는 Cursor·Codex 경로도 안내하지만 공유 스킬 디렉터리 하나로 10개 CLI가 함께 봅니다',
|
|
259
269
|
'item.skill.diagram-design.note': '브랜드 색·폰트로 다이어그램 27종을 그리는 HTML/SVG 스킬. Mermaid·draw.io 원본도 다시 그립니다. 기본 폰트(Instrument Serif·Geist)에 한글 글리프가 없어 style-guide.md에서 바꿔야 하고, PNG 내보내기는 Playwright·Chromium이 따로 필요합니다. 상류 플러그인의 명령 3개는 빠지지만 그 본문인 references/는 그대로 옵니다',
|
|
270
|
+
'item.skill.superpowers.note': '공유 .agents/skills에 스킬 14종을 복사해 10개 CLI가 함께 봅니다. 플러그인 판과 하나만 고를 수 있습니다 — 이쪽은 session-start 훅이 없어 대화 시작 시 자동 주입이 되지 않고, 스킬을 불러서 씁니다',
|
|
271
|
+
'item.skill.mattpocock-skills.note': '공유 .agents/skills에 스킬 35종을 복사해 10개 CLI가 함께 봅니다. 상류가 직접 안내하는 경로이며 훅이 없어 플러그인 판에 견줘 잃는 것이 없습니다. 둘 중 하나만 고를 수 있습니다',
|
|
260
272
|
'item.skill.karpathy.note': '과설계와 모호한 완료 기준을 줄이는 행동 지침',
|
|
261
273
|
'item.skill.gstack.note': '저장소 로컬 clone + setup (bash 필요, Windows는 Git Bash). 런타임 상태(~/.gstack)는 전역에 생길 수 있음.',
|
|
262
274
|
'item.skill.gsd.note': 'npx @opengsd/gsd-core 프로젝트 로컬 설치. 상류는 Codex·Antigravity 등 18개 런타임을 지원합니다(이 항목은 --claude)',
|
|
@@ -268,13 +280,16 @@ export default {
|
|
|
268
280
|
'log.gitmessage.remove': ' [dry-run] {path} 제거',
|
|
269
281
|
'error.gitmessageForeign': '{path}가 이미 있고 이 도구가 쓴 내용이 아닙니다. 손으로 쓴 템플릿은 덮어쓰지 않습니다 — 먼저 옮기거나 지우세요.',
|
|
270
282
|
'error.gitmessageConfig': 'git config commit.template 실패: {output}',
|
|
271
|
-
'error.exclusiveItems': '
|
|
283
|
+
'error.exclusiveItems': '한 자리를 두고 다투는 항목이라 하나만 고를 수 있습니다: {ids}',
|
|
272
284
|
'error.gstackClone': 'gstack clone 실패: {output}',
|
|
273
285
|
'error.gstackSetup': 'gstack setup 실패: {output}',
|
|
274
286
|
'error.gsdInstall': 'GSD 설치 실패: {output}',
|
|
275
287
|
'error.gsdUninstall': 'GSD 제거 실패: {output}',
|
|
276
288
|
'error.ponytailOpencodePlugin': '{file}: plugin 키가 배열이 아닙니다. 덮어쓰지 않고 그대로 둡니다 — "@dietrichgebert/ponytail"을 직접 추가하세요.',
|
|
289
|
+
'error.globalExec': '하니스 명령이 실패했습니다 — {detail}',
|
|
290
|
+
'error.globalOpencodePlugin': '{file}: plugin 키가 배열이 아닙니다. 덮어쓰지 않고 그대로 둡니다 — "{entry}"을 직접 추가하세요.',
|
|
277
291
|
'error.registrySkillInstall': '스킬 레지스트리 설치 실패 ({skill}): {output}',
|
|
292
|
+
'error.registrySkillAnchor': "{id}: skill이 '*'이면 detect가 볼 대표 스킬을 anchor로 지정해야 합니다",
|
|
278
293
|
'error.itemFieldMissing': '{file}: {field} 누락',
|
|
279
294
|
'error.itemReasonMissing': '{id}: 미지원 CLI \'{cli}\'의 사유(unsupported.{cli})가 필요합니다',
|
|
280
295
|
'error.shellQuote': '셸에 넘길 수 없는 큰따옴표가 인자에 있습니다: {value}',
|
|
@@ -309,7 +324,7 @@ export default {
|
|
|
309
324
|
'tui.toggledAll': '{tab} 탭의 보이는 항목을 모두 선택했습니다.',
|
|
310
325
|
'tui.toggledNone': '{tab} 탭의 보이는 항목을 모두 해제했습니다.',
|
|
311
326
|
// 배타 묶음에서 형제가 함께 꺼졌을 때. en.mjs 쪽 주석 참고.
|
|
312
|
-
'tui.exclusiveSwitched': '{kept}(으)로 바꿨습니다 —
|
|
327
|
+
'tui.exclusiveSwitched': '{kept}(으)로 바꿨습니다 — 한 자리를 두고 다투므로 {dropped}은(는) 해제했습니다.',
|
|
313
328
|
'tui.nonInteractive': '대화형 화면은 터미널에서만 열립니다. --list · --set 으로도 다룰 수 있습니다.',
|
|
314
329
|
'tui.pressAnyKey': '계속하려면 아무 키나 누르세요…',
|
|
315
330
|
'tui.confirmSuffix': ' [y/N] ',
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineGlobalPlugin } from '../global-plugin.mjs'
|
|
2
|
+
import { CLI_IDS } from '../clis.mjs'
|
|
3
|
+
import { msg } from '../i18n/index.mjs'
|
|
4
|
+
|
|
5
|
+
// Ponytail의 하니스별 전역 설치 판. 공통 기구는 global-plugin.mjs에 있다.
|
|
6
|
+
//
|
|
7
|
+
// 하니스별 근거(2026-08-15 상류 README 재검증):
|
|
8
|
+
// codex — `codex plugin marketplace add DietrichGebert/ponytail` +
|
|
9
|
+
// `codex plugin add ponytail@ponytail`. 상류는 설치 후 codex에서
|
|
10
|
+
// /hooks로 라이프사이클 훅 등록을 확인하고 새 스레드를 시작하라는
|
|
11
|
+
// 대화형 후속 단계를 안내한다 — 명령으로 대신할 수 없어 note에
|
|
12
|
+
// 적는다. 제거는 `codex plugin remove <id>`.
|
|
13
|
+
// copilot — `copilot plugin marketplace add` + `copilot plugin install
|
|
14
|
+
// ponytail@ponytail`. 상류 README에 제거 명령은 없지만
|
|
15
|
+
// `copilot plugin uninstall <이름>`은 copilot CLI 자체의 범용
|
|
16
|
+
// 명령이다(--help 실측).
|
|
17
|
+
// gemini — `gemini extensions install https://github.com/DietrichGebert/ponytail`.
|
|
18
|
+
//
|
|
19
|
+
// opencode는 프로젝트 항목(plugin.ponytail)이 opencode.jsonc의 plugin 배열로
|
|
20
|
+
// 이미 배선한다 — 전역 판에서 겹치면 같은 플러그인이 두 번 등록된다. claude도
|
|
21
|
+
// 같은 이유로 프로젝트 항목의 자리다. grok은 상류 명령이 있으나 감지·제거
|
|
22
|
+
// 경로를 실측할 CLI가 없어 superpowers 전역 판과 같은 기준으로 제외한다.
|
|
23
|
+
// kiro·vscode는 상류가 룰 파일 수동 배치만 안내하고, kilo·kimi는 경로가 없다.
|
|
24
|
+
//
|
|
25
|
+
// 상류는 플러그인 제거 전에 node scripts/uninstall.js로 ~/.config/ponytail
|
|
26
|
+
// 설정과 statusLine 항목을 정리하라고 안내한다 — 하니스마다 스크립트 위치가
|
|
27
|
+
// 달라 자동화하지 않고 note로 알린다. 이 항목은 플러그인만 제거한다.
|
|
28
|
+
const SUPPORTS = ['codex', 'gemini', 'copilot']
|
|
29
|
+
|
|
30
|
+
const CONFIG = {
|
|
31
|
+
id: 'global.ponytail', label: 'Ponytail (global)', group: '__token',
|
|
32
|
+
note: 'item.global.ponytail.note',
|
|
33
|
+
pluginName: 'ponytail',
|
|
34
|
+
marketplaceRepo: 'DietrichGebert/ponytail',
|
|
35
|
+
installId: 'ponytail@ponytail',
|
|
36
|
+
gemini: 'https://github.com/DietrichGebert/ponytail',
|
|
37
|
+
unsupported: Object.fromEntries(
|
|
38
|
+
CLI_IDS.filter((c) => !SUPPORTS.includes(c)).map((c) => [
|
|
39
|
+
c,
|
|
40
|
+
['claude', 'opencode'].includes(c) ? msg('item.unsupported.globalProjectItem', { item: 'Ponytail' })
|
|
41
|
+
: c === 'grok' ? msg('item.unsupported.ponytailGlobalGrok')
|
|
42
|
+
: ['kiro', 'vscode'].includes(c) ? msg('item.unsupported.ponytailRules')
|
|
43
|
+
: msg('item.unsupported.upstreamNone'),
|
|
44
|
+
]),
|
|
45
|
+
),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createItem(deps = {}) {
|
|
49
|
+
return defineGlobalPlugin(CONFIG, deps)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default createItem()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineGlobalPlugin } from '../global-plugin.mjs'
|
|
2
|
+
import { CLI_IDS } from '../clis.mjs'
|
|
3
|
+
import { msg } from '../i18n/index.mjs'
|
|
4
|
+
|
|
5
|
+
// superpowers를 프로젝트 스코프로 배선할 길이 없는 하니스에 상류의 하니스별
|
|
6
|
+
// 전역 설치("install separately for each one")를 대신 해 주는 항목. 공통
|
|
7
|
+
// 기구는 global-plugin.mjs에 있고, 여기는 상류 좌표와 사유만 든다.
|
|
8
|
+
//
|
|
9
|
+
// 하니스별 근거(2026-08-15, v6.3.0):
|
|
10
|
+
// codex — 이 머신에서 marketplace add + plugin add를 실측. 기록은
|
|
11
|
+
// $CODEX_HOME/config.toml의 [plugins."superpowers@…"].
|
|
12
|
+
// copilot — 이 머신에서 설치·목록·제거를 실측(~/.copilot/config.json).
|
|
13
|
+
// gemini — 공식 extensions 기구. 로컬 CLI가 없어 문서 근거다.
|
|
14
|
+
// opencode— 상류 .opencode/INSTALL.md가 전역 opencode.json의 plugin 배열에
|
|
15
|
+
// 아래 항목 한 줄을 더하라고 안내한다.
|
|
16
|
+
//
|
|
17
|
+
// grok은 상류 명령(grok plugin install superpowers@xai-official --trust)이
|
|
18
|
+
// 있지만 감지·제거 경로를 실측할 CLI가 없어 아직 배선하지 않고, kimi는 설치가
|
|
19
|
+
// 대화형 /plugins뿐이라 헤드리스 경로가 없다. claude는 프로젝트 스코프 항목
|
|
20
|
+
// (plugin.superpowers)의 자리다.
|
|
21
|
+
//
|
|
22
|
+
// skill.superpowers(공유 .agents/skills)와 배타로 묶지 않는다 — 저쪽은 이
|
|
23
|
+
// 저장소에만, 이쪽은 머신 전체에 작용해 평면이 다르고, "claude는 프로젝트
|
|
24
|
+
// 플러그인 + 나머지는 전역" 같은 조합이 정당하다. 겹치는 하니스가 같은 스킬을
|
|
25
|
+
// 두 곳에서 본다는 것은 note에 적어 둔다.
|
|
26
|
+
const SUPPORTS = ['codex', 'gemini', 'opencode', 'copilot']
|
|
27
|
+
|
|
28
|
+
const CONFIG = {
|
|
29
|
+
id: 'global.superpowers', label: 'superpowers (global)', group: '__flow',
|
|
30
|
+
note: 'item.global.superpowers.note',
|
|
31
|
+
pluginName: 'superpowers',
|
|
32
|
+
marketplaceRepo: 'obra/superpowers-marketplace',
|
|
33
|
+
installId: 'superpowers@superpowers-marketplace',
|
|
34
|
+
gemini: 'https://github.com/obra/superpowers',
|
|
35
|
+
opencode: 'superpowers@git+https://github.com/obra/superpowers.git',
|
|
36
|
+
unsupported: Object.fromEntries(
|
|
37
|
+
CLI_IDS.filter((c) => !SUPPORTS.includes(c)).map((c) => [
|
|
38
|
+
c,
|
|
39
|
+
c === 'claude' ? msg('item.unsupported.superpowersGlobalClaude')
|
|
40
|
+
: c === 'grok' ? msg('item.unsupported.superpowersGlobalGrok')
|
|
41
|
+
: c === 'kimi' ? msg('item.unsupported.superpowersGlobalKimi')
|
|
42
|
+
: msg('item.unsupported.upstreamNone'),
|
|
43
|
+
]),
|
|
44
|
+
),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 테스트가 홈·환경·PATH 탐지를 갈아끼울 수 있게 공장으로 노출한다.
|
|
48
|
+
export function createItem(deps = {}) {
|
|
49
|
+
return defineGlobalPlugin(CONFIG, deps)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default createItem()
|
|
@@ -6,12 +6,15 @@ import { msg } from '../i18n/index.mjs'
|
|
|
6
6
|
// 부트스트랩이 만든 `.agents/skills` Junction을 끊는다 — 공유 스킬이 전부
|
|
7
7
|
// Claude Code 화면에서 사라진다. 버그가 아니라 설계다: 상류
|
|
8
8
|
// `cli/bin/commands/skills.mjs`의 isInProjectProviderLink가 "프로젝트 안 다른
|
|
9
|
-
//
|
|
9
|
+
// 프로바이더의 skills를 가리키는 링크"를 의도적으로 unlink한다(3.6.0 재확인,
|
|
10
|
+
// 2026-08-15 — 저장소 밖을 가리키는 링크는 보존하지만, 부트스트랩 Junction은
|
|
11
|
+
// 프로젝트 안 `.agents/skills`를 가리켜 여전히 파괴 대상이다).
|
|
10
12
|
// 플러그인 경로는 `.claude/skills`를 건드리지 않는다.
|
|
11
13
|
// 또 그 명령은 `--providers`와 `--scope`를 함께 주지 않으면 대화형으로 물으며,
|
|
12
14
|
// 비TTY에서는 조용히 global을 골라 홈에 설치한다.
|
|
13
|
-
// 상류 공식
|
|
14
|
-
//
|
|
15
|
+
// 상류 공식 프로바이더는 3.6.0 기준 16개(.agent=antigravity·.hermes가 새로
|
|
16
|
+
// 들어옴 — README는 아직 수동 복사만 안내해 코드가 앞선다). 이 저장소의 CLI와
|
|
17
|
+
// 겹치는 것은 아래 UPSTREAM 그대로다.
|
|
15
18
|
const UPSTREAM = ['codex', 'gemini', 'opencode', 'kiro', 'grok', 'copilot']
|
|
16
19
|
export default definePlugin({
|
|
17
20
|
id: 'plugin.impeccable', label: 'impeccable', group: '__style',
|
|
@@ -7,7 +7,8 @@ import { msg } from '../i18n/index.mjs'
|
|
|
7
7
|
// 아니라 "우리가 플러그인 쪽을 골랐다"이고, 사유가 그렇게 말해야 레지스트리
|
|
8
8
|
// 항목으로 옮길 수 있다는 것도 화면에서 읽힌다.
|
|
9
9
|
export default definePlugin({
|
|
10
|
-
id: 'plugin.mattpocock-skills', label: 'Matt Pocock
|
|
10
|
+
id: 'plugin.mattpocock-skills', label: 'Matt Pocock (plugin)', group: '__flow',
|
|
11
|
+
exclusive: 'mattpocock',
|
|
11
12
|
installId: 'mattpocock-skills@mattpocock',
|
|
12
13
|
detectIds: ['mattpocock-skills@mattpocock'],
|
|
13
14
|
marketplace: { name: 'mattpocock', repo: 'mattpocock/skills' },
|
|
@@ -13,7 +13,8 @@ import { msg } from '../i18n/index.mjs'
|
|
|
13
13
|
const SEPARATE = ['codex', 'gemini', 'opencode', 'kimi', 'copilot', 'grok']
|
|
14
14
|
|
|
15
15
|
export default definePlugin({
|
|
16
|
-
id: 'plugin.superpowers', label: 'superpowers', group: '__flow',
|
|
16
|
+
id: 'plugin.superpowers', label: 'superpowers (plugin)', group: '__flow',
|
|
17
|
+
exclusive: 'superpowers',
|
|
17
18
|
installId: 'superpowers@claude-plugins-official',
|
|
18
19
|
detectIds: ['superpowers@claude-plugins-official', 'superpowers@superpowers-marketplace'],
|
|
19
20
|
note: 'item.plugin.superpowers.note',
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineRegistrySkill } from '../catalog.mjs'
|
|
2
|
+
// 상류가 "Codex, and other agents" 절에서 이 경로를 직접 안내한다 —
|
|
3
|
+
// `npx skills add mattpocock/skills`다. 그러니 이쪽이 곁길이 아니라 상류가
|
|
4
|
+
// 의도한 두 경로 중 하나다. 훅이 없는 스킬 묶음이라 플러그인 판에 견줘 잃는
|
|
5
|
+
// 것도 없다(35개 실측, skills/<분류>/<이름>/SKILL.md 배치).
|
|
6
|
+
//
|
|
7
|
+
// anchor는 setup-matt-pocock-skills다. 상류 README가 "이것만은 반드시 고르라"고
|
|
8
|
+
// 적어 둔 스킬이라 설치 판정의 기준으로도 맞는다.
|
|
9
|
+
export default defineRegistrySkill({
|
|
10
|
+
id: 'skill.mattpocock-skills', label: 'Matt Pocock (skills)', group: '__flow',
|
|
11
|
+
source: 'https://github.com/mattpocock/skills',
|
|
12
|
+
skill: '*',
|
|
13
|
+
anchor: 'setup-matt-pocock-skills',
|
|
14
|
+
exclusive: 'mattpocock',
|
|
15
|
+
note: 'item.skill.mattpocock-skills.note',
|
|
16
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineRegistrySkill } from '../catalog.mjs'
|
|
2
|
+
// 같은 상류를 플러그인(plugin.superpowers)으로도 넣을 수 있다. 그쪽은 Claude
|
|
3
|
+
// 하나만 닿는 대신 session-start 훅으로 알아서 켜지고, 이쪽은 공유
|
|
4
|
+
// .agents/skills라 10개 CLI가 함께 보는 대신 훅이 빠진다 — 스킬은 부르면 되지만
|
|
5
|
+
// "대화 시작 시 using-superpowers 주입"은 오지 않는다.
|
|
6
|
+
// 둘을 함께 켜면 같은 스킬이 플러그인 캐시와 공유 디렉터리 양쪽에서 잡혀 중복
|
|
7
|
+
// 등록되므로 배타로 묶는다.
|
|
8
|
+
//
|
|
9
|
+
// 상류 배치가 skills/<이름>/SKILL.md라 레지스트리가 그대로 읽는다(14개 실측).
|
|
10
|
+
// anchor는 using-superpowers다 — 상류가 "대화 시작 시 반드시 읽으라"고 못 박은
|
|
11
|
+
// 부트스트랩 스킬이라, 이것이 없으면 나머지가 있어도 설치로 볼 수 없다.
|
|
12
|
+
export default defineRegistrySkill({
|
|
13
|
+
id: 'skill.superpowers', label: 'superpowers (skills)', group: '__flow',
|
|
14
|
+
source: 'https://github.com/obra/superpowers',
|
|
15
|
+
skill: '*',
|
|
16
|
+
anchor: 'using-superpowers',
|
|
17
|
+
exclusive: 'superpowers',
|
|
18
|
+
note: 'item.skill.superpowers.note',
|
|
19
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rch4com/agent-setup",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Repository-scoped bootstrap and optional-item installer for using multiple coding-agent CLIs in one repository | 여러 코딩 에이전트 CLI를 한 저장소에서 함께 쓰기 위한 저장소 범위 부트스트랩과 선택 항목 설치기",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents-md",
|
|
@@ -20,10 +20,21 @@
|
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"type": "module",
|
|
23
|
-
"engines": {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"agent-setup": "install.mjs"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"install.mjs",
|
|
31
|
+
"lib/",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
27
38
|
"scripts": {
|
|
28
39
|
"start": "node install.mjs",
|
|
29
40
|
"test": "node --test",
|