@rch4com/agent-setup 1.28.0 → 2.2.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/README.md +31 -20
- package/install.mjs +25 -8
- package/lib/activation.mjs +31 -0
- package/lib/args.mjs +5 -2
- package/lib/bootstrap/manifest.mjs +21 -47
- package/lib/bootstrap/templates.mjs +7 -60
- package/lib/catalog.mjs +22 -241
- package/lib/clis.mjs +11 -118
- package/lib/commands.mjs +47 -0
- package/lib/engine.mjs +16 -11
- package/lib/i18n/catalog/en.mjs +58 -49
- package/lib/i18n/catalog/ko.mjs +58 -49
- package/lib/items/mcp.codebase-memory.mjs +6 -4
- package/lib/items/mcp.graft.mjs +6 -4
- package/lib/items/mcp.graphify.mjs +6 -4
- package/lib/items/mcp.headroom.mjs +6 -4
- package/lib/items/mcp.notion.mjs +7 -4
- package/lib/items/mcp.supabase.mjs +7 -4
- package/lib/items/mcp.vercel.mjs +7 -4
- package/lib/items/plugin.ai-memory.mjs +29 -0
- package/lib/items/plugin.cli-anything.mjs +18 -15
- package/lib/items/plugin.ecc.mjs +22 -20
- package/lib/items/plugin.hyperframes.mjs +17 -13
- package/lib/items/plugin.impeccable.mjs +19 -22
- package/lib/items/plugin.mattpocock-skills.mjs +23 -17
- package/lib/items/plugin.ponytail.mjs +24 -106
- package/lib/items/plugin.superpowers.mjs +20 -24
- package/lib/items/plugin.understand-anything.mjs +21 -19
- package/lib/items/skill.agent-browser.mjs +6 -6
- package/lib/items/skill.agent-handoff-manager.mjs +14 -0
- package/lib/items/skill.archify.mjs +5 -5
- package/lib/items/skill.caveman.mjs +5 -5
- package/lib/items/skill.diagram-design.mjs +5 -5
- package/lib/items/skill.eli5.mjs +6 -6
- package/lib/items/skill.find-skills.mjs +5 -5
- package/lib/items/skill.gsd.mjs +28 -89
- package/lib/items/skill.gstack.mjs +58 -65
- package/lib/items/skill.hallmark.mjs +5 -5
- package/lib/items/skill.html-plan.mjs +15 -0
- package/lib/items/skill.i-have-adhd.mjs +6 -6
- package/lib/items/skill.im-not-ai.mjs +5 -6
- package/lib/items/skill.impeccable.mjs +6 -6
- package/lib/items/skill.karpathy.mjs +5 -5
- package/lib/items/skill.mattpocock-skills.mjs +5 -6
- package/lib/items/skill.mcp-builder.mjs +5 -5
- package/lib/items/skill.officecli.mjs +5 -5
- package/lib/items/skill.prompt-master.mjs +5 -5
- package/lib/items/skill.remotion.mjs +5 -5
- package/lib/items/skill.strix.mjs +5 -6
- package/lib/items/skill.superpowers.mjs +6 -7
- package/lib/items/skill.taste.mjs +5 -5
- package/lib/items/skill.unslop.mjs +12 -0
- package/lib/probe.mjs +136 -0
- package/lib/recipe-helpers.mjs +281 -0
- package/lib/recipe.mjs +181 -0
- package/lib/skills-registry.mjs +93 -0
- package/lib/status.mjs +7 -3
- package/lib/tui/detail.mjs +5 -25
- package/lib/tui/rows.mjs +25 -14
- package/lib/tui/run.mjs +35 -8
- package/package.json +4 -3
- package/lib/claude-plugins.mjs +0 -64
- package/lib/global-plugin.mjs +0 -239
- package/lib/items/global.ponytail.mjs +0 -56
- package/lib/items/global.superpowers.mjs +0 -58
- package/lib/tomlfile.mjs +0 -67
package/lib/global-plugin.mjs
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
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
|
-
// grok ~/.grok/installed-plugins/registry.json의 repos[*].plugins 키
|
|
16
|
-
// (2026-09-05 grok 1.0.5로 설치·제거 왕복 실측). 설치는
|
|
17
|
-
// `grok plugin install <소스> --trust`가 registry와 디렉터리를 만들고
|
|
18
|
-
// config.toml [plugins].enabled에도 이름을 넣는다. 제거
|
|
19
|
-
// (`grok plugin uninstall <이름> --confirm`)는 디렉터리·registry만
|
|
20
|
-
// 지우고 enabled 항목은 남기므로, 감지의 근거는 registry여야 한다 —
|
|
21
|
-
// enabled를 보면 제거한 뒤에도 영원히 설치됨으로 읽힌다.
|
|
22
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
23
|
-
import { homedir } from 'node:os'
|
|
24
|
-
import { delimiter, join } from 'node:path'
|
|
25
|
-
import { parse as parseToml } from 'smol-toml'
|
|
26
|
-
import { CLIS } from './clis.mjs'
|
|
27
|
-
import { readJson, setKey, removeKey } from './jsonfile.mjs'
|
|
28
|
-
import { msg, LocalizedError } from './i18n/index.mjs'
|
|
29
|
-
|
|
30
|
-
// 실행은 cmd 셸을 거치므로(catalog.mjs makeExec) win32에서는 PATHEXT 확장자가
|
|
31
|
-
// 있어야 실제로 부를 수 있다 — copilot.ps1만 있는 PATH는 "없음"이 맞다.
|
|
32
|
-
function defaultHasBinary(name, env = process.env) {
|
|
33
|
-
const dirs = (env.PATH ?? '').split(delimiter).filter(Boolean)
|
|
34
|
-
const exts = process.platform === 'win32'
|
|
35
|
-
? (env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean)
|
|
36
|
-
: ['']
|
|
37
|
-
return dirs.some((dir) => exts.some((ext) => existsSync(join(dir, name + ext))))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// superpowers@openai-curated처럼 다른 마켓플레이스로 설치된 판도 잡아야
|
|
41
|
-
// 하므로(실측: 이런 머신이 실제로 있다) 이름이 아니라 접두사로 고른다.
|
|
42
|
-
// 제거도 여기서 찾은 id를 그대로 쓴다 — 감지가 "있다"고 말한 판을 지워야
|
|
43
|
-
// 상태가 맞아떨어진다.
|
|
44
|
-
function codexPluginIds(file, pluginName) {
|
|
45
|
-
if (!existsSync(file)) return []
|
|
46
|
-
try {
|
|
47
|
-
const data = parseToml(readFileSync(file, 'utf8'))
|
|
48
|
-
return Object.keys(data.plugins ?? {}).filter((id) => id.startsWith(`${pluginName}@`))
|
|
49
|
-
} catch {
|
|
50
|
-
// 깨진 config.toml은 미설치로 둔다 — 설치를 시도하면 codex가 스스로
|
|
51
|
-
// 진단하고, "설치됨"으로 오판해 제거를 그리로 보내는 것보다 낫다.
|
|
52
|
-
return []
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function copilotInstalled(file, pluginName) {
|
|
57
|
-
const list = readJson(file)?.installedPlugins
|
|
58
|
-
return Array.isArray(list) && list.some((p) => p?.name === pluginName)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ponytail(items/plugin.ponytail.mjs)과 같은 규칙: plugin 키가 배열이 아니면
|
|
62
|
-
// 손대지 않는다.
|
|
63
|
-
function opencodePlugins(file) {
|
|
64
|
-
const value = readJson(file)?.plugin
|
|
65
|
-
if (value === undefined) return []
|
|
66
|
-
if (!Array.isArray(value)) return null
|
|
67
|
-
return value
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// config:
|
|
71
|
-
// pluginName 감지·제거가 쓰는 상류 이름 (codex 접두사, copilot name,
|
|
72
|
-
// gemini 확장 디렉터리 이름)
|
|
73
|
-
// marketplaceRepo codex·copilot 마켓플레이스 (owner/repo)
|
|
74
|
-
// installId 마켓플레이스 설치 id (<이름>@<마켓>)
|
|
75
|
-
// gemini gemini extensions install에 줄 git URL, 없으면 null
|
|
76
|
-
// opencode 전역 opencode.json plugin 배열에 넣을 항목, 없으면 null
|
|
77
|
-
// grok grok plugin install에 줄 소스(마켓 id 또는 owner/repo), 없으면 null
|
|
78
|
-
// supports는 어댑터 유무로 정해진다 — codex·copilot은 항상, gemini·opencode·grok은
|
|
79
|
-
// 좌표가 있을 때만.
|
|
80
|
-
export function defineGlobalPlugin(
|
|
81
|
-
{ id, label, group, note, exclusive = null, unsupported = {}, pluginName, marketplaceRepo, installId, gemini = null, opencode = null, grok = null, verified = null },
|
|
82
|
-
{ home = null, env = process.env, hasBinary = defaultHasBinary } = {},
|
|
83
|
-
) {
|
|
84
|
-
const supports = ['codex', ...(gemini ? ['gemini'] : []), ...(opencode ? ['opencode'] : []), 'copilot', ...(grok ? ['grok'] : [])]
|
|
85
|
-
|
|
86
|
-
const files = () => {
|
|
87
|
-
const base = home ?? homedir()
|
|
88
|
-
return {
|
|
89
|
-
codex: join(env.CODEX_HOME || join(base, '.codex'), 'config.toml'),
|
|
90
|
-
copilot: join(base, '.copilot', 'config.json'),
|
|
91
|
-
gemini: join(base, '.gemini', 'extensions', pluginName),
|
|
92
|
-
opencode: join(env.XDG_CONFIG_HOME || join(base, '.config'), 'opencode', 'opencode.json'),
|
|
93
|
-
grok: join(base, '.grok', 'installed-plugins', 'registry.json'),
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// registry.json: { repos: { "<이름>-<해시>": { plugins: { "<이름>": {...} } } } }.
|
|
98
|
-
// 이름은 repos 키(해시가 붙는다)가 아니라 plugins 키에서 본다.
|
|
99
|
-
const grokInstalled = (file) => {
|
|
100
|
-
const repos = readJson(file)?.repos
|
|
101
|
-
if (!repos || typeof repos !== 'object') return false
|
|
102
|
-
return Object.values(repos).some((r) => Object.hasOwn(r?.plugins ?? {}, pluginName))
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const isOurEntry = (p) => String(p).startsWith(`${pluginName}@`)
|
|
106
|
-
const wired = {
|
|
107
|
-
codex: (f) => codexPluginIds(f.codex, pluginName).length > 0,
|
|
108
|
-
copilot: (f) => copilotInstalled(f.copilot, pluginName),
|
|
109
|
-
gemini: (f) => existsSync(f.gemini),
|
|
110
|
-
opencode: (f) => (opencodePlugins(f.opencode) ?? []).some(isOurEntry),
|
|
111
|
-
grok: (f) => grokInstalled(f.grok),
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
id, category: 'plugin', label, scope: 'user', group, exclusive, verified,
|
|
116
|
-
supports: [...supports],
|
|
117
|
-
unsupported,
|
|
118
|
-
note,
|
|
119
|
-
|
|
120
|
-
// 상태는 "이 머신에 있는 하니스" 기준이다. CLI가 없는 하니스까지 누락으로
|
|
121
|
-
// 세면 gemini 없는 머신은 영원히 partial이라 apply가 매번 헛돈다.
|
|
122
|
-
async detect() {
|
|
123
|
-
const f = files()
|
|
124
|
-
const present = supports.filter((c) => wired[c](f))
|
|
125
|
-
const machine = supports.filter((c) => hasBinary(c))
|
|
126
|
-
const noCli = supports.filter((c) => !machine.includes(c))
|
|
127
|
-
const missing = machine.filter((c) => !present.includes(c))
|
|
128
|
-
const detail = noCli.length > 0 ? msg('item.global.noCli', { list: noCli.join(', ') }) : undefined
|
|
129
|
-
// excluded는 detail과 별도로 구조체로 넘긴다 — 행 힌트가 CLI 이름을
|
|
130
|
-
// 나열할 때 이 머신에 없는 것을 빼고 따로 적으려면 문장이 아니라 목록이 필요하다.
|
|
131
|
-
if (present.length === 0) return { status: 'absent', detail, excluded: noCli }
|
|
132
|
-
if (missing.length === 0) return { status: 'installed', detail, excluded: noCli }
|
|
133
|
-
return {
|
|
134
|
-
status: 'partial',
|
|
135
|
-
detail: msg('item.plugin.partial', { present: present.join(', '), missing: missing.join(', ') }),
|
|
136
|
-
excluded: noCli,
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
async install({ dryRun, exec, log = () => {}, t }) {
|
|
141
|
-
const f = files()
|
|
142
|
-
// opencode 설정이 손댈 수 없는 꼴이면 아무것도 만지기 전에 멈춘다 —
|
|
143
|
-
// 절반만 설치된 채 실패하는 것보다 통째로 거절이 낫다.
|
|
144
|
-
const needsOpencode = opencode && !wired.opencode(f) && hasBinary('opencode')
|
|
145
|
-
if (needsOpencode && !dryRun && opencodePlugins(f.opencode) === null) {
|
|
146
|
-
throw new LocalizedError('error.globalOpencodePlugin', { file: f.opencode, entry: opencode })
|
|
147
|
-
}
|
|
148
|
-
const skipped = []
|
|
149
|
-
const failures = []
|
|
150
|
-
const fail = (cli, output) => failures.push(`${cli}: ${String(output).trim().split('\n')[0]}`)
|
|
151
|
-
for (const cli of supports) {
|
|
152
|
-
if (wired[cli](f)) continue
|
|
153
|
-
if (!hasBinary(cli)) {
|
|
154
|
-
skipped.push(cli)
|
|
155
|
-
continue
|
|
156
|
-
}
|
|
157
|
-
if (cli === 'codex') {
|
|
158
|
-
// 마켓 등록은 이미 있어도 해가 없다 — 성패는 add가 판정한다.
|
|
159
|
-
await exec('codex', ['plugin', 'marketplace', 'add', marketplaceRepo])
|
|
160
|
-
const r = await exec('codex', ['plugin', 'add', installId])
|
|
161
|
-
if (!r.ok) fail(cli, r.output)
|
|
162
|
-
} else if (cli === 'copilot') {
|
|
163
|
-
await exec('copilot', ['plugin', 'marketplace', 'add', marketplaceRepo])
|
|
164
|
-
// 실측(2026-08-15): 다른 copilot 세션이 떠 있으면 os error 5로 한 번
|
|
165
|
-
// 실패하고 재시도에 성공한다. 한 번만 다시 시도한다.
|
|
166
|
-
let r = await exec('copilot', ['plugin', 'install', installId])
|
|
167
|
-
if (!r.ok) r = await exec('copilot', ['plugin', 'install', installId])
|
|
168
|
-
if (!r.ok) fail(cli, r.output)
|
|
169
|
-
} else if (cli === 'gemini') {
|
|
170
|
-
const r = await exec('gemini', ['extensions', 'install', gemini])
|
|
171
|
-
if (!r.ok) fail(cli, r.output)
|
|
172
|
-
} else if (cli === 'grok') {
|
|
173
|
-
// --trust는 확인 프롬프트를 건너뛴다 — stdin이 닫힌 채 도는 실행이라
|
|
174
|
-
// 프롬프트가 뜨면 영원히 멈춘다.
|
|
175
|
-
const r = await exec('grok', ['plugin', 'install', grok, '--trust'])
|
|
176
|
-
if (!r.ok) fail(cli, r.output)
|
|
177
|
-
} else {
|
|
178
|
-
if (dryRun) {
|
|
179
|
-
log(t('log.plugin.add', { cli: CLIS.opencode.label, name: opencode }))
|
|
180
|
-
continue
|
|
181
|
-
}
|
|
182
|
-
const list = opencodePlugins(f.opencode) ?? []
|
|
183
|
-
if (list.length === 0) setKey(f.opencode, ['plugin'], [opencode])
|
|
184
|
-
else setKey(f.opencode, ['plugin', list.length], opencode)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (failures.length > 0) throw new LocalizedError('error.globalExec', { detail: failures.join(' / ') })
|
|
188
|
-
if (skipped.length > 0) return { message: msg('item.global.skipped', { list: skipped.join(', ') }) }
|
|
189
|
-
},
|
|
190
|
-
|
|
191
|
-
async uninstall({ dryRun, exec, log = () => {}, t }) {
|
|
192
|
-
const f = files()
|
|
193
|
-
const skipped = []
|
|
194
|
-
const failures = []
|
|
195
|
-
const fail = (cli, output) => failures.push(`${cli}: ${String(output).trim().split('\n')[0]}`)
|
|
196
|
-
for (const cli of supports) {
|
|
197
|
-
if (!wired[cli](f)) continue
|
|
198
|
-
if (cli === 'opencode') {
|
|
199
|
-
if (dryRun) {
|
|
200
|
-
log(t('log.plugin.remove', { cli: CLIS.opencode.label, name: opencode }))
|
|
201
|
-
continue
|
|
202
|
-
}
|
|
203
|
-
const kept = (opencodePlugins(f.opencode) ?? []).filter((p) => !isOurEntry(p))
|
|
204
|
-
// 우리 항목만 빼고 비면 키째 지운다 — 빈 배열은 우리가 남긴 흔적이다.
|
|
205
|
-
if (kept.length === 0) removeKey(f.opencode, ['plugin'])
|
|
206
|
-
else setKey(f.opencode, ['plugin'], kept)
|
|
207
|
-
continue
|
|
208
|
-
}
|
|
209
|
-
if (!hasBinary(cli)) {
|
|
210
|
-
skipped.push(cli)
|
|
211
|
-
continue
|
|
212
|
-
}
|
|
213
|
-
if (cli === 'codex') {
|
|
214
|
-
for (const pid of codexPluginIds(f.codex, pluginName)) {
|
|
215
|
-
const r = await exec('codex', ['plugin', 'remove', pid])
|
|
216
|
-
if (!r.ok) fail(cli, r.output)
|
|
217
|
-
}
|
|
218
|
-
// 마켓플레이스 등록은 남긴다 — 우리가 등록했다는 보장이 없고,
|
|
219
|
-
// 같은 마켓의 다른 플러그인이 쓰고 있을 수 있다.
|
|
220
|
-
} else if (cli === 'copilot') {
|
|
221
|
-
let r = await exec('copilot', ['plugin', 'uninstall', pluginName])
|
|
222
|
-
if (!r.ok) r = await exec('copilot', ['plugin', 'uninstall', pluginName])
|
|
223
|
-
if (!r.ok) fail(cli, r.output)
|
|
224
|
-
} else if (cli === 'grok') {
|
|
225
|
-
// --confirm은 한 저장소에 플러그인이 여럿일 때의 확인을 건너뛴다.
|
|
226
|
-
// config.toml [plugins].enabled의 이름은 상류가 남긴다 — 우리가
|
|
227
|
-
// 등록한 줄이라는 보장이 없어 건드리지 않는다(note에 적는다).
|
|
228
|
-
const r = await exec('grok', ['plugin', 'uninstall', pluginName, '--confirm'])
|
|
229
|
-
if (!r.ok) fail(cli, r.output)
|
|
230
|
-
} else {
|
|
231
|
-
const r = await exec('gemini', ['extensions', 'uninstall', pluginName])
|
|
232
|
-
if (!r.ok) fail(cli, r.output)
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
if (failures.length > 0) throw new LocalizedError('error.globalExec', { detail: failures.join(' / ') })
|
|
236
|
-
if (skipped.length > 0) return { message: msg('item.global.skipped', { list: skipped.join(', ') }) }
|
|
237
|
-
},
|
|
238
|
-
}
|
|
239
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
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
|
-
// 같은 이유로 프로젝트 항목의 자리다.
|
|
22
|
-
// grok — `grok plugin install DietrichGebert/ponytail --trust`. 2026-09-05
|
|
23
|
-
// 이 머신(grok 1.0.5)에서 설치·감지·제거 왕복을 실측했다. 상류
|
|
24
|
-
// README는 "기본 꺼짐이라 /plugins에서 켜라"고 적지만 1.0.5는
|
|
25
|
-
// --trust 설치 직후 config.toml [plugins].enabled에 이름을 넣었다.
|
|
26
|
-
// kiro·vscode는 상류가 룰 파일 수동 배치만 안내하고, kilo·kimi는 경로가 없다.
|
|
27
|
-
//
|
|
28
|
-
// 상류는 플러그인 제거 전에 node scripts/uninstall.js로 ~/.config/ponytail
|
|
29
|
-
// 설정과 statusLine 항목을 정리하라고 안내한다 — 하니스마다 스크립트 위치가
|
|
30
|
-
// 달라 자동화하지 않고 note로 알린다. 이 항목은 플러그인만 제거한다.
|
|
31
|
-
const SUPPORTS = ['codex', 'gemini', 'copilot', 'grok']
|
|
32
|
-
|
|
33
|
-
const CONFIG = {
|
|
34
|
-
id: 'global.ponytail', label: 'Ponytail', group: '__token',
|
|
35
|
-
note: 'item.global.ponytail.note',
|
|
36
|
-
verified: '2026-09-05',
|
|
37
|
-
pluginName: 'ponytail',
|
|
38
|
-
marketplaceRepo: 'DietrichGebert/ponytail',
|
|
39
|
-
installId: 'ponytail@ponytail',
|
|
40
|
-
gemini: 'https://github.com/DietrichGebert/ponytail',
|
|
41
|
-
grok: 'DietrichGebert/ponytail',
|
|
42
|
-
unsupported: Object.fromEntries(
|
|
43
|
-
CLI_IDS.filter((c) => !SUPPORTS.includes(c)).map((c) => [
|
|
44
|
-
c,
|
|
45
|
-
['claude', 'opencode'].includes(c) ? msg('item.unsupported.globalProjectItem', { item: 'Ponytail' })
|
|
46
|
-
: ['kiro', 'vscode'].includes(c) ? msg('item.unsupported.ponytailRules')
|
|
47
|
-
: msg('item.unsupported.upstreamNone'),
|
|
48
|
-
]),
|
|
49
|
-
),
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function createItem(deps = {}) {
|
|
53
|
-
return defineGlobalPlugin(CONFIG, deps)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default createItem()
|
|
@@ -1,58 +0,0 @@
|
|
|
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 — 2026-09-05 이 머신(grok 1.0.5)에서 `grok plugin install
|
|
18
|
-
// superpowers@xai-official --trust`로 설치·감지·제거 왕복을 실측.
|
|
19
|
-
// 기록은 ~/.grok/installed-plugins/registry.json(global-plugin.mjs).
|
|
20
|
-
//
|
|
21
|
-
// kimi는 설치가 대화형 /plugins뿐이라 헤드리스 경로가 없다. claude는 프로젝트
|
|
22
|
-
// 스코프 항목(plugin.superpowers)의 자리다.
|
|
23
|
-
//
|
|
24
|
-
// skill.superpowers(공유 .agents/skills)와 배타로 묶지 않는다 — 저쪽은 이
|
|
25
|
-
// 저장소에만, 이쪽은 머신 전체에 작용해 평면이 다르고, "claude는 프로젝트
|
|
26
|
-
// 플러그인 + 나머지는 전역" 같은 조합이 정당하다. 겹치는 하니스가 같은 스킬을
|
|
27
|
-
// 두 곳에서 본다는 것은 note에 적어 둔다.
|
|
28
|
-
const SUPPORTS = ['codex', 'gemini', 'opencode', 'copilot', 'grok']
|
|
29
|
-
|
|
30
|
-
const CONFIG = {
|
|
31
|
-
id: 'global.superpowers', label: 'superpowers', group: '__flow',
|
|
32
|
-
note: 'item.global.superpowers.note',
|
|
33
|
-
verified: '2026-09-05',
|
|
34
|
-
pluginName: 'superpowers',
|
|
35
|
-
marketplaceRepo: 'obra/superpowers-marketplace',
|
|
36
|
-
installId: 'superpowers@superpowers-marketplace',
|
|
37
|
-
gemini: 'https://github.com/obra/superpowers',
|
|
38
|
-
opencode: 'superpowers@git+https://github.com/obra/superpowers.git',
|
|
39
|
-
grok: 'superpowers@xai-official',
|
|
40
|
-
unsupported: Object.fromEntries(
|
|
41
|
-
CLI_IDS.filter((c) => !SUPPORTS.includes(c)).map((c) => [
|
|
42
|
-
c,
|
|
43
|
-
c === 'claude' ? msg('item.unsupported.superpowersGlobalClaude')
|
|
44
|
-
: c === 'kimi' ? msg('item.unsupported.superpowersGlobalKimi')
|
|
45
|
-
// 상류 README가 Antigravity 설치를 안내하지만 감지·제거 경로를 실측할
|
|
46
|
-
// CLI가 없다 — grok과 같은 기준으로 아직 배선하지 않는다.
|
|
47
|
-
: c === 'antigravity' ? msg('item.unsupported.unmeasuredUpstream')
|
|
48
|
-
: msg('item.unsupported.upstreamNone'),
|
|
49
|
-
]),
|
|
50
|
-
),
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 테스트가 홈·환경·PATH 탐지를 갈아끼울 수 있게 공장으로 노출한다.
|
|
54
|
-
export function createItem(deps = {}) {
|
|
55
|
-
return defineGlobalPlugin(CONFIG, deps)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export default createItem()
|
package/lib/tomlfile.mjs
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
2
|
-
import { dirname } from 'node:path'
|
|
3
|
-
import { parse } from 'smol-toml'
|
|
4
|
-
import { LocalizedError } from './i18n/index.mjs'
|
|
5
|
-
import { escapeRegExp } from './untrusted.mjs'
|
|
6
|
-
|
|
7
|
-
function readText(file) {
|
|
8
|
-
return existsSync(file) ? readFileSync(file, 'utf8') : ''
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// 깨진 TOML은 읽기·쓰기 모두 거부한다. 예전에는 파싱 실패를 "섹션 없음"으로
|
|
12
|
-
// 돌려줘, 닫는 대괄호가 빠진 파일 끝에 같은 섹션이 하나 더 붙고 사용자는
|
|
13
|
-
// 파일이 깨진 것을 끝까지 몰랐다 — jsonfile.mjs의 JSON 규칙과 같다.
|
|
14
|
-
function parseStrict(text, file) {
|
|
15
|
-
try {
|
|
16
|
-
return parse(text)
|
|
17
|
-
} catch (err) {
|
|
18
|
-
throw new LocalizedError('error.tomlInvalid', { path: file, message: err.message.split('\n')[0] })
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function hasSection(file, name) {
|
|
23
|
-
const text = readText(file)
|
|
24
|
-
if (!text.trim()) return false
|
|
25
|
-
const data = parseStrict(text, file)
|
|
26
|
-
return data.mcp_servers != null && Object.hasOwn(data.mcp_servers, name)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function appendSection(file, name, lines) {
|
|
30
|
-
let text = readText(file)
|
|
31
|
-
if (text.trim()) parseStrict(text, file)
|
|
32
|
-
if (text.length > 0 && !text.endsWith('\n')) text += '\n'
|
|
33
|
-
const separator = text.length > 0 ? '\n' : ''
|
|
34
|
-
text += `${separator}[mcp_servers.${name}]\n${lines.join('\n')}\n`
|
|
35
|
-
mkdirSync(dirname(file), { recursive: true })
|
|
36
|
-
writeFileSync(file, text)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function removeSection(file, name) {
|
|
40
|
-
const text = readText(file)
|
|
41
|
-
if (!text) return
|
|
42
|
-
parseStrict(text, file)
|
|
43
|
-
const lines = text.split('\n')
|
|
44
|
-
const headerRe = new RegExp(`^\\s*\\[mcp_servers\\.${escapeRegExp(name)}(\\.|\\])`)
|
|
45
|
-
const anyHeaderRe = /^\s*\[/
|
|
46
|
-
const out = []
|
|
47
|
-
let skipping = false
|
|
48
|
-
let removedAt = -1
|
|
49
|
-
for (const line of lines) {
|
|
50
|
-
if (skipping && anyHeaderRe.test(line) && !headerRe.test(line)) skipping = false
|
|
51
|
-
if (headerRe.test(line)) {
|
|
52
|
-
skipping = true
|
|
53
|
-
if (removedAt === -1) removedAt = out.length
|
|
54
|
-
}
|
|
55
|
-
if (!skipping) out.push(line)
|
|
56
|
-
}
|
|
57
|
-
// 제거 지점의 경계에서만 빈 줄 하나를 정리해, 다른 위치의 빈 줄들은 그대로 보존한다.
|
|
58
|
-
if (
|
|
59
|
-
removedAt > 0 &&
|
|
60
|
-
removedAt < out.length &&
|
|
61
|
-
out[removedAt - 1].trim() === '' &&
|
|
62
|
-
(out[removedAt] ?? '').trim() === ''
|
|
63
|
-
) {
|
|
64
|
-
out.splice(removedAt - 1, 1)
|
|
65
|
-
}
|
|
66
|
-
writeFileSync(file, out.join('\n'))
|
|
67
|
-
}
|