deel-local-cli 1.1.0 → 1.2.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.en.md +347 -9
- package/README.md +317 -6
- package/package.json +2 -2
- package/src/agent/budget.js +0 -14
- package/src/agent/compact.js +87 -0
- package/src/agent/evolve.js +213 -0
- package/src/agent/loop.js +38 -1
- package/src/agent/modes.js +2 -0
- package/src/agent/route.js +55 -3
- package/src/agent/session.js +145 -1
- package/src/agent/threads.js +154 -0
- package/src/backend/http.js +41 -4
- package/src/commands.js +268 -4
- package/src/config.js +10 -1
- package/src/oneshot.js +4 -0
- package/src/preview/serve.js +326 -0
- package/src/repl.js +210 -34
- package/src/safety/guard.js +37 -2
- package/src/safety/undo.js +1 -1
- package/src/setup.js +2 -2
- 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 +18 -3
- package/src/tools/edit-match.js +16 -5
- package/src/tools/excel-com.js +1 -1
- package/src/tools/excel.js +1 -1
- package/src/tools/fsutil.js +0 -8
- package/src/tools/index.js +1 -1
- package/src/tools/todo.js +19 -3
- package/src/ui/ansi.js +6 -2
- package/src/ui/inputbox.js +37 -8
- package/src/ui/md.js +227 -0
- package/src/ui/motion.js +212 -0
- package/src/ui/prompt.js +1 -1
- package/src/ui/screen.js +1 -1
- package/src/ui/status.js +14 -1
- package/src/ui/working.js +0 -3
- package/src/ui/wrap.js +1 -8
package/src/ui/md.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// 모델 답을 사람이 읽는 모양으로 그린다.
|
|
2
|
+
//
|
|
3
|
+
// ── 왜 만드나 ───────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// 여태 답을 **글자 그대로** 찍었다. 모델은 마크다운으로 말하는데 화면은 그걸
|
|
6
|
+
// 모르니, 실제로 이렇게 보였다.
|
|
7
|
+
//
|
|
8
|
+
// ▌ ## 고친 것
|
|
9
|
+
// ▌ **src/runner.js** 의 `console.log` 를 바꿨습니다.
|
|
10
|
+
// ▌ - 로그 형식 통일
|
|
11
|
+
// ▌ ```js
|
|
12
|
+
// ▌ log.info('x')
|
|
13
|
+
// ▌ ```
|
|
14
|
+
//
|
|
15
|
+
// 별표와 백틱과 우물정자가 글에 섞여 눈이 걸린다. 무엇이 제목이고 무엇이
|
|
16
|
+
// 코드인지 사람이 머릿속에서 다시 파싱해야 한다. 답이 길수록 심해진다.
|
|
17
|
+
//
|
|
18
|
+
// ── 왜 줄 단위인가 ─────────────────────────────────────────────────────
|
|
19
|
+
//
|
|
20
|
+
// 답은 토막토막 흘러온다. `**굵` 까지 왔을 때는 그게 굵은 글씨가 될지 알 수 없다.
|
|
21
|
+
// 한 번 찍은 글자는 되돌릴 수 없으니, **줄이 끝나야** 그릴 수 있다.
|
|
22
|
+
//
|
|
23
|
+
// 그런데 줄이 안 끝나면? 긴 문단 하나가 통째로 한 줄이면 몇 초 동안 화면에
|
|
24
|
+
// 아무것도 안 나온다 — 사람 눈에는 멈춘 것으로 보인다. 그래서 한 줄이 화면 폭을
|
|
25
|
+
// 넘게 길어지면 거기까지를 **날것 그대로** 흘려보낸다. 모양은 못 내지만 글은
|
|
26
|
+
// 계속 흐른다. 모양보다 '살아 있다' 가 먼저다.
|
|
27
|
+
//
|
|
28
|
+
// 실제로는 제목·목록·코드펜스가 다 짧은 줄이라 거의 다 그려진다. 안 그려지는
|
|
29
|
+
// 것은 긴 산문뿐인데, 산문은 원래 꾸밀 것이 없다.
|
|
30
|
+
import { c, width } from './ansi.js';
|
|
31
|
+
|
|
32
|
+
const 굵게 = (s) => c.bold(s);
|
|
33
|
+
const 코드 = (s) => c.hyellow(s);
|
|
34
|
+
|
|
35
|
+
// 줄 전체를 흐리게 할 때 쓴다.
|
|
36
|
+
//
|
|
37
|
+
// 그냥 c.gray() 로 감싸면 안 된다. 안쪽 꾸밈(링크·코드)이 제 색을 끝내면서
|
|
38
|
+
// 리셋을 남기는데, 그 리셋이 바깥 흐림까지 같이 꺼 버린다 — 한 줄이 반만
|
|
39
|
+
// 흐려 보인다. 그래서 안쪽이 색을 끝낼 때마다 흐림을 다시 켠다.
|
|
40
|
+
const 회색앞 = c.gray('·').replace(/·[\s\S]*$/, '');
|
|
41
|
+
const 이어흐리게 = (s) => c.gray(String(s).replace(/\x1b\[0m/g, `\x1b[0m${회색앞}`));
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 한 줄 안쪽 꾸밈. `코드` → **굵게** → *기울임* → ~~취소~~ → [글](주소) 차례.
|
|
45
|
+
*
|
|
46
|
+
* 코드부터 하는 이유: 코드 안에 별표가 들어 있으면(`a ** b`) 굵게 처리가 먼저
|
|
47
|
+
* 먹어서 코드가 깨진다. 먼저 뽑아서 자리표시자로 빼 두고 맨 끝에 되돌린다.
|
|
48
|
+
*/
|
|
49
|
+
export function 인라인(글) {
|
|
50
|
+
// 들어온 NUL 은 먼저 버린다. 아래에서 코드 조각을 빼 둘 때 NUL 을 자리표시자로
|
|
51
|
+
// 쓰는데, 답에 NUL 이 섞여 오면(도구가 읽은 바이너리를 되읊는 경우) 그것이
|
|
52
|
+
// 자리표시자로 읽혀 엉뚱한 글자로 바뀐다 — 모델이 한 말과 화면이 달라진다.
|
|
53
|
+
let s = String(글 ?? '').replace(/\x00/g, '');
|
|
54
|
+
const 뺀것 = [];
|
|
55
|
+
s = s.replace(/`([^`]+)`/g, (_, 안) => {
|
|
56
|
+
뺀것.push(코드(안));
|
|
57
|
+
return `\x00${뺀것.length - 1}\x00`;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
s = s.replace(/\*\*([^*]+)\*\*/g, (_, 안) => 굵게(안));
|
|
61
|
+
s = s.replace(/(^|[\s(])\*([^*\s][^*]*)\*(?=$|[\s.,)])/g, (_, 앞, 안) => `${앞}${c.italic(안)}`);
|
|
62
|
+
s = s.replace(/~~([^~]+)~~/g, (_, 안) => c.gray(안));
|
|
63
|
+
// 링크는 글만 보이고 주소는 흐리게. 주소를 지우면 따라갈 수가 없다.
|
|
64
|
+
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, 글자, 주소) => `${c.under(글자)} ${c.gray(주소)}`);
|
|
65
|
+
|
|
66
|
+
return s.replace(/\x00(\d+)\x00/g, (_, i) => 뺀것[Number(i)]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 줄 하나를 그린다.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} 줄
|
|
73
|
+
* @param {object} 상태 {펜스: boolean} — 코드 블록 안인가
|
|
74
|
+
* @param {number} 폭
|
|
75
|
+
* @returns {{글: string, 상태: object}}
|
|
76
|
+
*/
|
|
77
|
+
export function 한줄(줄, 상태 = { 펜스: false }, 폭 = 80) {
|
|
78
|
+
// 줄 끝의 \r 를 먼저 뗀다. 서버가 \r\n 으로 흘려보내면 여기까지 딸려 오는데,
|
|
79
|
+
// 아래 정규식들이 다 `(.*)$` 로 끝나고 `.` 은 \r 를 안 먹는다 — 그래서 제목도
|
|
80
|
+
// 목록도 하나도 안 잡히고 별표와 우물정자가 그대로 화면에 뜬다.
|
|
81
|
+
const s = String(줄 ?? '').replace(/\r+$/, '');
|
|
82
|
+
|
|
83
|
+
// 코드 울타리. 여는 줄에 언어가 붙어 있으면 그걸 알려 준다.
|
|
84
|
+
const 울타리 = s.match(/^\s*```(.*)$/);
|
|
85
|
+
if (울타리) {
|
|
86
|
+
const 안쪽 = 울타리[1].trim();
|
|
87
|
+
return {
|
|
88
|
+
글: 상태.펜스 ? c.gray('└' + '─'.repeat(Math.max(2, Math.min(30, 폭 - 6))))
|
|
89
|
+
: c.gray('┌' + '─'.repeat(Math.max(2, Math.min(30, 폭 - 6))) + (안쪽 ? ` ${안쪽}` : '')),
|
|
90
|
+
상태: { ...상태, 펜스: !상태.펜스 },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 코드 안에서는 아무것도 해석하지 않는다. 별표는 별표고 우물정자는 우물정자다.
|
|
95
|
+
if (상태.펜스) return { 글: `${c.gray('│')} ${c.white(s)}`, 상태 };
|
|
96
|
+
|
|
97
|
+
// 가로줄
|
|
98
|
+
if (/^\s*([-*_])\s*\1\s*\1[\s\-*_]*$/.test(s)) {
|
|
99
|
+
return { 글: c.gray('─'.repeat(Math.max(4, Math.min(40, 폭 - 6)))), 상태 };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 제목. 굵기와 색으로 가른다 — 우물정자를 남기면 그게 제일 먼저 눈에 띈다.
|
|
103
|
+
const 제목 = s.match(/^(#{1,6})\s+(.*)$/);
|
|
104
|
+
if (제목) {
|
|
105
|
+
const 깊이 = 제목[1].length;
|
|
106
|
+
const 글자 = 인라인(제목[2]);
|
|
107
|
+
if (깊이 <= 2) return { 글: `${c.hcyan('▍')} ${c.bold(글자)}`, 상태 };
|
|
108
|
+
return { 글: c.bold(글자), 상태 };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 인용
|
|
112
|
+
const 인용 = s.match(/^\s*>\s?(.*)$/);
|
|
113
|
+
if (인용) return { 글: `${c.gray('│')} ${이어흐리게(인라인(인용[1]))}`, 상태 };
|
|
114
|
+
|
|
115
|
+
// 할 일 목록은 목록보다 먼저 본다 — `- [ ]` 가 그냥 목록으로 먹히면 안 된다.
|
|
116
|
+
const 할일 = s.match(/^(\s*)[-*+]\s+\[([ xX])\]\s+(.*)$/);
|
|
117
|
+
if (할일) {
|
|
118
|
+
const 표 = 할일[2] === ' ' ? c.gray('☐') : c.green('☑');
|
|
119
|
+
return { 글: `${할일[1]}${표} ${인라인(할일[3])}`, 상태 };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 글머리표
|
|
123
|
+
const 목록 = s.match(/^(\s*)[-*+]\s+(.*)$/);
|
|
124
|
+
if (목록) return { 글: `${목록[1]}${c.hcyan('•')} ${인라인(목록[2])}`, 상태 };
|
|
125
|
+
|
|
126
|
+
// 번호 목록. 번호는 살린다 — 순서가 뜻을 갖는 자리다.
|
|
127
|
+
const 번호 = s.match(/^(\s*)(\d+)([.)])\s+(.*)$/);
|
|
128
|
+
if (번호) return { 글: `${번호[1]}${c.hcyan(번호[2] + 번호[3])} ${인라인(번호[4])}`, 상태 };
|
|
129
|
+
|
|
130
|
+
// 표는 세로줄만 흐리게 한다. 칸을 맞추려면 표 전체를 들고 있어야 하는데,
|
|
131
|
+
// 그러면 표가 끝날 때까지 화면에 아무것도 안 나온다.
|
|
132
|
+
if (/^\s*\|.*\|\s*$/.test(s)) {
|
|
133
|
+
// 가름줄(`|---|---|`). 그대로 두면 화면에 작대기가 그대로 뜬다.
|
|
134
|
+
// 바로 앞 머리줄의 칸 너비를 기억해 뒀다가 그 자리에 맞춰 긋는다 —
|
|
135
|
+
// 그래야 세로줄과 가로줄이 만나서 표처럼 보인다.
|
|
136
|
+
if (/^\s*\|[\s:|-]+\|\s*$/.test(s)) {
|
|
137
|
+
const 너비 = 상태.표너비 ?? s.split('|').map(() => 3);
|
|
138
|
+
return { 글: c.gray(너비.map((w) => '─'.repeat(w)).join('┼')), 상태 };
|
|
139
|
+
}
|
|
140
|
+
// 칸에 먼저 꾸밈을 먹이고 그 다음에 잇는다. 순서가 반대면 색 제어문자가
|
|
141
|
+
// 이미 낀 글에 정규식을 돌리게 되어 칸이 통째로 망가진다.
|
|
142
|
+
const 칸들 = s.split('|').map((칸) => 인라인(칸));
|
|
143
|
+
return { 글: 칸들.join(c.gray('│')), 상태: { ...상태, 표너비: 칸들.map((칸) => width(칸)) } };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { 글: 인라인(s), 상태 };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 흘러오는 토막을 받아 그릴 수 있게 된 줄만 내놓는다.
|
|
151
|
+
*
|
|
152
|
+
* 쓰는 쪽은 이렇게 한다:
|
|
153
|
+
* for (const 줄 of md.넣기(토막)) 화면에 찍는다
|
|
154
|
+
* ... 답이 끝나면
|
|
155
|
+
* for (const 줄 of md.끝()) 화면에 찍는다
|
|
156
|
+
*/
|
|
157
|
+
export class 마크다운 {
|
|
158
|
+
constructor({ 폭 = 80 } = {}) {
|
|
159
|
+
// 창이 좁으면 부르는 쪽이 (columns - 6) 을 넘기는데, 그게 0 이나 음수가 될 수
|
|
160
|
+
// 있다. 그러면 모든 줄이 '폭을 넘는다' 가 되어 **아무것도 안 그려진다.**
|
|
161
|
+
// 바닥을 둬서, 좁은 창에서도 짧은 줄은 제 모양으로 나가게 한다.
|
|
162
|
+
this.폭 = Math.max(20, Number(폭) || 0);
|
|
163
|
+
this.담긴것 = '';
|
|
164
|
+
this.상태 = { 펜스: false };
|
|
165
|
+
// 이 줄은 이미 날것으로 흘려보내기 시작했다 — 뒤늦게 꾸미면 앞뒤가 안 맞는다.
|
|
166
|
+
this.날것중 = false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#그리기(줄) {
|
|
170
|
+
// 이 줄은 이미 날것으로 나가기 시작했다. 뒤늦게 꾸미면 앞뒤가 안 맞으므로
|
|
171
|
+
// 남은 만큼을 그대로 이어 붙이고 줄만 끝낸다.
|
|
172
|
+
if (this.날것중) { this.날것중 = false; return { 이어붙임: String(줄).replace(/\r/g, ''), 끝났나: true }; }
|
|
173
|
+
const r = 한줄(줄, this.상태, this.폭);
|
|
174
|
+
this.상태 = r.상태;
|
|
175
|
+
return r.글;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
넣기(토막) {
|
|
179
|
+
this.담긴것 += String(토막 ?? '');
|
|
180
|
+
const 나갈것 = [];
|
|
181
|
+
|
|
182
|
+
for (;;) {
|
|
183
|
+
const i = this.담긴것.indexOf('\n');
|
|
184
|
+
if (i >= 0) {
|
|
185
|
+
// \r\n 으로 온 줄은 여기서 \r 를 뗀다. 남겨서 내보내면 터미널에서 커서가
|
|
186
|
+
// 줄 맨 앞으로 돌아가 앞에 세운 세로줄 위에 덮어쓴다 — 글자가 먹힌 것처럼
|
|
187
|
+
// 보인다. 토막이 \r 와 \n 사이에서 끊겨 와도 여기로 모여서 걸린다.
|
|
188
|
+
나갈것.push(this.#그리기(this.담긴것.slice(0, i)));
|
|
189
|
+
this.담긴것 = this.담긴것.slice(i + 1);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
// 줄이 안 끝나는데 너무 길어졌다. 여기까지 날것으로 흘려보낸다 —
|
|
193
|
+
// 멈춘 것처럼 보이는 것보다 안 꾸며진 글이 흐르는 편이 낫다.
|
|
194
|
+
if (width(this.담긴것) > this.폭) {
|
|
195
|
+
// 날것으로 나가는 길에는 한줄() 이 안 끼므로 여기서 \r 를 턴다.
|
|
196
|
+
나갈것.push({ 이어붙임: this.담긴것.replace(/\r/g, ''), 첫조각: !this.날것중, 끝났나: false });
|
|
197
|
+
this.담긴것 = '';
|
|
198
|
+
this.날것중 = true;
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
return 나갈것;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** 답이 끝났다. 남은 것을 내놓는다. */
|
|
206
|
+
끝() {
|
|
207
|
+
// 담긴 것은 없는데 날것으로 나가던 중이면 줄만 끝내 준다.
|
|
208
|
+
if (!this.담긴것) {
|
|
209
|
+
const 닫을것 = this.날것중 ? [{ 이어붙임: '', 끝났나: true }] : [];
|
|
210
|
+
this.날것중 = false;
|
|
211
|
+
return 닫을것;
|
|
212
|
+
}
|
|
213
|
+
const 남은것 = this.#그리기(this.담긴것);
|
|
214
|
+
this.담긴것 = '';
|
|
215
|
+
this.날것중 = false;
|
|
216
|
+
return [남은것];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** 통째로 있는 글을 한 번에 그린다 (스트리밍이 아닌 자리). */
|
|
221
|
+
export function 그리기(글, 폭 = 80) {
|
|
222
|
+
const md = new 마크다운({ 폭 });
|
|
223
|
+
const 줄들 = [];
|
|
224
|
+
for (const x of md.넣기(String(글 ?? ''))) 줄들.push(typeof x === 'string' ? x : x.이어붙임);
|
|
225
|
+
for (const x of md.끝()) 줄들.push(typeof x === 'string' ? x : x.이어붙임);
|
|
226
|
+
return 줄들;
|
|
227
|
+
}
|
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/prompt.js
CHANGED
package/src/ui/screen.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* 대화는 그냥 흘려보내기로 했다. 그러면 stdout 에 쓰는 모든 코드가 손 안 대고
|
|
31
31
|
* 그대로 맞다. 스크롤·복사·`| tee` 도 그대로다.
|
|
32
32
|
*/
|
|
33
|
-
import { c, say, cursor, box
|
|
33
|
+
import { c, say, cursor, box } from './ansi.js';
|
|
34
34
|
import { statusLine, contextWarning } from './status.js';
|
|
35
35
|
import { spin } from './spinner.js';
|
|
36
36
|
|
package/src/ui/status.js
CHANGED
|
@@ -15,6 +15,19 @@ export const SEGMENTS = {
|
|
|
15
15
|
// 이 조각 하나가 상태줄을 통째로 넘겨 줄이 접힌다(모델 이름은 이미 자르고 있었다).
|
|
16
16
|
dir: { desc: '작업 폴더', make: (s) => c.gray(clip(base(s.root), 20)) },
|
|
17
17
|
|
|
18
|
+
/*
|
|
19
|
+
* 지금 어느 대화 갈래인가.
|
|
20
|
+
*
|
|
21
|
+
* 갈래를 안 쓰면 **아무것도 안 그린다.** 상태줄은 이미 빽빽해서, 안 쓰는
|
|
22
|
+
* 사람에게까지 한 칸을 더 내주면 좁은 터미널에서 뒤엣것이 떨어져 나간다.
|
|
23
|
+
* 갈래가 둘 이상일 때만 repl 이 session.갈래표 를 채운다(agent/threads.js).
|
|
24
|
+
*/
|
|
25
|
+
thread: {
|
|
26
|
+
desc: '대화 갈래',
|
|
27
|
+
make: (s) => (s.갈래표 ? c.hcyan(`⑂ ${clip(s.갈래표, 12)}`) : null),
|
|
28
|
+
short: (s) => (s.갈래표 ? c.hcyan(`⑂ ${clip(s.갈래표, 6)}`) : null),
|
|
29
|
+
},
|
|
30
|
+
|
|
18
31
|
/*
|
|
19
32
|
* 모델 이름.
|
|
20
33
|
*
|
|
@@ -138,7 +151,7 @@ export const SEGMENTS = {
|
|
|
138
151
|
* 그러면 눈이 세 번만 멈춘다.
|
|
139
152
|
*/
|
|
140
153
|
export const SEGMENT_GROUPS = [
|
|
141
|
-
['dir', 'model'],
|
|
154
|
+
['dir', 'thread', 'model'],
|
|
142
155
|
['ctx', 'grade'],
|
|
143
156
|
['work', 'think', 'mode'],
|
|
144
157
|
['tok'],
|
package/src/ui/working.js
CHANGED
|
@@ -109,9 +109,6 @@ export function 문구고르기(갈래, 회차 = 0) {
|
|
|
109
109
|
return 목록[i];
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
// 돌아가는 표시. 점자 여덟 칸짜리는 폭이 한 칸이라 테두리를 안 밀어낸다.
|
|
113
|
-
export const 돌림틀 = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
114
|
-
|
|
115
112
|
/** 걸린 시간을 짧게. 1분이 넘으면 분까지 적는다 — 90초보다 1분 30초가 읽힌다. */
|
|
116
113
|
export function 걸린시간(ms) {
|
|
117
114
|
const 초 = Math.max(0, Math.floor(ms / 1000));
|
package/src/ui/wrap.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// 전에는 전체화면(tui.js)에만 쓰던 것이라 거기 있었다. 전체화면을 걷어내면서
|
|
4
4
|
// 여기로 옮긴다 — 입력 상자가 긴 입력을 접을 때 같은 것이 필요하다.
|
|
5
|
-
import { width
|
|
5
|
+
import { width } from './ansi.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 색을 유지하면서 폭에 맞춰 접는다.
|
|
@@ -59,10 +59,3 @@ export function 접어쓰기(글, 폭) {
|
|
|
59
59
|
if (지금.trim() !== '' || 줄들.length === 0) 줄들.push(지금 + (색 ? '\x1b[0m' : ''));
|
|
60
60
|
return 줄들;
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
/** 폭에 맞춰 오른쪽을 공백으로 채운다. 안 채우면 앞 그림이 남는다. */
|
|
64
|
-
export function 채움(글, 폭) {
|
|
65
|
-
const w = width(글);
|
|
66
|
-
if (w > 폭) return clip(글, 폭);
|
|
67
|
-
return 글 + ' '.repeat(폭 - w);
|
|
68
|
-
}
|