deel-local-cli 1.0.2 → 1.1.1
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.en.md +381 -7
- package/README.md +493 -9
- package/bin/deel.js +266 -234
- package/package.json +4 -3
- package/src/agent/budget.js +167 -0
- package/src/agent/grade.js +207 -0
- package/src/agent/loop.js +778 -574
- package/src/agent/modes.js +109 -28
- package/src/agent/project.js +171 -0
- package/src/agent/route.js +55 -3
- package/src/agent/session.js +131 -12
- package/src/backend/http.js +41 -4
- package/src/backend/learn.js +46 -4
- package/src/commands.js +178 -3
- package/src/oneshot.js +390 -327
- package/src/preview/serve.js +326 -0
- package/src/repl.js +1184 -887
- package/src/safety/audit.js +3 -1
- package/src/skills/builtin//352/262/200/354/202/254-/353/250/274/354/240/200/SKILL.md +64 -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 +78 -0
- package/src/skills/builtin//353/201/235/352/271/214/354/247/200-/355/225/230/352/270/260/SKILL.md +65 -0
- package/src/skills/builtin//354/212/244/354/212/244/353/241/234-/352/262/200/355/206/240/SKILL.md +74 -0
- package/src/skills/builtin//354/260/224/353/237/254/353/263/264/352/270/260/SKILL.md +59 -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 +73 -0
- package/src/skills/builtin//354/275/224/353/223/234-/354/244/204/354/235/264/352/270/260/SKILL.md +66 -0
- package/src/skills/discover.js +17 -2
- package/src/tools/index.js +500 -107
- package/src/tools/jobs.js +670 -0
- package/src/tools/outline.js +331 -0
- package/src/tools/task.js +153 -0
- package/src/tools/verify.js +306 -0
- package/src/tools/webfetch.js +152 -16
- package/src/ui/inputbox.js +102 -7
- package/src/ui/motion.js +212 -0
- package/src/ui/screen.js +51 -3
- package/src/ui/status.js +69 -4
- package/src/ui/working.js +8 -1
package/src/ui/inputbox.js
CHANGED
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
import { c, width, cursor, clip } from './ansi.js';
|
|
37
37
|
import { statusLine, contextWarning } from './status.js';
|
|
38
38
|
import { 접어쓰기 } from './wrap.js';
|
|
39
|
-
import { 문구고르기,
|
|
39
|
+
import { 문구고르기, 걸린시간, 느긋해질때, 문구주기 } from './working.js';
|
|
40
|
+
import { 그림고르기 } from './motion.js';
|
|
40
41
|
import { 최대추천 } from './complete.js';
|
|
41
42
|
|
|
42
43
|
const 줄끝지움 = '\x1b[K';
|
|
@@ -98,9 +99,34 @@ export function 프레임({ 글 = '', 커서: 커서자리 = null, 폭 = 80, 상
|
|
|
98
99
|
// 자리가 모자라면 오른쪽(걸린 시간·중단 안내)을 버린다. 왼쪽이 본문이다.
|
|
99
100
|
const 안 = 남 >= 2 ? `${왼쪽}${' '.repeat(남)}${오른쪽}` : 왼쪽;
|
|
100
101
|
줄들.push(` ${c.gray('│')} ${안}${' '.repeat(Math.max(0, 일안쪽 - width(안)))} ${c.gray('│')}`);
|
|
102
|
+
|
|
103
|
+
/*
|
|
104
|
+
* 일하는 도중에 미리 쳐 두는 자리.
|
|
105
|
+
*
|
|
106
|
+
* 로컬 모델은 한 턴에 몇 분이 걸린다. 그 동안 생각난 것을 못 적어 두고
|
|
107
|
+
* 끝나기를 지켜보고 있어야 한다면, 기다리는 시간이 통째로 버려진다.
|
|
108
|
+
* 사실 readline 은 그 동안에도 글을 받아 두고 있었다 — 다만 화면에
|
|
109
|
+
* 아무것도 안 보여서, 쳐도 안 먹는 줄 알았을 뿐이다.
|
|
110
|
+
*
|
|
111
|
+
* 그래서 보여만 준다. 새로 받는 게 아니라 **이미 받고 있던 것**이다.
|
|
112
|
+
*/
|
|
113
|
+
if (일감.대기) {
|
|
114
|
+
const 대기줄들 = 접어쓰기(일감.대기, 안쪽).slice(0, 3);
|
|
115
|
+
for (const [i, 한줄] of 대기줄들.entries()) {
|
|
116
|
+
const 표 = i === 0 ? c.hcyan('❯') : ' ';
|
|
117
|
+
줄들.push(` ${c.gray('│')} ${표} ${한줄}${' '.repeat(Math.max(0, 안쪽 - width(한줄)))} ${c.gray('│')}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
101
121
|
줄들.push(c.gray(` ╰${가로}╯`));
|
|
102
|
-
if (
|
|
103
|
-
|
|
122
|
+
if (일감.대기) 줄들.push(` ${c.gray('Enter 를 치면 지금 일이 끝난 뒤에 보냅니다')}`);
|
|
123
|
+
else if (곁말) 줄들.push(` ${c.gray(곁말)}`);
|
|
124
|
+
|
|
125
|
+
// 커서는 미리 치는 중일 때만 보여 준다. 아니면 칠 자리가 아니다.
|
|
126
|
+
if (일감.대기) {
|
|
127
|
+
const 마지막 = 접어쓰기(일감.대기, 안쪽).slice(0, 3).at(-1) ?? '';
|
|
128
|
+
return { 줄들, 커서: { 위: 2, 열: Math.min(앞머리 + 1 + width(마지막), 칸 - 1) } };
|
|
129
|
+
}
|
|
104
130
|
return { 줄들, 커서: { 위: (곁말 ? 1 : 0), 열: 1 } };
|
|
105
131
|
}
|
|
106
132
|
|
|
@@ -204,8 +230,38 @@ export class InputBox {
|
|
|
204
230
|
this.일감 = null;
|
|
205
231
|
this.박자 = null; // 돌아가는 표시를 돌리는 시계
|
|
206
232
|
this.틱 = 0;
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* 대화가 **줄 중간**에 멈춰 있나.
|
|
236
|
+
*
|
|
237
|
+
* 이걸 안 들고 있으면 답이 잘려 보인다. 실제로 나갔던 결함이다.
|
|
238
|
+
*
|
|
239
|
+
* 모델이 답을 흘리는 동안 글은 토막토막 온다. 한 토막을 찍으면 커서는
|
|
240
|
+
* 그 줄 한가운데에 선다. 그런데 돌아가는 표시는 90ms 마다 제 시계로
|
|
241
|
+
* 상자를 다시 그리므로, 그 순간 **상자가 답 줄에 얹혀 그려진다.**
|
|
242
|
+
*
|
|
243
|
+
* ▌ **계획표** ▏myproject · qwen… ▏ ▰▱▱▱▱ 13% ▏ ◎ 종합
|
|
244
|
+
*
|
|
245
|
+
* 그 다음 지우기는 `\r` 로 1칸째에 간 뒤 `\x1b[K` 로 줄을 지운다.
|
|
246
|
+
* 상자만 지우려던 것인데 **같은 줄에 있던 답까지 같이 지워진다.**
|
|
247
|
+
* 사람 눈에는 답의 앞부분이 사라진 것으로 보인다 — `**계획표**` 가
|
|
248
|
+
* `표**` 만 남는 식이다. 글자가 안 온 게 아니라 지워진 것이라,
|
|
249
|
+
* 모델을 아무리 바꿔도 똑같이 나온다.
|
|
250
|
+
*
|
|
251
|
+
* 그래서 규칙을 하나로 못 박는다 — **줄 중간에서는 아무것도 안 그린다.**
|
|
252
|
+
*/
|
|
253
|
+
this.줄중간 = false;
|
|
207
254
|
}
|
|
208
255
|
|
|
256
|
+
/** 대화 글이 줄 중간까지 쓰였다. 줄이 끝날 때까지 상자를 안 세운다. */
|
|
257
|
+
줄중간표시() {
|
|
258
|
+
this.지우기();
|
|
259
|
+
this.줄중간 = true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** 줄이 끝났다. 이제 아래에 상자를 세워도 안전하다. */
|
|
263
|
+
줄끝남() { this.줄중간 = false; }
|
|
264
|
+
|
|
209
265
|
/**
|
|
210
266
|
* 일하기 시작. 상자 안엣것이 입력칸에서 '돌아가는 중' 으로 바뀐다.
|
|
211
267
|
*
|
|
@@ -214,7 +270,8 @@ export class InputBox {
|
|
|
214
270
|
*/
|
|
215
271
|
일시작(session, 갈래 = '기본') {
|
|
216
272
|
this.session = session ?? this.session;
|
|
217
|
-
|
|
273
|
+
// 대기 = 일하는 도중에 미리 치고 있는 글, 예약수 = 이미 Enter 를 친 것들.
|
|
274
|
+
this.일감 = { 갈래, 시작: Date.now(), 곁정보: '', 대기: '', 예약수: 0 };
|
|
218
275
|
this.틱 = 0;
|
|
219
276
|
this.일그리기();
|
|
220
277
|
if (this.박자) clearInterval(this.박자);
|
|
@@ -239,15 +296,41 @@ export class InputBox {
|
|
|
239
296
|
this.지우기();
|
|
240
297
|
}
|
|
241
298
|
|
|
242
|
-
|
|
299
|
+
/**
|
|
300
|
+
* 일하는 도중에 사람이 치고 있는 글 · 예약해 둔 개수.
|
|
301
|
+
*
|
|
302
|
+
* 답이 흘러나오는 동안(줄 중간)에는 안 그려진다 — 그건 일그리기() 가
|
|
303
|
+
* 알아서 넘긴다. 글은 readline 이 계속 들고 있으므로 잃지 않는다.
|
|
304
|
+
*/
|
|
305
|
+
대기갱신(글 = '', 예약수 = 0) {
|
|
243
306
|
if (!this.일감) return;
|
|
307
|
+
if (this.일감.대기 === 글 && this.일감.예약수 === 예약수) return;
|
|
308
|
+
this.일감.대기 = 글;
|
|
309
|
+
this.일감.예약수 = 예약수;
|
|
310
|
+
this.일그리기();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
일그리기() {
|
|
314
|
+
// 줄 중간이면 안 그린다. 여기가 시계로 저절로 도는 유일한 자리라,
|
|
315
|
+
// 답이 흘러나오는 동안 상자가 답 줄에 얹히는 것을 여기서 막는다.
|
|
316
|
+
if (!this.일감 || this.줄중간) return;
|
|
244
317
|
const 지난 = Date.now() - this.일감.시작;
|
|
245
318
|
const 갈래 = 지난 >= 느긋해질때 ? '느긋' : this.일감.갈래;
|
|
246
319
|
const 회차 = Math.floor((this.틱 * 돌림주기) / 문구주기);
|
|
247
320
|
this.그리기(this.session, '', 0, {
|
|
248
|
-
|
|
321
|
+
// 갈래에 맞는 작은 그림. 무엇을 하는 중인지 글자와 그림이 같이 말한다.
|
|
322
|
+
// DEEL_NO_MOTION=1 이면 예전처럼 한 칸짜리 돌림표가 온다.
|
|
323
|
+
돌림: 그림고르기(갈래, this.틱),
|
|
249
324
|
말: 문구고르기(갈래, 회차),
|
|
250
|
-
곁: [
|
|
325
|
+
곁: [
|
|
326
|
+
걸린시간(지난),
|
|
327
|
+
this.일감.곁정보,
|
|
328
|
+
// 예약해 둔 것이 있으면 잊지 않게 세어 준다. 몇 분짜리 일이 끝난 뒤
|
|
329
|
+
// 엉뚱한 말이 저절로 나가는 것처럼 보이면 안 된다.
|
|
330
|
+
this.일감.예약수 ? `${this.일감.예약수}건 예약됨` : '',
|
|
331
|
+
'Ctrl+C 중단',
|
|
332
|
+
].filter(Boolean).join(' · '),
|
|
333
|
+
대기: this.일감.대기,
|
|
251
334
|
});
|
|
252
335
|
}
|
|
253
336
|
|
|
@@ -277,6 +360,18 @@ export class InputBox {
|
|
|
277
360
|
*/
|
|
278
361
|
그리기(session, 글 = '', 커서자리 = null, 일감 = null, 추천 = []) {
|
|
279
362
|
this.session = session ?? this.session;
|
|
363
|
+
/*
|
|
364
|
+
* 줄 중간에서는 절대 안 그린다 — 여기가 그 규칙을 지키는 마지막 자리다.
|
|
365
|
+
*
|
|
366
|
+
* 시계로 도는 일그리기() 는 아예 안 그리고 넘어가지만, 사람이 입력을
|
|
367
|
+
* 기다리는 자리(입력자리·입력갱신)는 그리긴 그려야 한다. 그때는 줄을
|
|
368
|
+
* 먼저 끝내고 그린다. 안 그러면 상자가 대화 줄에 얹히고, 다음 지우기가
|
|
369
|
+
* 그 줄을 통째로 지운다.
|
|
370
|
+
*/
|
|
371
|
+
if (this.줄중간) {
|
|
372
|
+
process.stdout.write('\n');
|
|
373
|
+
this.줄중간 = false;
|
|
374
|
+
}
|
|
280
375
|
this.지우기();
|
|
281
376
|
|
|
282
377
|
const { 줄들, 커서: 자리 } = 프레임({
|
package/src/ui/motion.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 일하는 중에 도는 작은 그림.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 넣나 ─────────────────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* working.js 에 적어 둔 이유가 그대로 여기에도 해당한다. 로컬 모델은 한 걸음에
|
|
7
|
+
* 수십 초가 걸리고, 그 동안 화면이 조용하면 사람은 멈춘 줄 알고 Ctrl+C 를 누른다.
|
|
8
|
+
*
|
|
9
|
+
* 그런데 이유가 하나 더 있다. **몇 분을 기다리는 화면은 보고 있을 만해야 한다.**
|
|
10
|
+
* 점 하나가 도는 것과 지금 무슨 일을 하는지가 그림으로 보이는 것은 다르다.
|
|
11
|
+
* 파일을 읽는 중이면 훑는 줄이 내려가고, 코드를 짜는 중이면 자판을 두드린다.
|
|
12
|
+
* 재미로 넣은 것이 맞고, 그 재미가 기다리는 시간을 견디게 한다.
|
|
13
|
+
*
|
|
14
|
+
* ── 왜 점자인가 ─────────────────────────────────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* 상자 안에 들어가야 하므로 폭이 정확해야 한다. 이모지는 터미널마다 한 칸이
|
|
17
|
+
* 되기도 두 칸이 되기도 해서 테두리가 어긋난다. ● ○ ▪ 같은 기호도 동아시아
|
|
18
|
+
* 로캘에서는 두 칸으로 잡히는 것들이 섞여 있다(East Asian Ambiguous).
|
|
19
|
+
*
|
|
20
|
+
* 점자(U+2800~)는 어디서나 **한 칸**이다. 이 저장소가 이미 점자 돌림표를 쓰는
|
|
21
|
+
* 이유가 그것이고, 같은 이유로 여기서도 점자만 쓴다.
|
|
22
|
+
*
|
|
23
|
+
* 그리고 점자 한 칸은 **가로 2 · 세로 4 점**이다. 세 칸을 붙이면 6×4 격자가
|
|
24
|
+
* 생긴다. 작지만 자판을 두드리는 노트북이 들어간다.
|
|
25
|
+
*
|
|
26
|
+
* ── 어떻게 그리나 ───────────────────────────────────────────────────────
|
|
27
|
+
*
|
|
28
|
+
* 그림을 점자 코드로 직접 적으면 나중에 아무도 못 고친다. 그래서 눈에 보이는
|
|
29
|
+
* 대로 '#' 과 '.' 으로 적고, 켜질 때 한 번 점자로 바꾼다.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
// 점 번호와 자리. 점자는 이렇게 번호가 매겨져 있다 —
|
|
33
|
+
// 1 4
|
|
34
|
+
// 2 5
|
|
35
|
+
// 3 6
|
|
36
|
+
// 7 8
|
|
37
|
+
// 아래 표는 [행][열] → 그 점의 비트값이다.
|
|
38
|
+
const 비트 = [
|
|
39
|
+
[0x01, 0x08],
|
|
40
|
+
[0x02, 0x10],
|
|
41
|
+
[0x04, 0x20],
|
|
42
|
+
[0x40, 0x80],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export const 칸수 = 3; // 점자 세 칸
|
|
46
|
+
export const 가로 = 칸수 * 2; // 점 6개
|
|
47
|
+
export const 세로 = 4; // 점 4줄
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 6×4 그림 한 장을 점자 세 글자로. 격자를 벗어난 자리는 꺼진 것으로 본다.
|
|
51
|
+
*
|
|
52
|
+
* @param {string[]} 그림 '#'(켜짐)과 그 밖(꺼짐)으로 그린 4줄
|
|
53
|
+
*/
|
|
54
|
+
export function 점자로(그림) {
|
|
55
|
+
let out = '';
|
|
56
|
+
for (let 칸 = 0; 칸 < 칸수; 칸++) {
|
|
57
|
+
let v = 0;
|
|
58
|
+
for (let r = 0; r < 세로; r++) {
|
|
59
|
+
for (let cc = 0; cc < 2; cc++) {
|
|
60
|
+
if (그림[r]?.[칸 * 2 + cc] === '#') v |= 비트[r][cc];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
out += String.fromCharCode(0x2800 + v);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* 갈래별 그림.
|
|
70
|
+
*
|
|
71
|
+
* 한 장이 90ms(inputbox 의 돌림주기)이므로, 장수가 곧 한 바퀴 시간이다.
|
|
72
|
+
* 자판 두드리기는 빨라야 자판 같고, 오래 기다리는 표시는 느려야 조급해 보이지 않는다.
|
|
73
|
+
* 그래서 장수를 갈래마다 다르게 뒀다 — 같은 수로 맞추면 하나는 반드시 어색해진다.
|
|
74
|
+
*/
|
|
75
|
+
export const 그림들 = {
|
|
76
|
+
// 생각 — 부풀었다 줄어드는 덩어리. 무엇을 하는지 모를 때의 '머리 굴리는 중'.
|
|
77
|
+
생각: [
|
|
78
|
+
['......', '..##..', '..##..', '......'],
|
|
79
|
+
['..##..', '.####.', '.####.', '..##..'],
|
|
80
|
+
['.####.', '######', '######', '.####.'],
|
|
81
|
+
['..##..', '.####.', '.####.', '..##..'],
|
|
82
|
+
],
|
|
83
|
+
|
|
84
|
+
// 읽기 — 훑는 줄이 위에서 아래로 내려간다. 스캐너처럼 보이는 것이 목적이다.
|
|
85
|
+
읽기: [
|
|
86
|
+
['######', '......', '......', '......'],
|
|
87
|
+
['......', '######', '......', '......'],
|
|
88
|
+
['......', '......', '######', '......'],
|
|
89
|
+
['......', '......', '......', '######'],
|
|
90
|
+
],
|
|
91
|
+
|
|
92
|
+
/*
|
|
93
|
+
* 쓰기 — 노트북이 자판을 두드린다.
|
|
94
|
+
*
|
|
95
|
+
* .####. 화면 위
|
|
96
|
+
* .#..#. 화면 안 (글이 한 칸씩 찬다)
|
|
97
|
+
* ###### 받침
|
|
98
|
+
* ..#... 눌린 자판이 옮겨 다닌다
|
|
99
|
+
*/
|
|
100
|
+
쓰기: [
|
|
101
|
+
['.####.', '.#..#.', '######', '#.....'],
|
|
102
|
+
['.####.', '.##.#.', '######', '..#...'],
|
|
103
|
+
['.####.', '.####.', '######', '....#.'],
|
|
104
|
+
['.####.', '.#..#.', '######', '..#...'],
|
|
105
|
+
],
|
|
106
|
+
|
|
107
|
+
// 명령 — 프롬프트 뒤로 결과가 한 줄씩 쌓인다.
|
|
108
|
+
명령: [
|
|
109
|
+
['##....', '......', '......', '......'],
|
|
110
|
+
['##.#..', '......', '......', '......'],
|
|
111
|
+
['##.#..', '###...', '......', '......'],
|
|
112
|
+
['##.#..', '###...', '#####.', '......'],
|
|
113
|
+
['##.#..', '###...', '#####.', '##....'],
|
|
114
|
+
],
|
|
115
|
+
|
|
116
|
+
// 답 — 글이 왼쪽 위부터 차오른다. 답을 받아쓰는 중이라는 뜻.
|
|
117
|
+
답: [
|
|
118
|
+
['##....', '......', '......', '......'],
|
|
119
|
+
['####..', '##....', '......', '......'],
|
|
120
|
+
['######', '####..', '##....', '......'],
|
|
121
|
+
['######', '######', '####..', '##....'],
|
|
122
|
+
],
|
|
123
|
+
|
|
124
|
+
// 웹 — 신호 막대가 왼쪽부터 차오른다. 점 하나짜리로 그렸더니 옆 갈래들에
|
|
125
|
+
// 비해 너무 흐렸다. 두 칸짜리 막대로 키웠다.
|
|
126
|
+
웹: [
|
|
127
|
+
['......', '......', '......', '######'],
|
|
128
|
+
['......', '......', '##....', '######'],
|
|
129
|
+
['......', '##....', '####..', '######'],
|
|
130
|
+
['##....', '####..', '######', '######'],
|
|
131
|
+
['......', '##....', '####..', '######'],
|
|
132
|
+
],
|
|
133
|
+
|
|
134
|
+
// 하위 — 셋이 나란히 내려간다. 여럿이 동시에 도는 중이라는 뜻.
|
|
135
|
+
하위: [
|
|
136
|
+
['#.#.#.', '......', '......', '......'],
|
|
137
|
+
['......', '#.#.#.', '......', '......'],
|
|
138
|
+
['......', '......', '#.#.#.', '......'],
|
|
139
|
+
['......', '......', '......', '#.#.#.'],
|
|
140
|
+
],
|
|
141
|
+
|
|
142
|
+
// 접기 — 위에서부터 눌려 한 줄로 모인다.
|
|
143
|
+
접기: [
|
|
144
|
+
['######', '######', '######', '######'],
|
|
145
|
+
['......', '######', '######', '######'],
|
|
146
|
+
['......', '......', '######', '######'],
|
|
147
|
+
['......', '......', '......', '######'],
|
|
148
|
+
],
|
|
149
|
+
|
|
150
|
+
// 기본 — 무슨 일인지 아직 모를 때. 한쪽으로 지나가는 덩어리.
|
|
151
|
+
// 점 하나로 그렸더니 옆 갈래들 옆에서 꺼진 것처럼 보였다. 두 칸으로 키웠다.
|
|
152
|
+
기본: [
|
|
153
|
+
['......', '##....', '##....', '......'],
|
|
154
|
+
['......', '.##...', '.##...', '......'],
|
|
155
|
+
['......', '..##..', '..##..', '......'],
|
|
156
|
+
['......', '...##.', '...##.', '......'],
|
|
157
|
+
['......', '....##', '....##', '......'],
|
|
158
|
+
],
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
* 느긋 — 오래 걸릴 때. 왕복한다.
|
|
162
|
+
*
|
|
163
|
+
* 한쪽으로만 지나가는 것과 왔다 갔다 하는 것은 느낌이 다르다. 앞의 것은
|
|
164
|
+
* '진행 중' 으로 읽히고, 뒤의 것은 '기다리는 중' 으로 읽힌다. 45초가 넘은
|
|
165
|
+
* 자리에서 진행 중인 척하면 곧 끝날 것처럼 보여서 오히려 답답해진다.
|
|
166
|
+
* 열 장이라 한 바퀴가 0.9초 — 다른 것들보다 느긋하다.
|
|
167
|
+
*/
|
|
168
|
+
느긋: [
|
|
169
|
+
['......', '##....', '##....', '......'],
|
|
170
|
+
['......', '.##...', '.##...', '......'],
|
|
171
|
+
['......', '..##..', '..##..', '......'],
|
|
172
|
+
['......', '...##.', '...##.', '......'],
|
|
173
|
+
['......', '....##', '....##', '......'],
|
|
174
|
+
['......', '...##.', '...##.', '......'],
|
|
175
|
+
['......', '..##..', '..##..', '......'],
|
|
176
|
+
['......', '.##...', '.##...', '......'],
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// 켜질 때 한 번만 바꾼다. 90ms 마다 다시 만들 이유가 없다.
|
|
181
|
+
export const 틀 = Object.fromEntries(
|
|
182
|
+
Object.entries(그림들).map(([갈래, 장들]) => [갈래, 장들.map(점자로)]),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
/*
|
|
186
|
+
* 그림을 끄고 싶을 때.
|
|
187
|
+
*
|
|
188
|
+
* 점자가 안 나오는 터미널·화면 읽기 프로그램·화면을 그대로 캡처해 붙여야 하는
|
|
189
|
+
* 자리가 있다. 그럴 때 DEEL_NO_MOTION=1 이면 예전처럼 한 칸짜리 돌림표만 돈다.
|
|
190
|
+
* 아예 못 끄게 해 두면 그런 사람은 도구를 못 쓴다.
|
|
191
|
+
*/
|
|
192
|
+
export const 끔 = () => !!process.env.DEEL_NO_MOTION;
|
|
193
|
+
|
|
194
|
+
// 못 끄는 자리에서 쓸 한 칸짜리. working.js 의 돌림틀과 같은 것이다.
|
|
195
|
+
const 한칸 = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 지금 그릴 그림 한 장.
|
|
199
|
+
*
|
|
200
|
+
* 틱을 받는다. 시각을 안에서 읽으면 검사가 '한 장 뒤' 를 재려고 진짜로
|
|
201
|
+
* 기다려야 한다 — working.js 의 문구고르기와 같은 이유다.
|
|
202
|
+
*
|
|
203
|
+
* @param {string} 갈래 working.js 의 갈래 이름
|
|
204
|
+
* @param {number} 틱 90ms 마다 하나씩 오르는 수
|
|
205
|
+
* @returns {string} 점자 세 글자 (끈 상태면 한 글자)
|
|
206
|
+
*/
|
|
207
|
+
export function 그림고르기(갈래, 틱 = 0) {
|
|
208
|
+
const n = Math.trunc(틱) || 0;
|
|
209
|
+
if (끔()) return 한칸[((n % 한칸.length) + 한칸.length) % 한칸.length];
|
|
210
|
+
const 장들 = 틀[갈래] ?? 틀.기본;
|
|
211
|
+
return 장들[((n % 장들.length) + 장들.length) % 장들.length];
|
|
212
|
+
}
|
package/src/ui/screen.js
CHANGED
|
@@ -69,12 +69,36 @@ export class LineScreen {
|
|
|
69
69
|
// 다음에 오는 짧은 글이 그 줄 위에 겹쳐 찍힌다.
|
|
70
70
|
this.임시중 = false;
|
|
71
71
|
this.돌림 = null;
|
|
72
|
+
/*
|
|
73
|
+
* 하위 작업 안쪽인가 — 그렇다면 몇 겹인가.
|
|
74
|
+
*
|
|
75
|
+
* 하위 작업(tools/task.js)은 제 대화에서 따로 돌지만 화면은 하나다.
|
|
76
|
+
* 표시가 없으면 하위가 부른 도구 줄이 부모 것과 똑같이 찍혀서, 사람은
|
|
77
|
+
* 부모가 파일 열두 개를 읽은 줄로 본다. 한 단 들여 그으면 어디부터
|
|
78
|
+
* 어디까지가 떼어 낸 일인지가 그냥 보인다.
|
|
79
|
+
*
|
|
80
|
+
* 여기 한 군데서만 붙인다. repl.js 의 say() 자리 예순 곳을 고치는 대신
|
|
81
|
+
* 이벤트가 들고 온 깊이를 화면에 알려 주는 쪽이 안 빠뜨린다.
|
|
82
|
+
*/
|
|
83
|
+
this.깊이 = 0;
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
// ── 흘러가는 글 ────────────────────────────────────────────────────────
|
|
75
87
|
|
|
88
|
+
/** 지금부터 찍는 줄이 몇 겹 안쪽인지. 0 이면 평소대로. */
|
|
89
|
+
들여쓰기(n) { this.깊이 = Math.max(0, Number(n) || 0); }
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* 하위 작업 구간에 세로줄을 세운다.
|
|
93
|
+
*
|
|
94
|
+
* 빈 칸으로만 밀면 어디서 시작해 어디서 끝났는지가 안 보인다 — 특히 하위가
|
|
95
|
+
* 파일을 열 개 만지고 나면, 스크롤을 올려 여는 줄을 찾아야 그게 하위였는지
|
|
96
|
+
* 안다. 세로줄 한 칸이면 훑는 것만으로 구간이 잡힌다.
|
|
97
|
+
*/
|
|
98
|
+
#안내() { return this.깊이 ? c.gray(' │').repeat(this.깊이) : ''; }
|
|
99
|
+
|
|
76
100
|
/** 한 줄. 이미 색이 입혀진 글을 받는다. */
|
|
77
|
-
줄(s = '') { this.임시지움(); say(s); }
|
|
101
|
+
줄(s = '') { this.임시지움(); say(this.깊이 && s ? this.#안내() + s : s); }
|
|
78
102
|
|
|
79
103
|
/** 줄바꿈 없이 이어 붙인다 — 스트리밍으로 오는 답. */
|
|
80
104
|
붙임(s) { this.임시지움(); process.stdout.write(s); }
|
|
@@ -150,6 +174,12 @@ export class LineScreen {
|
|
|
150
174
|
/** 사람이 치는 중인 글이 바뀌었다. 줄화면은 readline 이 알아서 되비춘다. */
|
|
151
175
|
입력갱신() { }
|
|
152
176
|
|
|
177
|
+
/** 일하는 중 미리 치는 글. 줄화면은 상자가 없어 보여 줄 자리가 없다. */
|
|
178
|
+
대기갱신() { }
|
|
179
|
+
|
|
180
|
+
/** 줄화면은 붙여 쓰기 전에 임시글만 지우면 되므로 따로 셀 것이 없다. */
|
|
181
|
+
줄중간인가() { return false; }
|
|
182
|
+
|
|
153
183
|
/*
|
|
154
184
|
* 일하는 중 표시. 줄화면은 안 그린다.
|
|
155
185
|
*
|
|
@@ -189,7 +219,13 @@ export class BoxScreen extends LineScreen {
|
|
|
189
219
|
* 찍히는 순간 상자가 사라지고, 그 뒤로는 일이 끝날 때까지 화면 아래가
|
|
190
220
|
* 텅 빈 채로 남는다 — 정확히 그게 "밋밋해 보인다" 는 상태다.
|
|
191
221
|
*/
|
|
192
|
-
줄(s = '') {
|
|
222
|
+
줄(s = '') {
|
|
223
|
+
this.상자.지우기();
|
|
224
|
+
super.줄(s);
|
|
225
|
+
// 줄이 끝났다 — 이제 아래에 상자를 세워도 대화를 안 덮는다.
|
|
226
|
+
this.상자.줄끝남();
|
|
227
|
+
this.상자.일그리기();
|
|
228
|
+
}
|
|
193
229
|
|
|
194
230
|
/*
|
|
195
231
|
* 스트리밍으로 이어 붙이는 글은 다시 안 세운다.
|
|
@@ -198,13 +234,25 @@ export class BoxScreen extends LineScreen {
|
|
|
198
234
|
* 위아래로 튄다. 답이 흘러나오는 동안에는 그 자체가 살아 있다는 신호라
|
|
199
235
|
* 상자가 없어도 밋밋하지 않다. 답이 끝나면 repl.js 가 다시 세운다.
|
|
200
236
|
*/
|
|
201
|
-
붙임(s) {
|
|
237
|
+
붙임(s) {
|
|
238
|
+
// 상자를 걷고, **줄 중간에 멈춰 있다고 표시한다.** 이 표시가 없으면
|
|
239
|
+
// 돌아가는 표시가 90ms 뒤에 제 시계로 상자를 답 줄 위에 그려 버리고,
|
|
240
|
+
// 그 다음 지우기가 답까지 같이 지운다.
|
|
241
|
+
this.상자.줄중간표시();
|
|
242
|
+
super.붙임(s);
|
|
243
|
+
}
|
|
202
244
|
|
|
203
245
|
/** 일하기 시작·바뀜·끝. 상자 안엣것만 바뀐다 (ui/working.js 의 문구). */
|
|
204
246
|
일시작(session, 갈래) { this.임시지움(); this.상자.일시작(session, 갈래); }
|
|
205
247
|
일바꿈(갈래, 곁정보) { this.상자.일바꿈(갈래, 곁정보); }
|
|
206
248
|
일끝() { this.상자.일끝(); }
|
|
207
249
|
|
|
250
|
+
/** 일하는 도중에 미리 치고 있는 글 · 예약해 둔 개수. */
|
|
251
|
+
대기갱신(글, 예약수) { this.상자.대기갱신(글, 예약수); }
|
|
252
|
+
|
|
253
|
+
/** 대화가 줄 중간에 멈춰 있나 (답이 흘러나오는 중). */
|
|
254
|
+
줄중간인가() { return this.상자.줄중간; }
|
|
255
|
+
|
|
208
256
|
/*
|
|
209
257
|
* 줄화면에서 '생각 중…' 을 한 줄로 흘리던 자리.
|
|
210
258
|
*
|
package/src/ui/status.js
CHANGED
|
@@ -30,6 +30,28 @@ export const SEGMENTS = {
|
|
|
30
30
|
short: (s) => c.hcyan(clip(s.conn.model, 14)),
|
|
31
31
|
},
|
|
32
32
|
|
|
33
|
+
/*
|
|
34
|
+
* 모델 급 — 얼마나 알아서 하나 (agent/grade.js).
|
|
35
|
+
*
|
|
36
|
+
* 컨텍스트 게이지 바로 옆에 둔다. 그 둘이 다른 축이라는 것이 화면에서
|
|
37
|
+
* 나란히 보여야 한다 — 창이 128k 인데 급이 '작음' 인 경우가 실제로 있고,
|
|
38
|
+
* 그때 "왜 이렇게 조심스럽게 하지" 의 답이 이 한 글자다.
|
|
39
|
+
*
|
|
40
|
+
* 짐작일 때는 흐리게 그린다. 정한 것과 짐작한 것을 같은 색으로 보여주면,
|
|
41
|
+
* 사람은 프로그램이 확인한 사실이라고 읽는다.
|
|
42
|
+
*/
|
|
43
|
+
grade: {
|
|
44
|
+
desc: '모델 급 (얼마나 알아서 하나)',
|
|
45
|
+
make: (s) => {
|
|
46
|
+
const g = 볼만한급(s);
|
|
47
|
+
return g ? (g.짐작 ? c.gray(`◈ ${g.급}?`) : c.white(`◈ ${g.급}`)) : '';
|
|
48
|
+
},
|
|
49
|
+
short: (s) => {
|
|
50
|
+
const g = 볼만한급(s);
|
|
51
|
+
return g ? (g.짐작 ? c.gray(`◈${g.급[0]}?`) : c.white(`◈${g.급[0]}`)) : '';
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
|
|
33
55
|
ctx: {
|
|
34
56
|
desc: '컨텍스트 사용량',
|
|
35
57
|
make: (s) => {
|
|
@@ -117,7 +139,7 @@ export const SEGMENTS = {
|
|
|
117
139
|
*/
|
|
118
140
|
export const SEGMENT_GROUPS = [
|
|
119
141
|
['dir', 'model'],
|
|
120
|
-
['ctx'],
|
|
142
|
+
['ctx', 'grade'],
|
|
121
143
|
['work', 'think', 'mode'],
|
|
122
144
|
['tok'],
|
|
123
145
|
];
|
|
@@ -125,6 +147,23 @@ export const SEGMENT_GROUPS = [
|
|
|
125
147
|
// 옛 이름. 조각 이름을 직접 넘기던 자리(검사·설정)가 그대로 돌게 남긴다.
|
|
126
148
|
export const DEFAULT_SEGMENTS = SEGMENT_GROUPS.flat();
|
|
127
149
|
|
|
150
|
+
/**
|
|
151
|
+
* 이 급을 화면에 낼 값어치가 있나.
|
|
152
|
+
*
|
|
153
|
+
* '보통' 을 짐작으로 잡은 것은 **아무것도 못 알아낸 상태**다. 사내 게이트웨이가
|
|
154
|
+
* 그렇고, 그건 기본값이지 알아낸 사실이 아니다. 그걸 상태줄에 적으면 자리만
|
|
155
|
+
* 먹고, 좁은 터미널에서는 그 한 조각 때문에 승인 방식이 밀려 사라진다 —
|
|
156
|
+
* 내 파일이 안 물어보고 바뀌는지가 화면에서 없어지는 것이라 훨씬 비싸다.
|
|
157
|
+
*
|
|
158
|
+
* 알아낸 것이 있거나(짐작이 아님) 기본이 아닌 급일 때만 낸다.
|
|
159
|
+
*/
|
|
160
|
+
function 볼만한급(s) {
|
|
161
|
+
if (typeof s.급 !== 'function') return null;
|
|
162
|
+
const g = s.급();
|
|
163
|
+
if (g.급 === '보통' && g.짐작) return null;
|
|
164
|
+
return g;
|
|
165
|
+
}
|
|
166
|
+
|
|
128
167
|
// 컨텍스트 게이지가 쓰는 숫자. 자세한 꼴과 짧은 꼴이 같은 값을 봐야 한다.
|
|
129
168
|
function 참값(s) {
|
|
130
169
|
const b = s.breakdown();
|
|
@@ -167,9 +206,10 @@ export function statusLine(session, { segments = null, max = cols() - 2 } = {})
|
|
|
167
206
|
? segments.map((k) => [k])
|
|
168
207
|
: SEGMENT_GROUPS;
|
|
169
208
|
|
|
170
|
-
const 그리기 = (
|
|
209
|
+
const 그리기 = (짧게, 뺄것 = null) => 덩이들
|
|
171
210
|
.map((그룹) => 그룹
|
|
172
211
|
.map((k) => {
|
|
212
|
+
if (뺄것?.has(k)) return null;
|
|
173
213
|
const seg = SEGMENTS[k];
|
|
174
214
|
if (!seg) return null;
|
|
175
215
|
return 짧게 && seg.short ? seg.short(session) : seg.make(session);
|
|
@@ -194,7 +234,16 @@ export function statusLine(session, { segments = null, max = cols() - 2 } = {})
|
|
|
194
234
|
let parts = 그리기(false);
|
|
195
235
|
if (!맞나(parts)) parts = 그리기(true);
|
|
196
236
|
|
|
197
|
-
|
|
237
|
+
/*
|
|
238
|
+
* 그래도 모자라면 **급부터** 접는다.
|
|
239
|
+
*
|
|
240
|
+
* 뒤에서부터 떨구면 승인 방식이 먼저 사라진다. 모델 급은 알아 두면 좋은
|
|
241
|
+
* 것이고, 승인 방식은 지금 봐야 하는 것이다 — 내 파일이 안 물어보고 바뀌는지가
|
|
242
|
+
* 거기 적혀 있다. 둘 중 하나를 접어야 하면 언제나 이쪽이다.
|
|
243
|
+
*/
|
|
244
|
+
if (!맞나(parts) && !segments) parts = 그리기(true, new Set(['grade']));
|
|
245
|
+
|
|
246
|
+
// 여전히 모자라면 뒤에서부터 떨군다. 앞쪽(폴더·모델·컨텍스트)이 마지막까지 남는다.
|
|
198
247
|
while (parts.length > 1 && !맞나(parts)) parts.pop();
|
|
199
248
|
|
|
200
249
|
return ` ${c.gray('▏')}${parts.join(칸막이)}`;
|
|
@@ -212,6 +261,13 @@ export function contextWarning(session) {
|
|
|
212
261
|
/**
|
|
213
262
|
* 켤 때 한 번 그리는 머리말 상자 안쪽 줄들.
|
|
214
263
|
*/
|
|
264
|
+
/** 머리말 모델 줄에 붙는 급 표시. 못 매기면 아무것도 안 붙는다. */
|
|
265
|
+
function 급머리말(session) {
|
|
266
|
+
if (typeof session.급 !== 'function') return '';
|
|
267
|
+
const g = session.급();
|
|
268
|
+
return c.gray(` · 급 ${g.급}${g.짐작 ? ' 짐작' : ''}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
215
271
|
export function headerLines(session, found) {
|
|
216
272
|
const conn = session.conn;
|
|
217
273
|
const 능력 = [
|
|
@@ -232,7 +288,16 @@ export function headerLines(session, found) {
|
|
|
232
288
|
const lines = [
|
|
233
289
|
`${c.hcyan(c.bold('deel'))} ${c.gray(conn.kind === 'ollama' ? 'Ollama 규격' : 'OpenAI 호환 규격')}`,
|
|
234
290
|
'',
|
|
235
|
-
|
|
291
|
+
/*
|
|
292
|
+
* 모델 줄에 급을 같이 적는다.
|
|
293
|
+
*
|
|
294
|
+
* 창 크기(토큰)와 급은 다른 축인데, 켤 때 이 둘을 나란히 보지 않으면
|
|
295
|
+
* 나중에 상태줄의 `◈ 작음?` 이 무슨 뜻인지 알 길이 없다. 여기서 한 번
|
|
296
|
+
* 같이 읽고 나면 그 다음부터는 글자만 봐도 안다.
|
|
297
|
+
* 짐작이면 그렇다고 적는다 — 알아낸 사실과 같은 낯으로 내밀면 안 된다.
|
|
298
|
+
*/
|
|
299
|
+
`${c.gray('모델')} ${c.white(conn.model)} ${c.gray('(' + short(conn.ctx) + ' 토큰')}`
|
|
300
|
+
+ `${급머리말(session)}${c.gray(')')}`,
|
|
236
301
|
`${c.gray('보냄')} ${목적지} ${c.gray('← 여기 말고는 어디로도 안 갑니다')}`,
|
|
237
302
|
`${c.gray('연결')} ${능력}`,
|
|
238
303
|
`${c.gray('폴더')} ${c.white(clip(session.root, 56))}`,
|
package/src/ui/working.js
CHANGED
|
@@ -54,6 +54,12 @@ export const 문구 = {
|
|
|
54
54
|
'지난 대화 접는 중',
|
|
55
55
|
'자리 만드는 중',
|
|
56
56
|
],
|
|
57
|
+
// 하위 작업을 돌리는 중. 부모는 기다리기만 하니, 무엇을 기다리는지 말해 준다.
|
|
58
|
+
하위: [
|
|
59
|
+
'떼어 낸 일 맡기는 중',
|
|
60
|
+
'하위 작업 도는 중',
|
|
61
|
+
'한 덩이씩 끝내는 중',
|
|
62
|
+
],
|
|
57
63
|
// 무슨 일인지 아직 모를 때. 첫 걸음과 도구 사이의 짧은 틈이다.
|
|
58
64
|
기본: [
|
|
59
65
|
'열심히 일하는 중',
|
|
@@ -79,7 +85,8 @@ export const 문구 = {
|
|
|
79
85
|
const 도구갈래 = {
|
|
80
86
|
Read: '읽기', Glob: '읽기', Grep: '읽기', Skill: '읽기', Recall: '읽기',
|
|
81
87
|
Write: '쓰기', Edit: '쓰기', Append: '쓰기', Remember: '쓰기', TodoWrite: '쓰기',
|
|
82
|
-
Bash: '명령',
|
|
88
|
+
Bash: '명령', Jobs: '명령',
|
|
89
|
+
Task: '하위',
|
|
83
90
|
WebFetch: '웹',
|
|
84
91
|
};
|
|
85
92
|
|