deel-local-cli 1.20.13 → 2.0.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.ko.md +96 -78
- package/README.md +119 -81
- package/bin/deel.js +398 -32
- package/package.json +2 -1
- package/src/acp/jsonrpc.js +87 -7
- package/src/acp/map.js +23 -3
- package/src/acp/serve.js +333 -25
- package/src/agent/agents.js +110 -12
- package/src/agent/askcheck.js +50 -6
- package/src/agent/asks.js +59 -5
- package/src/agent/budget.js +9 -2
- package/src/agent/card.js +8 -2
- package/src/agent/commit.js +168 -16
- package/src/agent/compact.js +93 -21
- package/src/agent/{/354/213/240/353/242/260/353/217/204.js → confidence.js} +1 -1
- package/src/agent/effort.js +85 -15
- package/src/agent/evidence.js +63 -7
- package/src/agent/evolve.js +132 -24
- package/src/agent/grade.js +12 -3
- package/src/agent/loop.js +408 -81
- package/src/agent/memory.js +176 -29
- package/src/agent/mention.js +22 -5
- package/src/agent/models.js +31 -7
- package/src/agent/modes.js +25 -10
- package/src/agent/outschema.js +381 -56
- package/src/agent/pins.js +33 -2
- package/src/agent/project.js +73 -8
- package/src/agent/recall.js +40 -9
- package/src/agent/{/354/204/261/355/225/234/352/270/260/353/241/235.js → recordshape.js} +19 -8
- package/src/agent/review.js +143 -13
- package/src/agent/route.js +253 -26
- package/src/agent/salvage.js +2 -1
- package/src/agent/session.js +457 -31
- package/src/agent/store.js +232 -22
- package/src/agent/threads.js +52 -3
- package/src/backend/adapter.js +508 -85
- package/src/backend/azure.js +12 -1
- package/src/backend/cachemark.js +31 -5
- package/src/backend/clientcert.js +23 -1
- package/src/backend/ctxsize.js +56 -9
- package/src/backend/detect.js +252 -19
- package/src/backend/http.js +383 -42
- package/src/backend/learn.js +101 -11
- package/src/backend/mcp.js +333 -24
- package/src/backend/price.js +61 -13
- package/src/backend/probe.js +188 -36
- package/src/backend/proxy.js +108 -14
- package/src/backend/quota.js +226 -30
- package/src/backend/retry.js +46 -3
- package/src/backend/scan.js +47 -10
- package/src/backend/scanui.js +57 -7
- package/src/backend/toolfit.js +204 -22
- package/src/backend/vision.js +14 -1
- package/src/backend/wire.js +58 -10
- package/src/cmdnames.js +16 -0
- package/src/commands/common.js +70 -0
- package/src/commands/extend.js +300 -0
- package/src/commands/model.js +852 -0
- package/src/commands/view.js +328 -0
- package/src/commands/work.js +823 -0
- package/src/commands.js +193 -2082
- package/src/completion.js +45 -3
- package/src/config.js +452 -39
- package/src/configexplain.js +125 -14
- package/src/doctor.js +164 -26
- package/src/i18n/en.js +22 -0
- package/src/i18n/index.js +31 -1
- package/src/i18n/ja.js +25 -1
- package/src/i18n/ko.js +25 -2
- package/src/i18n/zh.js +25 -1
- package/src/lsp/client.js +111 -9
- package/src/lsp/servers.js +86 -22
- package/src/oneshot.js +325 -65
- package/src/pack/sbom.js +28 -5
- package/src/pack/selfpack.js +54 -5
- package/src/pack/sheet.en.js +24 -3
- package/src/pack/tar.js +45 -8
- package/src/pack/zip.js +103 -8
- package/src/plugins/manage.js +165 -14
- package/src/preview/serve.js +239 -23
- package/src/providers/bedrock.js +30 -5
- package/src/providers/gemini.js +8 -0
- package/src/providers/index.js +17 -1
- package/src/repl.js +254 -61
- package/src/report.js +7 -2
- package/src/reset.js +151 -25
- package/src/safety/audit.js +108 -8
- package/src/safety/authcmd.js +75 -5
- package/src/safety/guard.js +507 -15
- package/src/safety/hooks.js +172 -20
- package/src/safety/keystore.js +178 -18
- package/src/safety/network.js +209 -6
- package/src/safety/policy.js +377 -36
- package/src/safety/runmode.js +9 -2
- package/src/safety/secrets.js +243 -17
- package/src/safety/shellenv.js +64 -3
- package/src/safety/trust.js +298 -32
- package/src/safety/undo.js +409 -17
- package/src/setup.js +175 -17
- package/src/skills/discover.js +122 -19
- package/src/stats.js +57 -16
- package/src/tools/{/355/231/225/354/235/270/353/262/225.js → checkmethods.js} +26 -1
- package/src/tools/clipboard.js +53 -20
- package/src/tools/convert.js +118 -37
- package/src/tools/desc.en.js +48 -5
- package/src/tools/doc2md.js +11 -1
- package/src/tools/docs.js +94 -18
- package/src/tools/edit-match.js +212 -13
- package/src/tools/encoding.js +525 -28
- package/src/tools/excel-com.js +63 -15
- package/src/tools/excel.js +12 -7
- package/src/tools/fastgrep.js +444 -36
- package/src/tools/fig.js +21 -4
- package/src/tools/fsutil.js +235 -21
- package/src/tools/hwpxwrite.js +80 -19
- package/src/tools/ignore.js +55 -7
- package/src/tools/index.js +1029 -145
- package/src/tools/jobs.js +325 -50
- package/src/tools/kiwi.js +50 -5
- package/src/tools/lsp.js +164 -32
- package/src/tools/outline.js +52 -12
- package/src/tools/pdf.js +366 -56
- package/src/tools/shell.js +12 -4
- package/src/tools/spawn.js +203 -18
- package/src/tools/todo.js +73 -4
- package/src/tools/verify.js +312 -43
- package/src/tools/webfetch.js +184 -25
- package/src/tools/xlsx.js +232 -31
- package/src/ui/ansi.js +209 -15
- package/src/ui/banner.js +2 -1
- package/src/ui/complete.js +46 -4
- package/src/ui/diff.js +4 -2
- package/src/ui/export.js +91 -17
- package/src/ui/inputbox.js +58 -18
- package/src/ui/intro.js +9 -2
- package/src/ui/level.js +32 -7
- package/src/ui/md.js +101 -15
- package/src/ui/motion.js +5 -2
- package/src/ui/notify.js +7 -1
- package/src/ui/office.js +37 -5
- package/src/ui/pastechip.js +3 -2
- package/src/ui/pick.js +85 -12
- package/src/ui/prompt.js +176 -13
- package/src/ui/screen.js +2 -1
- package/src/ui/spinner.js +6 -1
- package/src/ui/status.js +80 -12
- package/src/ui/wrap.js +23 -37
- /package/src/agent/{/353/213/250/352/263/204.js" → phase.js} +0 -0
- /package/src/skills/builtin/{/352/271/212/354/235/264/354/236/210/352/262/214-/353/247/214/353/223/244/352/270/260/SKILL.md" → build-deep/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/250/352/267/274/354/260/250/352/267/274-/353/224/224/353/262/204/352/271/205/SKILL.md" → debug-step-by-step/SKILL.md} +0 -0
- /package/src/skills/builtin/{/353/201/235/352/271/214/354/247/200-/355/225/230/352/270/260/SKILL.md" → finish-all/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/212/244/354/212/244/353/241/234-/352/262/200/355/206/240/SKILL.md" → self-review/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/275/224/353/223/234-/354/244/204/354/235/264/352/270/260/SKILL.md" → simplify-code/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/224/353/237/254/353/263/264/352/270/260/SKILL.md" → spike/SKILL.md} +0 -0
- /package/src/skills/builtin/{/352/262/200/354/202/254-/353/250/274/354/240/200/SKILL.md" → test-first/SKILL.md} +0 -0
package/src/reset.js
CHANGED
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
// 설정을 먼저 읽는다. 그래서 이 파일은 config.js 의 load() 를 안 쓴다 —
|
|
27
27
|
// 파일을 직접 읽고, 못 읽으면 못 읽었다고 적은 채로 계속 간다.
|
|
28
28
|
// (`deel --version` 을 연결 없이 답하게 만든 것과 같은 이유다.)
|
|
29
|
-
import { join, resolve, sep } from 'node:path';
|
|
30
|
-
import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
29
|
+
import { join, resolve, sep, dirname, basename } from 'node:path';
|
|
30
|
+
import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync, chmodSync, realpathSync } from 'node:fs';
|
|
31
31
|
import { c, say, mark, rule, pad } from './ui/ansi.js';
|
|
32
32
|
import { ask, confirm } from './ui/prompt.js';
|
|
33
|
-
import { homeDir } from './config.js';
|
|
33
|
+
import { homeDir, 소식줄들 } from './config.js';
|
|
34
34
|
import { pluginsDir } from './plugins/manage.js';
|
|
35
35
|
import { 잠긴것인가, 잠금지우기, 보관방식 } from './safety/keystore.js';
|
|
36
36
|
|
|
@@ -64,6 +64,12 @@ function 안에것(폴더, 거르기 = () => true) {
|
|
|
64
64
|
/** 여러 자리를 센 것을 하나로 모은다. 한 자리라도 못 셌으면 못 센 것이다. */
|
|
65
65
|
const 셈모으기 = (값들) => (값들.some((n) => n === null) ? null : 값들.reduce((a, n) => a + n, 0));
|
|
66
66
|
|
|
67
|
+
/** 두 경로가 같은 파일인가. 윈도우는 대소문자를 안 가린다. */
|
|
68
|
+
const 같은자리 = (a, b) => {
|
|
69
|
+
const [x, y] = [resolve(a), resolve(b)];
|
|
70
|
+
return process.platform === 'win32' ? x.toLowerCase() === y.toLowerCase() : x === y;
|
|
71
|
+
};
|
|
72
|
+
|
|
67
73
|
/**
|
|
68
74
|
* 설정을 읽어 본다. **깨져 있어도 답한다.**
|
|
69
75
|
*
|
|
@@ -90,12 +96,24 @@ export function 설정살피기(파일) {
|
|
|
90
96
|
* 적어 둔 **금지 규칙**이 초기화 한 번에 없어진다. 이 파일 머리말이
|
|
91
97
|
* 「사람이 손으로 적은 것은 어떤 길로도 안 지운다」 고 적어 둔 그 약속이다.
|
|
92
98
|
*/
|
|
93
|
-
|
|
99
|
+
/*
|
|
100
|
+
* `apiVersion` 은 Azure 판 번호가 실제로 읽히는 이름이다(backend/azure.js).
|
|
101
|
+
* 여기에 `api-version` 만 있어서 그 칸이 「사람이 적은 다른 것」 으로 잡혔다.
|
|
102
|
+
* `version` 은 이 프로그램이 파일 모양 번호로 적는 칸이라 사람 것이 아니다 —
|
|
103
|
+
* 이걸 세면 한 번이라도 저장한 집 설정은 언제나 「다른 것이 있다」 가 된다.
|
|
104
|
+
*/
|
|
105
|
+
const 연결칸 = new Set(['profiles', 'active', '정책주소', 'api-version', 'apiVersion']);
|
|
106
|
+
const 구조칸 = new Set(['version']);
|
|
107
|
+
const 객체 = j && typeof j === 'object' && !Array.isArray(j) ? j : {};
|
|
94
108
|
return {
|
|
95
109
|
있나: true,
|
|
96
110
|
프로필: 목록.length,
|
|
97
111
|
잠긴열쇠: 목록.map((p) => p?.apiKey).filter((k) => 잠긴것인가(k)),
|
|
98
|
-
다른것: Object.keys(
|
|
112
|
+
다른것: Object.keys(객체).filter((k) => !연결칸.has(k) && !구조칸.has(k)),
|
|
113
|
+
// 걷을 연결 칸이 하나라도 있나. 없으면 되쓸 것도 지웠다고 할 것도 없다.
|
|
114
|
+
연결있나: Object.keys(객체).some((k) => 연결칸.has(k)),
|
|
115
|
+
// 연결 칸만 걷고 남길 때 적을 모양. 구조 칸은 같이 남긴다.
|
|
116
|
+
남길값: Object.fromEntries(Object.entries(객체).filter(([k]) => !연결칸.has(k))),
|
|
99
117
|
왜: '',
|
|
100
118
|
};
|
|
101
119
|
}
|
|
@@ -113,23 +131,51 @@ export function 살펴보기({ home = homeDir(), root = process.cwd() } = {}) {
|
|
|
113
131
|
const 일 = (...n) => join(root, '.deel', ...n);
|
|
114
132
|
|
|
115
133
|
const 집설정 = 설정살피기(집('config.json'));
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
/*
|
|
135
|
+
* DEEL_HOME 이 곧 이 폴더의 .deel 이면(홈 폴더에서 돌리면 그렇다) 두 자리는 **한 파일**이다.
|
|
136
|
+
* 따로 읽으면 프로필을 두 번 세고 지울 자리에도 두 번 적는다. 그때는 집 설정으로만 본다.
|
|
137
|
+
*/
|
|
138
|
+
const 한파일 = 같은자리(집('config.json'), 일('config.json'));
|
|
139
|
+
const 일설정 = 한파일 ? { 있나: false, 프로필: 0, 잠긴열쇠: [], 왜: '' } : 설정살피기(일('config.json'));
|
|
140
|
+
// 저장소 설정에 연결 말고 다른 것이 적혀 있거나 못 읽으면 그 파일은 안 건드린다.
|
|
119
141
|
const 일설정남길까 = 일설정.있나 && (일설정.프로필 === null || (일설정.다른것?.length ?? 0) > 0);
|
|
142
|
+
// 남기는 파일이 가리키는 열쇠는 잠금장치에서도 안 지운다 — 파일만 남고 열쇠가 사라진다.
|
|
143
|
+
const 열쇠들 = [...집설정.잠긴열쇠, ...(일설정남길까 ? [] : 일설정.잠긴열쇠)];
|
|
144
|
+
/*
|
|
145
|
+
* 집 설정은 사정이 다르다 — 이건 이 PC 의 연결이라 초기화하려는 바로 그것이다.
|
|
146
|
+
* 그런데 같은 파일에 사람이 건 금지·봉인·셸도 산다. 통째로 지우면 「연결
|
|
147
|
+
* 초기화」 에 그것들이 딸려 사라졌다. 그래서 파일은 남기고 **연결 칸만** 걷는다.
|
|
148
|
+
*
|
|
149
|
+
* 깨졌으면(프로필 null) 통째로 지운다. 깨진 설정이야말로 지우려는 것이고,
|
|
150
|
+
* 못 읽으면 무엇이 사람 것인지 가를 수도 없다.
|
|
151
|
+
*/
|
|
152
|
+
const 집설정다듬기 = 집설정.있나 && 집설정.프로필 !== null && 집설정.연결있나 && (집설정.다른것?.length ?? 0) > 0;
|
|
153
|
+
// 걷을 연결 칸이 아예 없고 사람 칸만 있으면 파일을 **안 건드린다.** 되쓰면 바뀐 것도
|
|
154
|
+
// 없이 「연결·프로필 0개 지웠습니다」 가 뜨고, 지우는 쪽으로 가면 사람 칸이 없어진다.
|
|
155
|
+
const 집설정그대로 = 집설정.있나 && 집설정.프로필 !== null && !집설정.연결있나 && (집설정.다른것?.length ?? 0) > 0;
|
|
120
156
|
const 플러그인자리 = pluginsDir(home);
|
|
157
|
+
// 배운 것도 홈 폴더에서 켜면 두 자리가 **한 파일**이다 — 위 `한파일` 과 같은 판인데 여기만 빠져
|
|
158
|
+
// 「배운 것 2곳」 으로 셌다 (2.0.0 6회차 · Gemini 되돌림6ab-a R2).
|
|
159
|
+
const 배운자리 = 같은자리(집('배운것.json'), 일('배운것.json')) ? [집('배운것.json')] : [집('배운것.json'), 일('배운것.json')];
|
|
121
160
|
|
|
122
161
|
const 항목 = [
|
|
123
162
|
{
|
|
124
163
|
키: 'model',
|
|
125
164
|
이름: '연결·프로필',
|
|
126
|
-
자리: [
|
|
127
|
-
|
|
128
|
-
|
|
165
|
+
자리: [
|
|
166
|
+
...(집설정그대로 ? [] : [집('config.json')]),
|
|
167
|
+
...(일설정남길까 || 한파일 ? [] : [일('config.json')]),
|
|
168
|
+
],
|
|
169
|
+
// 남기는 저장소 설정의 프로필은 **안 센다.** 세면 「3개 → 지웁니다」 라고
|
|
170
|
+
// 적어 놓고 2개만 지운다. 셈과 지우는 것이 같은 목록을 봐야 한다.
|
|
171
|
+
몇: 집설정.프로필 === null
|
|
172
|
+
? null : (집설정.프로필 ?? 0) + (일설정남길까 ? 0 : (일설정.프로필 ?? 0)),
|
|
129
173
|
단위: '개',
|
|
130
174
|
탈: 집설정.왜 || 일설정.왜,
|
|
131
175
|
뒤: 'deel setup 을 다시 하게 됩니다',
|
|
132
176
|
all: true,
|
|
177
|
+
// 지우지 않고 연결 칸만 걷어 되쓰는 자리.
|
|
178
|
+
다듬기: 집설정다듬기 ? [{ 자리: 집('config.json'), 남길값: 집설정.남길값, 남긴칸: 집설정.다른것 }] : [],
|
|
133
179
|
// 설정 파일을 지우기 **전에** 잠금장치를 먼저 손봐야 한다. 파일을
|
|
134
180
|
// 지우고 나면 어느 열쇠가 잠겨 있었는지 알 길이 없다.
|
|
135
181
|
열쇠: 열쇠들,
|
|
@@ -155,8 +201,8 @@ export function 살펴보기({ home = homeDir(), root = process.cwd() } = {}) {
|
|
|
155
201
|
{
|
|
156
202
|
키: 'learned',
|
|
157
203
|
이름: '배운 것',
|
|
158
|
-
자리:
|
|
159
|
-
몇:
|
|
204
|
+
자리: 배운자리,
|
|
205
|
+
몇: 배운자리.filter((p) => existsSync(p)).length,
|
|
160
206
|
단위: '곳',
|
|
161
207
|
뒤: '다시 배웁니다',
|
|
162
208
|
all: true,
|
|
@@ -211,7 +257,14 @@ export function 살펴보기({ home = homeDir(), root = process.cwd() } = {}) {
|
|
|
211
257
|
// 어떤 길로도 안 지우는 것. 화면에 이름을 적어 둔다 — 「왜 이건 안 지웠지」
|
|
212
258
|
// 를 나중에 묻게 하지 않으려고.
|
|
213
259
|
const 안건드림 = [
|
|
214
|
-
{
|
|
260
|
+
{
|
|
261
|
+
이름: 일설정.프로필 === null
|
|
262
|
+
? '.deel/config.json (읽을 수 없어서 안 건드립니다 — 저장소에 딸린 파일입니다)'
|
|
263
|
+
: '.deel/config.json (연결 말고 다른 것이 적혀 있습니다)',
|
|
264
|
+
있나: 일설정남길까,
|
|
265
|
+
},
|
|
266
|
+
{ 이름: `이 PC 설정의 ${(집설정.다른것 ?? []).join(' · ')} (연결 칸만 걷습니다)`, 있나: 집설정다듬기 },
|
|
267
|
+
{ 이름: `이 PC 설정의 ${(집설정.다른것 ?? []).join(' · ')} (걷을 연결 칸이 없습니다)`, 있나: 집설정그대로 },
|
|
215
268
|
{ 이름: '.deel/mcp.json', 있나: existsSync(일('mcp.json')) },
|
|
216
269
|
{ 이름: '.deelignore', 있나: existsSync(join(root, '.deelignore')) },
|
|
217
270
|
{ 이름: 'DEEL.md', 있나: existsSync(join(root, 'DEEL.md')) },
|
|
@@ -229,13 +282,43 @@ export function 살펴보기({ home = homeDir(), root = process.cwd() } = {}) {
|
|
|
229
282
|
* 것까지 딸려 가면 이 기능을 못 믿게 된다.
|
|
230
283
|
*/
|
|
231
284
|
export function 울타리안인가(경로, 울타리들) {
|
|
232
|
-
const p =
|
|
285
|
+
const p = 윗자리풀기(경로);
|
|
233
286
|
return 울타리들.some((u) => {
|
|
234
287
|
const r = resolve(u);
|
|
235
288
|
return p !== r && p.startsWith(r + sep);
|
|
236
289
|
});
|
|
237
290
|
}
|
|
238
291
|
|
|
292
|
+
/*
|
|
293
|
+
* ── 링크를 따라간 진짜 자리로 본다 (2.0.0 6회차 · Gemini 되돌림6ab-b R4) ────────────
|
|
294
|
+
*
|
|
295
|
+
* resolve 는 글자만 푼다. 작업 폴더의 `.deel` 이 다른 폴더로 가는 링크면(리눅스·맥 git 은 받은
|
|
296
|
+
* 저장소의 링크를 그대로 푼다) 그 너머의 memory.md · tmp · export 가 「작업 폴더 안」 으로 읽혀
|
|
297
|
+
* 통째로 지워졌다 — 임시 폴더로 실행해 확인. 그래서 **윗자리들**을 진짜 경로로 푼 뒤 본다.
|
|
298
|
+
*
|
|
299
|
+
* 자리 그 자체는 안 푼다. 그것이 링크면 rmSync 는 링크만 지우고 너머는 그대로다(검사로 확인).
|
|
300
|
+
* 없는 자리는 있는 데까지 올라가 푼 뒤 나머지 이름을 붙인다 — 글자만 준 검사도 그대로 돈다.
|
|
301
|
+
*/
|
|
302
|
+
function 윗자리풀기(경로) {
|
|
303
|
+
const p = resolve(경로);
|
|
304
|
+
const 꼬리 = [basename(p)];
|
|
305
|
+
let 위 = dirname(p);
|
|
306
|
+
for (;;) {
|
|
307
|
+
try { return join(realpathSync.native(위), ...꼬리); } catch {
|
|
308
|
+
const 더위 = dirname(위);
|
|
309
|
+
if (더위 === 위) return p;
|
|
310
|
+
꼬리.unshift(basename(위));
|
|
311
|
+
위 = 더위;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/*
|
|
317
|
+
* 울타리 쪽 풀기. 살림 자리(home)는 끝까지 푼다 — 사람이 DEEL_HOME 을 링크로 옮겨 둔 것은
|
|
318
|
+
* 그 사람의 뜻이다. 작업 폴더는 폴더까지만 풀고 `.deel` 은 **안 따라간다** — 거기가 R4 의 문이다.
|
|
319
|
+
*/
|
|
320
|
+
const 실경로 = (p) => { try { return realpathSync.native(p); } catch { return resolve(p); } };
|
|
321
|
+
|
|
239
322
|
/**
|
|
240
323
|
* 고른 것을 지운다.
|
|
241
324
|
*
|
|
@@ -245,7 +328,8 @@ export function 울타리안인가(경로, 울타리들) {
|
|
|
245
328
|
*/
|
|
246
329
|
export function 지우기(무엇, { home = homeDir(), root = process.cwd(), hard = false } = {}) {
|
|
247
330
|
const 본것 = 살펴보기({ home, root });
|
|
248
|
-
|
|
331
|
+
// 울타리도 진짜 경로로 — 자리 쪽을 윗자리까지 푸는 것과 짝이다 (6회차 R4 · 위 실경로 머리말).
|
|
332
|
+
const 울타리 = [실경로(home), join(실경로(root), '.deel')];
|
|
249
333
|
|
|
250
334
|
const 고른것 = 무엇 === 'all'
|
|
251
335
|
? [...본것.항목.filter((x) => x.all), ...(hard ? 본것.굳은것 : [])]
|
|
@@ -254,11 +338,27 @@ export function 지우기(무엇, { home = homeDir(), root = process.cwd(), hard
|
|
|
254
338
|
const 지운것 = [];
|
|
255
339
|
const 못한것 = [];
|
|
256
340
|
let 열쇠 = null;
|
|
341
|
+
let 열쇠들 = [];
|
|
257
342
|
|
|
258
343
|
for (const 것 of 고른것) {
|
|
259
344
|
// 설정을 지우기 전에 잠금장치부터. 순서가 반대면 어느 열쇠였는지 잃는다.
|
|
345
|
+
/*
|
|
346
|
+
* ── 첫 열쇠 하나만 지우고 있었다 ────────────────────────────────────
|
|
347
|
+
*
|
|
348
|
+
* 여기가 `잠금지우기(것.열쇠[0])` 였다. 프로필마다 키체인 자리를 따로 쓰니
|
|
349
|
+
* (safety/keystore.js 의 키체인이름) 잠긴 열쇠는 여럿일 수 있는데, 두 번째부터는
|
|
350
|
+
* 키체인에 그대로 남고 화면은 「잠금장치의 열쇠 ✓」 한 줄이었다.
|
|
351
|
+
* 전부 지우고 하나하나 무엇을 했는지 돌려준다. 같은 태그는 한 번만 — 두 번째는
|
|
352
|
+
* 「이미 없습니다」 로 돌아와 못 지운 것처럼 읽힌다.
|
|
353
|
+
* `열쇠` 는 모아 본 답이다. 하나라도 못 지웠으면 지웠다고 안 한다.
|
|
354
|
+
*/
|
|
260
355
|
if (것.키 === 'model' && 것.열쇠?.length) {
|
|
261
|
-
|
|
356
|
+
열쇠들 = [...new Set(것.열쇠)].map((태그) => {
|
|
357
|
+
const 갈래 = /^keychain:(.*)$/s.exec(String(태그));
|
|
358
|
+
return { 이름: 갈래 ? 갈래[1] : null, ...잠금지우기(태그) };
|
|
359
|
+
});
|
|
360
|
+
const 못한 = 열쇠들.find((x) => !x.지움);
|
|
361
|
+
열쇠 = { 지움: !못한, 방식: (못한 ?? 열쇠들[0]).방식, 왜: 못한?.왜 ?? '' };
|
|
262
362
|
}
|
|
263
363
|
// 한 갈래가 여러 자리에 걸쳐 있다 (배운 것은 이 PC 와 이 폴더 둘 다).
|
|
264
364
|
// 화면에는 **갈래 하나로** 적는다 — 자리마다 한 줄씩 내면 「배운 것 2곳」
|
|
@@ -271,9 +371,16 @@ export function 지우기(무엇, { home = homeDir(), root = process.cwd(), hard
|
|
|
271
371
|
못한것.push({ 이름: 것.이름, 자리, 왜: '작업 폴더 밖이라 안 건드렸습니다' });
|
|
272
372
|
continue;
|
|
273
373
|
}
|
|
374
|
+
const 다듬 = (것.다듬기 ?? []).find((x) => x.자리 === 자리);
|
|
274
375
|
try {
|
|
275
|
-
|
|
276
|
-
|
|
376
|
+
if (다듬) {
|
|
377
|
+
writeFileSync(자리, JSON.stringify(다듬.남길값, null, 2) + '\n', 'utf8');
|
|
378
|
+
try { chmodSync(자리, 0o600); } catch { /* 안 먹는 파일 체계가 있다 — 원래 모드 그대로다 */ }
|
|
379
|
+
지운자리.push(`${자리} (연결 칸만 — ${다듬.남긴칸.join(' · ')} 은 남겼습니다)`);
|
|
380
|
+
} else {
|
|
381
|
+
rmSync(자리, { recursive: true, force: true });
|
|
382
|
+
지운자리.push(자리);
|
|
383
|
+
}
|
|
277
384
|
} catch (err) {
|
|
278
385
|
못한것.push({ 이름: 것.이름, 자리, 왜: String(err.message).slice(0, 80) });
|
|
279
386
|
}
|
|
@@ -282,7 +389,7 @@ export function 지우기(무엇, { home = homeDir(), root = process.cwd(), hard
|
|
|
282
389
|
지운것.push({ 키: 것.키, 이름: 것.이름, 몇: 것.몇, 단위: 것.단위, 자리들: 지운자리 });
|
|
283
390
|
}
|
|
284
391
|
}
|
|
285
|
-
return { 지운것, 못한것,
|
|
392
|
+
return { 지운것, 못한것, 열쇠, 열쇠들 };
|
|
286
393
|
}
|
|
287
394
|
|
|
288
395
|
// ── 화면 ────────────────────────────────────────────────────────────────
|
|
@@ -322,7 +429,15 @@ function 보여주기(본것) {
|
|
|
322
429
|
function 지운뒤적기(결과) {
|
|
323
430
|
say('');
|
|
324
431
|
// 「완료」 한 줄은 확인이 안 된다. 무엇이 몇 개 없어졌는지 적는다.
|
|
325
|
-
|
|
432
|
+
/*
|
|
433
|
+
* **못 지운 것이 있으면 「비어 있다」 고 하지 않는다.**
|
|
434
|
+
*
|
|
435
|
+
* 지운 것이 0 이라는 것만 보고 있어서, 있는데 못 지운 판에서도 이 줄이 그대로
|
|
436
|
+
* 나갔다 — 화면에는 「⚠ 이미 비어 있습니다」 와 「✗ … 안 건드렸습니다」 가 나란히
|
|
437
|
+
* 붙었다. 앞줄을 믿은 사람은 지워진 줄 알고 그 PC 를 넘기고, 지우려던 것은 그대로
|
|
438
|
+
* 남는다. 「없어서 안 지웠다」 와 「있는데 못 지웠다」 는 사람이 할 일이 정반대다.
|
|
439
|
+
*/
|
|
440
|
+
if (!결과.지운것.length && !결과.못한것.length) {
|
|
326
441
|
say(` ${mark.warn} ${c.gray('지울 것이 없었습니다 — 이미 비어 있습니다.')}`);
|
|
327
442
|
}
|
|
328
443
|
for (const x of 결과.지운것) {
|
|
@@ -330,10 +445,15 @@ function 지운뒤적기(결과) {
|
|
|
330
445
|
say(` ${mark.ok} ${c.bold(x.이름)}${셈}`);
|
|
331
446
|
for (const 자리 of x.자리들) say(` ${c.gray(자리)}`);
|
|
332
447
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
448
|
+
// 열쇠마다 한 줄. 모아서 한 줄로 적으면 둘 중 하나만 지운 것이 「✓」 뒤에 숨는다.
|
|
449
|
+
// 같은 말(DPAPI 는 전부 「설정 파일 안에」)은 한 번만 적는다.
|
|
450
|
+
const 적은줄 = new Set();
|
|
451
|
+
for (const k of 결과.열쇠들 ?? (결과.열쇠 ? [결과.열쇠] : [])) {
|
|
452
|
+
const 이름 = k.이름 ? ` ${k.이름}` : '';
|
|
453
|
+
const 줄 = k.지움
|
|
454
|
+
? ` ${mark.ok} ${c.bold('잠금장치의 열쇠')} ${c.gray(`${k.방식}${이름}`)}`
|
|
455
|
+
: ` ${mark.warn} ${c.gray(`잠금장치${이름} — ${k.왜}`)}`;
|
|
456
|
+
if (!적은줄.has(줄)) { 적은줄.add(줄); say(줄); }
|
|
337
457
|
}
|
|
338
458
|
for (const x of 결과.못한것) {
|
|
339
459
|
say(` ${mark.no} ${x.이름} ${c.gray(x.자리)}`);
|
|
@@ -364,6 +484,12 @@ export async function runReset(args = [], flags = {}) {
|
|
|
364
484
|
}
|
|
365
485
|
|
|
366
486
|
const 본것 = 살펴보기({ home, root });
|
|
487
|
+
/*
|
|
488
|
+
* 모아 둔 소식을 비운다 (config.js 의 소식줄들). 이 문은 설정을 안 읽으므로
|
|
489
|
+
* 대개 관리 정책이 깨졌다는 말 하나다 — 초기화를 찾는 사람이 「왜 설정이
|
|
490
|
+
* 안 먹나」 를 쫓고 있다면 제일 먼저 봐야 할 줄이다.
|
|
491
|
+
*/
|
|
492
|
+
for (const 줄 of 소식줄들()) say(줄);
|
|
367
493
|
보여주기(본것);
|
|
368
494
|
|
|
369
495
|
if (!무엇) {
|
package/src/safety/audit.js
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
// 감사 로그. 무엇을 언제 어떻게 했는지 전부 남긴다.
|
|
2
2
|
// 자율 실행을 사내에 설득할 때 이 파일이 근거가 된다.
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { appendFileSync, mkdirSync, existsSync, readFileSync, chmodSync } from 'node:fs';
|
|
4
|
+
import { appendFileSync, mkdirSync, existsSync, readFileSync, chmodSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
5
5
|
import { 가리기 } from './secrets.js';
|
|
6
6
|
import { 첫이름 } from '../tools/label.js';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* 살림 폴더(.deel · .deel/history)를 만든다. 못 만들면 **사람 말로** 던진다 (6회차 C3).
|
|
10
|
+
*
|
|
11
|
+
* 앞서는 mkdirSync 의 원시 오류가 그대로 올라가, 대화 화면·deel -p·에디터 셋 다
|
|
12
|
+
* 「EEXIST: file already exists, mkdir '…\.deel'」 한 줄만 보였다. 안 죽기는 했지만
|
|
13
|
+
* 무엇이 막혔고 어떻게 하면 되는지가 없었다. 작업 폴더에 같은 이름의 파일이 있거나
|
|
14
|
+
* 읽기 전용 자리면 여기서 막힌다.
|
|
15
|
+
*
|
|
16
|
+
* 던지는 것은 그대로 둔다 — 감사기록·되돌리기 이력 없이 조용히 켜지면 안 된다.
|
|
17
|
+
* 되돌리기 이력(undo.js)도 같은 말을 쓰게 여기서 내준다.
|
|
18
|
+
*/
|
|
19
|
+
export function 살림폴더만들기(dir, 덧 = {}) {
|
|
20
|
+
try {
|
|
21
|
+
mkdirSync(dir, { recursive: true, ...덧 });
|
|
22
|
+
} catch (err) {
|
|
23
|
+
const 까닭 = {
|
|
24
|
+
EEXIST: '같은 이름의 파일이 있습니다', ENOTDIR: '같은 이름의 파일이 있습니다',
|
|
25
|
+
EACCES: '쓸 권한이 없습니다', EPERM: '쓸 권한이 없습니다', EROFS: '읽기 전용 자리입니다',
|
|
26
|
+
}[err?.code] ?? String(err?.message ?? err);
|
|
27
|
+
const e = new Error(`작업 폴더에 deel 살림 폴더를 만들지 못했습니다 (${dir}) — ${까닭}.`
|
|
28
|
+
+ ' 같은 이름의 파일이면 옮기거나 지우고, 쓸 수 없는 폴더면 쓸 수 있는 곳에서 켜세요.');
|
|
29
|
+
e.code = err?.code;
|
|
30
|
+
e.cause = err;
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
7
34
|
import { 환경속열쇠들 } from '../config.js';
|
|
8
35
|
|
|
9
36
|
/**
|
|
@@ -36,9 +63,19 @@ export class Audit {
|
|
|
36
63
|
*/
|
|
37
64
|
constructor(root, { 열쇠들 = [] } = {}) {
|
|
38
65
|
const dir = join(root, '.deel');
|
|
39
|
-
|
|
66
|
+
살림폴더만들기(dir);
|
|
40
67
|
this.file = join(dir, 'audit.jsonl');
|
|
41
|
-
|
|
68
|
+
/*
|
|
69
|
+
* 세션 이름은 시각으로 시작하되 그것만으로 끝내지 않는다.
|
|
70
|
+
*
|
|
71
|
+
* 초 단위 시각 하나였더니 같은 초에 띄운 창 두 개(또는 한 판 안의 하위 작업)가
|
|
72
|
+
* 같은 이름을 나눠 가졌다. `deel stats` 는 세션을 이름으로 세고 증거모으기는
|
|
73
|
+
* 이 이름으로 「이번 세션」 을 가르므로, 남의 기록이 내 것으로 섞였다.
|
|
74
|
+
* agent/store.js 의 freeId 처럼 밀리초·프로세스 번호·난수를 덧붙인다.
|
|
75
|
+
*/
|
|
76
|
+
const 지금 = new Date();
|
|
77
|
+
this.session = `${지금.toISOString().slice(0, 19).replace(/[:T]/g, '')}`
|
|
78
|
+
+ `-${String(지금.getMilliseconds()).padStart(3, '0')}-${process.pid}-${Math.random().toString(36).slice(2, 6)}`;
|
|
42
79
|
/** 적으려다 못 적은 건수와 첫 까닭. 0 이 아니면 이 세션의 기록은 불완전하다. */
|
|
43
80
|
this.못쓴수 = 0;
|
|
44
81
|
this.못쓴까닭 = null;
|
|
@@ -78,12 +115,47 @@ export class Audit {
|
|
|
78
115
|
* 아니라 아예 **안 고친 것처럼** 보고서가 짧아진다.
|
|
79
116
|
* 그래서 못 적은 건수를 세어 두고, 보고서와 화면이 그걸 말하게 한다.
|
|
80
117
|
*/
|
|
118
|
+
/*
|
|
119
|
+
* ── 반쪽 줄 뒤에 이어 적지 않는다 ─────────────────────────────────────────
|
|
120
|
+
*
|
|
121
|
+
* 적는 도중에 죽으면 마지막 줄이 개행 없이 반만 남는다. 다음 판이 그 뒤에 그대로
|
|
122
|
+
* 이어 적으면 새 기록이 반쪽에 **붙어** 한 줄이 되고, 그 줄은 JSON 이 아니라
|
|
123
|
+
* recent()·deel stats·증거모으기가 통째로 버린다 — 다음 판이 한 첫 일이, 그것도
|
|
124
|
+
* 막힌 명령이나 되돌리기 같은 것이 기록에서 사라진다.
|
|
125
|
+
* agent/store.js 와 같은 방법이다. 이 기록으로 처음 적을 때 한 번만 파일 끝을 보고,
|
|
126
|
+
* 개행이 아니면 개행부터 붙인다. 줄마다 재지 않는다 — 그 뒤로는 우리가 적은 줄이다.
|
|
127
|
+
*/
|
|
128
|
+
#줄끝봤나 = false;
|
|
129
|
+
#줄로끝나나() {
|
|
130
|
+
let fd = null;
|
|
131
|
+
try {
|
|
132
|
+
const 크기 = statSync(this.file).size;
|
|
133
|
+
if (!크기) return true;
|
|
134
|
+
fd = openSync(this.file, 'r');
|
|
135
|
+
const 한바이트 = Buffer.alloc(1);
|
|
136
|
+
readSync(fd, 한바이트, 0, 1, 크기 - 1);
|
|
137
|
+
return 한바이트[0] === 0x0a;
|
|
138
|
+
} catch { return true; } // 없거나 못 읽으면 붙일 반쪽도 없다
|
|
139
|
+
finally { if (fd != null) { try { closeSync(fd); } catch { /* 닫다 터져도 적기는 한다 */ } } }
|
|
140
|
+
}
|
|
141
|
+
|
|
81
142
|
write(kind, data) {
|
|
82
143
|
const rec = { at: new Date().toISOString(), session: this.session, kind, ...data };
|
|
83
144
|
try {
|
|
84
|
-
|
|
145
|
+
let 줄 = JSON.stringify(rec) + '\n';
|
|
146
|
+
if (!this.#줄끝봤나) {
|
|
147
|
+
if (!this.#줄로끝나나()) 줄 = '\n' + 줄;
|
|
148
|
+
this.#줄끝봤나 = true;
|
|
149
|
+
}
|
|
150
|
+
appendFileSync(this.file, 줄, 'utf8');
|
|
85
151
|
this.#잠그기();
|
|
86
152
|
} catch (err) {
|
|
153
|
+
/*
|
|
154
|
+
* 막혔으면 끝을 다시 보게 되돌린다 (2.0.0 6회차 · Gemini 감사6aa C2 — agent/store.js 저장6w X1 과
|
|
155
|
+
* 같은 까닭). 본 표시를 적기 전에 세워서, 첫 적기가 막히면 다음 기록이 반쪽 줄 뒤에 붙었다 —
|
|
156
|
+
* 못쓴수 에는 막힌 한 건만 오르고, 붙은 기록은 적었다고 친 채 deel audit · 증거모으기에서 사라진다.
|
|
157
|
+
*/
|
|
158
|
+
this.#줄끝봤나 = false; // 막혔으니 다음 적기에서 끝을 다시 본다
|
|
87
159
|
this.못쓴수 += 1;
|
|
88
160
|
if (!this.못쓴까닭) this.못쓴까닭 = err?.message ?? String(err);
|
|
89
161
|
}
|
|
@@ -108,6 +180,9 @@ export class Audit {
|
|
|
108
180
|
#잠그기() {
|
|
109
181
|
if (this.#잠갔나) return;
|
|
110
182
|
this.#잠갔나 = true;
|
|
183
|
+
// 윈도우에서 chmod 는 아무 일도 안 하고 **성공한다.** 그 성공을 적으면 위 머리말이 금지한
|
|
184
|
+
// 「잠근 척」 이다 — agent/store.js 와 같이 안 걸었다고 적는다.
|
|
185
|
+
if (process.platform === 'win32') { this.잠금 = { 못함: 'windows' }; return; }
|
|
111
186
|
try { chmodSync(this.file, 0o600); this.잠금 = { 모드: 0o600 }; }
|
|
112
187
|
catch (err) { this.잠금 = { 못함: err?.code ?? String(err) }; }
|
|
113
188
|
}
|
|
@@ -129,19 +204,44 @@ export class Audit {
|
|
|
129
204
|
// 이 빠져 있어서, 웹을 읽은 줄과 스킬을 부른 줄이 감사기록에서만
|
|
130
205
|
// 무엇을 향한 것인지 없이 남았다.
|
|
131
206
|
target: this.#가린것(첫이름(args) ?? args?.command ?? null),
|
|
132
|
-
|
|
207
|
+
// 실패는 error 로만 오지 않는다. Bash 는 종료 코드가 0 이 아니면 `failed: true`(tools/index.js),
|
|
208
|
+
// Verify 는 탈이 나면 `failed: true` 를 싣고 error 는 비운다. error 만 보면 빨간 검사가
|
|
209
|
+
// ok 로 남고, 증거(agent/evidence.js)가 그걸 「확인한 것」 으로 내민다 (6회차 근거6at EV4).
|
|
210
|
+
ok: !result?.error && result?.failed !== true,
|
|
133
211
|
note: this.#가린것(result?.error ?? result?.summary ?? null),
|
|
134
212
|
});
|
|
135
213
|
}
|
|
136
214
|
|
|
137
|
-
|
|
138
|
-
|
|
215
|
+
/*
|
|
216
|
+
* ── 가린 뒤에 자른다 ────────────────────────────────────────────────
|
|
217
|
+
*
|
|
218
|
+
* 자르기가 먼저였다. 열쇠가 500자(300자) 선에 걸치면 앞 조각만 남는데, 조각은
|
|
219
|
+
* 정확히 아는 열쇠라도 **통째가 아니라서** 못 알아본다 — 게이트웨이 열쇠 앞
|
|
220
|
+
* 스무 글자가 그대로 적혔다. 가리기가 글을 짧게 만들 뿐 길게 만들지는 않으니
|
|
221
|
+
* 자르는 선은 그대로 지켜진다.
|
|
222
|
+
*
|
|
223
|
+
* 막힌 까닭(why)도 가린다. guard.js 가 던지는 말은 막은 명령 앞 120자를 그대로
|
|
224
|
+
* 담아서, what 은 가렸는데 why 칸에 `Authorization: Bearer …` 가 평문으로 남았다.
|
|
225
|
+
*/
|
|
226
|
+
turn(text) { return this.write('turn', { text: this.#가린것(String(text)).slice(0, 500) }); }
|
|
227
|
+
blocked(why, what) {
|
|
228
|
+
return this.write('blocked', { why: this.#가린것(why), what: this.#가린것(String(what)).slice(0, 300) });
|
|
229
|
+
}
|
|
139
230
|
undo(info) { return this.write('undo', info); }
|
|
140
231
|
|
|
232
|
+
/*
|
|
233
|
+
* 최근 n 줄. 0 이하는 「내줄 줄이 없다」 는 말이다.
|
|
234
|
+
*
|
|
235
|
+
* `slice(-0)` 은 `slice(0)` 이라 **전부** 나왔다. 0 을 넘기는 호출부는 지금 없지만,
|
|
236
|
+
* 여기서 나온 줄은 `deel audit` 화면·증거모으기·`deel stats` 로 곧장 간다 —
|
|
237
|
+
* 「최근 0줄만」 이 기록 전체를 읽어 오는 것은 부르는 쪽이 못 알아챈다.
|
|
238
|
+
*/
|
|
141
239
|
recent(n = 20) {
|
|
240
|
+
const 몇 = Math.floor(Number(n));
|
|
241
|
+
if (!Number.isFinite(몇) || 몇 <= 0) return [];
|
|
142
242
|
if (!existsSync(this.file)) return [];
|
|
143
243
|
return readFileSync(this.file, 'utf8')
|
|
144
|
-
.split('\n').filter(Boolean).slice(
|
|
244
|
+
.split('\n').filter(Boolean).slice(-몇)
|
|
145
245
|
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
146
246
|
.filter(Boolean);
|
|
147
247
|
}
|
package/src/safety/authcmd.js
CHANGED
|
@@ -151,6 +151,9 @@ export function 쓸수있나(설정, { auth = 'bearer', 봉인 = isOffline() } =
|
|
|
151
151
|
*/
|
|
152
152
|
const 토큰글자 = /^[A-Za-z0-9._~+/=-]{16,8192}$/;
|
|
153
153
|
|
|
154
|
+
/** 명령이 뱉은 것을 이만큼까지만 받는다. 넘으면 토큰이 아니다 (한번받기 의 data 머리말). */
|
|
155
|
+
const 받을상한 = 1024 * 1024;
|
|
156
|
+
|
|
154
157
|
/**
|
|
155
158
|
* 명령이 뱉은 것을 읽는다.
|
|
156
159
|
*
|
|
@@ -165,14 +168,34 @@ export function 읽기(글, { 수명 = 기본수명, 지금 = Date.now() } = {})
|
|
|
165
168
|
const s = String(글 ?? '').trim();
|
|
166
169
|
if (!s) return { ok: false, 왜: '아무것도 안 나왔습니다', 보인것: '' };
|
|
167
170
|
|
|
168
|
-
|
|
171
|
+
/*
|
|
172
|
+
* 보여 주는 첫 줄도 **가린다.**
|
|
173
|
+
*
|
|
174
|
+
* 여기만 날것이었다. 같은 파일의 stderr 자리(한번받기)는 처음부터 가리기 를
|
|
175
|
+
* 거치는데, 나온 것을 못 읽었을 때 보여 주는 이 값만 안 거쳤다. 그런데 못
|
|
176
|
+
* 읽는 까닭 1등이 「배너와 토큰이 같이 나왔다」 라서, 거절하는 그 줄에 토큰이
|
|
177
|
+
* 들어 있는 것이 예외가 아니라 보통이다. 이 값은 repl · oneshot · acp 가
|
|
178
|
+
* 그대로 찍는다 — 화면 사진·로그·심사서에 통째로 남는다.
|
|
179
|
+
*
|
|
180
|
+
* 자르고 나서 가린다. 1MB 짜리 한 줄을 통째로 가리기 에 넣으면 그 무늬들이
|
|
181
|
+
* 몇 분을 돈다 (아래 한번받기 의 받을상한 자리에 같은 까닭이 적혀 있다).
|
|
182
|
+
*/
|
|
183
|
+
const 첫줄 = 가리기(s.slice(0, 400).split('\n')[0].trim(), {}).글.slice(0, 120);
|
|
169
184
|
|
|
170
185
|
if (s.startsWith('{')) {
|
|
171
186
|
let 것;
|
|
172
187
|
try { 것 = JSON.parse(s); } catch (err) {
|
|
173
188
|
return { ok: false, 왜: `JSON 처럼 시작하는데 못 읽었습니다 (${err.message})`, 보인것: 첫줄 };
|
|
174
189
|
}
|
|
175
|
-
|
|
190
|
+
/*
|
|
191
|
+
* `accessToken` 도 본다.
|
|
192
|
+
*
|
|
193
|
+
* 이 파일 머리말이 권하는 명령은 `--query accessToken -o tsv` 라 한 줄이
|
|
194
|
+
* 나온다. 그런데 그 뒤를 빠뜨리는 것이 제일 흔한 실수이고, 그때 az 가 뱉는
|
|
195
|
+
* 기본 JSON 의 칸 이름이 정확히 이것이다. 이름 하나를 안 봐서 「token 이
|
|
196
|
+
* 없습니다」 로 거절했고, 거절하는 김에 토큰이 든 줄을 화면에 꺼내 놓았다.
|
|
197
|
+
*/
|
|
198
|
+
const token = String(것?.token ?? 것?.access_token ?? 것?.accessToken ?? '').trim();
|
|
176
199
|
if (!token) return { ok: false, 왜: 'JSON 은 읽었는데 token 이 없습니다', 보인것: 첫줄 };
|
|
177
200
|
if (!토큰글자.test(token)) return { ok: false, 왜: 'token 에 토큰이 아닌 글자가 들었습니다', 보인것: 첫줄 };
|
|
178
201
|
/*
|
|
@@ -242,8 +265,35 @@ export async function 한번받기(설정, { 기다림 = 기본기다림, signal
|
|
|
242
265
|
if (signal?.aborted) return 끊기();
|
|
243
266
|
signal?.addEventListener?.('abort', 끊기, { once: true });
|
|
244
267
|
|
|
245
|
-
|
|
246
|
-
|
|
268
|
+
/*
|
|
269
|
+
* ── 받는 양에 뚜껑을 씌운다 (사냥5 M7) ────────────────────────────────
|
|
270
|
+
*
|
|
271
|
+
* 여기는 `나온것 += b` 뿐이었다. 망가진 로그인 도구가 배너·로그를 끝없이 뱉거나
|
|
272
|
+
* 700MB 를 쏟으면 문자열 상한에 걸려 **이 data 처리기 안에서** RangeError 가 났다.
|
|
273
|
+
* 그건 잡을 자리가 없는 던짐이라 deel 이 대화째 죽었다.
|
|
274
|
+
*
|
|
275
|
+
* 토큰은 많아야 몇 KB 다(토큰글자 는 8192자까지, JSON 머리말을 붙여도). 1MB 를
|
|
276
|
+
* 넘으면 토큰이 아니라고 보고 **그 자리에서** 죽이고 까닭을 말한다. 우리 쪽 파이프도
|
|
277
|
+
* 놓는다 — 셸 아래 손자가 계속 쓰고 있으면 그놈이 EPIPE 를 맞고 멈추게.
|
|
278
|
+
* stderr 는 첫 줄만 쓰므로 앞부분만 모은다.
|
|
279
|
+
*/
|
|
280
|
+
kid.stdout.on('data', (b) => {
|
|
281
|
+
if (끝났나) return;
|
|
282
|
+
if (나온것.length + b.length > 받을상한) {
|
|
283
|
+
try { kid.kill('SIGKILL'); } catch { /* 이미 죽었으면 그만 */ }
|
|
284
|
+
try { kid.stdout.destroy(); kid.stderr.destroy(); } catch { /* 이미 닫힘 */ }
|
|
285
|
+
// 먼저 자르고 가린다 — 1MB 짜리 한 줄을 통째로 가리기 에 넣으면 그 무늬들이
|
|
286
|
+
// 몇 분을 돈다(처음 고칠 때 검사가 여기서 멈춰 섰다).
|
|
287
|
+
const 첫줄 = 가리기(나온것.slice(0, 400).trim().split('\n')[0] ?? '', {}).글.slice(0, 120);
|
|
288
|
+
return 마치기({
|
|
289
|
+
ok: false,
|
|
290
|
+
왜: `명령이 ${받을상한 / (1024 * 1024)}MB 넘게 뱉어서 끊었습니다 — 너무 많이 나옵니다. 토큰 한 줄만 나오게 해 주세요`,
|
|
291
|
+
보인것: 첫줄,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
나온것 += b;
|
|
295
|
+
});
|
|
296
|
+
kid.stderr.on('data', (b) => { if (탈난것.length < 65536) 탈난것 += b; });
|
|
247
297
|
kid.on('error', (err) => 마치기({ ok: false, 왜: `명령을 못 띄웠습니다 (${err.message})`, 보인것: '' }));
|
|
248
298
|
kid.on('close', (code) => {
|
|
249
299
|
if (code !== 0) {
|
|
@@ -287,10 +337,30 @@ export async function 열쇠(설정, { 다시 = false, 물어보기 = null, sign
|
|
|
287
337
|
*
|
|
288
338
|
* 한 판에 한 번만 묻는다. 토큰이 한 시간짜리면 세 시간 일하는 동안 세 번
|
|
289
339
|
* 받아 오는데, 세 번 다 물으면 사람은 그냥 손이 가는 대로 누른다.
|
|
340
|
+
*
|
|
341
|
+
* ── 물을 자리가 없으면 **안 띄운다** (2.0.0 8회차) ──────────────────────
|
|
342
|
+
*
|
|
343
|
+
* 여기가 `설정.곳 !== '정책' && 물어보기` 였다. 갈고리가 안 넘어오면 승인
|
|
344
|
+
* 블록을 통째로 건너뛰고 그냥 띄웠다. 그 자리가 실제로 열린다 — 세 문
|
|
345
|
+
* (repl · oneshot · acp)이 다 **켤 때** `if (conn.열쇠받기)` 로만 갈고리를
|
|
346
|
+
* 달아서, 열쇠받기 없는 프로필로 켠 뒤 `/model` 로 갈아타면
|
|
347
|
+
* `session.열쇠물어보기` 가 영영 안 붙는다. 그 뒤로는 남이 적어 준 로그인
|
|
348
|
+
* 명령이 승인 한 번 없이 돈다. 잰 것: `ok=true · 물음 없이 명령이 돌았나: true`.
|
|
349
|
+
*
|
|
350
|
+
* 물을 자리가 없는 것은 「안 물어도 된다」 가 아니라 「승인을 못 받았다」 다.
|
|
351
|
+
* 안 띄우고 까닭을 말한다 — adapter.js 는 이걸 듣고 던지지 않고 원래 열쇠로
|
|
352
|
+
* 가므로, 이 자물쇠가 멀쩡하던 연결을 끊지는 않는다.
|
|
353
|
+
*
|
|
354
|
+
* 이미 이 판에서 승인을 받아 뒀으면 그 승인이 그대로 산다. 갈고리를 안
|
|
355
|
+
* 넘기는 뒷일(요약·되돌아보기)이 같은 판에서 열쇠를 다시 받는 자리가 있고,
|
|
356
|
+
* 거기서까지 막으면 사람이 승인을 했는데도 열쇠가 안 붙는다.
|
|
290
357
|
*/
|
|
291
|
-
if (설정.곳 !== '정책'
|
|
358
|
+
if (설정.곳 !== '정책') {
|
|
292
359
|
if (물어본것 === false) return { ok: false, 왜: '이 판에서는 안 부르기로 했습니다' };
|
|
293
360
|
if (물어본것 === null) {
|
|
361
|
+
if (typeof 물어보기 !== 'function') {
|
|
362
|
+
return { ok: false, 왜: '이 명령을 띄워도 되는지 물어볼 자리가 없습니다 — 승인 없이는 안 띄웁니다' };
|
|
363
|
+
}
|
|
294
364
|
물어본것 = !!(await 물어보기(설정));
|
|
295
365
|
if (!물어본것) return { ok: false, 왜: '이 판에서는 안 부르기로 했습니다' };
|
|
296
366
|
}
|