@rch4com/agent-setup 1.8.0 → 1.10.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.
Files changed (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +199 -197
  3. package/install.mjs +163 -163
  4. package/lib/args.mjs +276 -276
  5. package/lib/bootstrap/adapter.mjs +96 -96
  6. package/lib/bootstrap/apply.mjs +380 -380
  7. package/lib/bootstrap/flow.mjs +140 -140
  8. package/lib/bootstrap/record.mjs +154 -154
  9. package/lib/bootstrap/templates.mjs +148 -148
  10. package/lib/bootstrap/text.mjs +19 -19
  11. package/lib/catalog.mjs +234 -234
  12. package/lib/claude-plugins.mjs +64 -64
  13. package/lib/clis.mjs +111 -111
  14. package/lib/context.mjs +66 -66
  15. package/lib/deps.mjs +17 -17
  16. package/lib/design-md/catalog.json +454 -454
  17. package/lib/design-md/catalog.mjs +266 -266
  18. package/lib/design-md/flow.mjs +192 -192
  19. package/lib/design-md/open.mjs +51 -51
  20. package/lib/design-md/providers/awesome-design-md.mjs +124 -124
  21. package/lib/design-md/providers/index.mjs +9 -9
  22. package/lib/design-md/scan.mjs +295 -295
  23. package/lib/engine.mjs +115 -115
  24. package/lib/gitignore.mjs +18 -18
  25. package/lib/i18n/catalog/en.mjs +426 -425
  26. package/lib/i18n/catalog/ko.mjs +411 -410
  27. package/lib/i18n/detect.mjs +39 -39
  28. package/lib/i18n/index.mjs +93 -93
  29. package/lib/items/mcp.codebase-memory.mjs +6 -6
  30. package/lib/items/mcp.notion.mjs +6 -6
  31. package/lib/items/mcp.supabase.mjs +6 -6
  32. package/lib/items/mcp.vercel.mjs +6 -6
  33. package/lib/items/plugin.mattpocock-skills.mjs +8 -8
  34. package/lib/items/plugin.ponytail.mjs +104 -104
  35. package/lib/items/skill.gsd.mjs +44 -44
  36. package/lib/items/skill.gstack.mjs +54 -54
  37. package/lib/items/skill.hallmark.mjs +10 -0
  38. package/lib/jsonfile.mjs +40 -40
  39. package/lib/status.mjs +93 -93
  40. package/lib/tomlfile.mjs +60 -60
  41. package/lib/tui/detail.mjs +127 -127
  42. package/lib/tui/progress.mjs +160 -160
  43. package/lib/tui/render.mjs +268 -268
  44. package/lib/tui/rows.mjs +317 -317
  45. package/lib/tui/run.mjs +396 -396
  46. package/lib/tui/state.mjs +249 -249
  47. package/lib/update.mjs +85 -85
  48. package/lib/width.mjs +115 -115
  49. package/package.json +36 -36
package/lib/update.mjs CHANGED
@@ -1,85 +1,85 @@
1
- // update 흐름 — 관리 파일을 최신 템플릿으로 옮기고 드리프트를 보고한다.
2
- //
3
- // 갱신 판정은 apply.mjs의 updateFiles·updateBlocks가 하고, 여기는 순서와
4
- // 보고, 그리고 기록 갱신만 담당한다. flow.mjs가 생성 쪽에서 하는 역할과 같다.
5
- import { execFileSync } from 'node:child_process'
6
- import { MANIFEST } from './bootstrap/manifest.mjs'
7
- import {
8
- configureAdapterSafe, ensureIgnore, ensureJsonKeys, updateBlocks, updateFiles,
9
- } from './bootstrap/apply.mjs'
10
- import { RECORD_REL, managedKey, readRecord, toolVersion, writeRecord } from './bootstrap/record.mjs'
11
- import { LocalizedError, createT, toText } from './i18n/index.mjs'
12
-
13
- // git이 유일한 되돌리기 수단이다. 커밋되지 않은 변경 위에 덮어쓰면 사용자가
14
- // 잃은 것을 복구할 방법이 없다.
15
- function assertCleanWorktree(root) {
16
- const out = execFileSync('git', ['-C', root, 'status', '--porcelain'], { encoding: 'utf8' })
17
- if (out.trim()) {
18
- throw new LocalizedError('error.forceNeedsCleanTree')
19
- }
20
- }
21
-
22
- export async function runUpdate(root, opts = {}) {
23
- const { dryRun = false, force = false, log = console.log, manifest = MANIFEST, t = createT('en') } = opts
24
- const say = (message) => log(`[agent-setup] ${message}`)
25
-
26
- const record = readRecord(root)
27
- if (!record) {
28
- throw new LocalizedError('error.noRecordForUpdate', { path: RECORD_REL })
29
- }
30
-
31
- if (force) assertCleanWorktree(root)
32
-
33
- // 2단계에서는 버전 차이를 보고만 한다. 여기서 중단시키면 기존 사용자의
34
- // 실행이 오류로 죽어 파괴적 변경이 된다. 엄격 검사는 apply와 함께 3단계다.
35
- const running = toolVersion()
36
- if (record.pinnedVersion && record.pinnedVersion !== running) {
37
- say(t('update.versionMove', { pinned: record.pinnedVersion, running }))
38
- }
39
-
40
- const ctx = { dryRun, force, log: say, managed: record.managed, t }
41
- const results = [
42
- ...updateFiles(root, manifest.files, ctx),
43
- ...updateBlocks(root, manifest.blocks, ctx),
44
- // 키 보장과 gitignore 항목 추가는 원래 멱등이라 그대로 재실행한다.
45
- ...ensureJsonKeys(root, manifest.settings ?? [], { dryRun, log: say, t }),
46
- ...ensureIgnore(root, manifest.ignore, { dryRun, log: say, t }),
47
- ]
48
-
49
- // 어댑터는 링크가 끊겼을 수 있어 재검증한다. 항목별로 실패를 격리한다.
50
- for (const entry of manifest.adapters) {
51
- results.push(configureAdapterSafe(root, entry, {
52
- dryRun, log: say, skillMode: record.skillMode, t,
53
- }))
54
- }
55
-
56
- // 성공한 것만 기록에 옮긴다. 드리프트는 hash를 담지 않으므로 옛 해시가
57
- // 남아, 사용자가 원복하면 다음 update가 다시 집어간다.
58
- const managed = { ...record.managed }
59
- for (const r of results) {
60
- if (!r.hash) continue
61
- const isBlock = manifest.blocks.some((b) => b.path === r.path)
62
- managed[managedKey(r.path, isBlock)] = r.hash
63
- }
64
-
65
- const drift = results.filter((r) => r.action === 'drift')
66
- const updated = results.filter((r) => r.action === 'update')
67
- const created = results.filter((r) => r.action === 'create')
68
-
69
- if (!dryRun) writeRecord(root, { ...record, managed }, { dryRun, log: say, t })
70
-
71
- log('')
72
- say(t('update.summary', { updated: updated.length, created: created.length, drift: drift.length }))
73
- if (drift.length > 0) {
74
- say(t('update.driftHeader'))
75
- // 결과 message는 구조화 메시지(msg())다. 문자열 삽입 자리에 그대로
76
- // 꽂으면 "[object Object]"가 찍히므로 toText로 활성 로케일에 맞춰 푼다.
77
- for (const d of drift) {
78
- const drifted = toText(t, d.message)
79
- say(` ${d.path}${drifted ? ` — ${drifted}` : ''}`)
80
- }
81
- say(t('update.driftHint'))
82
- }
83
-
84
- return { results, drift, record: { ...record, managed } }
85
- }
1
+ // update 흐름 — 관리 파일을 최신 템플릿으로 옮기고 드리프트를 보고한다.
2
+ //
3
+ // 갱신 판정은 apply.mjs의 updateFiles·updateBlocks가 하고, 여기는 순서와
4
+ // 보고, 그리고 기록 갱신만 담당한다. flow.mjs가 생성 쪽에서 하는 역할과 같다.
5
+ import { execFileSync } from 'node:child_process'
6
+ import { MANIFEST } from './bootstrap/manifest.mjs'
7
+ import {
8
+ configureAdapterSafe, ensureIgnore, ensureJsonKeys, updateBlocks, updateFiles,
9
+ } from './bootstrap/apply.mjs'
10
+ import { RECORD_REL, managedKey, readRecord, toolVersion, writeRecord } from './bootstrap/record.mjs'
11
+ import { LocalizedError, createT, toText } from './i18n/index.mjs'
12
+
13
+ // git이 유일한 되돌리기 수단이다. 커밋되지 않은 변경 위에 덮어쓰면 사용자가
14
+ // 잃은 것을 복구할 방법이 없다.
15
+ function assertCleanWorktree(root) {
16
+ const out = execFileSync('git', ['-C', root, 'status', '--porcelain'], { encoding: 'utf8' })
17
+ if (out.trim()) {
18
+ throw new LocalizedError('error.forceNeedsCleanTree')
19
+ }
20
+ }
21
+
22
+ export async function runUpdate(root, opts = {}) {
23
+ const { dryRun = false, force = false, log = console.log, manifest = MANIFEST, t = createT('en') } = opts
24
+ const say = (message) => log(`[agent-setup] ${message}`)
25
+
26
+ const record = readRecord(root)
27
+ if (!record) {
28
+ throw new LocalizedError('error.noRecordForUpdate', { path: RECORD_REL })
29
+ }
30
+
31
+ if (force) assertCleanWorktree(root)
32
+
33
+ // 2단계에서는 버전 차이를 보고만 한다. 여기서 중단시키면 기존 사용자의
34
+ // 실행이 오류로 죽어 파괴적 변경이 된다. 엄격 검사는 apply와 함께 3단계다.
35
+ const running = toolVersion()
36
+ if (record.pinnedVersion && record.pinnedVersion !== running) {
37
+ say(t('update.versionMove', { pinned: record.pinnedVersion, running }))
38
+ }
39
+
40
+ const ctx = { dryRun, force, log: say, managed: record.managed, t }
41
+ const results = [
42
+ ...updateFiles(root, manifest.files, ctx),
43
+ ...updateBlocks(root, manifest.blocks, ctx),
44
+ // 키 보장과 gitignore 항목 추가는 원래 멱등이라 그대로 재실행한다.
45
+ ...ensureJsonKeys(root, manifest.settings ?? [], { dryRun, log: say, t }),
46
+ ...ensureIgnore(root, manifest.ignore, { dryRun, log: say, t }),
47
+ ]
48
+
49
+ // 어댑터는 링크가 끊겼을 수 있어 재검증한다. 항목별로 실패를 격리한다.
50
+ for (const entry of manifest.adapters) {
51
+ results.push(configureAdapterSafe(root, entry, {
52
+ dryRun, log: say, skillMode: record.skillMode, t,
53
+ }))
54
+ }
55
+
56
+ // 성공한 것만 기록에 옮긴다. 드리프트는 hash를 담지 않으므로 옛 해시가
57
+ // 남아, 사용자가 원복하면 다음 update가 다시 집어간다.
58
+ const managed = { ...record.managed }
59
+ for (const r of results) {
60
+ if (!r.hash) continue
61
+ const isBlock = manifest.blocks.some((b) => b.path === r.path)
62
+ managed[managedKey(r.path, isBlock)] = r.hash
63
+ }
64
+
65
+ const drift = results.filter((r) => r.action === 'drift')
66
+ const updated = results.filter((r) => r.action === 'update')
67
+ const created = results.filter((r) => r.action === 'create')
68
+
69
+ if (!dryRun) writeRecord(root, { ...record, managed }, { dryRun, log: say, t })
70
+
71
+ log('')
72
+ say(t('update.summary', { updated: updated.length, created: created.length, drift: drift.length }))
73
+ if (drift.length > 0) {
74
+ say(t('update.driftHeader'))
75
+ // 결과 message는 구조화 메시지(msg())다. 문자열 삽입 자리에 그대로
76
+ // 꽂으면 "[object Object]"가 찍히므로 toText로 활성 로케일에 맞춰 푼다.
77
+ for (const d of drift) {
78
+ const drifted = toText(t, d.message)
79
+ say(` ${d.path}${drifted ? ` — ${drifted}` : ''}`)
80
+ }
81
+ say(t('update.driftHint'))
82
+ }
83
+
84
+ return { results, drift, record: { ...record, managed } }
85
+ }
package/lib/width.mjs CHANGED
@@ -1,115 +1,115 @@
1
- // 터미널 표시 폭 계산. TUI 화면과 비대화형 목록이 함께 쓴다.
2
- //
3
- // 아무것도 import 하지 않는다 — install.mjs가 정적으로 끌어오므로
4
- // bootstrap.isolation.test.mjs가 이 파일까지 의존성 0을 요구한다. 그리고
5
- // design-md/flow.mjs도 여기에 닿는데, 이 함수들이 tui/render.mjs에 남아
6
- // 있으면 render → flow → render 순환 import가 된다.
7
- //
8
- // String.padEnd는 코드 유닛을 센다. 한글 한 글자가 두 칸을 차지하는 터미널에서
9
- // padEnd로 열을 맞추면 로케일이 바뀔 때마다 열이 어긋난다 — 그래서 폭 계산은
10
- // 화면이든 목록이든 전부 이 모듈을 거친다.
11
-
12
- // 동아시아 글자는 터미널에서 두 칸을 차지한다. 전체 wcwidth 표 대신
13
- // 실제로 쓰이는 구간(한글·한자·가나·전각)만 넓게 센다.
14
- function charWidth(cp) {
15
- return (cp >= 0x1100 && cp <= 0x115f) // 한글 자모
16
- || (cp >= 0x2e80 && cp <= 0xa4cf) // 한자 부수·가나·한자
17
- || (cp >= 0xac00 && cp <= 0xd7a3) // 한글 음절
18
- || (cp >= 0xf900 && cp <= 0xfaff) // 한자 호환
19
- || (cp >= 0xfe30 && cp <= 0xfe6f) // 전각 형태
20
- || (cp >= 0xff00 && cp <= 0xff60) // 전각 영숫자
21
- || (cp >= 0xffe0 && cp <= 0xffe6)
22
- ? 2 : 1
23
- }
24
-
25
- export function width(text) {
26
- let w = 0
27
- for (const ch of String(text ?? '')) w += charWidth(ch.codePointAt(0))
28
- return w
29
- }
30
-
31
- // 색 코드는 폭에 포함되면 안 되므로, 자르기는 항상 색을 입히기 **전에** 한다.
32
- export function cut(text, limit) {
33
- if (limit <= 0) return ''
34
- const s = String(text ?? '')
35
- if (width(s) <= limit) return s
36
- let out = ''
37
- let w = 0
38
- for (const ch of s) {
39
- const cw = charWidth(ch.codePointAt(0))
40
- if (w + cw > limit - 1) break
41
- out += ch
42
- w += cw
43
- }
44
- return `${out}…`
45
- }
46
-
47
- export function pad(text, limit) {
48
- const s = cut(text, limit)
49
- return s + ' '.repeat(Math.max(0, limit - width(s)))
50
- }
51
-
52
- // 접기. cut은 넘치는 글을 버리고 pad는 모자란 폭을 채우지만, 상세 패널과
53
- // 진행 화면은 넘치는 글을 다음 줄로 이어 가야 한다.
54
- //
55
- // 공백이 있으면 마지막 공백에서 끊고(영문), 한 낱말이 limit보다 길거나
56
- // 공백이 없으면(한글) 표시 폭에서 끊는다. limit이 0 이하면 빈 배열을
57
- // 돌려준다 — 호출부가 폭을 잘못 계산했을 때 무한 루프에 빠지지 않게 한다.
58
- //
59
- // 문자 하나의 표시 폭이 limit보다 넓으면 그 문자를 버린다. limit < 2에서는
60
- // 폭을 지키면서 동시에 모든 문자를 담을 수 없다. 폭을 넘기면 TUI 레이아웃이
61
- // 무너지지만, 버리면 내용만 손실된다. 따라서 폭 불변식을 택했다.
62
- export function wrap(text, limit) {
63
- const s = String(text ?? '')
64
- if (limit <= 0 || s === '') return []
65
- const out = []
66
- for (const para of s.split('\n')) {
67
- let line = ''
68
- let w = 0
69
- let breakAt = -1 // line 안에서 마지막으로 본 공백의 위치
70
- for (const ch of para) {
71
- const cw = charWidth(ch.codePointAt(0))
72
- // 공백을 먼저 기록한다. 이렇게 해야 가장 최근의 공백을 추적할 수 있다.
73
- // 뒤에 아직 추가될 문자들이 있어도, 그 공백이 overflow가 되지 않으면
74
- // breakAt으로 유지되고, 되면 그 공백에서 끊을 준비가 된다.
75
- if (ch === ' ') breakAt = line.length
76
- if (w + cw > limit) {
77
- // Overflow: line이 limit을 초과할 것이다.
78
- if (breakAt > 0) {
79
- // 이전에 공백을 본 적이 있다. 그 공백에서 끊는다.
80
- out.push(line.slice(0, breakAt))
81
- line = line.slice(breakAt + 1)
82
- } else {
83
- // 공백이 없다. 현재 line을 그대로 끊는다.
84
- // 단, 현재 line이 비어있으면 빈 항목을 만들지 않는다.
85
- if (line) out.push(line)
86
- line = ''
87
- }
88
- w = width(line)
89
- breakAt = -1
90
- // Overflow를 유발한 공백은 버린다. 그 공백은 이미 line을 초과시켰으므로
91
- // 다음 줄에 포함하면 불필요한 공백이 앞에 붙는다.
92
- if (ch === ' ') continue
93
- }
94
- // Overflow가 아니라면, 또는 처리 후라면, 문자를 추가한다.
95
- // 단, 현재 line이 비어있는데 그 문자 자체가 limit보다 넓으면 건너뛴다.
96
- // 호출부가 폭을 잘못 계산해도 이 함수가 limit을 지키기 위함이다.
97
- if (w === 0 && cw > limit) {
98
- continue
99
- }
100
- line += ch
101
- w += cw
102
- }
103
- // 이번 문단을 끝낸다. 문단이 비어있거나 line이 비어있지 않으면 추가한다.
104
- // 이렇게 해서 "접힘 자리의 공백 때문에 빈 줄이 생기는" 문제를 피한다.
105
- if (line || para === '') out.push(line)
106
- }
107
- return out
108
- }
109
-
110
- // 상태 라벨은 로케일마다 길이가 다르다(영어 'Not installed' 13, 한국어
111
- // '미설치' 6). 한 번 재어 상수로 박으면 다른 로케일에서 열이 깨지므로,
112
- // 화면을 그릴 때 그 로케일의 실제 라벨에서 폭을 뽑는다.
113
- export function labelWidth(t, keys) {
114
- return Math.max(...keys.map((key) => width(t(key))))
115
- }
1
+ // 터미널 표시 폭 계산. TUI 화면과 비대화형 목록이 함께 쓴다.
2
+ //
3
+ // 아무것도 import 하지 않는다 — install.mjs가 정적으로 끌어오므로
4
+ // bootstrap.isolation.test.mjs가 이 파일까지 의존성 0을 요구한다. 그리고
5
+ // design-md/flow.mjs도 여기에 닿는데, 이 함수들이 tui/render.mjs에 남아
6
+ // 있으면 render → flow → render 순환 import가 된다.
7
+ //
8
+ // String.padEnd는 코드 유닛을 센다. 한글 한 글자가 두 칸을 차지하는 터미널에서
9
+ // padEnd로 열을 맞추면 로케일이 바뀔 때마다 열이 어긋난다 — 그래서 폭 계산은
10
+ // 화면이든 목록이든 전부 이 모듈을 거친다.
11
+
12
+ // 동아시아 글자는 터미널에서 두 칸을 차지한다. 전체 wcwidth 표 대신
13
+ // 실제로 쓰이는 구간(한글·한자·가나·전각)만 넓게 센다.
14
+ function charWidth(cp) {
15
+ return (cp >= 0x1100 && cp <= 0x115f) // 한글 자모
16
+ || (cp >= 0x2e80 && cp <= 0xa4cf) // 한자 부수·가나·한자
17
+ || (cp >= 0xac00 && cp <= 0xd7a3) // 한글 음절
18
+ || (cp >= 0xf900 && cp <= 0xfaff) // 한자 호환
19
+ || (cp >= 0xfe30 && cp <= 0xfe6f) // 전각 형태
20
+ || (cp >= 0xff00 && cp <= 0xff60) // 전각 영숫자
21
+ || (cp >= 0xffe0 && cp <= 0xffe6)
22
+ ? 2 : 1
23
+ }
24
+
25
+ export function width(text) {
26
+ let w = 0
27
+ for (const ch of String(text ?? '')) w += charWidth(ch.codePointAt(0))
28
+ return w
29
+ }
30
+
31
+ // 색 코드는 폭에 포함되면 안 되므로, 자르기는 항상 색을 입히기 **전에** 한다.
32
+ export function cut(text, limit) {
33
+ if (limit <= 0) return ''
34
+ const s = String(text ?? '')
35
+ if (width(s) <= limit) return s
36
+ let out = ''
37
+ let w = 0
38
+ for (const ch of s) {
39
+ const cw = charWidth(ch.codePointAt(0))
40
+ if (w + cw > limit - 1) break
41
+ out += ch
42
+ w += cw
43
+ }
44
+ return `${out}…`
45
+ }
46
+
47
+ export function pad(text, limit) {
48
+ const s = cut(text, limit)
49
+ return s + ' '.repeat(Math.max(0, limit - width(s)))
50
+ }
51
+
52
+ // 접기. cut은 넘치는 글을 버리고 pad는 모자란 폭을 채우지만, 상세 패널과
53
+ // 진행 화면은 넘치는 글을 다음 줄로 이어 가야 한다.
54
+ //
55
+ // 공백이 있으면 마지막 공백에서 끊고(영문), 한 낱말이 limit보다 길거나
56
+ // 공백이 없으면(한글) 표시 폭에서 끊는다. limit이 0 이하면 빈 배열을
57
+ // 돌려준다 — 호출부가 폭을 잘못 계산했을 때 무한 루프에 빠지지 않게 한다.
58
+ //
59
+ // 문자 하나의 표시 폭이 limit보다 넓으면 그 문자를 버린다. limit < 2에서는
60
+ // 폭을 지키면서 동시에 모든 문자를 담을 수 없다. 폭을 넘기면 TUI 레이아웃이
61
+ // 무너지지만, 버리면 내용만 손실된다. 따라서 폭 불변식을 택했다.
62
+ export function wrap(text, limit) {
63
+ const s = String(text ?? '')
64
+ if (limit <= 0 || s === '') return []
65
+ const out = []
66
+ for (const para of s.split('\n')) {
67
+ let line = ''
68
+ let w = 0
69
+ let breakAt = -1 // line 안에서 마지막으로 본 공백의 위치
70
+ for (const ch of para) {
71
+ const cw = charWidth(ch.codePointAt(0))
72
+ // 공백을 먼저 기록한다. 이렇게 해야 가장 최근의 공백을 추적할 수 있다.
73
+ // 뒤에 아직 추가될 문자들이 있어도, 그 공백이 overflow가 되지 않으면
74
+ // breakAt으로 유지되고, 되면 그 공백에서 끊을 준비가 된다.
75
+ if (ch === ' ') breakAt = line.length
76
+ if (w + cw > limit) {
77
+ // Overflow: line이 limit을 초과할 것이다.
78
+ if (breakAt > 0) {
79
+ // 이전에 공백을 본 적이 있다. 그 공백에서 끊는다.
80
+ out.push(line.slice(0, breakAt))
81
+ line = line.slice(breakAt + 1)
82
+ } else {
83
+ // 공백이 없다. 현재 line을 그대로 끊는다.
84
+ // 단, 현재 line이 비어있으면 빈 항목을 만들지 않는다.
85
+ if (line) out.push(line)
86
+ line = ''
87
+ }
88
+ w = width(line)
89
+ breakAt = -1
90
+ // Overflow를 유발한 공백은 버린다. 그 공백은 이미 line을 초과시켰으므로
91
+ // 다음 줄에 포함하면 불필요한 공백이 앞에 붙는다.
92
+ if (ch === ' ') continue
93
+ }
94
+ // Overflow가 아니라면, 또는 처리 후라면, 문자를 추가한다.
95
+ // 단, 현재 line이 비어있는데 그 문자 자체가 limit보다 넓으면 건너뛴다.
96
+ // 호출부가 폭을 잘못 계산해도 이 함수가 limit을 지키기 위함이다.
97
+ if (w === 0 && cw > limit) {
98
+ continue
99
+ }
100
+ line += ch
101
+ w += cw
102
+ }
103
+ // 이번 문단을 끝낸다. 문단이 비어있거나 line이 비어있지 않으면 추가한다.
104
+ // 이렇게 해서 "접힘 자리의 공백 때문에 빈 줄이 생기는" 문제를 피한다.
105
+ if (line || para === '') out.push(line)
106
+ }
107
+ return out
108
+ }
109
+
110
+ // 상태 라벨은 로케일마다 길이가 다르다(영어 'Not installed' 13, 한국어
111
+ // '미설치' 6). 한 번 재어 상수로 박으면 다른 로케일에서 열이 깨지므로,
112
+ // 화면을 그릴 때 그 로케일의 실제 라벨에서 폭을 뽑는다.
113
+ export function labelWidth(t, keys) {
114
+ return Math.max(...keys.map((key) => width(t(key))))
115
+ }
package/package.json CHANGED
@@ -1,36 +1,36 @@
1
- {
2
- "name": "@rch4com/agent-setup",
3
- "version": "1.8.0",
4
- "description": "Repository-scoped bootstrap and optional-item installer for using multiple coding-agent CLIs in one repository | 여러 코딩 에이전트 CLI를 한 저장소에서 함께 쓰기 위한 저장소 범위 부트스트랩과 선택 항목 설치기",
5
- "keywords": [
6
- "agents-md",
7
- "claude-code",
8
- "codex",
9
- "gemini-cli",
10
- "copilot",
11
- "mcp",
12
- "agent-skills"
13
- ],
14
- "homepage": "https://github.com/rch4com/Agent-Setup#readme",
15
- "bugs": "https://github.com/rch4com/Agent-Setup/issues",
16
- "repository": {
17
- "type": "git",
18
- "url": "git+https://github.com/rch4com/Agent-Setup.git",
19
- "directory": "agent-installer"
20
- },
21
- "license": "MIT",
22
- "type": "module",
23
- "engines": { "node": ">=20" },
24
- "bin": { "agent-setup": "install.mjs" },
25
- "files": ["install.mjs", "lib/", "README.md", "LICENSE"],
26
- "publishConfig": { "access": "public" },
27
- "scripts": {
28
- "start": "node install.mjs",
29
- "test": "node --test",
30
- "refresh-bundle": "node scripts/refresh-bundle.mjs"
31
- },
32
- "dependencies": {
33
- "jsonc-parser": "^3.3.1",
34
- "smol-toml": "^1.3.1"
35
- }
36
- }
1
+ {
2
+ "name": "@rch4com/agent-setup",
3
+ "version": "1.10.0",
4
+ "description": "Repository-scoped bootstrap and optional-item installer for using multiple coding-agent CLIs in one repository | 여러 코딩 에이전트 CLI를 한 저장소에서 함께 쓰기 위한 저장소 범위 부트스트랩과 선택 항목 설치기",
5
+ "keywords": [
6
+ "agents-md",
7
+ "claude-code",
8
+ "codex",
9
+ "gemini-cli",
10
+ "copilot",
11
+ "mcp",
12
+ "agent-skills"
13
+ ],
14
+ "homepage": "https://github.com/rch4com/Agent-Setup#readme",
15
+ "bugs": "https://github.com/rch4com/Agent-Setup/issues",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/rch4com/Agent-Setup.git",
19
+ "directory": "agent-installer"
20
+ },
21
+ "license": "MIT",
22
+ "type": "module",
23
+ "engines": { "node": ">=20" },
24
+ "bin": { "agent-setup": "install.mjs" },
25
+ "files": ["install.mjs", "lib/", "README.md", "LICENSE"],
26
+ "publishConfig": { "access": "public" },
27
+ "scripts": {
28
+ "start": "node install.mjs",
29
+ "test": "node --test",
30
+ "refresh-bundle": "node scripts/refresh-bundle.mjs"
31
+ },
32
+ "dependencies": {
33
+ "jsonc-parser": "^3.3.1",
34
+ "smol-toml": "^1.3.1"
35
+ }
36
+ }