deel-local-cli 1.6.3 → 1.8.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 (53) hide show
  1. package/README.ko.md +1182 -0
  2. package/README.md +850 -636
  3. package/bin/deel.js +44 -3
  4. package/package.json +3 -3
  5. package/src/acp/map.js +1 -1
  6. package/src/acp/serve.js +25 -2
  7. package/src/agent/loop.js +41 -9
  8. package/src/agent/session.js +15 -9
  9. package/src/backend/adapter.js +277 -4
  10. package/src/backend/ctxsize.js +16 -2
  11. package/src/backend/detect.js +104 -6
  12. package/src/backend/price.js +197 -0
  13. package/src/backend/retry.js +3 -0
  14. package/src/backend/scanui.js +2 -1
  15. package/src/backend/vision.js +26 -4
  16. package/src/commands.js +141 -38
  17. package/src/completion.js +21 -2
  18. package/src/i18n/en.js +160 -1
  19. package/src/i18n/index.js +22 -0
  20. package/src/i18n/ja.js +152 -1
  21. package/src/i18n/ko.js +173 -1
  22. package/src/i18n/zh.js +152 -1
  23. package/src/oneshot.js +39 -6
  24. package/src/pack/selfpack.js +1 -1
  25. package/src/providers/anthropic.js +48 -0
  26. package/src/providers/bedrock.js +102 -0
  27. package/src/providers/custom.js +65 -0
  28. package/src/providers/gemini.js +64 -0
  29. package/src/providers/index.js +121 -0
  30. package/src/providers/openai.js +68 -0
  31. package/src/repl.js +198 -33
  32. package/src/report.js +3 -2
  33. package/src/reset.js +397 -0
  34. package/src/safety/audit.js +5 -2
  35. package/src/safety/authcmd.js +305 -0
  36. package/src/safety/keystore.js +53 -0
  37. package/src/safety/runmode.js +123 -0
  38. package/src/safety/secrets.js +61 -0
  39. package/src/safety/undo.js +3 -2
  40. package/src/setup.js +215 -12
  41. package/src/tools/desc.en.js +19 -14
  42. package/src/tools/index.js +141 -43
  43. package/src/tools/jobs.js +17 -13
  44. package/src/tools/lsp.js +5 -4
  45. package/src/tools/outline.js +7 -3
  46. package/src/tools/task.js +10 -6
  47. package/src/tools/todo.js +9 -2
  48. package/src/tools/verify.js +9 -3
  49. package/src/tools/webfetch.js +2 -1
  50. package/src/ui/export.js +1 -1
  51. package/src/ui/histline.js +67 -0
  52. package/src/ui/status.js +88 -3
  53. package/README.en.md +0 -1025
package/src/setup.js CHANGED
@@ -1,15 +1,18 @@
1
1
  // 첫 실행 마법사 + 진단 실행.
2
2
  import { c, say, rule, mark } from './ui/ansi.js';
3
3
  import { 주소가리기 } from './safety/secrets.js';
4
- import { ask, pick } from './ui/prompt.js';
4
+ import { ask, pick, confirm } from './ui/prompt.js';
5
5
  import { spin } from './ui/spinner.js';
6
6
  import { detect } from './backend/detect.js';
7
+ import { 규격이름 } from './backend/adapter.js';
7
8
  import { probe } from './backend/probe.js';
8
9
  import { renderHeader, renderLine, verdict, renderVerdict, plainReport } from './report.js';
9
10
  import { load, save, upsert, slug, resolveKey, activeProfile, configPath } from './config.js';
10
11
  import { 보관방식 } from './safety/keystore.js';
11
12
  import { 애저풀기, 애저base } from './backend/azure.js';
12
13
  import { allowEndpoint } from './safety/network.js';
14
+ import { 바깥인가 } from './safety/runmode.js';
15
+ import { 제공자들, 제공자고르기, 어디것일까, 주소후보, 막힌까닭 } from './providers/index.js';
13
16
  import { writeFileSync } from 'node:fs';
14
17
 
15
18
  export function banner() {
@@ -19,7 +22,11 @@ export function banner() {
19
22
  }
20
23
 
21
24
  // 주소와 키를 받아 연결을 찾아낸다. 실패하면 null.
22
- async function connect(url, key) {
25
+ //
26
+ // 조용히 = 후보를 여럿 훑는 중이라, 실패해도 아직 화면에 안 적는다.
27
+ // 세 개를 줄줄이 시도하면서 실패 안내를 세 번 찍으면, 마지막에 성공해도
28
+ // 사람 눈에는 「뭔가 잔뜩 실패했다」 로 남는다.
29
+ async function connect(url, key, { 조용히 = false, 제공자: 어디 = null } = {}) {
23
30
  const s = spin(`${url} 확인 중...`);
24
31
  /*
25
32
  * 사용자가 방금 적어 넣은 주소다. 확인하는 동안만 문을 연다.
@@ -31,19 +38,54 @@ async function connect(url, key) {
31
38
  * 그 호스트 하나뿐이라 넓어지는 것이 아니다.
32
39
  */
33
40
  allowEndpoint(/^https?:\/\//i.test(url) ? url : [`http://${url}`, `https://${url}`]);
34
- const found = await detect(url, key);
41
+ /*
42
+ * 자물쇠가 막으면 **던져서 끝나지 않게** 받아 둔다.
43
+ *
44
+ * 봉인(offline)이 켜져 있으면 checkUrl 이 예외를 던진다. 예전에는 그것이
45
+ * 여기를 그냥 지나쳐 `deel setup` 전체를 죽였다 — 화면에는 스택 자국이
46
+ * 뜨고, 사람은 자기가 켜 둔 봉인 때문이라는 것을 알 길이 없다.
47
+ *
48
+ * 못 붙은 것으로 치고 그 말을 아래 「연결 실패」 자리에 그대로 싣는다.
49
+ */
50
+ let found;
51
+ try { found = await detect(url, key); }
52
+ catch (err) { found = { kind: null, tried: [url], status: 0, why: String(err?.message ?? err) }; }
35
53
  if (!found.kind) {
54
+ if (조용히) { s.stop(''); return null; }
36
55
  s.stop(` ${mark.no} ${c.red('연결 실패')}`);
37
56
  say('');
57
+ /*
58
+ * 무엇이 막았는지를 먼저 말한다 (providers/index.js 의 막힌까닭).
59
+ *
60
+ * 여태는 401 도 404 도 403 도 전부 「연결 실패」 한 마디였다. 그런데 이
61
+ * 셋은 고칠 자리가 완전히 다르다 — 401 은 닿은 것이라 주소를 의심하면
62
+ * 안 되고, 404 는 주소를 봐야 하고, Bedrock 의 AccessDenied 는 콘솔에서
63
+ * 신청 버튼 한 번 누르면 되는 일이다. 뭉쳐 놓으면 사람은 셋 다 주소
64
+ * 문제로 알고 엉뚱한 데를 판다.
65
+ *
66
+ * 모르는 것은 지어내지 않는다 — 그때는 아래 「확인할 것」 이 그대로 남는다.
67
+ */
68
+ const 까닭 = 막힌까닭(어디, { status: found.status ?? 0, 서버말: found.why ?? '' });
69
+ /*
70
+ * 옮길 말이 없으면 **받은 말을 그대로** 보여 준다.
71
+ *
72
+ * 사람 말로 옮기는 표는 HTTP 상태코드가 있을 때만 쓸모가 있다. 닿지도
73
+ * 못했을 때(status 0)가 오히려 원인이 또렷한 경우가 많다 — 봉인에 막힘 ·
74
+ * 포트 닫힘 · 인증서 · 프록시. 그 한 줄을 버리면 화면에는 「연결 실패」
75
+ * 네 글자만 남고, 정작 답이 적힌 문장을 우리가 지운 셈이 된다.
76
+ */
77
+ if (까닭) { say(` ${mark.warn} ${c.yellow(까닭)}`); say(''); }
78
+ else if (found.why) { say(` ${mark.warn} ${c.yellow(found.why)}`); say(''); }
38
79
  say(` 시도한 주소:`);
39
80
  for (const t of found.tried) say(` ${c.gray(주소가리기(t.includes('?') ? t : `${t}/models`))}`);
40
81
  say('');
41
82
  say(` ${c.gray('확인할 것 — 주소·포트가 맞는지, 프록시가 필요한지,')}`);
42
83
  say(` ${c.gray('사내 인증서라면 NODE_EXTRA_CA_CERTS 환경변수가 필요합니다.')}`);
84
+ if (어디?.열쇠받는곳) say(` ${c.gray('열쇠 받는 곳')} ${c.cyan(어디.열쇠받는곳)}`);
43
85
  say('');
44
86
  return null;
45
87
  }
46
- const kindName = found.kind === 'ollama' ? `Ollama ${found.version ?? ''}`.trim() : 'OpenAI 호환';
88
+ const kindName = found.kind === 'ollama' ? `Ollama ${found.version ?? ''}`.trim() : 규격이름(found.kind);
47
89
  s.stop(` ${mark.ok} ${c.green('연결됨')} ${c.gray(`${kindName} · ${found.ms}ms`)}`);
48
90
  // 물음표 뒤에 열쇠를 싣는 앞단이 있다 (safety/secrets.js 의 주소가리기).
49
91
  say(` ${c.gray('주소')} ${주소가리기(found.base)}`);
@@ -80,22 +122,143 @@ export async function runProbe(conn, { out = null } = {}) {
80
122
  return { facts, results, v };
81
123
  }
82
124
 
125
+ /*
126
+ * ── 어디에 붙일까 ───────────────────────────────────────────────────────
127
+ *
128
+ * 세 갈래가 다 같은 문으로 들어간다. 다른 것은 **사람이 채울 빈칸 개수**뿐이다.
129
+ *
130
+ * 열쇠만 있을 때 빈칸 1개 — 앞머리로 어디 것인지 알아본다
131
+ * 아는 곳에서 고르기 빈칸 1~2개 — 주소를 대신 채워 준다
132
+ * 주소를 직접 넣기 빈칸 2개 — 규격·인증은 스캐너가 알아낸다
133
+ *
134
+ * 「직접 넣기」 를 목록 위쪽에 둔다. 목록이 지원 명단처럼 보이면, 거기 없는
135
+ * 회사는 안 되는 줄 알고 돌아선다 (providers/index.js 머리말).
136
+ *
137
+ * @returns {{제공자, 주소들: string[], 열쇠: string, 이름: string} | null}
138
+ */
139
+ async function 붙일곳고르기() {
140
+ const 목록 = [
141
+ { id: '열쇠먼저', label: '열쇠만 있습니다 — 어디 것인지 알아봐 주세요', note: '빈칸 1개' },
142
+ { id: 'custom', label: '주소를 직접 넣기', note: '사내 게이트웨이 · 목록에 없는 곳' },
143
+ ...제공자들.filter((p) => p.id !== 'custom').map((p) => ({
144
+ id: p.id,
145
+ label: p.이름,
146
+ note: `빈칸 ${p.빈칸.length}개 (${p.빈칸.join(' + ')})`
147
+ + (p.규격됐나 === false ? ' · 규격 붙이는 중' : ''),
148
+ })),
149
+ ];
150
+ const i = await pick('어디에 붙일까요?', 목록, { def: 1 });
151
+ const 고른 = 목록[i];
152
+
153
+ // ── 열쇠 먼저 ────────────────────────────────────────────────────────
154
+ //
155
+ // 열쇠를 여러 곳에 던져 보고 200 이 오는 데를 찾지 **않는다.** 그러면 남의
156
+ // 서버에 내 열쇠가 남는다. 앞머리로 짐작해 한 곳만 묻고, 모르면 물어본다.
157
+ let 제공자 = null;
158
+ let 열쇠 = '';
159
+ if (고른.id === '열쇠먼저') {
160
+ 열쇠 = (await ask('API 키', { mask: true })).trim();
161
+ if (!열쇠) { say(` ${mark.no} 열쇠가 비었습니다.`); return null; }
162
+ const 짚은것 = 어디것일까(열쇠);
163
+ if (짚은것) {
164
+ 제공자 = 짚은것.제공자;
165
+ say(` ${mark.ok} ${c.bold(제공자.이름)} 열쇠로 보입니다. ${c.gray(`(${짚은것.왜})`)}`);
166
+ say(` ${c.gray('여기 말고 다른 데는 안 물어봅니다 — 열쇠를 여기저기 던지지 않습니다.')}`);
167
+ } else {
168
+ say(` ${mark.warn} ${c.yellow('어디 열쇠인지 모르겠습니다.')}`);
169
+ say(` ${c.gray('짐작으로 여기저기 보내지 않습니다. 어디 것인지 골라 주세요.')}`);
170
+ const 나머지 = 제공자들.map((p) => ({ label: p.이름, note: p.한줄 }));
171
+ 제공자 = 제공자들[await pick('어디 열쇠인가요?', 나머지, { def: 0 })];
172
+ }
173
+ } else {
174
+ 제공자 = 제공자고르기(고른.id);
175
+ }
176
+ if (!제공자) return null;
177
+
178
+ /*
179
+ * 아직 말할 줄 모르는 규격은 여기서 멈춘다.
180
+ *
181
+ * 열쇠를 받고, 연결도 되고, 저장까지 해 놓고 첫 한마디에서 400 이 나면
182
+ * 그 화면으로는 무엇이 잘못됐는지 알 길이 없다. 못 하는 것은 못 한다고
183
+ * 지금 말하는 편이 언제나 싸다.
184
+ */
185
+ if (제공자.규격됐나 === false) {
186
+ say('');
187
+ say(` ${mark.no} ${c.yellow(`${제공자.이름} 는 규격을 아직 다 못 붙였습니다.`)}`);
188
+ say(` ${c.gray('주소와 열쇠는 아는데 말을 아직 못 합니다. 붙는 대로 열립니다.')}`);
189
+ say(` ${c.gray('지금 쓰시려면 OpenAI 호환 창구가 있는 곳(Gemini · Bedrock)을 골라 주세요.')}`);
190
+ say('');
191
+ return null;
192
+ }
193
+
194
+ // ── 리전 ─────────────────────────────────────────────────────────────
195
+ //
196
+ // 리전을 고르면 그 자리에서 진짜로 물어본다. 표에 「서울에 무슨 모델이
197
+ // 있다」 를 박아 두지 않는다 — 그 표는 반드시 낡는다.
198
+ let 리전 = null;
199
+ if (제공자.리전들) {
200
+ const 것들 = [...제공자.리전들.map((r) => ({ label: `${r.id} ${r.어디}`, note: r.왜 })),
201
+ { label: '직접 입력', note: '목록은 낡습니다 — 빠져나갈 구멍을 늘 둡니다' }];
202
+ const j = await pick('어느 리전인가요?', 것들, { def: 0 });
203
+ 리전 = j < 제공자.리전들.length
204
+ ? 제공자.리전들[j].id
205
+ : (await ask('리전 (예: ap-northeast-2)')).trim();
206
+ }
207
+
208
+ // ── 주소 ─────────────────────────────────────────────────────────────
209
+ let 주소들 = 주소후보(제공자, { 리전 });
210
+ if (!주소들.length) {
211
+ if (제공자.리전들 && 리전) {
212
+ say(` ${mark.no} ${c.yellow(`리전 이름이 이상합니다: ${리전}`)} ${c.gray('(예: us-east-1 · ap-northeast-2)')}`);
213
+ return null;
214
+ }
215
+ const url = (await ask('주소 (Base URL)')).trim();
216
+ if (!url) { say(` ${mark.no} 주소가 비었습니다.`); return null; }
217
+ 주소들 = [url];
218
+ } else {
219
+ say(` ${c.gray('주소')} ${주소들.map((u) => 주소가리기(u)).join(c.gray(' · '))}`);
220
+ }
221
+
222
+ // ── 열쇠 ─────────────────────────────────────────────────────────────
223
+ if (!열쇠) {
224
+ if (제공자.열쇠받는곳) say(` ${c.gray('열쇠 받는 곳')} ${c.cyan(제공자.열쇠받는곳)}`);
225
+ 열쇠 = (await ask('API 키', { mask: true })).trim();
226
+ }
227
+
228
+ const 기본이름 = 제공자.id === 'custom' ? '사내게이트웨이' : 제공자.이름;
229
+ const 이름 = (await ask('이름', { def: 기본이름 })).trim() || 기본이름;
230
+ return { 제공자, 주소들, 열쇠, 이름 };
231
+ }
232
+
83
233
  export async function runSetup() {
84
234
  banner();
85
235
  // 설정에 적어 둔 api-version 을 **붙기 전에** 읽는다. load() 가 애저정하기()를
86
236
  // 부른다. 이걸 뒤에서 하면 사내에서 판을 고정해 둔 곳이 확인만 GA판으로 하고,
87
237
  // 문서에 적어 둔 대로 안 도는 셈이 된다.
88
238
  load();
89
- say(` ${c.gray('모델 연결을 설정합니다. 주소와 키만 있으면 됩니다.')}`);
90
- say('');
239
+ say(` ${c.gray('모델 연결을 설정합니다.')}`);
240
+ // 빈칸 0개짜리 길을 제일 먼저 알려 준다. 이 PC 에 이미 모델이 떠 있는데
241
+ // 주소를 손으로 치게 만드는 것은 우리 잘못이다.
242
+ say(` ${c.gray('이 PC 에 로컬 모델이 떠 있다면')} ${c.cyan('deel scan --save')} ${c.gray('가 빈칸 없이 찾아 줍니다.')}`);
91
243
 
92
- const name = (await ask('이름', { def: '사내게이트웨이' })).trim();
93
- const url = (await ask('주소 (Base URL)')).trim();
94
- if (!url) { say(` ${mark.no} 주소가 비었습니다.`); return 1; }
95
- const key = (await ask('API 키', { mask: true })).trim();
244
+ const 고른것 = await 붙일곳고르기();
245
+ if (!고른것) return 1;
246
+ const { 제공자: 붙일곳, 주소들, 열쇠: key, 이름: name } = 고른것;
96
247
 
97
248
  say('');
98
- const found = await connect(url, key);
249
+ /*
250
+ * 후보를 앞에서부터 두드린다. 하나라도 붙으면 그것으로 간다.
251
+ *
252
+ * Bedrock 처럼 창구가 여럿인 곳이 있고, 리전·계정마다 열린 창구가 다르다.
253
+ * 표를 믿고 하나만 쓰면 그 표가 낡은 날 「연결 실패」 만 남는다 —
254
+ * 물어보면 되는 것을 짐작으로 정하지 않는다.
255
+ */
256
+ let found = null;
257
+ for (const [n, url] of 주소들.entries()) {
258
+ const 마지막 = n === 주소들.length - 1;
259
+ found = await connect(url, key, { 조용히: !마지막, 제공자: 붙일곳 });
260
+ if (found) break;
261
+ }
99
262
  if (!found) return 1;
100
263
 
101
264
  const model = await chooseModel(found);
@@ -116,6 +279,29 @@ export async function runSetup() {
116
279
  const conn = { kind: found.kind, base: found.base, auth: found.auth, key, model };
117
280
  const { facts } = await runProbe(conn);
118
281
 
282
+ /*
283
+ * ── 「나가도 된다」 를 여기서 명시적으로 받는다 ───────────────────────
284
+ *
285
+ * 주소와 허가를 뗀 것이 이번 자물쇠의 요점이다(safety/runmode.js). 주소는
286
+ * 설정 파일에 그대로 있고 손으로도 고칠 수 있지만, 허가는 **사람이 고른
287
+ * 자리에서만** 붙는다. 그래서 둘 다 있어야 나간다 — 설정 파일 한 줄을
288
+ * 고쳐서는 못 나간다.
289
+ *
290
+ * 여기서 안 받아도 대화 화면이 첫 켤 때 다시 묻는다. 그러니 이건 막는
291
+ * 자리가 아니라, **지금 사실을 알려 주는** 자리다. 주소를 방금 친 사람이
292
+ * 그 주소가 바깥이라는 것을 제일 잘 알아들을 자리이기도 하다.
293
+ */
294
+ let 나가도되나 = false;
295
+ if (바깥인가(found.base)) {
296
+ say('');
297
+ say(` ${c.yellow('↗')} ${c.bold(주소가리기(found.base))} ${c.gray('는 이 컴퓨터 밖입니다.')}`);
298
+ say(` ${c.gray('쓰면 시킨 말과, 모델이 읽은 파일의 내용이 그리로 갑니다.')}`);
299
+ 나가도되나 = await confirm('이 연결로 바깥에 나가도 될까요?', true);
300
+ if (!나가도되나) {
301
+ say(` ${c.gray('허락 없이 저장합니다 — 이 연결로 처음 켤 때 다시 물어봅니다.')}`);
302
+ }
303
+ }
304
+
119
305
  const id = slug(name);
120
306
  const cfg = load();
121
307
  upsert(cfg, {
@@ -125,6 +311,12 @@ export async function runSetup() {
125
311
  auth: found.auth,
126
312
  apiKey: key,
127
313
  model,
314
+ // 어디 것인지 적어 둔다. 요금표 주소·열쇠 받는 곳처럼 「이 회사에만 해당되는
315
+ // 안내」를 나중에 꺼내 쓸 때 이 한 칸이 없으면 주소를 보고 다시 짐작해야 한다.
316
+ 제공자: 붙일곳.id,
317
+ // 로컬·사내망이면 이 칸 자체가 없다. 있는 것만으로도 「바깥이다」 라는 뜻이라,
318
+ // 설정 파일을 열어 본 사람이 그것만 보고도 안다.
319
+ ...(나가도되나 ? { online: true } : {}),
128
320
  ctx: facts.ctx ?? null,
129
321
  streaming: facts.streaming ?? false,
130
322
  tools: facts.tools ?? false,
@@ -200,7 +392,18 @@ export async function showStatus() {
200
392
  p.think ? c.green('추론') : c.gray('추론'),
201
393
  ].join(c.gray('·'));
202
394
  say(` ${c.bold(p.name)} ${c.gray(p.model)} ${caps}${here}`);
203
- say(` ${c.gray(주소가리기(p.baseUrl))}`);
395
+ /*
396
+ * 바깥으로 나가는 연결인지, 그리고 허락이 붙어 있는지를 같이 적는다.
397
+ *
398
+ * 주소만 보고는 모른다 — `gw.회사.com` 이 사내인지 바깥인지는 사람이
399
+ * 헷갈리는 자리다. 그리고 허락이 없으면 켤 때 물어본다는 사실도 여기서
400
+ * 미리 알려 준다. 안 그러면 "왜 갑자기 물어보지" 가 된다.
401
+ */
402
+ const 바깥 = 바깥인가(p.baseUrl);
403
+ const 표 = 바깥
404
+ ? `${c.yellow('↗ 바깥')}${p.online ? c.gray(' · 나가도 됨') : c.gray(' · 켤 때 물어봄')}`
405
+ : c.green('⌂ 이 안');
406
+ say(` ${c.gray(주소가리기(p.baseUrl))} ${표}`);
204
407
  }
205
408
  say('');
206
409
  say(` ${c.gray('설정 파일')} ${configPath()}`);
@@ -25,8 +25,12 @@
25
25
  * Bash “끝나지 않는 것은 background” · 시간 초과로 죽는 것을 막는 자리
26
26
  * Verify“확인 못 한 것은 못 했다고” · 이 프로그램이 거짓말을 안 하게 하는 자리
27
27
  *
28
- * 도구 이름과 인자 이름은 **안 옮긴다.** 그건 식별자다. Task 목적·할일처럼
29
- * 한글로 인자 이름도 그대로 둔다 이름을 바꾸면 도구가 아예 안 불린다.
28
+ * 도구 이름과 인자 이름은 **안 옮긴다.** 그건 식별자다 이름을 바꾸면 그 도구가
29
+ * 아예 불린다. 여기 적힌 이름은 tools/*.js 설명서와 **글자까지 같아야**
30
+ * 한다. 다르면 그 인자만 영어 설명이 조용히 빠진다.
31
+ *
32
+ * 인자 이름은 전부 영문이다. 우리 취향이 아니라 서버가 검사한다 — Bedrock 은
33
+ * `^[a-zA-Z0-9_.-]{1,64}$` 가 아니면 첫 한마디를 통째로 400 으로 돌려보낸다.
30
34
  */
31
35
  export const 도구설명EN = {
32
36
  Read: {
@@ -134,14 +138,14 @@ export const 도구설명EN = {
134
138
  },
135
139
  },
136
140
  Ask: {
137
- desc: 'Ask the person one question at a genuine fork. **First fill in `이해` with what you'
141
+ desc: 'Ask the person one question at a genuine fork. **First fill in `understanding` with what you'
138
142
  + ' understood this request to be**, and only ask if something is genuinely left to decide.'
139
143
  + ' Never write "let me know" and stop; asking in prose ends the turn and throws away'
140
144
  + ' everything you have looked at. Give 2-4 options and they answer with a single number.'
141
145
  + ' **Never ask back something the user already said.** "Tidy up the files" is the answer —'
142
146
  + ' go and do it. Deciding how is your job, not theirs.',
143
147
  params: {
144
- 이해: 'one line: what you understood this request to be. The person reads this line to judge'
148
+ understanding: 'one line: what you understood this request to be. The person reads this line to judge'
145
149
  + ' whether you actually read them. "I understand your request" is not an understanding —'
146
150
  + ' name the actual work',
147
151
  question: 'one sentence. Make it clear what has to be decided',
@@ -197,14 +201,14 @@ export const 도구설명EN = {
197
201
  + ' not pile up in your window. That is why work that creates or edits several files has to be'
198
202
  + ' divided this way to get to the end. One chunk must be finishable on its own (e.g. "create'
199
203
  + ' index.html and style.css"). The subtask cannot see your conversation — put everything it'
200
- + ' needs into 할일. Do not use this for one short job. Doing it yourself is faster.',
204
+ + ' needs into `task`. Do not use this for one short job. Doing it yourself is faster.',
201
205
  params: {
202
- 목적: 'this chunk in one line (e.g. "build the dashboard page skeleton")',
203
- 할일: 'everything the subtask has to do. It cannot see this conversation, so put the background,'
206
+ purpose: 'this chunk in one line (e.g. "build the dashboard page skeleton")',
207
+ task: 'everything the subtask has to do. It cannot see this conversation, so put the background,'
204
208
  + ' the decisions, and the file paths here. Say what counts as done, too.',
205
- 모드: 'how the subtask works: code (builds and edits) · debug (finds causes) · ask (reads and'
209
+ mode: 'how the subtask works: code (builds and edits) · debug (finds causes) · ask (reads and'
206
210
  + ' answers only). Defaults to code.',
207
- 모델: 'hand this chunk to a **different model**. Only profile names the user has configured'
211
+ model: 'hand this chunk to a **different model**. Only profile names the user has configured'
208
212
  + ' work (do not invent an address — it will not be accepted). Left out, it stays on the model'
209
213
  + ' you are using. Handing routine work (formatting, repetitive edits, short summaries) to a'
210
214
  + ' small model keeps your window from filling. Do the work that needs judgement yourself.',
@@ -236,12 +240,13 @@ export const 도구설명EN = {
236
240
  },
237
241
  },
238
242
  Jobs: {
239
- desc: 'List, read, and end background commands (Bash with background). Called with no number,'
240
- + ' you get the list. Given a number, you get whatever output arrived since last time.'
241
- + ' If you started a server, you must end it when the job is done.',
243
+ desc: 'List, read, and end background commands (Bash with background). Called with no `job`,'
244
+ + ' you get the list. Given a `job`, you get whatever output arrived since last time.'
245
+ + ' If you started a server, you must end it with `stop` when the job is done.',
242
246
  params: {
243
- 번호: 'job number to look at. Left out, the list',
244
- 끝내기: 'true to end that job (stop)',
247
+ job: 'job number to look at. Left out, the list',
248
+ stop: 'true to end that job',
249
+ from_start: 'true to read from the beginning again',
245
250
  },
246
251
  },
247
252
  };