deel-local-cli 1.13.0 → 1.14.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.
Files changed (54) hide show
  1. package/README.ko.md +32 -26
  2. package/README.md +32 -26
  3. package/bin/deel.js +6 -1
  4. package/package.json +3 -2
  5. package/src/acp/map.js +367 -367
  6. package/src/acp/serve.js +830 -810
  7. package/src/agent/budget.js +156 -156
  8. package/src/agent/commit.js +534 -534
  9. package/src/agent/compact.js +441 -441
  10. package/src/agent/effort.js +308 -308
  11. package/src/agent/evolve.js +263 -263
  12. package/src/agent/loop.js +1755 -1728
  13. package/src/agent/memory.js +156 -156
  14. package/src/agent/mention.js +210 -210
  15. package/src/agent/recall.js +222 -222
  16. package/src/agent/session.js +1056 -1056
  17. package/src/backend/adapter.js +1355 -1355
  18. package/src/backend/cachemark.js +223 -223
  19. package/src/backend/detect.js +327 -327
  20. package/src/backend/http.js +460 -460
  21. package/src/backend/mcp.js +407 -407
  22. package/src/backend/price.js +260 -260
  23. package/src/backend/probe.js +487 -487
  24. package/src/backend/quota.js +274 -274
  25. package/src/backend/tokens.js +59 -59
  26. package/src/backend/toolfit.js +352 -352
  27. package/src/backend/wire.js +715 -715
  28. package/src/cmdnames.js +68 -0
  29. package/src/commands.js +3116 -3184
  30. package/src/config.js +283 -283
  31. package/src/i18n/en.js +545 -545
  32. package/src/i18n/ja.js +498 -498
  33. package/src/i18n/ko.js +592 -592
  34. package/src/i18n/zh.js +498 -498
  35. package/src/oneshot.js +685 -625
  36. package/src/pack/selfpack.js +15 -2
  37. package/src/pack/sheet.en.js +16 -2
  38. package/src/pack/tar.js +154 -154
  39. package/src/pack/zip.js +235 -235
  40. package/src/plugins/manage.js +416 -416
  41. package/src/repl.js +2616 -2616
  42. package/src/safety/audit.js +148 -148
  43. package/src/safety/guard.js +714 -641
  44. package/src/safety/network.js +200 -200
  45. package/src/tools/fastgrep.js +229 -229
  46. package/src/tools/fsutil.js +312 -291
  47. package/src/tools/index.js +2590 -2560
  48. package/src/tools/jobs.js +876 -876
  49. package/src/tools/spawn.js +213 -213
  50. package/src/tools/verify.js +358 -358
  51. package/src/tools/webfetch.js +418 -418
  52. package/src/ui/export.js +233 -233
  53. package/src/ui/pick.js +115 -115
  54. package/src/ui/status.js +617 -617
@@ -1,148 +1,148 @@
1
- // 감사 로그. 무엇을 언제 어떻게 했는지 전부 남긴다.
2
- // 자율 실행을 사내에 설득할 때 이 파일이 근거가 된다.
3
- import { join } from 'node:path';
4
- import { appendFileSync, mkdirSync, existsSync, readFileSync, chmodSync } from 'node:fs';
5
- import { 가리기 } from './secrets.js';
6
- import { 첫이름 } from '../tools/label.js';
7
- import { 환경속열쇠들 } from '../config.js';
8
-
9
- /**
10
- * 감사기록에 넘길 '아는 열쇠' 를 묻는 길. loop.js 가 대화 쪽에서 쓰는 것과 같은 값이다.
11
- * 값이 아니라 길로 주는 까닭은 Audit 만들기 머리말에 적어 뒀다.
12
- *
13
- * ── 왜 `DEEL_API_KEY` 하나가 아닌가 ──────────────────────────────────
14
- *
15
- * 여기가 `[conn?.key, process.env.DEEL_API_KEY]` 였다. 그런데 열쇠는 한
16
- * 이름이 아니다 — `DEEL_KEY_WORK` 처럼 이름을 붙여 여러 개를 둘 수 있고,
17
- * 그게 이 프로그램이 스스로 권하는 방식이다.
18
- *
19
- * 그 열쇠들이 이 목록에 없으면, 모델이 `DEEL_KEY_WORK=… curl …` 같은
20
- * 명령을 부른 그 줄이 감사기록에 **그대로** 적힌다. 감사기록은 사내에
21
- * 자율 실행을 설득하려고 남기는 것이라 사람이 읽고 남에게도 보인다.
22
- * 가려 주는 자리에서 새면 안 가린 것보다 나쁘다 — 가려졌다고 믿기 때문이다.
23
- *
24
- * 「무엇이 열쇠인가」 는 config.js 한 곳에서 온다. 대화 쪽(loop.js)과 같은
25
- * 자를 쓴다 — 두 벌이면 한쪽만 새는 날이 온다.
26
- */
27
- export const 열쇠묻기 = (conn) => () => [conn?.key, ...환경속열쇠들()].filter(Boolean);
28
-
29
- export class Audit {
30
- /**
31
- * @param {string} root 작업 폴더
32
- * @param {object} o
33
- * @param {string[]|(() => string[])} o.열쇠들 정확히 아는 비밀 (설정에 든 게이트웨이 열쇠).
34
- * 값이 아니라 **묻는 길**로 받아도 된다 — /model 로 갈아타면 conn 이 통째로
35
- * 바뀌므로, 만들 때 붙잡아 둔 값은 그 판이 끝나기 전에 옛것이 된다.
36
- */
37
- constructor(root, { 열쇠들 = [] } = {}) {
38
- const dir = join(root, '.deel');
39
- mkdirSync(dir, { recursive: true });
40
- this.file = join(dir, 'audit.jsonl');
41
- this.session = `${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '')}`;
42
- /** 적으려다 못 적은 건수와 첫 까닭. 0 이 아니면 이 세션의 기록은 불완전하다. */
43
- this.못쓴수 = 0;
44
- this.못쓴까닭 = null;
45
- this.열쇠들 = 열쇠들;
46
- /** 파일 권한을 건 결과. `{모드}` 면 걸었고, `{못함}` 이면 못 걸었다. */
47
- this.잠금 = null;
48
- }
49
-
50
- /*
51
- * ── 적기 전에 가린다 ────────────────────────────────────────────────
52
- *
53
- * 여기 적히는 target 은 Bash 명령줄 그 자체다. 모델은 열쇠를 헤더로
54
- * (`-H "Authorization: Bearer …"`), 환경변수로(`DEEL_API_KEY=… node x`),
55
- * 주소 안에 박아서(`https://사람:토큰@github.com/…`) 넘긴다.
56
- *
57
- * 도구 **결과**는 loop.js 가 secrets.js 로 가려서 대화에 싣는데, 감사기록은
58
- * 그 길을 안 지난다. 그래서 대화에서는 지운 값이 디스크에는 평문으로 남았다 —
59
- * 그것도 `deel audit` 로 사내 심사 자료에 딸려 나가는 파일에.
60
- *
61
- * 가리는 것은 자유롭게 적히는 글뿐이다. 도구 이름·성패 같은 정해진 값은
62
- * 안 건드린다 — 넓게 잡으면 「무엇을 했는지」 가 «가림» 으로 뭉개진다.
63
- */
64
- #가린것(값) {
65
- if (값 == null) return 값;
66
- const 것들 = typeof this.열쇠들 === 'function' ? this.열쇠들() : this.열쇠들;
67
- return 가리기(String(값), { 열쇠들: 것들 ?? [] }).글;
68
- }
69
-
70
- /*
71
- * 못 적어도 던지지 않는다 — 기록을 남기려다 작업을 죽이면 본말이 뒤집힌다.
72
- *
73
- * 다만 **조용히 삼키면 안 된다.** 이 파일의 첫 줄이 파는 문장이
74
- * 「무엇을 언제 어떻게 했는지 전부 남는다」 이고, /mcp 화면은 대놓고
75
- * `.deel/audit.jsonl 에 남습니다` 라고 적어 준다. 디스크가 차거나 폴더가
76
- * 읽기 전용이면 그 두 문장이 거짓이 되는데, 그때도 화면은 아무 말이 없었다.
77
- * 더 나쁜 건 증거모으기다 — 기록이 안 적히면 「고쳤는데 확인 안 됨」이
78
- * 아니라 아예 **안 고친 것처럼** 보고서가 짧아진다.
79
- * 그래서 못 적은 건수를 세어 두고, 보고서와 화면이 그걸 말하게 한다.
80
- */
81
- write(kind, data) {
82
- const rec = { at: new Date().toISOString(), session: this.session, kind, ...data };
83
- try {
84
- appendFileSync(this.file, JSON.stringify(rec) + '\n', 'utf8');
85
- this.#잠그기();
86
- } catch (err) {
87
- this.못쓴수 += 1;
88
- if (!this.못쓴까닭) this.못쓴까닭 = err?.message ?? String(err);
89
- }
90
- return rec;
91
- }
92
-
93
- /*
94
- * 만들자마자 본인만 읽게 잠근다 (config.js 가 설정 파일에 하는 것과 같다).
95
- *
96
- * 가리기를 지나도 여기에는 무엇을 언제 어디서 했는지가 남는다 — 파일 경로,
97
- * 돌린 명령, 사람이 친 말. 같은 PC 를 여럿이 쓰거나 홈이 공유 폴더에 있으면
98
- * 그게 그대로 남에게 읽힌다. 한 번만 건다 — 줄마다 걸 일이 아니다.
99
- * 윈도우(NTFS)에서는 chmod 가 아무 일도 안 한다. 거기서는 못 잠근 것이지
100
- * 잠근 척하지 않는다 — 보관방식 화면이 그 사실을 따로 말한다.
101
- *
102
- * 건 결과를 `잠금` 에 남긴다. "본인만 읽게 잠근다" 는 말은 지켜지는지
103
- * **밖에서 볼 수 있어야** 뜻이 있다 (keystore.js 의 마지막명령줄 과 같은
104
- * 까닭). 윈도우에서 값이 남는다고 실제로 잠겼다는 뜻은 아니다 —
105
- * 우리가 걸어는 봤다는 뜻이다.
106
- */
107
- #잠갔나 = false;
108
- #잠그기() {
109
- if (this.#잠갔나) return;
110
- this.#잠갔나 = true;
111
- try { chmodSync(this.file, 0o600); this.잠금 = { 모드: 0o600 }; }
112
- catch (err) { this.잠금 = { 못함: err?.code ?? String(err) }; }
113
- }
114
-
115
- /** 기록이 새고 있나. 새고 있으면 `{수, 까닭}`, 멀쩡하면 null. */
116
- 못쓴것() {
117
- return this.못쓴수 ? { 수: this.못쓴수, 까닭: this.못쓴까닭 } : null;
118
- }
119
-
120
- tool(name, args, result) {
121
- return this.write('tool', {
122
- tool: name,
123
- // purpose 는 하위 작업(Task)이 쓰는 이름이다. 이게 없으면 감사기록에
124
- // '하위 작업을 돌렸다' 만 남고 **무엇을** 돌렸는지가 안 남는다.
125
- // 옛 이름(목적)도 같이 본다 — 이름을 바꾼 판 이전의 기록이 남아 있고,
126
- // 옛 이름으로 부르는 모델도 있다.
127
- //
128
- // 이름 차례는 tools/label.js 한 곳에서 온다. 여기 목록에는 url 과 name
129
- // 이 빠져 있어서, 웹을 읽은 줄과 스킬을 부른 줄이 감사기록에서만
130
- // 무엇을 향한 것인지 없이 남았다.
131
- target: this.#가린것(첫이름(args) ?? args?.command ?? null),
132
- ok: !result?.error,
133
- note: this.#가린것(result?.error ?? result?.summary ?? null),
134
- });
135
- }
136
-
137
- turn(text) { return this.write('turn', { text: this.#가린것(String(text).slice(0, 500)) }); }
138
- blocked(why, what) { return this.write('blocked', { why, what: this.#가린것(String(what).slice(0, 300)) }); }
139
- undo(info) { return this.write('undo', info); }
140
-
141
- recent(n = 20) {
142
- if (!existsSync(this.file)) return [];
143
- return readFileSync(this.file, 'utf8')
144
- .split('\n').filter(Boolean).slice(-n)
145
- .map((l) => { try { return JSON.parse(l); } catch { return null; } })
146
- .filter(Boolean);
147
- }
148
- }
1
+ // 감사 로그. 무엇을 언제 어떻게 했는지 전부 남긴다.
2
+ // 자율 실행을 사내에 설득할 때 이 파일이 근거가 된다.
3
+ import { join } from 'node:path';
4
+ import { appendFileSync, mkdirSync, existsSync, readFileSync, chmodSync } from 'node:fs';
5
+ import { 가리기 } from './secrets.js';
6
+ import { 첫이름 } from '../tools/label.js';
7
+ import { 환경속열쇠들 } from '../config.js';
8
+
9
+ /**
10
+ * 감사기록에 넘길 '아는 열쇠' 를 묻는 길. loop.js 가 대화 쪽에서 쓰는 것과 같은 값이다.
11
+ * 값이 아니라 길로 주는 까닭은 Audit 만들기 머리말에 적어 뒀다.
12
+ *
13
+ * ── 왜 `DEEL_API_KEY` 하나가 아닌가 ──────────────────────────────────
14
+ *
15
+ * 여기가 `[conn?.key, process.env.DEEL_API_KEY]` 였다. 그런데 열쇠는 한
16
+ * 이름이 아니다 — `DEEL_KEY_WORK` 처럼 이름을 붙여 여러 개를 둘 수 있고,
17
+ * 그게 이 프로그램이 스스로 권하는 방식이다.
18
+ *
19
+ * 그 열쇠들이 이 목록에 없으면, 모델이 `DEEL_KEY_WORK=… curl …` 같은
20
+ * 명령을 부른 그 줄이 감사기록에 **그대로** 적힌다. 감사기록은 사내에
21
+ * 자율 실행을 설득하려고 남기는 것이라 사람이 읽고 남에게도 보인다.
22
+ * 가려 주는 자리에서 새면 안 가린 것보다 나쁘다 — 가려졌다고 믿기 때문이다.
23
+ *
24
+ * 「무엇이 열쇠인가」 는 config.js 한 곳에서 온다. 대화 쪽(loop.js)과 같은
25
+ * 자를 쓴다 — 두 벌이면 한쪽만 새는 날이 온다.
26
+ */
27
+ export const 열쇠묻기 = (conn) => () => [conn?.key, ...환경속열쇠들()].filter(Boolean);
28
+
29
+ export class Audit {
30
+ /**
31
+ * @param {string} root 작업 폴더
32
+ * @param {object} o
33
+ * @param {string[]|(() => string[])} o.열쇠들 정확히 아는 비밀 (설정에 든 게이트웨이 열쇠).
34
+ * 값이 아니라 **묻는 길**로 받아도 된다 — /model 로 갈아타면 conn 이 통째로
35
+ * 바뀌므로, 만들 때 붙잡아 둔 값은 그 판이 끝나기 전에 옛것이 된다.
36
+ */
37
+ constructor(root, { 열쇠들 = [] } = {}) {
38
+ const dir = join(root, '.deel');
39
+ mkdirSync(dir, { recursive: true });
40
+ this.file = join(dir, 'audit.jsonl');
41
+ this.session = `${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '')}`;
42
+ /** 적으려다 못 적은 건수와 첫 까닭. 0 이 아니면 이 세션의 기록은 불완전하다. */
43
+ this.못쓴수 = 0;
44
+ this.못쓴까닭 = null;
45
+ this.열쇠들 = 열쇠들;
46
+ /** 파일 권한을 건 결과. `{모드}` 면 걸었고, `{못함}` 이면 못 걸었다. */
47
+ this.잠금 = null;
48
+ }
49
+
50
+ /*
51
+ * ── 적기 전에 가린다 ────────────────────────────────────────────────
52
+ *
53
+ * 여기 적히는 target 은 Bash 명령줄 그 자체다. 모델은 열쇠를 헤더로
54
+ * (`-H "Authorization: Bearer …"`), 환경변수로(`DEEL_API_KEY=… node x`),
55
+ * 주소 안에 박아서(`https://사람:토큰@github.com/…`) 넘긴다.
56
+ *
57
+ * 도구 **결과**는 loop.js 가 secrets.js 로 가려서 대화에 싣는데, 감사기록은
58
+ * 그 길을 안 지난다. 그래서 대화에서는 지운 값이 디스크에는 평문으로 남았다 —
59
+ * 그것도 `deel audit` 로 사내 심사 자료에 딸려 나가는 파일에.
60
+ *
61
+ * 가리는 것은 자유롭게 적히는 글뿐이다. 도구 이름·성패 같은 정해진 값은
62
+ * 안 건드린다 — 넓게 잡으면 「무엇을 했는지」 가 «가림» 으로 뭉개진다.
63
+ */
64
+ #가린것(값) {
65
+ if (값 == null) return 값;
66
+ const 것들 = typeof this.열쇠들 === 'function' ? this.열쇠들() : this.열쇠들;
67
+ return 가리기(String(값), { 열쇠들: 것들 ?? [] }).글;
68
+ }
69
+
70
+ /*
71
+ * 못 적어도 던지지 않는다 — 기록을 남기려다 작업을 죽이면 본말이 뒤집힌다.
72
+ *
73
+ * 다만 **조용히 삼키면 안 된다.** 이 파일의 첫 줄이 파는 문장이
74
+ * 「무엇을 언제 어떻게 했는지 전부 남는다」 이고, /mcp 화면은 대놓고
75
+ * `.deel/audit.jsonl 에 남습니다` 라고 적어 준다. 디스크가 차거나 폴더가
76
+ * 읽기 전용이면 그 두 문장이 거짓이 되는데, 그때도 화면은 아무 말이 없었다.
77
+ * 더 나쁜 건 증거모으기다 — 기록이 안 적히면 「고쳤는데 확인 안 됨」이
78
+ * 아니라 아예 **안 고친 것처럼** 보고서가 짧아진다.
79
+ * 그래서 못 적은 건수를 세어 두고, 보고서와 화면이 그걸 말하게 한다.
80
+ */
81
+ write(kind, data) {
82
+ const rec = { at: new Date().toISOString(), session: this.session, kind, ...data };
83
+ try {
84
+ appendFileSync(this.file, JSON.stringify(rec) + '\n', 'utf8');
85
+ this.#잠그기();
86
+ } catch (err) {
87
+ this.못쓴수 += 1;
88
+ if (!this.못쓴까닭) this.못쓴까닭 = err?.message ?? String(err);
89
+ }
90
+ return rec;
91
+ }
92
+
93
+ /*
94
+ * 만들자마자 본인만 읽게 잠근다 (config.js 가 설정 파일에 하는 것과 같다).
95
+ *
96
+ * 가리기를 지나도 여기에는 무엇을 언제 어디서 했는지가 남는다 — 파일 경로,
97
+ * 돌린 명령, 사람이 친 말. 같은 PC 를 여럿이 쓰거나 홈이 공유 폴더에 있으면
98
+ * 그게 그대로 남에게 읽힌다. 한 번만 건다 — 줄마다 걸 일이 아니다.
99
+ * 윈도우(NTFS)에서는 chmod 가 아무 일도 안 한다. 거기서는 못 잠근 것이지
100
+ * 잠근 척하지 않는다 — 보관방식 화면이 그 사실을 따로 말한다.
101
+ *
102
+ * 건 결과를 `잠금` 에 남긴다. "본인만 읽게 잠근다" 는 말은 지켜지는지
103
+ * **밖에서 볼 수 있어야** 뜻이 있다 (keystore.js 의 마지막명령줄 과 같은
104
+ * 까닭). 윈도우에서 값이 남는다고 실제로 잠겼다는 뜻은 아니다 —
105
+ * 우리가 걸어는 봤다는 뜻이다.
106
+ */
107
+ #잠갔나 = false;
108
+ #잠그기() {
109
+ if (this.#잠갔나) return;
110
+ this.#잠갔나 = true;
111
+ try { chmodSync(this.file, 0o600); this.잠금 = { 모드: 0o600 }; }
112
+ catch (err) { this.잠금 = { 못함: err?.code ?? String(err) }; }
113
+ }
114
+
115
+ /** 기록이 새고 있나. 새고 있으면 `{수, 까닭}`, 멀쩡하면 null. */
116
+ 못쓴것() {
117
+ return this.못쓴수 ? { 수: this.못쓴수, 까닭: this.못쓴까닭 } : null;
118
+ }
119
+
120
+ tool(name, args, result) {
121
+ return this.write('tool', {
122
+ tool: name,
123
+ // purpose 는 하위 작업(Task)이 쓰는 이름이다. 이게 없으면 감사기록에
124
+ // '하위 작업을 돌렸다' 만 남고 **무엇을** 돌렸는지가 안 남는다.
125
+ // 옛 이름(목적)도 같이 본다 — 이름을 바꾼 판 이전의 기록이 남아 있고,
126
+ // 옛 이름으로 부르는 모델도 있다.
127
+ //
128
+ // 이름 차례는 tools/label.js 한 곳에서 온다. 여기 목록에는 url 과 name
129
+ // 이 빠져 있어서, 웹을 읽은 줄과 스킬을 부른 줄이 감사기록에서만
130
+ // 무엇을 향한 것인지 없이 남았다.
131
+ target: this.#가린것(첫이름(args) ?? args?.command ?? null),
132
+ ok: !result?.error,
133
+ note: this.#가린것(result?.error ?? result?.summary ?? null),
134
+ });
135
+ }
136
+
137
+ turn(text) { return this.write('turn', { text: this.#가린것(String(text).slice(0, 500)) }); }
138
+ blocked(why, what) { return this.write('blocked', { why, what: this.#가린것(String(what).slice(0, 300)) }); }
139
+ undo(info) { return this.write('undo', info); }
140
+
141
+ recent(n = 20) {
142
+ if (!existsSync(this.file)) return [];
143
+ return readFileSync(this.file, 'utf8')
144
+ .split('\n').filter(Boolean).slice(-n)
145
+ .map((l) => { try { return JSON.parse(l); } catch { return null; } })
146
+ .filter(Boolean);
147
+ }
148
+ }