deel-local-cli 0.5.0 → 0.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.
- package/README.en.md +526 -135
- package/README.md +652 -149
- package/bin/deel.js +67 -1
- package/package.json +4 -3
- package/src/agent/compact.js +138 -0
- package/src/agent/effort.js +139 -0
- package/src/agent/loop.js +186 -60
- package/src/agent/modes.js +153 -0
- package/src/agent/session.js +14 -3
- package/src/agent/sessionui.js +59 -0
- package/src/agent/store.js +193 -0
- package/src/backend/adapter.js +12 -2
- package/src/backend/http.js +21 -3
- package/src/backend/scan.js +162 -0
- package/src/backend/scanui.js +147 -0
- package/src/commands.js +307 -17
- package/src/config.js +19 -6
- package/src/pack/selfpack.js +248 -0
- package/src/pack/tar.js +69 -0
- package/src/pack/zip.js +217 -0
- package/src/plugins/manage.js +272 -0
- package/src/repl.js +230 -38
- package/src/safety/network.js +95 -0
- package/src/safety/undo.js +46 -1
- package/src/setup.js +4 -0
- package/src/tools/encoding.js +333 -0
- package/src/tools/excel-com.js +254 -0
- package/src/tools/excel.js +118 -0
- package/src/tools/fsutil.js +47 -4
- package/src/tools/index.js +128 -14
- package/src/tools/todo.js +92 -0
- package/src/tools/webfetch.js +110 -0
- package/src/tools/xlsx.js +319 -0
- package/src/ui/ansi.js +78 -4
- package/src/ui/level.js +104 -0
- package/src/ui/prompt.js +34 -0
- package/src/ui/status.js +151 -0
package/src/ui/status.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// 상태줄. 지금 무엇으로, 어디까지 차서 돌고 있는지 한 줄로 보여 준다.
|
|
2
|
+
//
|
|
3
|
+
// 남의 패키지를 붙이지 않는다. 필요한 숫자는 전부 session 이 이미 갖고 있고,
|
|
4
|
+
// 화면 그리기는 ansi.js 만 쓴다. 반입 심사에 새로 설명할 것이 늘지 않게 하려는 뜻이다.
|
|
5
|
+
import { c, gauge, width, clip, cols, mark } from './ansi.js';
|
|
6
|
+
import { PROFILES } from '../agent/effort.js';
|
|
7
|
+
import { get as workMode, canWrite } from '../agent/modes.js';
|
|
8
|
+
import { isLocalHost, isOffline } from '../safety/network.js';
|
|
9
|
+
|
|
10
|
+
// 한 조각씩 따로 만든다. 좁은 화면에서는 뒤에서부터 떨군다.
|
|
11
|
+
// 순서 = 중요도 순. 앞쪽이 끝까지 살아남는다.
|
|
12
|
+
export const SEGMENTS = {
|
|
13
|
+
dir: { desc: '작업 폴더', make: (s) => c.gray(base(s.root)) },
|
|
14
|
+
|
|
15
|
+
model: { desc: '모델 이름', make: (s) => c.hcyan(clip(s.conn.model, 24)) },
|
|
16
|
+
|
|
17
|
+
ctx: {
|
|
18
|
+
desc: '컨텍스트 사용량',
|
|
19
|
+
make: (s) => {
|
|
20
|
+
const b = s.breakdown();
|
|
21
|
+
// 넘칠 수는 있지만 화면에 219% 라고 적으면 고장난 것처럼 보인다. 100 에서 멈춘다.
|
|
22
|
+
const r = Math.min(1, b.total > 0 ? b.used / b.total : 0);
|
|
23
|
+
const pct = Math.round(r * 100);
|
|
24
|
+
const tone = r > 0.85 ? c.hred : r > 0.6 ? c.hyellow : c.gray;
|
|
25
|
+
return `${gauge(r, 10)} ${tone(pct + '%')} ${c.gray(short(b.used) + '/' + short(b.total))}`;
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// 지금 무슨 일을 하는 중인가. 파일을 못 바꾸는 모드면 자물쇠를 같이 그린다 —
|
|
30
|
+
// 계획만 세우는 중인지 실제로 고치는 중인지 한눈에 보여야 한다.
|
|
31
|
+
work: {
|
|
32
|
+
desc: '작업 모드',
|
|
33
|
+
make: (s) => {
|
|
34
|
+
const w = workMode(s.work);
|
|
35
|
+
const 잠김 = !canWrite(s.work);
|
|
36
|
+
return `${c.hcyan(w.glyph)} ${잠김 ? c.green(w.name) : c.white(w.name)}${잠김 ? c.green(' 읽기만') : ''}`;
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
think: {
|
|
41
|
+
desc: '추론 강도·배분',
|
|
42
|
+
make: (s) => {
|
|
43
|
+
const p = PROFILES[s.effort] ?? PROFILES.save;
|
|
44
|
+
return `${c.gray('◇')} ${c.white(s.think)}${c.gray('·')}${c.magenta(p.name)}`;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
mode: {
|
|
49
|
+
desc: '실행 모드',
|
|
50
|
+
make: (s) => {
|
|
51
|
+
const tone = s.mode === 'auto' ? c.hyellow : s.mode === 'strict' ? c.hgreen : c.white;
|
|
52
|
+
return tone(s.mode);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
tok: {
|
|
57
|
+
desc: '주고받은 토큰',
|
|
58
|
+
make: (s) => s.usage.in || s.usage.out
|
|
59
|
+
? c.gray(`↑${short(s.usage.in)} ↓${short(s.usage.out)}`)
|
|
60
|
+
: null,
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
tools: { desc: '도구 호출 수', make: (s) => (s.usage.calls ? c.gray(`호출 ${s.usage.calls}`) : null) },
|
|
64
|
+
|
|
65
|
+
skills: {
|
|
66
|
+
desc: '이 PC 에서 찾은 스킬',
|
|
67
|
+
make: (s) => (s.skills?.length ? c.gray(`스킬 ${s.skills.length}`) : null),
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
time: {
|
|
71
|
+
desc: '켠 지 얼마나',
|
|
72
|
+
make: (s) => {
|
|
73
|
+
const ms = Date.now() - s.startedAt;
|
|
74
|
+
return c.gray(ms < 60000 ? `${Math.round(ms / 1000)}초` : `${Math.round(ms / 60000)}분`);
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const DEFAULT_SEGMENTS = ['dir', 'model', 'ctx', 'work', 'think', 'mode', 'tok'];
|
|
80
|
+
|
|
81
|
+
function base(p) {
|
|
82
|
+
const parts = String(p ?? '').split(/[\\/]/).filter(Boolean);
|
|
83
|
+
return parts[parts.length - 1] ?? '~';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 12345 → 12.3k. 상태줄은 자리가 없다.
|
|
87
|
+
function short(n) {
|
|
88
|
+
const v = Number(n) || 0;
|
|
89
|
+
if (v < 1000) return String(v);
|
|
90
|
+
if (v < 1000000) return (v / 1000).toFixed(v < 10000 ? 1 : 0) + 'k';
|
|
91
|
+
return (v / 1000000).toFixed(1) + 'M';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 상태줄 한 줄을 만든다. 폭이 모자라면 덜 중요한 조각부터 뺀다.
|
|
96
|
+
* @param {import('../agent/session.js').Session} session
|
|
97
|
+
*/
|
|
98
|
+
export function statusLine(session, { segments = DEFAULT_SEGMENTS, max = cols() - 2 } = {}) {
|
|
99
|
+
const sep = ` ${c.gray('▏')} `;
|
|
100
|
+
let parts = segments
|
|
101
|
+
.map((k) => SEGMENTS[k]?.make(session))
|
|
102
|
+
.filter((x) => x != null && x !== '');
|
|
103
|
+
|
|
104
|
+
// 뒤에서부터 떨궈 폭을 맞춘다. 앞쪽(폴더·모델·컨텍스트)이 마지막까지 남는다.
|
|
105
|
+
while (parts.length > 1 && width(parts.join(sep)) + 2 > max) parts.pop();
|
|
106
|
+
|
|
107
|
+
return ` ${c.gray('▏')}${parts.join(sep)}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 컨텍스트가 위험하면 한마디 붙인다. 상태줄만 보고 넘기지 않도록.
|
|
111
|
+
export function contextWarning(session) {
|
|
112
|
+
const b = session.breakdown();
|
|
113
|
+
const r = Math.min(1, b.total > 0 ? b.used / b.total : 0);
|
|
114
|
+
if (r > 0.9) return `${mark.warn} 컨텍스트 ${Math.round(r * 100)}% — ${c.cyan('/compact')} 또는 ${c.cyan('/clear')} 를 권합니다.`;
|
|
115
|
+
if (r > 0.8) return `${c.gray('컨텍스트 ' + Math.round(r * 100) + '% — 곧 오래된 대화를 줄입니다.')}`;
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 켤 때 한 번 그리는 머리말 상자 안쪽 줄들.
|
|
121
|
+
*/
|
|
122
|
+
export function headerLines(session, found) {
|
|
123
|
+
const conn = session.conn;
|
|
124
|
+
const 능력 = [
|
|
125
|
+
conn.streaming ? c.green('스트리밍') : c.gray('스트리밍 없음'),
|
|
126
|
+
conn.tools ? c.green('도구') : c.yellow('도구 미확인'),
|
|
127
|
+
conn.think ? c.green('추론 조절') : c.gray('추론 조절 없음'),
|
|
128
|
+
].join(c.gray(' · '));
|
|
129
|
+
|
|
130
|
+
// 코딩 에이전트는 소스를 통째로 모델에 보낸다. '어디로' 가 가장 중요한 한 줄이다.
|
|
131
|
+
let 목적지;
|
|
132
|
+
try {
|
|
133
|
+
const u = new URL(conn.base);
|
|
134
|
+
const 로컬 = isLocalHost(u.hostname);
|
|
135
|
+
목적지 = `${로컬 ? c.green('이 컴퓨터 안') : c.yellow('바깥')} ${c.white(u.host)}`
|
|
136
|
+
+ (isOffline() ? c.green(' [오프라인 잠금]') : '');
|
|
137
|
+
} catch { 목적지 = c.gray(String(conn.base)); }
|
|
138
|
+
|
|
139
|
+
const lines = [
|
|
140
|
+
`${c.hcyan(c.bold('deel'))} ${c.gray(conn.kind === 'ollama' ? 'Ollama 규격' : 'OpenAI 호환 규격')}`,
|
|
141
|
+
'',
|
|
142
|
+
`${c.gray('모델')} ${c.white(conn.model)} ${c.gray('(' + short(conn.ctx) + ' 토큰)')}`,
|
|
143
|
+
`${c.gray('보냄')} ${목적지} ${c.gray('← 여기 말고는 어디로도 안 갑니다')}`,
|
|
144
|
+
`${c.gray('연결')} ${능력}`,
|
|
145
|
+
`${c.gray('폴더')} ${c.white(clip(session.root, 56))}`,
|
|
146
|
+
];
|
|
147
|
+
if (found.skills.length || found.commands.length) {
|
|
148
|
+
lines.push(`${c.gray('이 PC')} ${c.white(`스킬 ${found.skills.length}`)}${c.gray(' · ')}${c.white(`명령 ${found.commands.length}`)}${c.gray(' · ')}${c.white(`플러그인 ${found.plugins.length}`)}`);
|
|
149
|
+
}
|
|
150
|
+
return lines;
|
|
151
|
+
}
|