deel-local-cli 1.13.0 → 1.13.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.
Files changed (50) hide show
  1. package/README.ko.md +3 -2
  2. package/README.md +3 -2
  3. package/package.json +1 -1
  4. package/src/acp/map.js +367 -367
  5. package/src/acp/serve.js +810 -810
  6. package/src/agent/budget.js +156 -156
  7. package/src/agent/commit.js +534 -534
  8. package/src/agent/compact.js +441 -441
  9. package/src/agent/effort.js +308 -308
  10. package/src/agent/evolve.js +263 -263
  11. package/src/agent/loop.js +1728 -1728
  12. package/src/agent/memory.js +156 -156
  13. package/src/agent/mention.js +210 -210
  14. package/src/agent/recall.js +222 -222
  15. package/src/agent/session.js +1056 -1056
  16. package/src/backend/adapter.js +1355 -1355
  17. package/src/backend/cachemark.js +223 -223
  18. package/src/backend/detect.js +327 -327
  19. package/src/backend/http.js +460 -460
  20. package/src/backend/mcp.js +407 -407
  21. package/src/backend/price.js +260 -260
  22. package/src/backend/probe.js +487 -487
  23. package/src/backend/quota.js +274 -274
  24. package/src/backend/tokens.js +59 -59
  25. package/src/backend/toolfit.js +352 -352
  26. package/src/backend/wire.js +715 -715
  27. package/src/commands.js +3184 -3184
  28. package/src/config.js +283 -283
  29. package/src/i18n/en.js +545 -545
  30. package/src/i18n/ja.js +498 -498
  31. package/src/i18n/ko.js +592 -592
  32. package/src/i18n/zh.js +498 -498
  33. package/src/oneshot.js +625 -625
  34. package/src/pack/tar.js +154 -154
  35. package/src/pack/zip.js +235 -235
  36. package/src/plugins/manage.js +416 -416
  37. package/src/repl.js +2616 -2616
  38. package/src/safety/audit.js +148 -148
  39. package/src/safety/guard.js +641 -641
  40. package/src/safety/network.js +200 -200
  41. package/src/tools/fastgrep.js +229 -229
  42. package/src/tools/fsutil.js +291 -291
  43. package/src/tools/index.js +2560 -2560
  44. package/src/tools/jobs.js +876 -876
  45. package/src/tools/spawn.js +213 -213
  46. package/src/tools/verify.js +358 -358
  47. package/src/tools/webfetch.js +418 -418
  48. package/src/ui/export.js +233 -233
  49. package/src/ui/pick.js +115 -115
  50. package/src/ui/status.js +617 -617
package/src/pack/tar.js CHANGED
@@ -1,154 +1,154 @@
1
- // tar 읽기. GitHub 가 주는 tarball 을 풀기 위한 최소 구현.
2
- // git 이 없는 기기에서도 플러그인을 받을 수 있어야 해서 필요하다.
3
- import { gunzipSync } from 'node:zlib';
4
- import { resolve, sep } from 'node:path';
5
-
6
- const BLOCK = 512;
7
-
8
- function str(buf, off, len) {
9
- const end = buf.indexOf(0, off);
10
- const stop = end >= 0 && end < off + len ? end : off + len;
11
- return buf.toString('utf8', off, stop).trim();
12
- }
13
-
14
- function octal(buf, off, len) {
15
- const s = str(buf, off, len).replace(/[^0-7]/g, '');
16
- return s ? parseInt(s, 8) : 0;
17
- }
18
-
19
- /*
20
- * ── 작게 받아서 크게 푸는 묶음(압축 폭탄) ───────────────────────────────
21
- *
22
- * 받는 쪽(plugins/manage.js)은 **압축된** 크기를 64MB 로 막는다. 그런데
23
- * 0 으로만 채운 파일은 천 배 넘게 줄어든다 — 64MB 짜리 하나가 풀면 수십 GB 다.
24
- * gunzipSync 는 그걸 통째로 메모리에 올리므로, 화면에는 아무 말도 안 남고
25
- * 프로세스가 그대로 죽는다. 사용자가 보는 것은 「플러그인을 깔아 줘」 라고
26
- * 시킨 뒤 deel 이 사라진 것뿐이다 — 하던 대화까지 같이.
27
- *
28
- * 그래서 **푼 크기**에도 상한을 둔다. 256MB 로 잡은 까닭은 받는 상한의 네 배라서다.
29
- * 플러그인은 글과 스크립트 묶음이라 서너 배까지는 부푼다. 그보다 더 부푸는 것은
30
- * 플러그인이 아니라 폭탄으로 본다.
31
- */
32
- export const 푼것상한 = 256 * 1024 * 1024;
33
-
34
- /**
35
- * gzip 된 tar 를 풀어 파일 목록을 돌려준다.
36
- * @param {Buffer} gz
37
- * @param {object} o
38
- * @param {number} o.상한 푼 크기 상한. 검사가 작은 값으로 부르려고 열어 뒀다 —
39
- * 256MB 를 진짜로 만들어 재면 그 검사 하나가 기계를 잡아먹는다.
40
- * @returns {Array<{name:string, data:Buffer, mode:number}>}
41
- */
42
- export function untargz(gz, { 상한 = 푼것상한 } = {}) {
43
- let buf;
44
- try {
45
- buf = gunzipSync(gz, { maxOutputLength: 상한 });
46
- } catch (err) {
47
- // 조용히 죽는 대신 왜 안 되는지 말한다. 이 말은 install 이 그대로 화면에 올린다.
48
- if (err?.code === 'ERR_BUFFER_TOO_LARGE') {
49
- // MB 로만 적으면 작은 상한이 「0MB」 가 된다 — 사람에게 아무 말도 안 하는 숫자다.
50
- const 상한말 = 상한 >= 1024 * 1024 ? `${Math.round(상한 / 1024 / 1024)}MB` : `${Math.round(상한 / 1024)}KB`;
51
- throw new Error(`푼 크기가 상한(${상한말})을 넘습니다`
52
- + ' — 압축만 작고 풀면 몇 GB 가 되는 묶음입니다. 저장소가 맞는지 확인하세요.');
53
- }
54
- throw err;
55
- }
56
- const out = [];
57
- let pos = 0;
58
- let longName = null;
59
-
60
- while (pos + BLOCK <= buf.length) {
61
- const head = buf.subarray(pos, pos + BLOCK);
62
- // 빈 블록 두 개면 끝.
63
- if (head.every((b) => b === 0)) break;
64
-
65
- const size = octal(head, 124, 12);
66
- const type = String.fromCharCode(head[156]) || '0';
67
- const mode = octal(head, 100, 8);
68
- const prefix = str(head, 345, 155);
69
- let name = str(head, 0, 100);
70
- if (prefix) name = `${prefix}/${name}`;
71
-
72
- pos += BLOCK;
73
- const data = buf.subarray(pos, pos + size);
74
- pos += Math.ceil(size / BLOCK) * BLOCK;
75
-
76
- if (type === 'L') { // GNU 긴 이름
77
- longName = data.toString('utf8').replace(/\0+$/, '');
78
- continue;
79
- }
80
- if (type === 'x' || type === 'g') { // pax 확장 머리 — path 만 본다
81
- const m = /\d+ path=([^\n]+)\n/.exec(data.toString('utf8'));
82
- if (m) longName = m[1];
83
- continue;
84
- }
85
- if (longName) { name = longName; longName = null; }
86
-
87
- if (type === '0' || type === '\0' || type === '') {
88
- out.push({ name: name.replace(/\\/g, '/'), data: Buffer.from(data), mode: mode || 0o644 });
89
- }
90
- // 폴더('5')·링크 등은 건너뛴다. 파일만 있으면 폴더는 만들면서 채운다.
91
- }
92
- return out;
93
- }
94
-
95
- // GitHub tarball 은 최상위에 <repo>-<커밋> 폴더 한 겹이 더 있다. 그걸 벗긴다.
96
- export function stripTop(files) {
97
- if (!files.length) return files;
98
- const first = files[0].name.split('/')[0];
99
- if (!files.every((f) => f.name.startsWith(first + '/'))) return files;
100
- return files.map((f) => ({ ...f, name: f.name.slice(first.length + 1) }));
101
- }
102
-
103
- /**
104
- * 이 이름이 풀 폴더 **안쪽**을 가리키나.
105
- *
106
- * ── 왜 있어야 하나 ──────────────────────────────────────────────────────
107
- *
108
- * tar 안의 이름은 **남이 적은 글자**다. 우리가 만든 것이 아니다. 그걸 그대로
109
- * join(dest, name) 에 넣으면 `../../../..` 하나로 폴더 밖에 파일을 쓴다.
110
- * 받는 쪽이 mkdirSync(recursive) 까지 해 주므로 없는 폴더도 만들어 가며 나간다.
111
- *
112
- * name: '../../../../윗동네에쓰기.txt'
113
- * dest: C:\Users\누군가\.deel\plugins\x
114
- * → C:\Users\윗동네에쓰기.txt
115
- *
116
- * 이 프로그램이 파는 문장이 「작업 폴더 밖은 안 만진다」 인데, 플러그인을
117
- * 받는 길에는 그 문장을 지키는 코드가 한 줄도 없었다. 여기서 막는다 —
118
- * 푸는 자리마다 따로 적으면 언젠가 한 곳이 빠진다.
119
- *
120
- * 절대경로·드라이브 문자도 같이 걸린다. resolve 는 절대경로를 만나면 뿌리를
121
- * 통째로 갈아 치우므로, 그 결과가 뿌리로 시작하지 않는다.
122
- */
123
- export function 안쪽인가(뿌리, 이름) {
124
- const 원래 = String(이름 ?? '');
125
- /*
126
- * ── 판마다 다르게 막으면 막은 게 아니다 ───────────────────────────────
127
- *
128
- * `resolve()` 는 윈도우에서만 역슬래시를 칸막이로 본다. 그래서
129
- * `..\\..\\밖으로.txt` 는 윈도우에서는 밖으로 잡히고 리눅스·맥에서는
130
- * **그냥 파일 이름 하나**로 통과한다. 같은 묶음이 판에 따라 다르게 처리되는
131
- * 셈이다.
132
- *
133
- * 플러그인 묶음은 기계를 옮겨 다닌다. 리눅스에서 받아 둔 것을 윈도우로
134
- * 옮기거나, 리눅스에서 푼 것을 그대로 다시 묶어 나눠 주는 일이 흔하다.
135
- * 한쪽에서만 막으면 다른 쪽으로 새어 나갈 길이 남는다.
136
- *
137
- * 그래서 역슬래시를 **어느 판에서든** 칸막이로 본다. 이 방향으로만 틀리면
138
- * 손해는 「이름에 역슬래시가 든 파일을 못 푼다」 뿐이고, 그런 이름이 정직한
139
- * 플러그인에 있을 까닭이 없다. 반대 방향으로 틀리면 남의 글자 한 줄로
140
- * 폴더 밖에 파일이 써진다.
141
- */
142
- const 이름꼴 = 원래.replace(/\\/g, '/');
143
- // 드라이브 글자(`C:`)도 마찬가지다. 리눅스에서는 그냥 이름이라 통과하는데,
144
- // 그 묶음을 윈도우에서 풀면 그 자리로 곧장 나간다.
145
- if (/^[a-zA-Z]:/.test(이름꼴)) return false;
146
- const 뿌리절대 = resolve(String(뿌리 ?? ''));
147
- const 갈곳 = resolve(뿌리절대, 이름꼴);
148
- return 갈곳 === 뿌리절대 || 갈곳.startsWith(뿌리절대 + sep);
149
- }
150
-
151
- /** 풀 폴더 밖을 가리키는 것만 골라 준다. 하나라도 있으면 그 묶음은 안 푼다. */
152
- export function 밖을가리키는것(뿌리, files) {
153
- return (files ?? []).filter((f) => !안쪽인가(뿌리, f?.name));
154
- }
1
+ // tar 읽기. GitHub 가 주는 tarball 을 풀기 위한 최소 구현.
2
+ // git 이 없는 기기에서도 플러그인을 받을 수 있어야 해서 필요하다.
3
+ import { gunzipSync } from 'node:zlib';
4
+ import { resolve, sep } from 'node:path';
5
+
6
+ const BLOCK = 512;
7
+
8
+ function str(buf, off, len) {
9
+ const end = buf.indexOf(0, off);
10
+ const stop = end >= 0 && end < off + len ? end : off + len;
11
+ return buf.toString('utf8', off, stop).trim();
12
+ }
13
+
14
+ function octal(buf, off, len) {
15
+ const s = str(buf, off, len).replace(/[^0-7]/g, '');
16
+ return s ? parseInt(s, 8) : 0;
17
+ }
18
+
19
+ /*
20
+ * ── 작게 받아서 크게 푸는 묶음(압축 폭탄) ───────────────────────────────
21
+ *
22
+ * 받는 쪽(plugins/manage.js)은 **압축된** 크기를 64MB 로 막는다. 그런데
23
+ * 0 으로만 채운 파일은 천 배 넘게 줄어든다 — 64MB 짜리 하나가 풀면 수십 GB 다.
24
+ * gunzipSync 는 그걸 통째로 메모리에 올리므로, 화면에는 아무 말도 안 남고
25
+ * 프로세스가 그대로 죽는다. 사용자가 보는 것은 「플러그인을 깔아 줘」 라고
26
+ * 시킨 뒤 deel 이 사라진 것뿐이다 — 하던 대화까지 같이.
27
+ *
28
+ * 그래서 **푼 크기**에도 상한을 둔다. 256MB 로 잡은 까닭은 받는 상한의 네 배라서다.
29
+ * 플러그인은 글과 스크립트 묶음이라 서너 배까지는 부푼다. 그보다 더 부푸는 것은
30
+ * 플러그인이 아니라 폭탄으로 본다.
31
+ */
32
+ export const 푼것상한 = 256 * 1024 * 1024;
33
+
34
+ /**
35
+ * gzip 된 tar 를 풀어 파일 목록을 돌려준다.
36
+ * @param {Buffer} gz
37
+ * @param {object} o
38
+ * @param {number} o.상한 푼 크기 상한. 검사가 작은 값으로 부르려고 열어 뒀다 —
39
+ * 256MB 를 진짜로 만들어 재면 그 검사 하나가 기계를 잡아먹는다.
40
+ * @returns {Array<{name:string, data:Buffer, mode:number}>}
41
+ */
42
+ export function untargz(gz, { 상한 = 푼것상한 } = {}) {
43
+ let buf;
44
+ try {
45
+ buf = gunzipSync(gz, { maxOutputLength: 상한 });
46
+ } catch (err) {
47
+ // 조용히 죽는 대신 왜 안 되는지 말한다. 이 말은 install 이 그대로 화면에 올린다.
48
+ if (err?.code === 'ERR_BUFFER_TOO_LARGE') {
49
+ // MB 로만 적으면 작은 상한이 「0MB」 가 된다 — 사람에게 아무 말도 안 하는 숫자다.
50
+ const 상한말 = 상한 >= 1024 * 1024 ? `${Math.round(상한 / 1024 / 1024)}MB` : `${Math.round(상한 / 1024)}KB`;
51
+ throw new Error(`푼 크기가 상한(${상한말})을 넘습니다`
52
+ + ' — 압축만 작고 풀면 몇 GB 가 되는 묶음입니다. 저장소가 맞는지 확인하세요.');
53
+ }
54
+ throw err;
55
+ }
56
+ const out = [];
57
+ let pos = 0;
58
+ let longName = null;
59
+
60
+ while (pos + BLOCK <= buf.length) {
61
+ const head = buf.subarray(pos, pos + BLOCK);
62
+ // 빈 블록 두 개면 끝.
63
+ if (head.every((b) => b === 0)) break;
64
+
65
+ const size = octal(head, 124, 12);
66
+ const type = String.fromCharCode(head[156]) || '0';
67
+ const mode = octal(head, 100, 8);
68
+ const prefix = str(head, 345, 155);
69
+ let name = str(head, 0, 100);
70
+ if (prefix) name = `${prefix}/${name}`;
71
+
72
+ pos += BLOCK;
73
+ const data = buf.subarray(pos, pos + size);
74
+ pos += Math.ceil(size / BLOCK) * BLOCK;
75
+
76
+ if (type === 'L') { // GNU 긴 이름
77
+ longName = data.toString('utf8').replace(/\0+$/, '');
78
+ continue;
79
+ }
80
+ if (type === 'x' || type === 'g') { // pax 확장 머리 — path 만 본다
81
+ const m = /\d+ path=([^\n]+)\n/.exec(data.toString('utf8'));
82
+ if (m) longName = m[1];
83
+ continue;
84
+ }
85
+ if (longName) { name = longName; longName = null; }
86
+
87
+ if (type === '0' || type === '\0' || type === '') {
88
+ out.push({ name: name.replace(/\\/g, '/'), data: Buffer.from(data), mode: mode || 0o644 });
89
+ }
90
+ // 폴더('5')·링크 등은 건너뛴다. 파일만 있으면 폴더는 만들면서 채운다.
91
+ }
92
+ return out;
93
+ }
94
+
95
+ // GitHub tarball 은 최상위에 <repo>-<커밋> 폴더 한 겹이 더 있다. 그걸 벗긴다.
96
+ export function stripTop(files) {
97
+ if (!files.length) return files;
98
+ const first = files[0].name.split('/')[0];
99
+ if (!files.every((f) => f.name.startsWith(first + '/'))) return files;
100
+ return files.map((f) => ({ ...f, name: f.name.slice(first.length + 1) }));
101
+ }
102
+
103
+ /**
104
+ * 이 이름이 풀 폴더 **안쪽**을 가리키나.
105
+ *
106
+ * ── 왜 있어야 하나 ──────────────────────────────────────────────────────
107
+ *
108
+ * tar 안의 이름은 **남이 적은 글자**다. 우리가 만든 것이 아니다. 그걸 그대로
109
+ * join(dest, name) 에 넣으면 `../../../..` 하나로 폴더 밖에 파일을 쓴다.
110
+ * 받는 쪽이 mkdirSync(recursive) 까지 해 주므로 없는 폴더도 만들어 가며 나간다.
111
+ *
112
+ * name: '../../../../윗동네에쓰기.txt'
113
+ * dest: C:\Users\누군가\.deel\plugins\x
114
+ * → C:\Users\윗동네에쓰기.txt
115
+ *
116
+ * 이 프로그램이 파는 문장이 「작업 폴더 밖은 안 만진다」 인데, 플러그인을
117
+ * 받는 길에는 그 문장을 지키는 코드가 한 줄도 없었다. 여기서 막는다 —
118
+ * 푸는 자리마다 따로 적으면 언젠가 한 곳이 빠진다.
119
+ *
120
+ * 절대경로·드라이브 문자도 같이 걸린다. resolve 는 절대경로를 만나면 뿌리를
121
+ * 통째로 갈아 치우므로, 그 결과가 뿌리로 시작하지 않는다.
122
+ */
123
+ export function 안쪽인가(뿌리, 이름) {
124
+ const 원래 = String(이름 ?? '');
125
+ /*
126
+ * ── 판마다 다르게 막으면 막은 게 아니다 ───────────────────────────────
127
+ *
128
+ * `resolve()` 는 윈도우에서만 역슬래시를 칸막이로 본다. 그래서
129
+ * `..\\..\\밖으로.txt` 는 윈도우에서는 밖으로 잡히고 리눅스·맥에서는
130
+ * **그냥 파일 이름 하나**로 통과한다. 같은 묶음이 판에 따라 다르게 처리되는
131
+ * 셈이다.
132
+ *
133
+ * 플러그인 묶음은 기계를 옮겨 다닌다. 리눅스에서 받아 둔 것을 윈도우로
134
+ * 옮기거나, 리눅스에서 푼 것을 그대로 다시 묶어 나눠 주는 일이 흔하다.
135
+ * 한쪽에서만 막으면 다른 쪽으로 새어 나갈 길이 남는다.
136
+ *
137
+ * 그래서 역슬래시를 **어느 판에서든** 칸막이로 본다. 이 방향으로만 틀리면
138
+ * 손해는 「이름에 역슬래시가 든 파일을 못 푼다」 뿐이고, 그런 이름이 정직한
139
+ * 플러그인에 있을 까닭이 없다. 반대 방향으로 틀리면 남의 글자 한 줄로
140
+ * 폴더 밖에 파일이 써진다.
141
+ */
142
+ const 이름꼴 = 원래.replace(/\\/g, '/');
143
+ // 드라이브 글자(`C:`)도 마찬가지다. 리눅스에서는 그냥 이름이라 통과하는데,
144
+ // 그 묶음을 윈도우에서 풀면 그 자리로 곧장 나간다.
145
+ if (/^[a-zA-Z]:/.test(이름꼴)) return false;
146
+ const 뿌리절대 = resolve(String(뿌리 ?? ''));
147
+ const 갈곳 = resolve(뿌리절대, 이름꼴);
148
+ return 갈곳 === 뿌리절대 || 갈곳.startsWith(뿌리절대 + sep);
149
+ }
150
+
151
+ /** 풀 폴더 밖을 가리키는 것만 골라 준다. 하나라도 있으면 그 묶음은 안 푼다. */
152
+ export function 밖을가리키는것(뿌리, files) {
153
+ return (files ?? []).filter((f) => !안쪽인가(뿌리, f?.name));
154
+ }