deel-local-cli 1.0.1 → 1.1.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 +520 -11
- package/README.md +548 -11
- 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 +110 -29
- package/src/agent/project.js +171 -0
- package/src/agent/session.js +131 -12
- package/src/backend/learn.js +46 -4
- package/src/commands.js +102 -4
- package/src/oneshot.js +390 -327
- package/src/repl.js +1074 -787
- package/src/safety/audit.js +3 -1
- 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/approve.js +66 -0
- package/src/ui/complete.js +89 -0
- package/src/ui/inputbox.js +221 -4
- package/src/ui/screen.js +108 -10
- package/src/ui/status.js +98 -10
- package/src/ui/working.js +125 -0
package/src/ui/status.js
CHANGED
|
@@ -6,6 +6,7 @@ import { c, gauge, width, clip, cols, mark } from './ansi.js';
|
|
|
6
6
|
import { PROFILES } from '../agent/effort.js';
|
|
7
7
|
import { get as workMode, canWrite } from '../agent/modes.js';
|
|
8
8
|
import { isLocalHost, isOffline } from '../safety/network.js';
|
|
9
|
+
import { 표시 as 승인표시, 고르기 as 승인고르기 } from './approve.js';
|
|
9
10
|
|
|
10
11
|
// 한 조각씩 따로 만든다. 좁은 화면에서는 뒤에서부터 떨군다.
|
|
11
12
|
// 순서 = 중요도 순. 앞쪽이 끝까지 살아남는다.
|
|
@@ -14,7 +15,42 @@ export const SEGMENTS = {
|
|
|
14
15
|
// 이 조각 하나가 상태줄을 통째로 넘겨 줄이 접힌다(모델 이름은 이미 자르고 있었다).
|
|
15
16
|
dir: { desc: '작업 폴더', make: (s) => c.gray(clip(base(s.root), 20)) },
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
/*
|
|
19
|
+
* 모델 이름.
|
|
20
|
+
*
|
|
21
|
+
* 좁을 때 더 줄인다. 사내 게이트웨이 이름은 `databricks-gpt-5-6-luna` 처럼
|
|
22
|
+
* 스물세 칸을 그냥 먹는데, 자리가 없을 때 이것 하나 때문에 **셋째 덩이가
|
|
23
|
+
* 통째로 떨어져 나간다** — 승인 방식이 화면에서 사라진다는 뜻이다.
|
|
24
|
+
* 무엇으로 돌고 있는지는 이미 알고 있다. 내 파일이 안 물어보고 바뀌는지는
|
|
25
|
+
* 지금 봐야 안다. 둘 중 하나를 접어야 하면 이쪽이다.
|
|
26
|
+
*/
|
|
27
|
+
model: {
|
|
28
|
+
desc: '모델 이름',
|
|
29
|
+
make: (s) => c.hcyan(clip(s.conn.model, 24)),
|
|
30
|
+
short: (s) => c.hcyan(clip(s.conn.model, 14)),
|
|
31
|
+
},
|
|
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
|
+
},
|
|
18
54
|
|
|
19
55
|
ctx: {
|
|
20
56
|
desc: '컨텍스트 사용량',
|
|
@@ -54,12 +90,17 @@ export const SEGMENTS = {
|
|
|
54
90
|
short: (s) => `${c.gray('◇')} ${c.white(s.think)}`,
|
|
55
91
|
},
|
|
56
92
|
|
|
93
|
+
/*
|
|
94
|
+
* 승인 방식 — 내 파일이 물어보고 바뀌는가, 안 물어보고 바뀌는가.
|
|
95
|
+
*
|
|
96
|
+
* 전에는 `auto` 라는 영문 한 낱말이었다. 옆에 `종합`·`medium·절약` 이 나란히
|
|
97
|
+
* 있으니 셋 다 그냥 '모드' 로 보였고, 그중 하나가 **묻지 않고 파일을 고친다**
|
|
98
|
+
* 는 뜻이라는 것은 화면 어디에도 없었다. 이건 꾸미기가 아니라 안전 표시다.
|
|
99
|
+
*/
|
|
57
100
|
mode: {
|
|
58
|
-
desc: '
|
|
59
|
-
make: (s) =>
|
|
60
|
-
|
|
61
|
-
return tone(s.mode);
|
|
62
|
-
},
|
|
101
|
+
desc: '승인 방식',
|
|
102
|
+
make: (s) => 승인표시(s.mode),
|
|
103
|
+
short: (s) => 승인표시(s.mode, { 짧게: true }),
|
|
63
104
|
},
|
|
64
105
|
|
|
65
106
|
tok: {
|
|
@@ -98,7 +139,7 @@ export const SEGMENTS = {
|
|
|
98
139
|
*/
|
|
99
140
|
export const SEGMENT_GROUPS = [
|
|
100
141
|
['dir', 'model'],
|
|
101
|
-
['ctx'],
|
|
142
|
+
['ctx', 'grade'],
|
|
102
143
|
['work', 'think', 'mode'],
|
|
103
144
|
['tok'],
|
|
104
145
|
];
|
|
@@ -106,6 +147,23 @@ export const SEGMENT_GROUPS = [
|
|
|
106
147
|
// 옛 이름. 조각 이름을 직접 넘기던 자리(검사·설정)가 그대로 돌게 남긴다.
|
|
107
148
|
export const DEFAULT_SEGMENTS = SEGMENT_GROUPS.flat();
|
|
108
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
|
+
|
|
109
167
|
// 컨텍스트 게이지가 쓰는 숫자. 자세한 꼴과 짧은 꼴이 같은 값을 봐야 한다.
|
|
110
168
|
function 참값(s) {
|
|
111
169
|
const b = s.breakdown();
|
|
@@ -148,9 +206,10 @@ export function statusLine(session, { segments = null, max = cols() - 2 } = {})
|
|
|
148
206
|
? segments.map((k) => [k])
|
|
149
207
|
: SEGMENT_GROUPS;
|
|
150
208
|
|
|
151
|
-
const 그리기 = (
|
|
209
|
+
const 그리기 = (짧게, 뺄것 = null) => 덩이들
|
|
152
210
|
.map((그룹) => 그룹
|
|
153
211
|
.map((k) => {
|
|
212
|
+
if (뺄것?.has(k)) return null;
|
|
154
213
|
const seg = SEGMENTS[k];
|
|
155
214
|
if (!seg) return null;
|
|
156
215
|
return 짧게 && seg.short ? seg.short(session) : seg.make(session);
|
|
@@ -175,7 +234,16 @@ export function statusLine(session, { segments = null, max = cols() - 2 } = {})
|
|
|
175
234
|
let parts = 그리기(false);
|
|
176
235
|
if (!맞나(parts)) parts = 그리기(true);
|
|
177
236
|
|
|
178
|
-
|
|
237
|
+
/*
|
|
238
|
+
* 그래도 모자라면 **급부터** 접는다.
|
|
239
|
+
*
|
|
240
|
+
* 뒤에서부터 떨구면 승인 방식이 먼저 사라진다. 모델 급은 알아 두면 좋은
|
|
241
|
+
* 것이고, 승인 방식은 지금 봐야 하는 것이다 — 내 파일이 안 물어보고 바뀌는지가
|
|
242
|
+
* 거기 적혀 있다. 둘 중 하나를 접어야 하면 언제나 이쪽이다.
|
|
243
|
+
*/
|
|
244
|
+
if (!맞나(parts) && !segments) parts = 그리기(true, new Set(['grade']));
|
|
245
|
+
|
|
246
|
+
// 여전히 모자라면 뒤에서부터 떨군다. 앞쪽(폴더·모델·컨텍스트)이 마지막까지 남는다.
|
|
179
247
|
while (parts.length > 1 && !맞나(parts)) parts.pop();
|
|
180
248
|
|
|
181
249
|
return ` ${c.gray('▏')}${parts.join(칸막이)}`;
|
|
@@ -193,6 +261,13 @@ export function contextWarning(session) {
|
|
|
193
261
|
/**
|
|
194
262
|
* 켤 때 한 번 그리는 머리말 상자 안쪽 줄들.
|
|
195
263
|
*/
|
|
264
|
+
/** 머리말 모델 줄에 붙는 급 표시. 못 매기면 아무것도 안 붙는다. */
|
|
265
|
+
function 급머리말(session) {
|
|
266
|
+
if (typeof session.급 !== 'function') return '';
|
|
267
|
+
const g = session.급();
|
|
268
|
+
return c.gray(` · 급 ${g.급}${g.짐작 ? ' 짐작' : ''}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
196
271
|
export function headerLines(session, found) {
|
|
197
272
|
const conn = session.conn;
|
|
198
273
|
const 능력 = [
|
|
@@ -213,10 +288,23 @@ export function headerLines(session, found) {
|
|
|
213
288
|
const lines = [
|
|
214
289
|
`${c.hcyan(c.bold('deel'))} ${c.gray(conn.kind === 'ollama' ? 'Ollama 규격' : 'OpenAI 호환 규격')}`,
|
|
215
290
|
'',
|
|
216
|
-
|
|
291
|
+
/*
|
|
292
|
+
* 모델 줄에 급을 같이 적는다.
|
|
293
|
+
*
|
|
294
|
+
* 창 크기(토큰)와 급은 다른 축인데, 켤 때 이 둘을 나란히 보지 않으면
|
|
295
|
+
* 나중에 상태줄의 `◈ 작음?` 이 무슨 뜻인지 알 길이 없다. 여기서 한 번
|
|
296
|
+
* 같이 읽고 나면 그 다음부터는 글자만 봐도 안다.
|
|
297
|
+
* 짐작이면 그렇다고 적는다 — 알아낸 사실과 같은 낯으로 내밀면 안 된다.
|
|
298
|
+
*/
|
|
299
|
+
`${c.gray('모델')} ${c.white(conn.model)} ${c.gray('(' + short(conn.ctx) + ' 토큰')}`
|
|
300
|
+
+ `${급머리말(session)}${c.gray(')')}`,
|
|
217
301
|
`${c.gray('보냄')} ${목적지} ${c.gray('← 여기 말고는 어디로도 안 갑니다')}`,
|
|
218
302
|
`${c.gray('연결')} ${능력}`,
|
|
219
303
|
`${c.gray('폴더')} ${c.white(clip(session.root, 56))}`,
|
|
304
|
+
// 켤 때 한 번은 사람 말로 알려 준다. 상태줄의 `⏵⏵ 자동 승인` 이 무슨
|
|
305
|
+
// 뜻인지 여기서 한 번 읽고 나면 그 다음부터는 글자만 봐도 안다.
|
|
306
|
+
`${c.gray('승인')} ${승인표시(session.mode)} ${c.gray('— ' + 승인고르기(session.mode).한줄)}`,
|
|
307
|
+
`${c.gray(' Shift+Tab')} ${c.gray('으로 바꿉니다 · ')}${c.gray('Tab 은 치던 / 명령을 채웁니다')}`,
|
|
220
308
|
];
|
|
221
309
|
if (found.skills.length || found.commands.length) {
|
|
222
310
|
lines.push(`${c.gray('이 PC')} ${c.white(`스킬 ${found.skills.length}`)}${c.gray(' · ')}${c.white(`명령 ${found.commands.length}`)}${c.gray(' · ')}${c.white(`플러그인 ${found.plugins.length}`)}`);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 일하는 중에 띄울 문구.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 아무 말이나 안 쓰나 ──────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* 재미 때문에 넣는 것이 아니다. 로컬 모델은 느리다 — 7B 라도 한 걸음에 수십
|
|
7
|
+
* 초가 걸리고, 그 동안 화면이 조용하면 사람은 **멈춘 줄 안다.** 실제로 Ctrl+C
|
|
8
|
+
* 를 눌러 버린다. 그러면 다 되어 가던 일이 날아간다.
|
|
9
|
+
*
|
|
10
|
+
* 그래서 문구는 **지금 진짜로 하는 일**을 따라간다. 파일을 읽는 중이면 읽는다고
|
|
11
|
+
* 하고, 명령을 돌리는 중이면 돌린다고 한다. 아무 말이나 돌려 대면 두 번째부터
|
|
12
|
+
* 아무도 안 읽고, 그때부터는 화면이 조용한 것과 같아진다.
|
|
13
|
+
*
|
|
14
|
+
* 한 갈래 안에서 여러 개를 돌리는 것은 다른 이유다. 같은 글자가 30초 동안
|
|
15
|
+
* 그대로 있으면 그것도 멈춘 것처럼 보인다. 돌아가는 표시만으로는 부족하다 —
|
|
16
|
+
* 터미널 렌더링이 밀리면 그것도 안 움직이는 것처럼 보이기 때문이다.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// 갈래별 문구. 앞엣것부터 차례로 돌린다 — 아무거나 뽑으면 눈에 거슬린다.
|
|
20
|
+
export const 문구 = {
|
|
21
|
+
생각: [
|
|
22
|
+
'머리 굴리는 중',
|
|
23
|
+
'어떻게 할지 궁리하는 중',
|
|
24
|
+
'수 읽는 중',
|
|
25
|
+
'따져 보는 중',
|
|
26
|
+
'생각 정리하는 중',
|
|
27
|
+
],
|
|
28
|
+
읽기: [
|
|
29
|
+
'파일 들여다보는 중',
|
|
30
|
+
'코드 훑는 중',
|
|
31
|
+
'어디 있나 뒤지는 중',
|
|
32
|
+
'단서 찾는 중',
|
|
33
|
+
],
|
|
34
|
+
쓰기: [
|
|
35
|
+
'코드 짜는 중',
|
|
36
|
+
'고쳐 넣는 중',
|
|
37
|
+
'손보는 중',
|
|
38
|
+
'한 줄씩 옮기는 중',
|
|
39
|
+
],
|
|
40
|
+
명령: [
|
|
41
|
+
'명령 돌리는 중',
|
|
42
|
+
'터미널 두드리는 중',
|
|
43
|
+
'결과 기다리는 중',
|
|
44
|
+
],
|
|
45
|
+
웹: [
|
|
46
|
+
'문서 찾아보는 중',
|
|
47
|
+
'읽어 오는 중',
|
|
48
|
+
],
|
|
49
|
+
답: [
|
|
50
|
+
'답 쓰는 중',
|
|
51
|
+
'정리해서 말하는 중',
|
|
52
|
+
],
|
|
53
|
+
접기: [
|
|
54
|
+
'지난 대화 접는 중',
|
|
55
|
+
'자리 만드는 중',
|
|
56
|
+
],
|
|
57
|
+
// 하위 작업을 돌리는 중. 부모는 기다리기만 하니, 무엇을 기다리는지 말해 준다.
|
|
58
|
+
하위: [
|
|
59
|
+
'떼어 낸 일 맡기는 중',
|
|
60
|
+
'하위 작업 도는 중',
|
|
61
|
+
'한 덩이씩 끝내는 중',
|
|
62
|
+
],
|
|
63
|
+
// 무슨 일인지 아직 모를 때. 첫 걸음과 도구 사이의 짧은 틈이다.
|
|
64
|
+
기본: [
|
|
65
|
+
'열심히 일하는 중',
|
|
66
|
+
'바쁘게 움직이는 중',
|
|
67
|
+
'뭔가 하는 중',
|
|
68
|
+
],
|
|
69
|
+
/*
|
|
70
|
+
* 오래 걸릴 때.
|
|
71
|
+
*
|
|
72
|
+
* 45초가 넘어가면 갈래를 이걸로 바꾼다. '아직 하는 중' 이라고 말해 주는 것과
|
|
73
|
+
* 아까 하던 말을 계속 돌리는 것은 다르다 — 후자는 붙박이처럼 보인다.
|
|
74
|
+
* 여기서 중요한 것은 재치가 아니라 **아직 살아 있다는 신호**다.
|
|
75
|
+
*/
|
|
76
|
+
느긋: [
|
|
77
|
+
'아직 하는 중',
|
|
78
|
+
'조금만 더',
|
|
79
|
+
'생각보다 오래 걸리는 중',
|
|
80
|
+
'그래도 하는 중',
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// 도구 이름 → 갈래. 없는 이름은 기본으로 떨어진다.
|
|
85
|
+
const 도구갈래 = {
|
|
86
|
+
Read: '읽기', Glob: '읽기', Grep: '읽기', Skill: '읽기', Recall: '읽기',
|
|
87
|
+
Write: '쓰기', Edit: '쓰기', Append: '쓰기', Remember: '쓰기', TodoWrite: '쓰기',
|
|
88
|
+
Bash: '명령', Jobs: '명령',
|
|
89
|
+
Task: '하위',
|
|
90
|
+
WebFetch: '웹',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** 도구 이름으로 갈래를 고른다. 밖에서 붙인 도구(mcp__…)는 무엇을 할지 모른다. */
|
|
94
|
+
export function 갈래고르기(도구이름) {
|
|
95
|
+
if (!도구이름) return '기본';
|
|
96
|
+
if (String(도구이름).startsWith('mcp__')) return '기본';
|
|
97
|
+
return 도구갈래[도구이름] ?? '기본';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 갈래와 회차로 문구 하나. 회차가 늘면 다음 것으로 넘어간다.
|
|
102
|
+
*
|
|
103
|
+
* 시각이 아니라 회차를 받는 이유: 이래야 검사가 잴 수 있다. 시각을 안에서
|
|
104
|
+
* 읽으면 '4초 뒤에 바뀌는가' 를 재려고 검사가 진짜로 4초를 기다려야 한다.
|
|
105
|
+
*/
|
|
106
|
+
export function 문구고르기(갈래, 회차 = 0) {
|
|
107
|
+
const 목록 = 문구[갈래] ?? 문구.기본;
|
|
108
|
+
const i = ((Math.trunc(회차) % 목록.length) + 목록.length) % 목록.length;
|
|
109
|
+
return 목록[i];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 돌아가는 표시. 점자 여덟 칸짜리는 폭이 한 칸이라 테두리를 안 밀어낸다.
|
|
113
|
+
export const 돌림틀 = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
114
|
+
|
|
115
|
+
/** 걸린 시간을 짧게. 1분이 넘으면 분까지 적는다 — 90초보다 1분 30초가 읽힌다. */
|
|
116
|
+
export function 걸린시간(ms) {
|
|
117
|
+
const 초 = Math.max(0, Math.floor(ms / 1000));
|
|
118
|
+
if (초 < 60) return `${초}초`;
|
|
119
|
+
return `${Math.floor(초 / 60)}분 ${String(초 % 60).padStart(2, '0')}초`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 이 시간이 넘으면 '느긋' 갈래로 바꾼다.
|
|
123
|
+
export const 느긋해질때 = 45000;
|
|
124
|
+
// 문구를 바꾸는 주기.
|
|
125
|
+
export const 문구주기 = 4000;
|