deel-local-cli 1.20.12 → 2.0.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.ko.md +96 -77
- package/README.md +119 -80
- package/bin/deel.js +398 -32
- package/package.json +2 -1
- package/src/acp/jsonrpc.js +87 -7
- package/src/acp/map.js +62 -13
- package/src/acp/serve.js +338 -30
- package/src/agent/agents.js +110 -12
- package/src/agent/askcheck.js +50 -6
- package/src/agent/asks.js +59 -5
- package/src/agent/budget.js +9 -2
- package/src/agent/card.js +8 -2
- package/src/agent/commit.js +168 -16
- package/src/agent/compact.js +93 -21
- package/src/agent/{/354/213/240/353/242/260/353/217/204.js → confidence.js} +1 -1
- package/src/agent/effort.js +85 -15
- package/src/agent/evidence.js +63 -7
- package/src/agent/evolve.js +132 -24
- package/src/agent/grade.js +12 -3
- package/src/agent/loop.js +408 -81
- package/src/agent/memory.js +176 -29
- package/src/agent/mention.js +22 -5
- package/src/agent/models.js +31 -7
- package/src/agent/modes.js +25 -10
- package/src/agent/outschema.js +381 -56
- package/src/agent/pins.js +33 -2
- package/src/agent/project.js +73 -8
- package/src/agent/recall.js +40 -9
- package/src/agent/{/354/204/261/355/225/234/352/270/260/353/241/235.js → recordshape.js} +19 -8
- package/src/agent/review.js +143 -13
- package/src/agent/route.js +506 -32
- package/src/agent/salvage.js +2 -1
- package/src/agent/session.js +457 -31
- package/src/agent/store.js +232 -22
- package/src/agent/threads.js +52 -3
- package/src/backend/adapter.js +508 -85
- package/src/backend/azure.js +12 -1
- package/src/backend/cachemark.js +31 -5
- package/src/backend/clientcert.js +23 -1
- package/src/backend/ctxsize.js +56 -9
- package/src/backend/detect.js +252 -19
- package/src/backend/http.js +383 -42
- package/src/backend/learn.js +101 -11
- package/src/backend/mcp.js +333 -24
- package/src/backend/price.js +61 -13
- package/src/backend/probe.js +188 -36
- package/src/backend/proxy.js +108 -14
- package/src/backend/quota.js +226 -30
- package/src/backend/retry.js +46 -3
- package/src/backend/scan.js +47 -10
- package/src/backend/scanui.js +57 -7
- package/src/backend/toolfit.js +204 -22
- package/src/backend/vision.js +14 -1
- package/src/backend/wire.js +58 -10
- package/src/cmdnames.js +16 -0
- package/src/commands/common.js +70 -0
- package/src/commands/extend.js +300 -0
- package/src/commands/model.js +852 -0
- package/src/commands/view.js +328 -0
- package/src/commands/work.js +823 -0
- package/src/commands.js +193 -2082
- package/src/completion.js +45 -3
- package/src/config.js +452 -39
- package/src/configexplain.js +125 -14
- package/src/doctor.js +164 -26
- package/src/i18n/en.js +22 -0
- package/src/i18n/index.js +31 -1
- package/src/i18n/ja.js +25 -1
- package/src/i18n/ko.js +25 -2
- package/src/i18n/zh.js +25 -1
- package/src/lsp/client.js +111 -9
- package/src/lsp/servers.js +86 -22
- package/src/oneshot.js +325 -65
- package/src/pack/sbom.js +28 -5
- package/src/pack/selfpack.js +54 -5
- package/src/pack/sheet.en.js +24 -3
- package/src/pack/tar.js +45 -8
- package/src/pack/zip.js +103 -8
- package/src/plugins/manage.js +165 -14
- package/src/preview/serve.js +239 -23
- package/src/providers/bedrock.js +30 -5
- package/src/providers/gemini.js +8 -0
- package/src/providers/index.js +17 -1
- package/src/repl.js +254 -61
- package/src/report.js +7 -2
- package/src/reset.js +151 -25
- package/src/safety/audit.js +108 -8
- package/src/safety/authcmd.js +75 -5
- package/src/safety/guard.js +507 -15
- package/src/safety/hooks.js +172 -20
- package/src/safety/keystore.js +178 -18
- package/src/safety/network.js +209 -6
- package/src/safety/policy.js +377 -36
- package/src/safety/runmode.js +9 -2
- package/src/safety/secrets.js +243 -17
- package/src/safety/shellenv.js +64 -3
- package/src/safety/trust.js +298 -32
- package/src/safety/undo.js +409 -17
- package/src/setup.js +175 -17
- package/src/skills/discover.js +122 -19
- package/src/stats.js +57 -16
- package/src/tools/{/355/231/225/354/235/270/353/262/225.js → checkmethods.js} +26 -1
- package/src/tools/clipboard.js +53 -20
- package/src/tools/convert.js +118 -37
- package/src/tools/desc.en.js +48 -5
- package/src/tools/doc2md.js +11 -1
- package/src/tools/docs.js +94 -18
- package/src/tools/edit-match.js +212 -13
- package/src/tools/encoding.js +525 -28
- package/src/tools/excel-com.js +63 -15
- package/src/tools/excel.js +12 -7
- package/src/tools/fastgrep.js +444 -36
- package/src/tools/fig.js +40 -9
- package/src/tools/fsutil.js +235 -21
- package/src/tools/hwpxwrite.js +80 -19
- package/src/tools/ignore.js +55 -7
- package/src/tools/index.js +1029 -145
- package/src/tools/jobs.js +325 -50
- package/src/tools/kiwi.js +50 -5
- package/src/tools/lsp.js +164 -32
- package/src/tools/outline.js +52 -12
- package/src/tools/pdf.js +366 -56
- package/src/tools/shell.js +12 -4
- package/src/tools/spawn.js +203 -18
- package/src/tools/todo.js +73 -4
- package/src/tools/verify.js +312 -43
- package/src/tools/webfetch.js +184 -25
- package/src/tools/xlsx.js +232 -31
- package/src/ui/ansi.js +209 -15
- package/src/ui/banner.js +2 -1
- package/src/ui/complete.js +46 -4
- package/src/ui/diff.js +4 -2
- package/src/ui/export.js +91 -17
- package/src/ui/inputbox.js +58 -18
- package/src/ui/intro.js +9 -2
- package/src/ui/level.js +32 -7
- package/src/ui/md.js +101 -15
- package/src/ui/motion.js +5 -2
- package/src/ui/notify.js +7 -1
- package/src/ui/office.js +37 -5
- package/src/ui/pastechip.js +3 -2
- package/src/ui/pick.js +85 -12
- package/src/ui/prompt.js +176 -13
- package/src/ui/screen.js +2 -1
- package/src/ui/spinner.js +6 -1
- package/src/ui/status.js +80 -12
- package/src/ui/wrap.js +23 -37
- /package/src/agent/{/353/213/250/352/263/204.js" → phase.js} +0 -0
- /package/src/skills/builtin/{/352/271/212/354/235/264/354/236/210/352/262/214-/353/247/214/353/223/244/352/270/260/SKILL.md" → build-deep/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/250/352/267/274/354/260/250/352/267/274-/353/224/224/353/262/204/352/271/205/SKILL.md" → debug-step-by-step/SKILL.md} +0 -0
- /package/src/skills/builtin/{/353/201/235/352/271/214/354/247/200-/355/225/230/352/270/260/SKILL.md" → finish-all/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/212/244/354/212/244/353/241/234-/352/262/200/355/206/240/SKILL.md" → self-review/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/275/224/353/223/234-/354/244/204/354/235/264/352/270/260/SKILL.md" → simplify-code/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/224/353/237/254/353/263/264/352/270/260/SKILL.md" → spike/SKILL.md} +0 -0
- /package/src/skills/builtin/{/352/262/200/354/202/254-/353/250/274/354/240/200/SKILL.md" → test-first/SKILL.md} +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// 슬래시 명령 — 밖에서 들여오거나 밖으로 띄우는 것: 플러그인 · 스킬 목록 · 미리보기 서버.
|
|
2
|
+
// commands.js 에서 역할별로 나눠 옮겼다(2.0.0). 명령을 가르는 자리(handle)는 그대로 commands.js 에 있다.
|
|
3
|
+
import { existsSync, statSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { c, say, rule, pad, mark, clip } from '../ui/ansi.js';
|
|
6
|
+
import { 말 } from '../i18n/index.js';
|
|
7
|
+
import { discover } from '../skills/discover.js';
|
|
8
|
+
import { install, list, remove, pack } from '../plugins/manage.js';
|
|
9
|
+
import { spin } from '../ui/spinner.js';
|
|
10
|
+
import { 띄우기, 브라우저로 } from '../preview/serve.js';
|
|
11
|
+
|
|
12
|
+
export async function doPlugin(session, arg) {
|
|
13
|
+
const [sub = '', ...rest] = arg.trim().split(/\s+/);
|
|
14
|
+
const param = rest.join(' ').trim();
|
|
15
|
+
|
|
16
|
+
// 설치·삭제 뒤에는 다시 훑어 이번 대화에 바로 반영한다.
|
|
17
|
+
const rescan = () => {
|
|
18
|
+
const f = discover(session.root);
|
|
19
|
+
session.skills = f.skills;
|
|
20
|
+
session.commands = f.commands;
|
|
21
|
+
session.plugins = f.plugins;
|
|
22
|
+
return f;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (sub === 'install' || sub === 'add') {
|
|
26
|
+
if (!param) {
|
|
27
|
+
say(` ${c.gray('예')} /plugin install affaan-m/ECC`);
|
|
28
|
+
say(` ${c.gray(' ')} /plugin install owner/repo#가지이름`);
|
|
29
|
+
say('');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
say('');
|
|
33
|
+
const s = spin(`${param} 받는 중...`);
|
|
34
|
+
const r = await install(param, { onStep: (m) => {} });
|
|
35
|
+
if (r.error) {
|
|
36
|
+
s.stop(` ${mark.no} ${c.red(r.error.split('\n')[0])}`);
|
|
37
|
+
for (const line of r.error.split('\n').slice(1)) say(` ${c.gray(line.trim())}`);
|
|
38
|
+
say(` ${c.gray('오프라인이면 zip 을 ~/.deel/plugins/ 에 직접 풀면 됩니다.')}`);
|
|
39
|
+
say('');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
s.stop(` ${mark.ok} ${c.bold(r.name)}${r.version ? ' ' + c.gray(r.version) : ''} ${c.gray(`(${r.license ?? '라이선스 미상'})`)}`);
|
|
43
|
+
say(` ${c.gray('스킬')} ${r.skills}개 ${c.gray('명령')} ${r.commands}개 ${c.gray(`· ${r.how} 로 받음`)}`);
|
|
44
|
+
if (r.hooks) say(` ${mark.warn} ${c.yellow(`실행 스크립트 ${r.hooks}개는 쓰지 않습니다`)} ${c.gray('(hook 미지원 · 반입 심사 대상)')}`);
|
|
45
|
+
/*
|
|
46
|
+
* 복사하다 빠진 것을 **여기서 말한다.**
|
|
47
|
+
*
|
|
48
|
+
* copyDir 은 심볼릭 링크를 말없이 건너뛴다. 그 목록을 install() 이
|
|
49
|
+
* 이제 올려 주는데, 안 적으면 「스킬 3개」 라고 해 놓고 2개만 깔린
|
|
50
|
+
* 상태가 그대로 초록으로 보인다 (plugins/manage.js 머리말).
|
|
51
|
+
*/
|
|
52
|
+
if (r.건너뜀?.length) {
|
|
53
|
+
say(` ${mark.warn} ${c.yellow(`${r.건너뜀.length}개는 못 복사했습니다`)} ${c.gray('(링크·특수 파일은 안 옮깁니다)')}`);
|
|
54
|
+
for (const 것 of r.건너뜀.slice(0, 5)) say(` ${c.gray('·')} ${c.gray(clip(것, 66))}`);
|
|
55
|
+
if (r.건너뜀.length > 5) say(` ${c.gray(`… 그 밖에 ${r.건너뜀.length - 5}개`)}`);
|
|
56
|
+
}
|
|
57
|
+
if (r.덜깔림) {
|
|
58
|
+
say(` ${mark.warn} ${c.yellow('담으려던 것과 깔린 것이 다릅니다')}`
|
|
59
|
+
+ ` ${c.gray(`— 스킬 ${r.덜깔림.담을것.skills}→${r.덜깔림.깔린것.skills} · 명령 ${r.덜깔림.담을것.commands}→${r.덜깔림.깔린것.commands}`)}`);
|
|
60
|
+
}
|
|
61
|
+
const f = rescan();
|
|
62
|
+
say(` ${c.gray(`이제 스킬 ${f.skills.length}개 · 명령 ${f.commands.length}개`)}`);
|
|
63
|
+
say('');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (sub === 'remove' || sub === 'rm' || sub === 'uninstall') {
|
|
68
|
+
if (!param) { say(` ${c.gray('예')} /plugin remove ecc`); say(''); return; }
|
|
69
|
+
const r = remove(param);
|
|
70
|
+
if (r.error) { say(` ${mark.no} ${c.red(r.error)}`); say(''); return; }
|
|
71
|
+
const f = rescan();
|
|
72
|
+
say(` ${mark.ok} ${c.bold(r.removed)} 지웠습니다. ${c.gray(`이제 스킬 ${f.skills.length}개`)}`);
|
|
73
|
+
say('');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (sub === 'pack') {
|
|
78
|
+
const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
79
|
+
const out = param || join(session.root, `deel-plugins-${stamp}.zip`);
|
|
80
|
+
const r = pack(out, { only: null });
|
|
81
|
+
if (r.error) { say(` ${mark.no} ${c.red(r.error)}`); say(''); return; }
|
|
82
|
+
say('');
|
|
83
|
+
say(` ${mark.ok} ${c.cyan(r.out)}`);
|
|
84
|
+
say(` ${c.gray('플러그인')} ${r.plugins.length}개 ${c.gray('· 파일')} ${r.files}개 ${c.gray('·')} ${(r.bytes / 1024).toFixed(0)}KB`);
|
|
85
|
+
if (r.skipped) say(` ${c.gray(`실행 스크립트 ${r.skipped}개는 뺐습니다`)}`);
|
|
86
|
+
say('');
|
|
87
|
+
rule('담긴 것', 74);
|
|
88
|
+
for (const p of r.plugins) {
|
|
89
|
+
say(` ${c.cyan(pad(p.name, 22))} ${c.gray(pad(p.version || '-', 9))} ${c.gray(pad(p.license || '미상', 14))} ${c.gray(`스킬 ${p.skills} · 명령 ${p.commands}`)}`);
|
|
90
|
+
}
|
|
91
|
+
say('');
|
|
92
|
+
say(` ${c.gray('오프라인 기기의')} ~/.deel/plugins/ ${c.gray('에 풀면 바로 인식됩니다. 설치 명령은 필요 없습니다.')}`);
|
|
93
|
+
say(` ${c.gray('묶음 안에 라이선스 표가 담긴 사용안내.txt 가 함께 들어 있습니다.')}`);
|
|
94
|
+
say('');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 인자 없으면 목록
|
|
99
|
+
const items = list();
|
|
100
|
+
if (!items.length) {
|
|
101
|
+
say('');
|
|
102
|
+
say(` ${c.gray('설치된 플러그인이 없습니다.')}`);
|
|
103
|
+
say(` ${c.gray('받기')} /plugin install owner/repo`);
|
|
104
|
+
say(` ${c.gray('직접')} ~/.deel/plugins/ 에 폴더를 풀어 넣어도 됩니다`);
|
|
105
|
+
say('');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
say('');
|
|
109
|
+
rule('플러그인', 74);
|
|
110
|
+
for (const p of items) {
|
|
111
|
+
say(` ${c.cyan(pad(p.name, 22))} ${c.gray(pad(p.version || '-', 9))} ${c.gray(pad(p.license || '미상', 14))} ${c.gray(`스킬 ${p.skills} · 명령 ${p.commands}`)}`);
|
|
112
|
+
say(` ${c.gray(p.from)}`);
|
|
113
|
+
}
|
|
114
|
+
say('');
|
|
115
|
+
say(` ${c.gray('/plugin install <owner/repo> /plugin remove <이름> /plugin pack [파일]')}`);
|
|
116
|
+
say('');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* 만든 웹을 그 자리에서 띄운다.
|
|
121
|
+
*
|
|
122
|
+
* 여기 있는 것은 화면과 말뿐이고, 서버는 preview/serve.js 가 한다.
|
|
123
|
+
* 한 번에 하나만 띄운다 — 여러 개를 띄워 놓으면 어느 주소가 무엇인지
|
|
124
|
+
* 아무도 못 외우고, 끌 때도 뭘 껐는지 모른다.
|
|
125
|
+
*/
|
|
126
|
+
let 미리보기중 = null;
|
|
127
|
+
|
|
128
|
+
/** 켜져 있으면 끈다. 프로그램을 끝낼 때도 이걸 부른다. */
|
|
129
|
+
export async function 미리보기끄기() {
|
|
130
|
+
if (!미리보기중) return null;
|
|
131
|
+
const 끈것 = 미리보기중;
|
|
132
|
+
미리보기중 = null;
|
|
133
|
+
try { await 끈것.서버.닫기(); } catch { /* 이미 닫혔다 */ }
|
|
134
|
+
return 끈것;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 지금 띄워 둔 것. 상태줄·검사에서 본다. */
|
|
138
|
+
export async function 미리보기(session, ctx, arg) {
|
|
139
|
+
const 말 = String(arg ?? '').trim();
|
|
140
|
+
|
|
141
|
+
if (/^(off|끄기|중지|stop)$/i.test(말)) {
|
|
142
|
+
const 끈것 = await 미리보기끄기();
|
|
143
|
+
say('');
|
|
144
|
+
say(끈것 ? ` ${mark.ok} 미리보기를 껐습니다. ${c.gray(끈것.서버.url)}`
|
|
145
|
+
: ` ${c.gray('띄워 둔 것이 없습니다.')}`);
|
|
146
|
+
say('');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 이미 떠 있는데 또 치면, 주소를 다시 알려 주고 브라우저만 연다.
|
|
151
|
+
// 여기서 조용히 하나 더 띄우면 포트가 둘이 되고 어느 쪽이 진짜인지 모르게 된다.
|
|
152
|
+
if (미리보기중 && !말) {
|
|
153
|
+
say('');
|
|
154
|
+
/*
|
|
155
|
+
* 처음에 알려 준 주소를 **그대로** 다시 준다 (8회차 그밖 명령4).
|
|
156
|
+
*
|
|
157
|
+
* 여기는 `서버.url` 만 썼다. 파일 하나를 주고 띄우면 아래에서 주소 끝에 그 파일이
|
|
158
|
+
* 붙는데(첫주소), 다시 부를 때는 그게 빠진 폴더 주소가 나오고 브라우저도 그리로 열렸다.
|
|
159
|
+
* 방금 보던 화면을 사람이 다시 못 찾는다 — 「이미 띄워 뒀습니다」 라고 말해 놓고
|
|
160
|
+
* 다른 것을 여는 셈이다. 그래서 띄울 때 정한 주소를 통째로 들고 있는다.
|
|
161
|
+
*/
|
|
162
|
+
say(` ${c.hgreen('▶')} 이미 띄워 뒀습니다 ${c.cyan(미리보기중.주소)} ${c.gray(미리보기중.보인이름)}`);
|
|
163
|
+
브라우저로(미리보기중.주소);
|
|
164
|
+
say(` ${c.gray('끄려면')} ${c.cyan('/preview off')}`);
|
|
165
|
+
say('');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// 다른 폴더를 주면 앞엣것은 끄고 새로 띄운다.
|
|
169
|
+
if (미리보기중) await 미리보기끄기();
|
|
170
|
+
|
|
171
|
+
let 뿌리;
|
|
172
|
+
try {
|
|
173
|
+
뿌리 = ctx.scope.resolve(말 || '.');
|
|
174
|
+
} catch (err) {
|
|
175
|
+
say('');
|
|
176
|
+
say(` ${c.red('띄울 수 없습니다')} ${c.gray(err.message)}`);
|
|
177
|
+
say('');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!existsSync(뿌리)) {
|
|
182
|
+
say('');
|
|
183
|
+
say(` ${c.red('그런 폴더가 없습니다')} ${c.gray(ctx.scope.show(뿌리))}`);
|
|
184
|
+
say('');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// 파일 하나를 줬으면 그 파일이 든 폴더를 띄우고, 브라우저는 그 파일로 연다.
|
|
188
|
+
let 첫주소 = '';
|
|
189
|
+
if (!statSync(뿌리).isDirectory()) {
|
|
190
|
+
첫주소 = encodeURIComponent(뿌리.split(/[\\/]/).pop());
|
|
191
|
+
뿌리 = join(뿌리, '..');
|
|
192
|
+
뿌리 = ctx.scope.resolve(뿌리);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let 서버;
|
|
196
|
+
try {
|
|
197
|
+
서버 = await 띄우기({ 뿌리, scope: ctx.scope });
|
|
198
|
+
} catch (err) {
|
|
199
|
+
say('');
|
|
200
|
+
say(` ${c.red('못 띄웠습니다')} ${c.gray(err.message)}`);
|
|
201
|
+
say('');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const url = 서버.url + 첫주소;
|
|
205
|
+
// 주소까지 같이 들고 있는다 — 다시 불렀을 때 같은 것을 주려면 이 값이 있어야 한다(위 갈래).
|
|
206
|
+
미리보기중 = { 서버, 보인이름: ctx.scope.show(뿌리), 주소: url };
|
|
207
|
+
|
|
208
|
+
say('');
|
|
209
|
+
say(` ${c.hgreen('▶')} ${c.bold('띄웠습니다')} ${c.cyan(url)}`);
|
|
210
|
+
say(` ${c.gray('보여 주는 것')} ${c.white(미리보기중.보인이름)}`);
|
|
211
|
+
say(서버.되살아나나
|
|
212
|
+
? ` ${c.gray('파일을 고치면 화면이 저절로 새로 뜹니다.')}`
|
|
213
|
+
: ` ${c.yellow('⚠')} ${c.gray('이 자리에서는 파일 변화를 못 봅니다 — 브라우저를 손으로 새로 고치세요.')}`);
|
|
214
|
+
// 어디까지 열리는지 반드시 말한다. '서버를 띄웠다' 는 말은 사람마다 다르게 읽힌다.
|
|
215
|
+
say(` ${c.gray('이 컴퓨터에서만 열립니다(127.0.0.1). 다른 PC 에서는 안 보입니다.')}`);
|
|
216
|
+
say(` ${c.gray('끄려면')} ${c.cyan('/preview off')}${c.gray(' · deel 을 끝내면 같이 꺼집니다.')}`);
|
|
217
|
+
브라우저로(url);
|
|
218
|
+
say('');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function showSkills(session, arg) {
|
|
222
|
+
const all = session.skills ?? [];
|
|
223
|
+
if (!all.length) {
|
|
224
|
+
say('');
|
|
225
|
+
say(` ${c.gray('이 PC 에서 찾은 스킬이 없습니다.')}`);
|
|
226
|
+
say(` ${c.gray('찾는 자리: ./.deel/skills ./.claude/skills ~/.claude/skills ~/.claude/plugins')}`);
|
|
227
|
+
say('');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 스킬은 남의 폴더·플러그인에서 온다. 앞머리(frontmatter)가 빠진 파일이 섞이면
|
|
232
|
+
// 이름이나 설명이 없다. 그걸 그대로 만지면 목록 하나 보려다 대화가 끝난다.
|
|
233
|
+
const 낮게 = (v) => String(v ?? '').toLowerCase();
|
|
234
|
+
|
|
235
|
+
const q = arg.trim();
|
|
236
|
+
if (q === 'all') {
|
|
237
|
+
all.forEach((s) => { s.enabled = true; });
|
|
238
|
+
session.maxSkillsListed = Math.min(all.length, 200);
|
|
239
|
+
say(` ${mark.ok} 전부 올립니다 (${session.listedSkills().length}개). ${c.yellow('컨텍스트를 많이 차지합니다 — /context 로 확인하세요.')}`);
|
|
240
|
+
say('');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (q === 'off') {
|
|
244
|
+
all.forEach((s) => { s.enabled = false; });
|
|
245
|
+
say(` ${mark.ok} 스킬을 모두 내렸습니다.`);
|
|
246
|
+
say('');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (q.startsWith('on ')) {
|
|
250
|
+
const term = q.slice(3).trim().toLowerCase();
|
|
251
|
+
const 걸린것 = all.filter((s) => 낮게(s.name).includes(term) || 낮게(s.description).includes(term));
|
|
252
|
+
/*
|
|
253
|
+
* 한 개도 안 걸리면 **아무것도 안 건드린다.**
|
|
254
|
+
*
|
|
255
|
+
* 앞서는 걸림 여부를 그대로 enabled 에 넣었다. 그래서 오타 하나면 전부
|
|
256
|
+
* false 가 되고, 화면에는 `✓ "리뷰" 에 걸리는 0개만 올립니다` 가 떴다.
|
|
257
|
+
* 올리려고 친 명령이 가지고 있던 것까지 다 내려 버리는데, 표시는 ✓ 다.
|
|
258
|
+
* 그다음 모델이 스킬을 못 쓰는 것을 보고 사람은 스킬이 깨진 줄 안다.
|
|
259
|
+
*/
|
|
260
|
+
if (!걸린것.length) {
|
|
261
|
+
say(` ${mark.no} "${term}" 에 걸리는 스킬이 없습니다 ${c.gray('— 올라간 것은 그대로 둡니다.')}`);
|
|
262
|
+
say(` ${c.gray('무엇이 있는지 보려면')} ${c.cyan('/skills')}`);
|
|
263
|
+
say('');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
for (const s of all) s.enabled = 걸린것.includes(s);
|
|
267
|
+
say(` ${mark.ok} "${term}" 에 걸리는 ${걸린것.length}개만 올립니다.`);
|
|
268
|
+
say('');
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const hits = q
|
|
273
|
+
? all.filter((s) => 낮게(s.name).includes(q.toLowerCase()) || 낮게(s.description).includes(q.toLowerCase()))
|
|
274
|
+
: session.listedSkills();
|
|
275
|
+
|
|
276
|
+
say('');
|
|
277
|
+
rule(q ? `스킬 검색: ${q}` : '지금 올라간 스킬', 74);
|
|
278
|
+
for (const s of hits.slice(0, 30)) {
|
|
279
|
+
const tag = s.enabled ? c.green('●') : c.gray('○');
|
|
280
|
+
// 설명이 없을 수 있다. 스킬은 남의 폴더·플러그인에서 오는 것이라
|
|
281
|
+
// 앞머리(frontmatter)가 빠진 파일이 섞인다. 여기서 터지면 목록 하나 보려다
|
|
282
|
+
// 대화가 통째로 끝난다 — 화면 그리기는 무슨 일이 있어도 안 죽어야 한다.
|
|
283
|
+
say(` ${tag} ${c.cyan(pad(s.name, 32))} ${c.gray(String(s.description ?? '').slice(0, 60))}`);
|
|
284
|
+
}
|
|
285
|
+
if (hits.length > 30) say(` ${c.gray(`… 그 밖에 ${hits.length - 30}개`)}`);
|
|
286
|
+
say('');
|
|
287
|
+
|
|
288
|
+
const bySource = { project: 0, user: 0, plugin: 0, builtin: 0 };
|
|
289
|
+
for (const s of all) bySource[s.source] = (bySource[s.source] ?? 0) + 1;
|
|
290
|
+
say(` ${c.gray('전체')} ${all.length}개 ${c.gray('(프로젝트')} ${bySource.project} ${c.gray('· 사용자')} ${bySource.user} ${c.gray('· 플러그인')} ${bySource.plugin} ${c.gray('· 품고 다니는 것')} ${bySource.builtin}${c.gray(')')}`);
|
|
291
|
+
say(` ${c.gray('프롬프트에 올라간 것')} ${session.listedSkills().length}개 ${c.gray(`(상한 ${session.maxSkillsListed})`)}`);
|
|
292
|
+
if ((session.plugins ?? []).length) {
|
|
293
|
+
say(` ${c.gray('플러그인')} ${session.plugins.filter((p) => p.skills > 0).map((p) => p.name).slice(0, 8).join(', ')}`);
|
|
294
|
+
}
|
|
295
|
+
say('');
|
|
296
|
+
say(` ${c.gray('/skills <검색어> 찾아보기')}`);
|
|
297
|
+
say(` ${c.gray('/skills on <검색어> 걸리는 것만 올리기')}`);
|
|
298
|
+
say(` ${c.gray('/skills all | off 전부 올리기 | 내리기')}`);
|
|
299
|
+
say('');
|
|
300
|
+
}
|