@rch4com/agent-setup 1.20.0 → 1.21.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/lib/bootstrap/adapter.mjs +19 -5
- package/lib/design-md/flow.mjs +6 -2
- package/lib/gitignore.mjs +15 -0
- package/lib/i18n/catalog/en.mjs +5 -1
- package/lib/i18n/catalog/ko.mjs +5 -1
- package/lib/items/skill.gstack.mjs +8 -3
- package/lib/tomlfile.mjs +16 -6
- package/lib/tui/rows.mjs +4 -2
- package/lib/tui/run.mjs +9 -1
- package/lib/width.mjs +16 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Windows는 Junction을 쓴다. 관리자 권한이 필요 없고, MSYS(Git Bash)의 ln -s가
|
|
3
3
|
// 링크 대신 복사를 만드는 문제도 우회한다.
|
|
4
4
|
import { cpSync, lstatSync, mkdirSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
|
5
|
-
import { dirname, join, relative, resolve } from 'node:path'
|
|
5
|
+
import { basename, dirname, join, relative, resolve } from 'node:path'
|
|
6
6
|
import { repoPath, repoPathStrict } from '../context.mjs'
|
|
7
7
|
import { createT, msg } from '../i18n/index.mjs'
|
|
8
8
|
import { pathExists } from './apply.mjs'
|
|
@@ -42,7 +42,12 @@ export function configureAdapter(root, { tool, path: rel }, { dryRun = false, sk
|
|
|
42
42
|
const target = repoPath(root, rel)
|
|
43
43
|
|
|
44
44
|
const linked = linkTarget(target)
|
|
45
|
-
|
|
45
|
+
// 대상이 사라진 링크는 보존할 것이 없다. Windows Junction은 만들 때의 절대
|
|
46
|
+
// 경로를 담으므로 저장소 디렉터리를 옮기거나 이름을 바꾸면 옛 경로를
|
|
47
|
+
// 가리킨 채 끊긴다 — 그것을 "다른 곳을 가리키는 링크"로 보고 보존하면
|
|
48
|
+
// 매 실행마다 경고만 찍히고 Claude Code는 공유 스킬을 영영 보지 못한다.
|
|
49
|
+
const dangling = linked !== null && !pathExists(linked)
|
|
50
|
+
if (linked !== null && !dangling) {
|
|
46
51
|
if (linked === repoPath(root, SOURCE_REL)) {
|
|
47
52
|
log(t('log.skill.linkOk', { tool, path: rel }))
|
|
48
53
|
return { ok: true, action: 'skip', path: rel }
|
|
@@ -51,8 +56,8 @@ export function configureAdapter(root, { tool, path: rel }, { dryRun = false, sk
|
|
|
51
56
|
return { ok: true, action: 'warn', path: rel, message: msg('msg.foreignLink') }
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
const managedCopy = pathExists(target) && pathExists(join(target, MARKER))
|
|
55
|
-
if (pathExists(target) && !managedCopy) {
|
|
59
|
+
const managedCopy = !dangling && pathExists(target) && pathExists(join(target, MARKER))
|
|
60
|
+
if (!dangling && pathExists(target) && !managedCopy) {
|
|
56
61
|
log(t('log.skill.warnUnmanaged', { path: rel }))
|
|
57
62
|
return { ok: true, action: 'warn', path: rel, message: msg('msg.unmanagedExisting') }
|
|
58
63
|
}
|
|
@@ -62,7 +67,14 @@ export function configureAdapter(root, { tool, path: rel }, { dryRun = false, sk
|
|
|
62
67
|
// 쓰기만 !dryRun으로 막는다 — apply.mjs의 ensureDirs와 같은 형태.
|
|
63
68
|
// source(.agents/skills)는 실제로 읽어 복사하는 대상이므로 여기서 함께 검사한다.
|
|
64
69
|
const source = repoPathStrict(root, SOURCE_REL)
|
|
65
|
-
|
|
70
|
+
// 끊긴 링크는 realpath가 ENOENT로 실패해 자기 자신을 엄격 검사할 수 없다.
|
|
71
|
+
// 지울 것은 링크 항목 하나뿐이니 그것이 놓인 부모 디렉터리를 검사한다 —
|
|
72
|
+
// 부모가 저장소 안이면 그 안의 항목 하나를 지우는 것도 저장소 안이다.
|
|
73
|
+
const strictTarget = dangling
|
|
74
|
+
? join(repoPathStrict(root, dirname(rel)), basename(rel))
|
|
75
|
+
: repoPathStrict(root, rel)
|
|
76
|
+
|
|
77
|
+
if (dangling) log(t('log.skill.relinkDangling', { path: rel }))
|
|
66
78
|
|
|
67
79
|
if (dryRun) {
|
|
68
80
|
log(t('log.skill.plan', { tool, path: rel, mode: skillMode }))
|
|
@@ -74,6 +86,8 @@ export function configureAdapter(root, { tool, path: rel }, { dryRun = false, sk
|
|
|
74
86
|
// 마커가 확인된 복사본만 지운다.
|
|
75
87
|
rmSync(strictTarget, { recursive: true, force: true })
|
|
76
88
|
}
|
|
89
|
+
// 링크 자체만 지운다 — rmSync는 링크를 따라 내려가지 않는다(대상이 없기도 하다).
|
|
90
|
+
if (dangling) rmSync(strictTarget, { recursive: true, force: true })
|
|
77
91
|
|
|
78
92
|
mkdirSync(dirname(strictTarget), { recursive: true })
|
|
79
93
|
|
package/lib/design-md/flow.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { scan, planChanges, apply } from '../engine.mjs'
|
|
2
|
-
import { loadCatalog, saveCatalog, buildItems, allEntries, sha256, resolveTokens, netFetch, CATALOG_PATH } from './catalog.mjs'
|
|
2
|
+
import { loadCatalog, saveCatalog, buildItems, allEntries, sha256, resolveTokens, netFetch, isSafeSegment, CATALOG_PATH } from './catalog.mjs'
|
|
3
3
|
import { PROVIDERS } from './providers/index.mjs'
|
|
4
4
|
import { discoverSources, extraDirsFromEnv } from './scan.mjs'
|
|
5
5
|
import { makeOpener, openPreview } from './open.mjs'
|
|
@@ -90,7 +90,11 @@ export async function refreshCatalog({ dryRun, fetchImpl, log, catalogFile = CAT
|
|
|
90
90
|
const next = { updatedAt: new Date().toISOString(), providers: {} }
|
|
91
91
|
for (const provider of PROVIDERS) {
|
|
92
92
|
try {
|
|
93
|
-
|
|
93
|
+
// 이름은 원격 README에서 오는 신뢰 경계 밖 값이다. buildItems가 설치
|
|
94
|
+
// 경로 앞에서 거르지만, 카탈로그 파일 자체에 `..` 같은 이름이 남으면
|
|
95
|
+
// 그 파일을 직접 읽는 번들 스크립트가 경로 이탈에 노출된다 — 저장
|
|
96
|
+
// 전에 거른다.
|
|
97
|
+
const entries = (await provider.fetchCatalog(fetchImpl)).filter((e) => isSafeSegment(e.name))
|
|
94
98
|
next.providers[provider.id] = { label: provider.label, entries }
|
|
95
99
|
log(t('design.provider.count', { label: provider.label, count: entries.length }))
|
|
96
100
|
} catch (err) {
|
package/lib/gitignore.mjs
CHANGED
|
@@ -16,3 +16,18 @@ export function ensureGitignoreEntries(root, entries) {
|
|
|
16
16
|
const sep = text.length === 0 || text.endsWith('\n') ? '' : eol
|
|
17
17
|
writeFileSync(file, text + sep + missing.join(eol) + eol)
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
// 정확히 일치하는 줄만 걷어낸다 — 설치가 넣은 항목을 제거가 도로 가져가는
|
|
21
|
+
// 용도라, 사용자가 손으로 적은 비슷한 패턴은 건드리지 않는다. 줄바꿈은
|
|
22
|
+
// 파일의 것을 그대로 따른다.
|
|
23
|
+
export function removeGitignoreEntries(root, entries) {
|
|
24
|
+
const file = repoPathStrict(root, '.gitignore')
|
|
25
|
+
if (!existsSync(file)) return
|
|
26
|
+
const text = readFileSync(file, 'utf8')
|
|
27
|
+
const eol = text.includes('\r\n') ? '\r\n' : '\n'
|
|
28
|
+
const drop = new Set(entries)
|
|
29
|
+
const lines = text.split(/\r?\n/)
|
|
30
|
+
const kept = lines.filter((line) => !drop.has(line))
|
|
31
|
+
if (kept.length === lines.length) return
|
|
32
|
+
writeFileSync(file, kept.join(eol))
|
|
33
|
+
}
|
package/lib/i18n/catalog/en.mjs
CHANGED
|
@@ -109,7 +109,8 @@ export default {
|
|
|
109
109
|
'Usage: npx @rch4com/agent-setup status [options]',
|
|
110
110
|
'',
|
|
111
111
|
'Shows intent (the install record), reality (a scan of the repository), and',
|
|
112
|
-
'the running tool version side by side. Changes nothing.',
|
|
112
|
+
'the running tool version side by side. Changes nothing. Asks the npm',
|
|
113
|
+
'registry for the latest version (5-second limit; offline just leaves it blank).',
|
|
113
114
|
'',
|
|
114
115
|
'Options:',
|
|
115
116
|
' --json Print machine-readable output (for CI checks).',
|
|
@@ -150,6 +151,7 @@ export default {
|
|
|
150
151
|
'log.record.write': 'install record written: {path}',
|
|
151
152
|
'error.recordUnreadable': 'Cannot read {path} ({code})',
|
|
152
153
|
'error.recordNotJson': 'Cannot read {path} — not JSON ({message})',
|
|
154
|
+
'error.tomlInvalid': 'Cannot read {path} — invalid TOML: {message}. Fix the file by hand; nothing was written.',
|
|
153
155
|
'error.jsonInvalid': 'Cannot read {path} — invalid JSON: {message}. Fix the file by hand; nothing was written.',
|
|
154
156
|
'error.recordVersion': '{path} has format version {found}. This tool uses {expected} — upgrade the tool or recreate the record.',
|
|
155
157
|
|
|
@@ -184,6 +186,7 @@ export default {
|
|
|
184
186
|
'msg.readFailed': 'read failed',
|
|
185
187
|
'msg.noRootObject': 'no root object',
|
|
186
188
|
'msg.foreignLink': 'link pointing elsewhere',
|
|
189
|
+
'log.skill.relinkDangling': 'warning: {path} is a link whose target no longer exists (the repository moved?). Recreating it.',
|
|
187
190
|
'msg.unmanagedExisting': 'existing entry not under management',
|
|
188
191
|
'msg.noManagedBlock': 'no managed block',
|
|
189
192
|
'msg.linkFailed': 'link creation failed: {message}',
|
|
@@ -345,6 +348,7 @@ export default {
|
|
|
345
348
|
'tui.noPreview': 'This item has no preview.',
|
|
346
349
|
'tui.opened': 'Opened: {target}',
|
|
347
350
|
'tui.noChanges': 'Nothing to change.',
|
|
351
|
+
'tui.actionFailed': 'The action failed: {message}',
|
|
348
352
|
'tui.submitCancelled': 'Submission cancelled.',
|
|
349
353
|
'tui.selectedGlobal': 'Machine-global item — applies to this whole computer',
|
|
350
354
|
'tui.toggledAll': 'Selected every visible item in the {tab} tab.',
|
package/lib/i18n/catalog/ko.mjs
CHANGED
|
@@ -103,7 +103,8 @@ export default {
|
|
|
103
103
|
'사용법: npx @rch4com/agent-setup status [옵션]',
|
|
104
104
|
'',
|
|
105
105
|
'설치 기록(의도) / 실제 저장소 상태(스캔) / 실행 중 도구 버전을',
|
|
106
|
-
'나란히 보여줍니다. 아무것도 바꾸지 않습니다.',
|
|
106
|
+
'나란히 보여줍니다. 아무것도 바꾸지 않습니다. 최신 버전은 npm 레지스트리에',
|
|
107
|
+
'묻습니다(5초 제한, 오프라인이면 그 자리만 비웁니다).',
|
|
107
108
|
'',
|
|
108
109
|
'옵션:',
|
|
109
110
|
' --json 기계가 읽을 형태로 출력합니다 (CI 판정용).',
|
|
@@ -144,6 +145,7 @@ export default {
|
|
|
144
145
|
'log.record.write': '설치 기록 기록: {path}',
|
|
145
146
|
'error.recordUnreadable': '{path}을 읽을 수 없습니다 ({code})',
|
|
146
147
|
'error.recordNotJson': '{path}을 읽을 수 없습니다 — JSON이 아닙니다 ({message})',
|
|
148
|
+
'error.tomlInvalid': '{path}을 읽을 수 없습니다 — TOML이 깨져 있습니다: {message}. 파일을 손으로 고치세요. 아무것도 쓰지 않았습니다.',
|
|
147
149
|
'error.jsonInvalid': '{path}을 읽을 수 없습니다 — JSON이 깨져 있습니다: {message}. 파일을 손으로 고치세요. 아무것도 쓰지 않았습니다.',
|
|
148
150
|
'error.recordVersion': '{path}의 형식 버전이 {found}입니다. 이 도구는 {expected}을 씁니다 — 도구를 올리거나 기록을 다시 만드세요.',
|
|
149
151
|
|
|
@@ -178,6 +180,7 @@ export default {
|
|
|
178
180
|
'msg.readFailed': '읽기 실패',
|
|
179
181
|
'msg.noRootObject': '루트 객체 없음',
|
|
180
182
|
'msg.foreignLink': '다른 위치를 가리키는 링크',
|
|
183
|
+
'log.skill.relinkDangling': '경고: {path} 링크의 대상이 더는 존재하지 않습니다(저장소를 옮겼나요?). 다시 만듭니다.',
|
|
181
184
|
'msg.unmanagedExisting': '관리 대상이 아닌 기존 항목',
|
|
182
185
|
'msg.noManagedBlock': '관리 블록 없음',
|
|
183
186
|
'msg.linkFailed': '링크 생성 실패: {message}',
|
|
@@ -335,6 +338,7 @@ export default {
|
|
|
335
338
|
'tui.noPreview': '이 항목은 미리보기를 제공하지 않습니다.',
|
|
336
339
|
'tui.opened': '열었습니다: {target}',
|
|
337
340
|
'tui.noChanges': '변경할 항목이 없습니다.',
|
|
341
|
+
'tui.actionFailed': '작업이 실패했습니다: {message}',
|
|
338
342
|
'tui.submitCancelled': '제출을 취소했습니다.',
|
|
339
343
|
'tui.selectedGlobal': '머신 전역 항목 — 이 컴퓨터 전체에 적용됩니다',
|
|
340
344
|
'tui.toggledAll': '{tab} 탭의 보이는 항목을 모두 선택했습니다.',
|
|
@@ -3,10 +3,11 @@ import { join } from 'node:path'
|
|
|
3
3
|
import { defineSkill } from '../catalog.mjs'
|
|
4
4
|
import { CLI_IDS } from '../clis.mjs'
|
|
5
5
|
import { repoPath, repoPathStrict } from '../context.mjs'
|
|
6
|
-
import { ensureGitignoreEntries } from '../gitignore.mjs'
|
|
6
|
+
import { ensureGitignoreEntries, removeGitignoreEntries } from '../gitignore.mjs'
|
|
7
7
|
import { LocalizedError, msg } from '../i18n/index.mjs'
|
|
8
8
|
|
|
9
9
|
const REL_DIR = '.claude/skills/gstack'
|
|
10
|
+
const IGNORE_ENTRIES = ['.claude/skills/gstack', '.agents/skills/gstack']
|
|
10
11
|
|
|
11
12
|
// 상류 setup은 --host codex·kiro·opencode(와 factory)도 지원하지만 이 항목은
|
|
12
13
|
// 인자 없이 실행하며 setup의 기본값이 claude 단독이다. README 표에 있는
|
|
@@ -58,12 +59,16 @@ export default defineSkill({
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
// 부트스트랩 저장소에서는 .claude/skills가 .agents/skills Junction이므로 두 경로 모두 무시 처리
|
|
61
|
-
if (!dryRun) ensureGitignoreEntries(root,
|
|
62
|
+
if (!dryRun) ensureGitignoreEntries(root, IGNORE_ENTRIES)
|
|
62
63
|
},
|
|
63
64
|
async uninstall({ root, dryRun, exec }) {
|
|
64
65
|
const dir = repoPathStrict(root, REL_DIR)
|
|
65
66
|
if (!existsSync(dir)) return
|
|
66
67
|
await exec('bash', [join(dir, 'bin', 'gstack-uninstall'), '--force'], { cwd: dir }) // 실패해도 디렉터리 삭제로 폴백
|
|
67
|
-
if (
|
|
68
|
+
if (dryRun) return
|
|
69
|
+
if (existsSync(dir)) rmSync(dir, { recursive: true, force: true })
|
|
70
|
+
// 설치가 넣은 두 줄을 도로 가져간다 — 남겨 두면 제거한 뒤에도 우리가 만든
|
|
71
|
+
// 흔적이 .gitignore에 계속 남는다.
|
|
72
|
+
removeGitignoreEntries(root, IGNORE_ENTRIES)
|
|
68
73
|
},
|
|
69
74
|
})
|
package/lib/tomlfile.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
2
2
|
import { dirname } from 'node:path'
|
|
3
3
|
import { parse } from 'smol-toml'
|
|
4
|
+
import { LocalizedError } from './i18n/index.mjs'
|
|
4
5
|
|
|
5
6
|
function readText(file) {
|
|
6
7
|
return existsSync(file) ? readFileSync(file, 'utf8') : ''
|
|
@@ -10,19 +11,27 @@ function escapeRegExp(text) {
|
|
|
10
11
|
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
// 깨진 TOML은 읽기·쓰기 모두 거부한다. 예전에는 파싱 실패를 "섹션 없음"으로
|
|
15
|
+
// 돌려줘, 닫는 대괄호가 빠진 파일 끝에 같은 섹션이 하나 더 붙고 사용자는
|
|
16
|
+
// 파일이 깨진 것을 끝까지 몰랐다 — jsonfile.mjs의 JSON 규칙과 같다.
|
|
17
|
+
function parseStrict(text, file) {
|
|
18
|
+
try {
|
|
19
|
+
return parse(text)
|
|
20
|
+
} catch (err) {
|
|
21
|
+
throw new LocalizedError('error.tomlInvalid', { path: file, message: err.message.split('\n')[0] })
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
export function hasSection(file, name) {
|
|
14
26
|
const text = readText(file)
|
|
15
27
|
if (!text.trim()) return false
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return data.mcp_servers != null && Object.hasOwn(data.mcp_servers, name)
|
|
19
|
-
} catch {
|
|
20
|
-
return false
|
|
21
|
-
}
|
|
28
|
+
const data = parseStrict(text, file)
|
|
29
|
+
return data.mcp_servers != null && Object.hasOwn(data.mcp_servers, name)
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
export function appendSection(file, name, lines) {
|
|
25
33
|
let text = readText(file)
|
|
34
|
+
if (text.trim()) parseStrict(text, file)
|
|
26
35
|
if (text.length > 0 && !text.endsWith('\n')) text += '\n'
|
|
27
36
|
const separator = text.length > 0 ? '\n' : ''
|
|
28
37
|
text += `${separator}[mcp_servers.${name}]\n${lines.join('\n')}\n`
|
|
@@ -33,6 +42,7 @@ export function appendSection(file, name, lines) {
|
|
|
33
42
|
export function removeSection(file, name) {
|
|
34
43
|
const text = readText(file)
|
|
35
44
|
if (!text) return
|
|
45
|
+
parseStrict(text, file)
|
|
36
46
|
const lines = text.split('\n')
|
|
37
47
|
const headerRe = new RegExp(`^\\s*\\[mcp_servers\\.${escapeRegExp(name)}(\\.|\\])`)
|
|
38
48
|
const anyHeaderRe = /^\s*\[/
|
package/lib/tui/rows.mjs
CHANGED
|
@@ -38,9 +38,11 @@ function groupRank(group) {
|
|
|
38
38
|
return i === -1 ? GROUP_ORDER.length : i
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// 코드 포인트 단위로 자른다 — slice는 코드 유닛을 세어 이모지 같은 서로게이트
|
|
42
|
+
// 쌍 한가운데를 자를 수 있다. 이 파일의 다른 자르기(width.cut)와 같은 규칙이다.
|
|
41
43
|
function short(text, n = 60) {
|
|
42
|
-
const
|
|
43
|
-
return
|
|
44
|
+
const chars = [...(text ?? '').replace(/\s+/g, ' ').trim()]
|
|
45
|
+
return chars.length > n ? `${chars.slice(0, n - 1).join('')}…` : chars.join('')
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
// 검색어에 섹션의 두 로케일 라벨을 모두 넣는다 — 한국어 화면에서도 영어 탭
|
package/lib/tui/run.mjs
CHANGED
|
@@ -387,7 +387,15 @@ export async function runTui(root, opts = {}) {
|
|
|
387
387
|
if (row?.id === 'action.language') { status = await cycleLanguage(); continue }
|
|
388
388
|
if (row?.kind === 'action') {
|
|
389
389
|
await suspend(async () => {
|
|
390
|
-
|
|
390
|
+
// 작업 하나의 예외가 화면 전체를 끌고 내려가면 안 된다 — 깨진
|
|
391
|
+
// 기록 파일(부트스트랩), 읽기 전용 설치 위치(카탈로그 동기화)가
|
|
392
|
+
// 던지면 TUI가 통째로 죽고 고르던 선택이 사라졌다. 적용(runApply)이
|
|
393
|
+
// 항목별로 격리하는 것과 같은 규칙이다.
|
|
394
|
+
try {
|
|
395
|
+
await row.run({ root, dryRun, skillMode, fetchImpl, catalogFile, log, confirm, t })
|
|
396
|
+
} catch (err) {
|
|
397
|
+
log(`\n${t('tui.actionFailed', { message: err.key ? t(err.key, err.params) : err.message })}`)
|
|
398
|
+
}
|
|
391
399
|
await pause()
|
|
392
400
|
})
|
|
393
401
|
await recollect()
|
package/lib/width.mjs
CHANGED
|
@@ -11,7 +11,23 @@
|
|
|
11
11
|
|
|
12
12
|
// 동아시아 글자는 터미널에서 두 칸을 차지한다. 전체 wcwidth 표 대신
|
|
13
13
|
// 실제로 쓰이는 구간(한글·한자·가나·전각)만 넓게 센다.
|
|
14
|
+
//
|
|
15
|
+
// 결합 문자(악센트·변형 선택자·폭 없는 문자)는 0칸이다 — 1로 세면 `é`가
|
|
16
|
+
// 두 칸으로 읽혀 열이 왼쪽으로 밀린다. 이모지는 2칸이다 — DESIGN.md의
|
|
17
|
+
// 라벨·설명에 실려 오는데 1로 세면 그 행만 오른쪽으로 밀린다. U+2600 구간의
|
|
18
|
+
// 기호(☀ 등)는 터미널마다 폭이 갈리는 모호 폭이라 넣지 않는다.
|
|
14
19
|
function charWidth(cp) {
|
|
20
|
+
if ((cp >= 0x0300 && cp <= 0x036f) // 결합 분음 부호
|
|
21
|
+
|| (cp >= 0x1ab0 && cp <= 0x1aff) || (cp >= 0x1dc0 && cp <= 0x1dff)
|
|
22
|
+
|| (cp >= 0x20d0 && cp <= 0x20ff) // 기호용 결합 부호
|
|
23
|
+
|| (cp >= 0xfe00 && cp <= 0xfe0f) // 변형 선택자
|
|
24
|
+
|| (cp >= 0xfe20 && cp <= 0xfe2f)
|
|
25
|
+
|| (cp >= 0x200b && cp <= 0x200f) // 폭 없는 공백·결합자·방향 표식
|
|
26
|
+
|| cp === 0xfeff) return 0
|
|
27
|
+
if ((cp >= 0x1f300 && cp <= 0x1f64f) // 이모지 그림·표정
|
|
28
|
+
|| (cp >= 0x1f680 && cp <= 0x1f6ff) // 교통·지도
|
|
29
|
+
|| (cp >= 0x1f900 && cp <= 0x1f9ff) // 보충 이모지
|
|
30
|
+
|| (cp >= 0x1fa70 && cp <= 0x1faff)) return 2
|
|
15
31
|
return (cp >= 0x1100 && cp <= 0x115f) // 한글 자모
|
|
16
32
|
|| (cp >= 0x2e80 && cp <= 0xa4cf) // 한자 부수·가나·한자
|
|
17
33
|
|| (cp >= 0xac00 && cp <= 0xd7a3) // 한글 음절
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rch4com/agent-setup",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.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",
|