deel-local-cli 1.2.0 → 1.4.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 +301 -1537
- package/README.md +272 -1582
- package/bin/deel.js +73 -4
- package/package.json +8 -3
- package/src/acp/jsonrpc.js +230 -0
- package/src/acp/map.js +219 -0
- package/src/acp/serve.js +556 -0
- package/src/agent/card.js +129 -0
- package/src/agent/compact.js +10 -2
- package/src/agent/effort.js +5 -0
- package/src/agent/evidence.js +186 -0
- package/src/agent/grade.js +20 -0
- package/src/agent/loop.js +134 -13
- package/src/agent/models.js +169 -0
- package/src/agent/modes.js +47 -1
- package/src/agent/pins.js +140 -0
- package/src/agent/preset.js +113 -0
- package/src/agent/project.js +10 -4
- package/src/agent/session.js +233 -13
- package/src/agent/store.js +31 -0
- package/src/backend/adapter.js +14 -0
- package/src/commands.js +616 -52
- package/src/i18n/en.js +265 -0
- package/src/i18n/index.js +126 -0
- package/src/i18n/ko.js +252 -0
- package/src/lsp/client.js +459 -0
- package/src/lsp/diag.js +112 -0
- package/src/lsp/rpc.js +84 -0
- package/src/lsp/servers.js +218 -0
- package/src/oneshot.js +19 -0
- package/src/pack/sbom.js +218 -0
- package/src/pack/selfpack.js +19 -3
- package/src/preview/serve.js +19 -2
- package/src/repl.js +193 -8
- package/src/safety/secrets.js +205 -0
- package/src/safety/undo.js +10 -3
- package/src/tools/desc.en.js +221 -0
- package/src/tools/docs.js +252 -0
- package/src/tools/index.js +190 -6
- package/src/tools/lsp.js +327 -0
- package/src/tools/task.js +30 -2
- package/src/ui/ansi.js +45 -0
- package/src/ui/approve.js +25 -21
- package/src/ui/banner.js +245 -0
- package/src/ui/export.js +217 -0
- package/src/ui/inputbox.js +37 -7
- package/src/ui/intro.js +206 -0
- package/src/ui/level.js +11 -5
- package/src/ui/notify.js +101 -0
- package/src/ui/status.js +181 -35
package/src/ui/status.js
CHANGED
|
@@ -2,18 +2,43 @@
|
|
|
2
2
|
//
|
|
3
3
|
// 남의 패키지를 붙이지 않는다. 필요한 숫자는 전부 session 이 이미 갖고 있고,
|
|
4
4
|
// 화면 그리기는 ansi.js 만 쓴다. 반입 심사에 새로 설명할 것이 늘지 않게 하려는 뜻이다.
|
|
5
|
-
import { c, gauge, width, clip, cols, mark } from './ansi.js';
|
|
5
|
+
import { c, gauge, 눈금게이지, width, clip, cols, mark } from './ansi.js';
|
|
6
6
|
import { PROFILES } from '../agent/effort.js';
|
|
7
|
-
import { get as workMode, canWrite } from '../agent/modes.js';
|
|
7
|
+
import { get as workMode, canWrite, 보일이름 } from '../agent/modes.js';
|
|
8
8
|
import { isLocalHost, isOffline } from '../safety/network.js';
|
|
9
|
+
import { COMPACT_AT, FOLD_AT } from '../agent/compact.js';
|
|
10
|
+
import { 말, 언어 } from '../i18n/index.js';
|
|
9
11
|
import { 표시 as 승인표시, 고르기 as 승인고르기 } from './approve.js';
|
|
10
12
|
|
|
11
13
|
// 한 조각씩 따로 만든다. 좁은 화면에서는 뒤에서부터 떨군다.
|
|
12
14
|
// 순서 = 중요도 순. 앞쪽이 끝까지 살아남는다.
|
|
13
15
|
export const SEGMENTS = {
|
|
16
|
+
/*
|
|
17
|
+
* 경계선 — 소스가 어디로 나가는가.
|
|
18
|
+
*
|
|
19
|
+
* 이 프로그램이 하는 말이 딱 하나다. **내 소스가 이 컴퓨터 밖으로 안 나간다.**
|
|
20
|
+
* 그런데 그 말이 켤 때 머리말에 한 번 뜨고는 스크롤에 밀려 사라졌다. 한 시간
|
|
21
|
+
* 뒤에 /model 로 사내 게이트웨이로 갈아탄 사람은, 화면 어디를 봐도 지금
|
|
22
|
+
* 소스가 밖으로 나가고 있다는 것을 알 수가 없었다.
|
|
23
|
+
*
|
|
24
|
+
* 그래서 한 글자를 상태줄 맨 앞에 박아 둔다. 한 칸이면 좁은 터미널에서도
|
|
25
|
+
* 안 밀린다 — 이건 밀리면 안 되는 것이다.
|
|
26
|
+
* ⌂ 이 컴퓨터 안
|
|
27
|
+
* ↗ 바깥으로 나간다
|
|
28
|
+
* 무슨 뜻인지는 켤 때 머리말에서 한 번 읽고 나면 그 다음부터는 글자만 봐도 안다.
|
|
29
|
+
*/
|
|
14
30
|
// 폴더 이름은 사람이 짓는다 — 한글로 길게 짓기도 한다. 안 자르면
|
|
15
31
|
// 이 조각 하나가 상태줄을 통째로 넘겨 줄이 접힌다(모델 이름은 이미 자르고 있었다).
|
|
16
|
-
|
|
32
|
+
//
|
|
33
|
+
// 경계 글자를 **폴더 앞에 붙여서** 낸다. 따로 조각을 두면 가운뎃점까지
|
|
34
|
+
// 네 칸을 먹는데, 100칸 터미널에서 그 네 칸 때문에 승인 방식이 짧은 이름
|
|
35
|
+
// ('위험만')으로 접혔다. 안전 표시를 흐리게 만드는 안전 표시는 손해다.
|
|
36
|
+
// 붙여 쓰면 두 칸이고, '어디 폴더에서 · 어디로 나가나' 가 한 덩이로 읽힌다.
|
|
37
|
+
dir: {
|
|
38
|
+
get desc() { return 말('seg.dir'); },
|
|
39
|
+
make: (s) => `${경계표(s)} ${c.gray(clip(base(s.root), 20))}`,
|
|
40
|
+
short: (s) => `${경계표(s)} ${c.gray(clip(base(s.root), 12))}`,
|
|
41
|
+
},
|
|
17
42
|
|
|
18
43
|
/*
|
|
19
44
|
* 지금 어느 대화 갈래인가.
|
|
@@ -23,7 +48,7 @@ export const SEGMENTS = {
|
|
|
23
48
|
* 갈래가 둘 이상일 때만 repl 이 session.갈래표 를 채운다(agent/threads.js).
|
|
24
49
|
*/
|
|
25
50
|
thread: {
|
|
26
|
-
desc
|
|
51
|
+
get desc() { return 말('seg.thread'); },
|
|
27
52
|
make: (s) => (s.갈래표 ? c.hcyan(`⑂ ${clip(s.갈래표, 12)}`) : null),
|
|
28
53
|
short: (s) => (s.갈래표 ? c.hcyan(`⑂ ${clip(s.갈래표, 6)}`) : null),
|
|
29
54
|
},
|
|
@@ -38,7 +63,7 @@ export const SEGMENTS = {
|
|
|
38
63
|
* 지금 봐야 안다. 둘 중 하나를 접어야 하면 이쪽이다.
|
|
39
64
|
*/
|
|
40
65
|
model: {
|
|
41
|
-
desc
|
|
66
|
+
get desc() { return 말('seg.model'); },
|
|
42
67
|
make: (s) => c.hcyan(clip(s.conn.model, 24)),
|
|
43
68
|
short: (s) => c.hcyan(clip(s.conn.model, 14)),
|
|
44
69
|
},
|
|
@@ -54,34 +79,51 @@ export const SEGMENTS = {
|
|
|
54
79
|
* 사람은 프로그램이 확인한 사실이라고 읽는다.
|
|
55
80
|
*/
|
|
56
81
|
grade: {
|
|
57
|
-
desc
|
|
82
|
+
get desc() { return 말('seg.grade'); },
|
|
58
83
|
make: (s) => {
|
|
59
84
|
const g = 볼만한급(s);
|
|
60
|
-
|
|
85
|
+
if (!g) return '';
|
|
86
|
+
const 이름 = 급이름(g.급);
|
|
87
|
+
return g.짐작 ? c.gray(`◈ ${이름}?`) : c.white(`◈ ${이름}`);
|
|
61
88
|
},
|
|
62
89
|
short: (s) => {
|
|
63
90
|
const g = 볼만한급(s);
|
|
64
|
-
|
|
91
|
+
if (!g) return '';
|
|
92
|
+
// 첫 글자만. 한글이든 영어든 한 글자면 뜻이 남는다 — 작/보/큼, s/m/l.
|
|
93
|
+
const 첫 = [...급이름(g.급)][0] ?? '';
|
|
94
|
+
return g.짐작 ? c.gray(`◈${첫}?`) : c.white(`◈${첫}`);
|
|
65
95
|
},
|
|
66
96
|
},
|
|
67
97
|
|
|
98
|
+
/*
|
|
99
|
+
* 컨텍스트 사용량.
|
|
100
|
+
*
|
|
101
|
+
* 게이지에 **눈금 두 개**를 그어 둔다. 55% 를 넘으면 오래된 도구 결과를 접기
|
|
102
|
+
* 시작하고, 80% 를 넘으면 대화를 요약한다. 둘 다 사람 눈에는 갑자기 일어나는
|
|
103
|
+
* 일이라 — 어느 날 갑자기 "앞선 대화를 줄였습니다" 가 뜨고, 모델이 방금 읽은
|
|
104
|
+
* 파일을 잊는다. 막대가 눈금에 다가가는 것이 보이면 먼저 손을 쓸 수 있다:
|
|
105
|
+
* 못 박아 두거나(/pin), 갈래를 새로 파거나, 지금 하던 것을 먼저 끝내거나.
|
|
106
|
+
*
|
|
107
|
+
* 자리를 코드에 두 번 적지 않는다. compact.js 의 값을 그대로 가져온다 —
|
|
108
|
+
* 문턱을 옮겼는데 화면 눈금만 옛 자리에 남으면, 그 눈금은 거짓말이 된다.
|
|
109
|
+
*/
|
|
68
110
|
ctx: {
|
|
69
|
-
desc
|
|
111
|
+
get desc() { return 말('seg.ctx'); },
|
|
70
112
|
make: (s) => {
|
|
71
113
|
const { r, pct, tone, b } = 참값(s);
|
|
72
|
-
return `${
|
|
114
|
+
return `${눈금게이지(r, 10, [FOLD_AT, COMPACT_AT])} ${tone(pct + '%')} ${c.gray(short(b.used) + '/' + short(b.total))}`;
|
|
73
115
|
},
|
|
74
116
|
// 자리가 모자라면 정확한 숫자를 접는다. 게이지와 %가 이미 같은 말을 하고 있다.
|
|
75
117
|
short: (s) => {
|
|
76
118
|
const { r, pct, tone } = 참값(s);
|
|
77
|
-
return `${
|
|
119
|
+
return `${눈금게이지(r, 10, [FOLD_AT, COMPACT_AT])} ${tone(pct + '%')}`;
|
|
78
120
|
},
|
|
79
121
|
},
|
|
80
122
|
|
|
81
123
|
// 지금 무슨 일을 하는 중인가. 파일을 못 바꾸는 모드면 자물쇠를 같이 그린다 —
|
|
82
124
|
// 계획만 세우는 중인지 실제로 고치는 중인지 한눈에 보여야 한다.
|
|
83
125
|
work: {
|
|
84
|
-
desc
|
|
126
|
+
get desc() { return 말('seg.work'); },
|
|
85
127
|
make: (s) => {
|
|
86
128
|
const 지금 = s.effectiveWork ? s.effectiveWork() : s.work;
|
|
87
129
|
const w = workMode(지금);
|
|
@@ -89,15 +131,22 @@ export const SEGMENTS = {
|
|
|
89
131
|
// 저절로 옮겨 간 것인지 사람이 고른 것인지 구분해서 보여준다.
|
|
90
132
|
// 이게 없으면 "왜 갑자기 읽기만 되지" 를 알 길이 없다.
|
|
91
133
|
const 저절로 = s.routed ? c.gray('~') : '';
|
|
92
|
-
|
|
134
|
+
// 모드 이름은 표에 영어가 이미 있다(modes.js 의 en). 화면 말이 영어면 그걸 쓴다 —
|
|
135
|
+
// 굳이 i18n 에 같은 말을 또 적어 두면 언젠가 둘이 갈라진다.
|
|
136
|
+
const 이름 = 보일이름(지금);
|
|
137
|
+
const 잠김말 = 언어() === 'en' ? ' read-only' : ' 읽기만';
|
|
138
|
+
return `${c.hcyan(w.glyph)} ${저절로}${잠김 ? c.green(이름) : c.white(이름)}${잠김 ? c.green(잠김말) : ''}`;
|
|
93
139
|
},
|
|
94
140
|
},
|
|
95
141
|
|
|
96
142
|
think: {
|
|
97
|
-
desc
|
|
143
|
+
get desc() { return 말('seg.think'); },
|
|
98
144
|
make: (s) => {
|
|
99
145
|
const p = PROFILES[s.effort] ?? PROFILES.save;
|
|
100
|
-
|
|
146
|
+
// 배분 이름도 화면 말을 따라간다. 강도(medium)는 어느 말에서나 같은
|
|
147
|
+
// 낱말이라, 여기만 한국어로 남으면 `medium·절약` 처럼 반씩 섞인다.
|
|
148
|
+
const 이름 = 언어() === 'en' ? (p.en ?? p.name) : p.name;
|
|
149
|
+
return `${c.gray('◇')} ${c.white(s.think)}${c.gray('·')}${c.magenta(이름)}`;
|
|
101
150
|
},
|
|
102
151
|
// 배분은 강도보다 덜 급하다. 좁으면 강도만 남긴다.
|
|
103
152
|
short: (s) => `${c.gray('◇')} ${c.white(s.think)}`,
|
|
@@ -111,27 +160,68 @@ export const SEGMENTS = {
|
|
|
111
160
|
* 는 뜻이라는 것은 화면 어디에도 없었다. 이건 꾸미기가 아니라 안전 표시다.
|
|
112
161
|
*/
|
|
113
162
|
mode: {
|
|
114
|
-
desc
|
|
163
|
+
get desc() { return 말('seg.mode'); },
|
|
115
164
|
make: (s) => 승인표시(s.mode),
|
|
116
165
|
short: (s) => 승인표시(s.mode, { 짧게: true }),
|
|
117
166
|
},
|
|
118
167
|
|
|
119
168
|
tok: {
|
|
120
|
-
desc
|
|
169
|
+
get desc() { return 말('seg.tok'); },
|
|
121
170
|
make: (s) => s.usage.in || s.usage.out
|
|
122
171
|
? c.gray(`↑${short(s.usage.in)} ↓${short(s.usage.out)}`)
|
|
123
172
|
: null,
|
|
124
173
|
},
|
|
125
174
|
|
|
126
|
-
|
|
175
|
+
/*
|
|
176
|
+
* 이번 대화에서 **내 폴더에 무슨 일이 있었나** — 다른 축이라 따로 묶는다.
|
|
177
|
+
*
|
|
178
|
+
* 셋 다 아무 일도 없으면 아무것도 안 그린다. 갓 켠 화면에 `✎ 0 · ↩ 0` 이
|
|
179
|
+
* 서 있으면 자리만 먹고, 좁은 터미널에서는 그것 때문에 뒤엣것이 떨어져 나간다.
|
|
180
|
+
*/
|
|
181
|
+
edits: {
|
|
182
|
+
get desc() { return 말('seg.edits'); },
|
|
183
|
+
make: (s) => (s.changes?.size ? c.white(`✎ ${s.changes.size}`) : null),
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
/*
|
|
187
|
+
* 확인한 것 (Verify).
|
|
188
|
+
*
|
|
189
|
+
* 탈이 있으면 빨갛게, 없으면 초록으로. 이게 중요한 이유는 모델이 "다
|
|
190
|
+
* 됐습니다" 로 답을 맺는 것과 **실제로 돌려 본 것**이 다른 일이기 때문이다.
|
|
191
|
+
* 화면에 초록 ✓ 가 없으면 아직 아무도 안 돌려 본 것이다.
|
|
192
|
+
*/
|
|
193
|
+
verify: {
|
|
194
|
+
get desc() { return 말('seg.verify'); },
|
|
195
|
+
make: (s) => {
|
|
196
|
+
const v = s.검증;
|
|
197
|
+
if (!v?.돈횟수) return null;
|
|
198
|
+
return v.탈 ? c.hred(`✓${v.확인} ✗${v.탈}`) : c.hgreen(`✓${v.확인}`);
|
|
199
|
+
},
|
|
200
|
+
short: (s) => (s.검증?.돈횟수 ? (s.검증.탈 ? c.hred(`✗${s.검증.탈}`) : c.hgreen(`✓${s.검증.확인}`)) : null),
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
/*
|
|
204
|
+
* 되돌릴 수 있는 턴 수.
|
|
205
|
+
*
|
|
206
|
+
* 이 프로젝트는 승인 관문 대신 **되돌리기**로 가기로 했다. 그러면 되돌릴
|
|
207
|
+
* 것이 얼마나 남아 있는지가 안전망의 잔량이다 — 그게 화면 어디에도 없으면
|
|
208
|
+
* "지금 /undo 를 누르면 어디까지 돌아가나" 를 눌러 봐야만 알 수 있다.
|
|
209
|
+
* 이력은 오래되면 잘려 나가므로(undo.js) 이 숫자는 줄어들기도 한다.
|
|
210
|
+
*/
|
|
211
|
+
undoable: {
|
|
212
|
+
get desc() { return 말('seg.undoable'); },
|
|
213
|
+
make: (s) => (s.되돌릴턴 > 0 ? c.gray(`↩ ${s.되돌릴턴}`) : null),
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
tools: { get desc() { return 말('seg.tools'); }, make: (s) => (s.usage.calls ? c.gray(`호출 ${s.usage.calls}`) : null) },
|
|
127
217
|
|
|
128
218
|
skills: {
|
|
129
|
-
desc
|
|
219
|
+
get desc() { return 말('seg.skills'); },
|
|
130
220
|
make: (s) => (s.skills?.length ? c.gray(`스킬 ${s.skills.length}`) : null),
|
|
131
221
|
},
|
|
132
222
|
|
|
133
223
|
time: {
|
|
134
|
-
desc
|
|
224
|
+
get desc() { return 말('seg.time'); },
|
|
135
225
|
make: (s) => {
|
|
136
226
|
const ms = Date.now() - s.startedAt;
|
|
137
227
|
return c.gray(ms < 60000 ? `${Math.round(ms / 1000)}초` : `${Math.round(ms / 60000)}분`);
|
|
@@ -154,6 +244,8 @@ export const SEGMENT_GROUPS = [
|
|
|
154
244
|
['dir', 'thread', 'model'],
|
|
155
245
|
['ctx', 'grade'],
|
|
156
246
|
['work', 'think', 'mode'],
|
|
247
|
+
// 내 폴더에 무슨 일이 있었나. 아무 일도 없으면 이 덩이는 아예 안 선다.
|
|
248
|
+
['edits', 'verify', 'undoable'],
|
|
157
249
|
['tok'],
|
|
158
250
|
];
|
|
159
251
|
|
|
@@ -183,10 +275,35 @@ function 참값(s) {
|
|
|
183
275
|
// 넘칠 수는 있지만 화면에 219% 라고 적으면 고장난 것처럼 보인다. 100 에서 멈춘다.
|
|
184
276
|
const r = Math.min(1, b.total > 0 ? b.used / b.total : 0);
|
|
185
277
|
const pct = Math.round(r * 100);
|
|
186
|
-
|
|
278
|
+
/*
|
|
279
|
+
* %의 색을 게이지와 **같은 자리에서** 바꾼다.
|
|
280
|
+
*
|
|
281
|
+
* 전에는 0.6·0.85 로 따로 잡혀 있어서, 62% 에서 숫자만 노래지고 눈금은
|
|
282
|
+
* 아직 멀쩡했다. 두 개가 다른 말을 하면 사람은 둘 다 안 믿는다.
|
|
283
|
+
* 이제 접기(55%)·요약(80%) 자리에서 같이 바뀐다 — 색이 바뀌는 그 순간이
|
|
284
|
+
* 실제로 무슨 일이 나는 순간이다.
|
|
285
|
+
*/
|
|
286
|
+
const tone = r > COMPACT_AT ? c.hred : r > FOLD_AT ? c.hyellow : c.gray;
|
|
187
287
|
return { b, r, pct, tone };
|
|
188
288
|
}
|
|
189
289
|
|
|
290
|
+
/**
|
|
291
|
+
* 경계 한 글자.
|
|
292
|
+
*
|
|
293
|
+
* 못 읽는 주소면 아무것도 안 그린다. 여기서 애매하면 **초록을 안 쓴다** —
|
|
294
|
+
* 초록은 '안 나간다' 는 약속이고, 확인 못 한 것을 확인한 낯으로 내밀면 안 된다.
|
|
295
|
+
*/
|
|
296
|
+
function 경계표(s) {
|
|
297
|
+
let 로컬 = null;
|
|
298
|
+
try { 로컬 = isLocalHost(new URL(s.conn.base).hostname); } catch { 로컬 = null; }
|
|
299
|
+
if (로컬 === null) return c.gray('?');
|
|
300
|
+
if (!로컬) return c.hyellow('↗');
|
|
301
|
+
// 오프라인 잠금은 '이 안' 보다 더 센 상태지만 글자는 같게 둔다 — 사람이
|
|
302
|
+
// 봐야 하는 판단은 '나가나 안 나가나' 하나뿐이고, 그 위에 글자를 하나 더
|
|
303
|
+
// 얹으면 정작 그 판단이 흐려진다. 잠금 여부는 켤 때 머리말에 적힌다.
|
|
304
|
+
return c.hgreen('⌂');
|
|
305
|
+
}
|
|
306
|
+
|
|
190
307
|
function base(p) {
|
|
191
308
|
const parts = String(p ?? '').split(/[\\/]/).filter(Boolean);
|
|
192
309
|
return parts[parts.length - 1] ?? '~';
|
|
@@ -275,18 +392,28 @@ export function contextWarning(session) {
|
|
|
275
392
|
* 켤 때 한 번 그리는 머리말 상자 안쪽 줄들.
|
|
276
393
|
*/
|
|
277
394
|
/** 머리말 모델 줄에 붙는 급 표시. 못 매기면 아무것도 안 붙는다. */
|
|
395
|
+
/** 급 이름을 화면 말로. 표에는 한글 id 로 들어 있다(agent/grade.js). */
|
|
396
|
+
export function 급이름(급) {
|
|
397
|
+
const 짝 = { 작음: 'grade.small', 보통: 'grade.medium', 큼: 'grade.large' }[급];
|
|
398
|
+
return 짝 ? 말(짝) : String(급 ?? '');
|
|
399
|
+
}
|
|
400
|
+
|
|
278
401
|
function 급머리말(session) {
|
|
279
402
|
if (typeof session.급 !== 'function') return '';
|
|
280
403
|
const g = session.급();
|
|
281
|
-
return c.gray(
|
|
404
|
+
return c.gray(말('head.gradeSuffix', {
|
|
405
|
+
급: 말('head.grade'),
|
|
406
|
+
급이름: 급이름(g.급),
|
|
407
|
+
짐작: g.짐작 ? ` ${말('head.guess')}` : '',
|
|
408
|
+
}));
|
|
282
409
|
}
|
|
283
410
|
|
|
284
411
|
export function headerLines(session, found) {
|
|
285
412
|
const conn = session.conn;
|
|
286
413
|
const 능력 = [
|
|
287
|
-
conn.streaming ? c.green('
|
|
288
|
-
conn.tools ? c.green('
|
|
289
|
-
conn.think ? c.green('
|
|
414
|
+
conn.streaming ? c.green(말('head.streaming')) : c.gray(말('head.noStreaming')),
|
|
415
|
+
conn.tools ? c.green(말('head.tools')) : c.yellow(말('head.toolsUnknown')),
|
|
416
|
+
conn.think ? c.green(말('head.thinkCtl')) : c.gray(말('head.noThinkCtl')),
|
|
290
417
|
].join(c.gray(' · '));
|
|
291
418
|
|
|
292
419
|
// 코딩 에이전트는 소스를 통째로 모델에 보낸다. '어디로' 가 가장 중요한 한 줄이다.
|
|
@@ -294,12 +421,23 @@ export function headerLines(session, found) {
|
|
|
294
421
|
try {
|
|
295
422
|
const u = new URL(conn.base);
|
|
296
423
|
const 로컬 = isLocalHost(u.hostname);
|
|
297
|
-
목적지 = `${로컬 ? c.green('
|
|
298
|
-
+ (isOffline() ? c.green(
|
|
424
|
+
목적지 = `${로컬 ? c.green(말('head.inside')) : c.yellow(말('head.outside'))} ${c.white(u.host)}`
|
|
425
|
+
+ (isOffline() ? c.green(` ${말('head.offlineLock')}`) : '');
|
|
299
426
|
} catch { 목적지 = c.gray(String(conn.base)); }
|
|
300
427
|
|
|
428
|
+
/*
|
|
429
|
+
* 이름칸 폭을 **말에 맞춰 잰다.**
|
|
430
|
+
*
|
|
431
|
+
* 8칸으로 못 박아 두었더니 영어에서 'approval'(8칸)만 한 칸도 못 벌려
|
|
432
|
+
* 그 줄만 오른쪽으로 밀렸다. 다른 말을 넣을 때마다 이 숫자를 다시 고르게
|
|
433
|
+
* 하는 대신, 제일 긴 이름에서 뽑는다.
|
|
434
|
+
*/
|
|
435
|
+
const 이름들 = ['head.model', 'head.send', 'head.conn', 'head.folder', 'head.approve', 'head.thisPC'];
|
|
436
|
+
const 이름칸 = Math.max(...이름들.map((k) => width(말(k)))) + 2;
|
|
437
|
+
const 자리 = (글) => `${글}${' '.repeat(Math.max(1, 이름칸 - width(글)))}`;
|
|
438
|
+
const 빈자리 = ' '.repeat(이름칸);
|
|
301
439
|
const lines = [
|
|
302
|
-
`${c.hcyan(c.bold('deel'))} ${c.gray(conn.kind === 'ollama' ? '
|
|
440
|
+
`${c.hcyan(c.bold('deel'))} ${c.gray(말(conn.kind === 'ollama' ? 'head.spec.ollama' : 'head.spec.openai'))}`,
|
|
303
441
|
'',
|
|
304
442
|
/*
|
|
305
443
|
* 모델 줄에 급을 같이 적는다.
|
|
@@ -309,18 +447,26 @@ export function headerLines(session, found) {
|
|
|
309
447
|
* 같이 읽고 나면 그 다음부터는 글자만 봐도 안다.
|
|
310
448
|
* 짐작이면 그렇다고 적는다 — 알아낸 사실과 같은 낯으로 내밀면 안 된다.
|
|
311
449
|
*/
|
|
312
|
-
`${c.gray('
|
|
450
|
+
`${c.gray(자리(말('head.model')))}${c.white(conn.model)} ${c.gray(`(${short(conn.ctx)} ${말('head.tokens')}`)}`
|
|
313
451
|
+ `${급머리말(session)}${c.gray(')')}`,
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
452
|
+
/*
|
|
453
|
+
* 상태줄 맨 앞의 한 글자가 무슨 뜻인지 여기서 한 번 가르친다.
|
|
454
|
+
*
|
|
455
|
+
* 승인 표시(⏵⏵)·급(◈)에 하던 것과 같은 방식이다. 켤 때 한 번 사람 말로
|
|
456
|
+
* 읽고 나면, 그 뒤로는 글자만 봐도 안다. 이 한 글자는 스크롤에 안 밀린다 —
|
|
457
|
+
* 그게 이 머리말 한 줄과 다른 점이고, 이 줄을 넣은 이유다.
|
|
458
|
+
*/
|
|
459
|
+
`${c.gray(자리(말('head.send')))}${목적지} ${c.gray(말('head.nowhereElse'))}`,
|
|
460
|
+
`${빈자리}${c.gray(말('head.glyphHint', { 안: c.hgreen('⌂'), 밖: c.hyellow('↗') }))}`,
|
|
461
|
+
`${c.gray(자리(말('head.conn')))}${능력}`,
|
|
462
|
+
`${c.gray(자리(말('head.folder')))}${c.white(clip(session.root, 56))}`,
|
|
317
463
|
// 켤 때 한 번은 사람 말로 알려 준다. 상태줄의 `⏵⏵ 자동 승인` 이 무슨
|
|
318
464
|
// 뜻인지 여기서 한 번 읽고 나면 그 다음부터는 글자만 봐도 안다.
|
|
319
|
-
`${c.gray('
|
|
320
|
-
`${c.gray('
|
|
465
|
+
`${c.gray(자리(말('head.approve')))}${승인표시(session.mode)} ${c.gray('— ' + 승인고르기(session.mode).한줄)}`,
|
|
466
|
+
`${빈자리}${c.gray(말('head.shiftTab'))}${c.gray(말('head.shiftTabHint'))}${c.gray(말('head.tabHint'))}`,
|
|
321
467
|
];
|
|
322
468
|
if (found.skills.length || found.commands.length) {
|
|
323
|
-
lines.push(`${c.gray('
|
|
469
|
+
lines.push(`${c.gray(자리(말('head.thisPC')))}${c.white(말('head.skills', { n: found.skills.length }))}${c.gray(' · ')}${c.white(말('head.commands', { n: found.commands.length }))}${c.gray(' · ')}${c.white(말('head.plugins', { n: found.plugins.length }))}`);
|
|
324
470
|
}
|
|
325
471
|
return lines;
|
|
326
472
|
}
|