@rch4com/agent-setup 1.17.0 → 1.19.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 +8 -1
- package/lib/catalog.mjs +37 -10
- package/lib/clis.mjs +11 -0
- package/lib/engine.mjs +4 -2
- package/lib/global-plugin.mjs +38 -6
- package/lib/i18n/catalog/en.mjs +42 -13
- package/lib/i18n/catalog/ko.mjs +42 -13
- package/lib/items/global.ponytail.mjs +8 -4
- package/lib/items/global.superpowers.mjs +12 -6
- package/lib/items/plugin.ecc.mjs +1 -0
- package/lib/items/plugin.impeccable.mjs +2 -1
- package/lib/items/plugin.mattpocock-skills.mjs +1 -0
- package/lib/items/plugin.ponytail.mjs +1 -0
- package/lib/items/plugin.superpowers.mjs +2 -1
- package/lib/items/plugin.understand-anything.mjs +1 -0
- package/lib/items/skill.agent-browser.mjs +1 -0
- package/lib/items/skill.find-skills.mjs +1 -0
- package/lib/items/skill.gsd.mjs +5 -0
- package/lib/items/skill.gstack.mjs +1 -0
- package/lib/items/skill.hallmark.mjs +1 -0
- package/lib/items/skill.mcp-builder.mjs +1 -0
- package/lib/items/skill.prompt-master.mjs +7 -0
- package/lib/items/skill.strix.mjs +13 -0
- package/lib/status.mjs +19 -1
- package/lib/tui/detail.mjs +42 -9
- package/lib/tui/render.mjs +155 -23
- package/lib/tui/rows.mjs +36 -7
- package/lib/tui/run.mjs +51 -11
- package/lib/tui/state.mjs +9 -5
- package/package.json +1 -1
- package/lib/items/plugin.bkit.mjs +0 -25
package/lib/tui/rows.mjs
CHANGED
|
@@ -104,16 +104,38 @@ export function designHint(state, multiProvider = false, t) {
|
|
|
104
104
|
return parts.filter(Boolean).join(' · ')
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// 이름을 나열할 수 있는 최대 CLI 수. 넷까지는 이름(codex·gemini·opencode·copilot)이
|
|
108
|
+
// 80칸 힌트 자리에 들고, 그보다 많으면 "몇 개나 되나"가 더 유용하다.
|
|
109
|
+
const NAME_LIMIT = 4
|
|
110
|
+
|
|
107
111
|
// 목록 행에 실제로 찍히는 힌트. 80칸 터미널이면 힌트 자리는 49칸(한글 24자)뿐이라,
|
|
108
112
|
// 예전처럼 사유까지 이어 붙이면 뒤쪽이 통째로 잘렸다. 사유·note·detail은
|
|
109
113
|
// 상세 패널이 여러 줄로 편다 — 여기 남는 것은 잘릴 일이 없는 두 가지뿐이다.
|
|
114
|
+
//
|
|
115
|
+
// 커버리지 표기는 탭과 무관하게 한 규칙이다 — 예전에는 plugin 탭만 이름을,
|
|
116
|
+
// 나머지는 수를 찍어 한 화면 안에서 규칙이 바뀌었다. 이제 지원 CLI가 넷
|
|
117
|
+
// 이하면 이름을, 그보다 많으면 `CLI n/N`을 찍는다. 이름을 나열할 때는 이
|
|
118
|
+
// 머신에 없는 CLI(state.excluded — 전역 항목이 detect에서 알린다)를 빼고
|
|
119
|
+
// 따로 적는다: 상세에는 "CLI 없음"이라 하면서 행에는 그 이름이 그대로
|
|
120
|
+
// 찍히면 어느 쪽이 참인지 알 수 없다.
|
|
121
|
+
export function coverageText(item, state, t = createT('en')) {
|
|
122
|
+
if (!item.supports) return ''
|
|
123
|
+
if (item.supports.length > NAME_LIMIT || item.supports.length === 0) {
|
|
124
|
+
return t('item.cliCoverage', { covered: item.supports.length, total: CLI_IDS.length })
|
|
125
|
+
}
|
|
126
|
+
const excluded = state?.excluded ?? []
|
|
127
|
+
const here = item.supports.filter((c) => !excluded.includes(c))
|
|
128
|
+
const parts = []
|
|
129
|
+
if (here.length > 0) parts.push(here.join('·'))
|
|
130
|
+
if (excluded.length > 0) parts.push(t('item.cliMissing', { list: excluded.join('·') }))
|
|
131
|
+
return parts.join(' · ')
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
export function agentShortHint(item, state, t = createT('en')) {
|
|
111
135
|
const parts = []
|
|
112
136
|
if (state.status !== 'absent') parts.push(t(`status.${state.status}`))
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (item.category === 'plugin' && item.supports?.length) parts.push(item.supports.join('·'))
|
|
116
|
-
else if (item.supports) parts.push(t('item.cliCoverage', { covered: item.supports.length, total: CLI_IDS.length }))
|
|
137
|
+
const cov = coverageText(item, state, t)
|
|
138
|
+
if (cov) parts.push(cov)
|
|
117
139
|
return parts.join(' · ')
|
|
118
140
|
}
|
|
119
141
|
|
|
@@ -151,11 +173,14 @@ function itemRow({ id, section, label, hint, fullHint = hint, statusDetail = nul
|
|
|
151
173
|
}
|
|
152
174
|
}
|
|
153
175
|
|
|
154
|
-
|
|
176
|
+
// section 기본값은 작업 탭이다. design.md 동기화처럼 한 탭에만 관계있는
|
|
177
|
+
// 작업은 그 탭을 넘겨 그 자리 맨 위에 놓는다 — 첫 화면의 작업 탭에 design.md
|
|
178
|
+
// 전용 작업이 섞여 있으면 무엇을 위한 화면인지부터 흐려진다.
|
|
179
|
+
function actionRow({ id, label, hint, run = null, section = ACTION_SECTION, t = createT('en') }) {
|
|
155
180
|
return {
|
|
156
181
|
kind: 'action',
|
|
157
182
|
id,
|
|
158
|
-
section
|
|
183
|
+
section,
|
|
159
184
|
group: null,
|
|
160
185
|
exclusive: null,
|
|
161
186
|
label,
|
|
@@ -165,7 +190,7 @@ function actionRow({ id, label, hint, run = null, t = createT('en') }) {
|
|
|
165
190
|
status: 'absent',
|
|
166
191
|
previewTarget: null,
|
|
167
192
|
run,
|
|
168
|
-
searchText: `${label} ${hint} ${sectionTerms(t,
|
|
193
|
+
searchText: `${label} ${hint} ${sectionTerms(t, section)}`.toLowerCase(),
|
|
169
194
|
}
|
|
170
195
|
}
|
|
171
196
|
|
|
@@ -192,8 +217,10 @@ export function buildActions(root, { designItems = [], t = createT('en') } = {})
|
|
|
192
217
|
run: ({ dryRun, skillMode, log, t: rt }) => runBootstrap(root, { dryRun, skillMode, log, t: rt }),
|
|
193
218
|
t,
|
|
194
219
|
}),
|
|
220
|
+
// design.md 전용 세 작업은 DESIGN.MD 탭 맨 위에 놓는다.
|
|
195
221
|
actionRow({
|
|
196
222
|
id: 'action.sync.installed',
|
|
223
|
+
section: 'design',
|
|
197
224
|
label: t('action.sync.installed.label'),
|
|
198
225
|
hint: t('action.sync.installed.hint'),
|
|
199
226
|
run: ({ dryRun, log, t: rt }) => updateInstalled(root, designItems, { dryRun, log, t: rt }),
|
|
@@ -201,6 +228,7 @@ export function buildActions(root, { designItems = [], t = createT('en') } = {})
|
|
|
201
228
|
}),
|
|
202
229
|
actionRow({
|
|
203
230
|
id: 'action.sync.catalog',
|
|
231
|
+
section: 'design',
|
|
204
232
|
label: t('action.sync.catalog.label'),
|
|
205
233
|
hint: t('action.sync.catalog.hint'),
|
|
206
234
|
run: ({ dryRun, fetchImpl, log, catalogFile, t: rt }) => refreshCatalog({ dryRun, fetchImpl, log, catalogFile, t: rt }),
|
|
@@ -208,6 +236,7 @@ export function buildActions(root, { designItems = [], t = createT('en') } = {})
|
|
|
208
236
|
}),
|
|
209
237
|
actionRow({
|
|
210
238
|
id: 'action.sync.stale',
|
|
239
|
+
section: 'design',
|
|
211
240
|
label: t('action.sync.stale.label'),
|
|
212
241
|
hint: t('action.sync.stale.hint'),
|
|
213
242
|
run: async ({ dryRun, log, confirm, t: rt }) => {
|
package/lib/tui/run.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
createState, setQuery, setFocus, move, moveTab, toggle, toggleVisible, scroll, currentRow, replaceRows, activeTab,
|
|
13
13
|
cycleCliFilter,
|
|
14
14
|
} from './state.mjs'
|
|
15
|
-
import { render, renderReview, bodyHeight } from './render.mjs'
|
|
15
|
+
import { render, renderReview, renderHelp, bodyHeight, footerHeight } from './render.mjs'
|
|
16
16
|
import { createProgress, applyEvent, progressLines } from './progress.mjs'
|
|
17
17
|
import { scopedLabel } from '../labels.mjs'
|
|
18
18
|
|
|
@@ -119,19 +119,36 @@ export async function runTui(root, opts = {}) {
|
|
|
119
119
|
|
|
120
120
|
const color = !env.NO_COLOR && stdout.isTTY
|
|
121
121
|
const draw = (lines) => stdout.write(HOME + lines.map((l) => l + CLEAR_LINE).join('\n') + '\n' + CLEAR_DOWN)
|
|
122
|
+
// 목록 지면은 바닥글 줄 수에 따라 달라진다 — render와 같은 산식을 써야
|
|
123
|
+
// 스크롤·페이지 이동이 화면과 어긋나지 않는다. t는 언어 행이 갈아끼우므로
|
|
124
|
+
// 그때그때 다시 잰다.
|
|
125
|
+
const listHeight = () => bodyHeight(stdout.rows ?? 24, detailExpanded, footerHeight(stdout.columns ?? 80, t))
|
|
122
126
|
const paint = () => {
|
|
123
127
|
const height = stdout.rows ?? 24
|
|
124
|
-
state = scroll(state,
|
|
128
|
+
state = scroll(state, listHeight())
|
|
125
129
|
draw(render(state, {
|
|
126
130
|
width: stdout.columns ?? 80, height, repo: root, dryRun, color, status, t,
|
|
127
131
|
detailExpanded, cliOptions: CLI_OPTIONS,
|
|
128
132
|
}))
|
|
129
133
|
}
|
|
134
|
+
const paintHelp = () => draw(renderHelp({ width: stdout.columns ?? 80, height: stdout.rows ?? 24, color, t }))
|
|
135
|
+
|
|
136
|
+
// 터미널 크기가 바뀌면 지금 떠 있는 화면을 새 크기로 다시 그린다 — 예전에는
|
|
137
|
+
// 다음 키를 누를 때까지 옛 크기로 남았다. 화면마다 자기 그리기 함수를
|
|
138
|
+
// redraw에 걸고, 화면 밖(suspend)에서는 null로 둬 아무것도 안 한다.
|
|
139
|
+
// 테스트의 가짜 stdout에는 on이 없을 수 있어 선택 호출이다.
|
|
140
|
+
let redraw = paint
|
|
141
|
+
const onResize = () => {
|
|
142
|
+
try { redraw?.() } catch { /* 그리기 실패로 루프를 죽이지 않는다 */ }
|
|
143
|
+
}
|
|
144
|
+
stdout.on?.('resize', onResize)
|
|
130
145
|
|
|
131
146
|
// 로그는 화면 안에 우겨넣지 않는다 — 실패 메시지가 길고, 잘리면 진단이 불가능해진다.
|
|
132
147
|
const suspend = async (fn) => {
|
|
148
|
+
const was = redraw
|
|
149
|
+
redraw = null
|
|
133
150
|
stdout.write(ALT_OFF + SHOW_CURSOR)
|
|
134
|
-
try { return await fn() } finally { stdout.write(ALT_ON + HIDE_CURSOR) }
|
|
151
|
+
try { return await fn() } finally { stdout.write(ALT_ON + HIDE_CURSOR); redraw = was }
|
|
135
152
|
}
|
|
136
153
|
const confirm = async (message) => {
|
|
137
154
|
stdout.write(`\n${message}${t('tui.confirmSuffix')}`)
|
|
@@ -201,12 +218,18 @@ export async function runTui(root, opts = {}) {
|
|
|
201
218
|
|
|
202
219
|
// 제출 검토 — 적용 직전에 변경 목록을 보여 주고 Enter/Esc만 받는다.
|
|
203
220
|
const review = async (changes) => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
const paintReview = () => draw(renderReview(changes, { width: stdout.columns ?? 80, height: stdout.rows ?? 24, dryRun, color, t }))
|
|
222
|
+
redraw = paintReview
|
|
223
|
+
try {
|
|
224
|
+
for (;;) {
|
|
225
|
+
paintReview()
|
|
226
|
+
const key = await keys.next()
|
|
227
|
+
if (key.ctrl && (key.name === 'c' || key.name === 'q')) return 'quit'
|
|
228
|
+
if (key.name === 'escape') return 'cancel'
|
|
229
|
+
if (key.name === 'return' || key.name === 'enter') return 'apply'
|
|
230
|
+
}
|
|
231
|
+
} finally {
|
|
232
|
+
redraw = paint
|
|
210
233
|
}
|
|
211
234
|
}
|
|
212
235
|
|
|
@@ -225,6 +248,7 @@ export async function runTui(root, opts = {}) {
|
|
|
225
248
|
width: stdout.columns ?? 80, height: stdout.rows ?? 24, color, dryRun, now: Date.now(), t,
|
|
226
249
|
}))
|
|
227
250
|
|
|
251
|
+
redraw = drawProgress
|
|
228
252
|
drawProgress()
|
|
229
253
|
const timer = setInterval(() => { checkAbort(); drawProgress() }, 100)
|
|
230
254
|
// 타이머가 프로세스를 붙잡지 않게 한다 — 화면 갱신은 종료를 미룰 이유가 없다.
|
|
@@ -255,6 +279,7 @@ export async function runTui(root, opts = {}) {
|
|
|
255
279
|
return results
|
|
256
280
|
} finally {
|
|
257
281
|
clearInterval(timer)
|
|
282
|
+
redraw = paint
|
|
258
283
|
}
|
|
259
284
|
}
|
|
260
285
|
|
|
@@ -284,6 +309,17 @@ export async function runTui(root, opts = {}) {
|
|
|
284
309
|
}
|
|
285
310
|
if (key.ctrl && key.name === 'd') { detailExpanded = !detailExpanded; continue }
|
|
286
311
|
|
|
312
|
+
// F1: 도움말. 바닥글 두 줄로는 키의 뜻까지 담을 수 없다. 아무 키나 닫는다 —
|
|
313
|
+
// 그 키는 소비만 하고 명령으로 해석하지 않는다(도움말을 닫으려 누른
|
|
314
|
+
// Space가 항목을 고르면 안 된다).
|
|
315
|
+
if (key.name === 'f1') {
|
|
316
|
+
redraw = paintHelp
|
|
317
|
+
paintHelp()
|
|
318
|
+
await keys.next()
|
|
319
|
+
redraw = paint
|
|
320
|
+
continue
|
|
321
|
+
}
|
|
322
|
+
|
|
287
323
|
// ── 검색칸에 포커스: 타이핑이 곧 검색어다(스페이스 포함, 두 단어 검색 가능).
|
|
288
324
|
if (state.focus === 'search') {
|
|
289
325
|
// Esc: 검색어가 있으면 지우고 검색칸에 남는다. 이미 비었으면 목록으로 내려간다.
|
|
@@ -319,8 +355,11 @@ export async function runTui(root, opts = {}) {
|
|
|
319
355
|
continue
|
|
320
356
|
}
|
|
321
357
|
if (key.name === 'down' || (key.ctrl && key.name === 'n')) { state = move(state, 1); continue }
|
|
322
|
-
if (key.name === 'pageup') { state = move(state, -
|
|
323
|
-
if (key.name === 'pagedown') { state = move(state,
|
|
358
|
+
if (key.name === 'pageup') { state = move(state, -listHeight()); continue }
|
|
359
|
+
if (key.name === 'pagedown') { state = move(state, listHeight()); continue }
|
|
360
|
+
// 76개짜리 DESIGN.MD 탭에서 끝으로 가려면 PgDn을 여러 번 눌러야 했다.
|
|
361
|
+
if (key.name === 'home') { state = move(state, -state.filtered.length); continue }
|
|
362
|
+
if (key.name === 'end') { state = move(state, state.filtered.length); continue }
|
|
324
363
|
|
|
325
364
|
// 탭 이동 — Tab/Shift+Tab, 좌우 화살표.
|
|
326
365
|
if (key.name === 'tab') { state = moveTab(state, key.shift ? -1 : 1); continue }
|
|
@@ -389,6 +428,7 @@ export async function runTui(root, opts = {}) {
|
|
|
389
428
|
}
|
|
390
429
|
} finally {
|
|
391
430
|
keys.stop()
|
|
431
|
+
stdout.off?.('resize', onResize)
|
|
392
432
|
stdout.write(ALT_OFF + SHOW_CURSOR)
|
|
393
433
|
stdin.setRawMode(false)
|
|
394
434
|
stdin.pause()
|
package/lib/tui/state.mjs
CHANGED
|
@@ -73,13 +73,17 @@ export function activeTab(state) {
|
|
|
73
73
|
// 탭별 적중 수 — 검색·필터 중에도 어느 탭에 결과가 있는지 한눈에 보이게 한다.
|
|
74
74
|
// total은 필터를 타지 않는다: 분모가 함께 줄면 "이 탭에서 몇 개가 걸러졌나"를
|
|
75
75
|
// 알 수 없다.
|
|
76
|
+
//
|
|
77
|
+
// 항목이 있는 탭은 항목만 센다 — DESIGN.MD 탭 맨 위의 작업 행 셋까지 세면
|
|
78
|
+
// 항목 76개인 탭이 79로 찍혀 머리글의 "전체"와 어긋난다. 작업만 있는 탭은
|
|
79
|
+
// 작업을 센다(0으로 두면 탭이 비어 보인다).
|
|
76
80
|
export function tabCounts(state) {
|
|
77
81
|
const hits = filterRows(state.rows, state.query).filter((r) => matchesCli(r, state.cliFilter))
|
|
78
|
-
return state.tabs.map((tab) =>
|
|
79
|
-
tab
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
})
|
|
82
|
+
return state.tabs.map((tab) => {
|
|
83
|
+
const hasItems = state.rows.some((r) => r.section === tab && r.kind === 'item')
|
|
84
|
+
const pick = (rows) => rows.filter((r) => r.section === tab && (!hasItems || r.kind === 'item'))
|
|
85
|
+
return { tab, shown: pick(hits).length, total: pick(state.rows).length }
|
|
86
|
+
})
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
export function currentRow(state) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rch4com/agent-setup",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.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",
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { definePlugin } from '../catalog.mjs'
|
|
2
|
-
import { CLI_IDS } from '../clis.mjs'
|
|
3
|
-
import { msg } from '../i18n/index.mjs'
|
|
4
|
-
|
|
5
|
-
// 상류의 Codex·Gemini 지원은 이 플러그인이 아니라 별도 리포의 독립 포팅본이다
|
|
6
|
-
// (bkit-codex: install 스크립트+MCP, 기능 241개 중 80개, hooks 없음 /
|
|
7
|
-
// bkit-gemini: Gemini CLI extension). 플러그인 항목을 재사용할 수 없어 사유만 남긴다.
|
|
8
|
-
// 예전에 "Gemini판은 Antigravity 승계 추정"이라 적었는데 근거가 없었다 —
|
|
9
|
-
// bkit-gemini는 v2.0.7까지 Gemini CLI 확장 그대로이고 README에 Antigravity가
|
|
10
|
-
// 한 번도 나오지 않는다. 다만 최종 푸시가 2026-05-20이라 Gemini CLI 종료
|
|
11
|
-
// (2026-06-18) 이후로는 갱신이 없다. 추정 대신 그 사실만 적는다(2026-08-15 실측).
|
|
12
|
-
const PORTS = ['codex', 'gemini']
|
|
13
|
-
|
|
14
|
-
export default definePlugin({
|
|
15
|
-
id: 'plugin.bkit', label: 'bkit', group: '__flow',
|
|
16
|
-
installId: 'bkit@bkit-marketplace',
|
|
17
|
-
detectIds: ['bkit@bkit-marketplace'],
|
|
18
|
-
marketplace: { name: 'bkit-marketplace', repo: 'popup-studio-ai/bkit-claude-code' },
|
|
19
|
-
unsupported: Object.fromEntries(
|
|
20
|
-
CLI_IDS.filter((c) => c !== 'claude').map((c) => [
|
|
21
|
-
c,
|
|
22
|
-
PORTS.includes(c) ? msg('item.unsupported.bkitPort') : msg('item.unsupported.upstreamNone'),
|
|
23
|
-
]),
|
|
24
|
-
),
|
|
25
|
-
})
|