deel-local-cli 1.20.13 → 2.0.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.ko.md +97 -78
- package/README.md +120 -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 +299 -0
- package/src/commands/model.js +851 -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 +454 -41
- package/src/configexplain.js +127 -15
- package/src/doctor.js +164 -27
- 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 +531 -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 +36 -4
- 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 +501 -38
- 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 +1083 -148
- package/src/tools/jobs.js +328 -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 +367 -59
- 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 +328 -45
- package/src/tools/webfetch.js +194 -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/configexplain.js
CHANGED
|
@@ -21,9 +21,35 @@
|
|
|
21
21
|
// 캡처되어 사내 메신저로 간다.
|
|
22
22
|
import { existsSync, readFileSync } from 'node:fs';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
|
-
import { homeDir } from './config.js';
|
|
25
|
-
import {
|
|
24
|
+
import { homeDir, 집설정파일인가, 열쇠환경이름 } from './config.js';
|
|
25
|
+
import { 믿나, 프로젝트거르기, BOM떼기, 연결칸 } from './safety/trust.js';
|
|
26
26
|
import { 정책읽기, 정책자리 } from './safety/policy.js';
|
|
27
|
+
import { 받기설정 } from './safety/authcmd.js';
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* 이 PC 프로필에 겹치는 저장소 프로필에서 연결 칸(baseUrl·kind·auth·제공자)을 걷는다.
|
|
31
|
+
*
|
|
32
|
+
* 이 칸들은 프로젝트거르기가 아니라 config.js 의 겹치기가 걷는다 — 이 PC 설정을 봐야
|
|
33
|
+
* 「겹치나」 를 알 수 있어서다. 여기가 그걸 몰라서, 믿는 저장소가 이 PC 프로필과 같은
|
|
34
|
+
* 이름으로 적은 baseUrl 이 「프로젝트 설정이 이긴다」 로 떴다. 실제로 붙는 주소는 이
|
|
35
|
+
* PC 것이다. 사람은 제 열쇠가 저장소 주소로 가는 줄 알고 놀라거나, 반대로 거기
|
|
36
|
+
* 적으면 먹는 줄 알고 계속 고친다.
|
|
37
|
+
*
|
|
38
|
+
* 맞추는 차례는 겹치기와 같다 — id 로, 없으면 name 으로. 저장소가 **새로 더한**
|
|
39
|
+
* 프로필은 안 겹치므로 그대로 둔다(그쪽 주소는 정말로 저장소가 정한다).
|
|
40
|
+
*/
|
|
41
|
+
function 겹칠때걷기(방값, 집값) {
|
|
42
|
+
const 집것 = Array.isArray(집값?.profiles) ? 집값.profiles : [];
|
|
43
|
+
const id들 = new Set(집것.map((x) => x?.id).filter((v) => v != null));
|
|
44
|
+
const 이름들 = new Set(집것.map((x) => x?.name).filter((v) => v != null));
|
|
45
|
+
for (const p of Array.isArray(방값?.profiles) ? 방값.profiles : []) {
|
|
46
|
+
if (!p || typeof p !== 'object') continue;
|
|
47
|
+
const 겹침 = (p.id != null && id들.has(p.id)) || (p.name != null && 이름들.has(p.name));
|
|
48
|
+
if (!겹침) continue;
|
|
49
|
+
for (const 칸 of 연결칸) delete p[칸];
|
|
50
|
+
}
|
|
51
|
+
return 방값;
|
|
52
|
+
}
|
|
27
53
|
|
|
28
54
|
/** 값을 안 보여 줄 칸. 이름만 봐도 알 수 있는 것만 넣는다. */
|
|
29
55
|
const 가릴칸 = new Set(['apiKey', 'key', 'passphrase', '암호']);
|
|
@@ -35,7 +61,9 @@ const 가릴칸 = new Set(['apiKey', 'key', 'passphrase', '암호']);
|
|
|
35
61
|
* 읽어야만 아는 것이 되는데, 그건 이 명령이 없애려는 바로 그 상태다.
|
|
36
62
|
*/
|
|
37
63
|
const 환경변수 = {
|
|
38
|
-
|
|
64
|
+
// 차례는 **이기는 차례**다 (config.js 의 resolveKey). 아래에서 첫 번째로 켜진 것을
|
|
65
|
+
// 「이 값」 으로 적으므로, 여기가 어긋나면 화면이 안 이기는 쪽을 가리킨다.
|
|
66
|
+
apiKey: ['DEEL_KEY_<프로필ID>', 'DEEL_API_KEY'],
|
|
39
67
|
shell: ['DEEL_SHELL'],
|
|
40
68
|
offline: [],
|
|
41
69
|
proxy: ['HTTPS_PROXY', 'HTTP_PROXY', 'ALL_PROXY', 'NO_PROXY'],
|
|
@@ -72,7 +100,8 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
72
100
|
|
|
73
101
|
const 파일읽기 = (자리) => {
|
|
74
102
|
if (!existsSync(자리)) return { 있나: false };
|
|
75
|
-
|
|
103
|
+
// BOM 은 설정을 읽는 자(config.js)와 똑같이 뗀다. 안 떼면 이 화면만 「못 읽었습니다」 다.
|
|
104
|
+
try { return { 있나: true, 값: JSON.parse(BOM떼기(readFileSync(자리, 'utf8'))) }; }
|
|
76
105
|
catch (err) { return { 있나: true, 탈: err?.message ?? String(err) }; }
|
|
77
106
|
};
|
|
78
107
|
|
|
@@ -94,7 +123,8 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
94
123
|
}
|
|
95
124
|
// 물려받은 이름(`constructor` · `toString`)으로 파고들면 **없는 칸이
|
|
96
125
|
// 있는 것이 된다.** 여기서 막아야 아래 세 층이 다 안전하다.
|
|
97
|
-
|
|
126
|
+
// 위 루프 머리에서 여기가 object 임을 이미 봤다 — 또 보지 않는다.
|
|
127
|
+
여기 = Object.hasOwn(여기, 이름) ? 여기[이름] : undefined;
|
|
98
128
|
}
|
|
99
129
|
return 여기;
|
|
100
130
|
};
|
|
@@ -113,12 +143,29 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
113
143
|
|
|
114
144
|
// ── 2. 프로젝트 설정 (믿는 폴더에서만) ──────────────────────────────
|
|
115
145
|
const 방자리 = join(root, '.deel', 'config.json');
|
|
116
|
-
|
|
146
|
+
// 집 폴더에서 켜면 이 파일이 곧 위의 「이 PC 설정」 이다. 한 파일을 두 층으로 그리지 않는다.
|
|
147
|
+
if (existsSync(방자리) && !집설정파일인가(방자리)) {
|
|
117
148
|
const 믿나결과 = 믿나(root);
|
|
118
149
|
const 방 = 파일읽기(방자리);
|
|
150
|
+
/*
|
|
151
|
+
* 저장소가 못 정하는 칸은 **걷은 뒤** 값으로 그린다 (safety/trust.js 의 프로젝트거르기).
|
|
152
|
+
*
|
|
153
|
+
* 날값을 그렸더니, 믿는 저장소의 `profiles[].offline:false` 가 「프로젝트 설정 =
|
|
154
|
+
* false 가 이긴다」 로 떴다. 실제로는 걷혀서 이 PC 의 봉인이 걸려 있다. 이 화면이
|
|
155
|
+
* 사람을 속이면, 봉인이 풀린 줄 알고 바깥 주소를 적거나 관리자에게 엉뚱한 것을 묻는다.
|
|
156
|
+
*/
|
|
157
|
+
const 걸러진 = 방.탈 ? undefined : 겹칠때걷기(프로젝트거르기(방.값).값, 집.탈 ? null : 집.값);
|
|
158
|
+
/*
|
|
159
|
+
* 걷힌 칸은 「이 칸은 없음」 이 아니라 **걷힌다**고 적는다. 파일에는 분명히 적혀
|
|
160
|
+
* 있으므로 「없음」 이라고 하면 사람은 이 화면을 못 믿는다. 적힌 값도 같이 보여 준다.
|
|
161
|
+
*/
|
|
162
|
+
const 날값 = 방.탈 ? undefined : 파고들기(방.값);
|
|
163
|
+
const 남은값 = 방.탈 ? undefined : 파고들기(걸러진);
|
|
164
|
+
const 걷힘 = 날값 !== undefined && 남은값 === undefined;
|
|
119
165
|
층들.push({
|
|
120
166
|
층: '프로젝트 설정', 자리: 방자리, 읽나: 믿나결과 && !방.탈,
|
|
121
|
-
값: 방.탈 ? undefined : 지운값(끝칸이름지금,
|
|
167
|
+
값: 방.탈 ? undefined : 지운값(끝칸이름지금, 남은값), 탈: 방.탈 ?? null,
|
|
168
|
+
걷힘, 걷힌값: 걷힘 ? 지운값(끝칸이름지금, 날값) : undefined,
|
|
122
169
|
// 읽지도 않는 파일에 적어 둔 것이 이 화면에서 제일 자주 나오는 답이다.
|
|
123
170
|
왜못읽나: 믿나결과 ? null : '믿는 폴더가 아닙니다 (deel trust)',
|
|
124
171
|
});
|
|
@@ -133,7 +180,25 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
133
180
|
* 바로 다음 줄에서 터졌다 — 화면에 뜬 말은 「오류 볼환경.filter is not a
|
|
134
181
|
* function」 이다. 없는 칸을 물었으면 없다고 해야 한다.
|
|
135
182
|
*/
|
|
136
|
-
|
|
183
|
+
/*
|
|
184
|
+
* `DEEL_KEY_<프로필ID>` 는 이름이 아니라 **틀**이다.
|
|
185
|
+
*
|
|
186
|
+
* 틀인 채로 두면 바로 아래 `<` 거르는 줄에서 통째로 빠진다. 그래서 그 환경변수가
|
|
187
|
+
* **지금 열쇠를 쥐고 있는** 판에서도 이 화면은 파일을 가리키거나 「아무 데도 안
|
|
188
|
+
* 적혀 있습니다」 라고 했다 — 이 명령이 없애려던 바로 그 상태를, 열쇠 칸에서만
|
|
189
|
+
* 제 손으로 만들고 있었다.
|
|
190
|
+
*
|
|
191
|
+
* 물어본 프로필의 진짜 id 로 펼친다. 이름을 짓는 자는 config.js 의 열쇠환경이름
|
|
192
|
+
* 하나다 — 집는 쪽과 그리는 쪽이 다른 이름을 쓰면 사람은 없는 자리를 고친다.
|
|
193
|
+
* (저장소가 더한 프로필은 그 환경변수를 안 집으므로 이 PC 설정에서만 찾는다.)
|
|
194
|
+
*/
|
|
195
|
+
const 이프로필환경 = (() => {
|
|
196
|
+
if (조각[0] !== 'profiles' || 조각.length < 2) return null;
|
|
197
|
+
const 집프로필 = Array.isArray(집.값?.profiles) ? 집.값.profiles : [];
|
|
198
|
+
return 열쇠환경이름(집프로필.find((x) => x?.name === 조각[1] || x?.id === 조각[1]) ?? null);
|
|
199
|
+
})();
|
|
200
|
+
const 볼환경 = (Object.hasOwn(환경변수, 끝칸) ? 환경변수[끝칸] : [])
|
|
201
|
+
.map((이름) => (이프로필환경 && 이름 === 'DEEL_KEY_<프로필ID>' ? 이프로필환경 : 이름));
|
|
137
202
|
const 켜진환경 = 볼환경.filter((이름) => !이름.includes('<') && env[이름]);
|
|
138
203
|
if (켜진환경.length) {
|
|
139
204
|
층들.push({
|
|
@@ -143,12 +208,44 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
143
208
|
}
|
|
144
209
|
|
|
145
210
|
// ── 4. 관리 정책 (제일 위) ──────────────────────────────────────────
|
|
211
|
+
/*
|
|
212
|
+
* 정책이 이 칸에 **실제로 얹는** 값만 그린다. 안 얹으면 undefined.
|
|
213
|
+
*
|
|
214
|
+
* 정책 파일에 적혀 있다고 다 걸리는 것이 아니다. 정책값을 읽는 자리는 셋뿐이다 —
|
|
215
|
+
* config.js 정책덮기(baseUrl·offline·permissions.deny) · policy.js 규칙모으기(permissions) ·
|
|
216
|
+
* authcmd.js 받기설정(열쇠받기). 날값을 그렸더니 정책 `offline:false`(끄지는 못한다) ·
|
|
217
|
+
* 빈 baseUrl · 빈 명령의 열쇠받기 · 아무도 안 읽는 `shell` 이 「관리 정책이 이긴다」 로
|
|
218
|
+
* 떴다 (2.0.0 6회차 CX5). 관리자에게 따지러 갈 근거가 통째로 거짓이 되는 자리다.
|
|
219
|
+
*
|
|
220
|
+
* 정책 파일은 JSON 이라 프로토타입을 그대로 물려받는다. 그래서 칸은 hasOwn 으로만 본다.
|
|
221
|
+
*/
|
|
222
|
+
const 정책이얹는값 = (정책값) => {
|
|
223
|
+
const 제칸 = (덩이, 이름) => (덩이 && typeof 덩이 === 'object' && Object.hasOwn(덩이, 이름) ? 덩이[이름] : undefined);
|
|
224
|
+
if (조각[0] === 'permissions') {
|
|
225
|
+
const 권한 = 제칸(정책값, 'permissions');
|
|
226
|
+
const 목록만 = {};
|
|
227
|
+
for (const k of ['allow', 'deny']) if (Array.isArray(제칸(권한, k))) 목록만[k] = 권한[k];
|
|
228
|
+
if (조각.length === 1) return Object.keys(목록만).length ? 목록만 : undefined;
|
|
229
|
+
return 조각.length === 2 ? 제칸(목록만, 조각[1]) : undefined;
|
|
230
|
+
}
|
|
231
|
+
if (끝칸 === 'baseUrl') {
|
|
232
|
+
const 주소 = 제칸(정책값, 'baseUrl');
|
|
233
|
+
return typeof 주소 === 'string' && 주소.trim() ? 주소.trim() : undefined;
|
|
234
|
+
}
|
|
235
|
+
if (끝칸 === 'offline') {
|
|
236
|
+
const 봉 = 제칸(정책값, 'offline');
|
|
237
|
+
return 봉 === true || 봉 === 'true' ? true : undefined;
|
|
238
|
+
}
|
|
239
|
+
if (끝칸 === '열쇠받기' || 끝칸 === 'authCommand') {
|
|
240
|
+
if (받기설정(null, { 정책값 })?.곳 !== '정책') return undefined;
|
|
241
|
+
return 제칸(정책값, '열쇠받기') ?? 제칸(정책값, 'authCommand');
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
};
|
|
245
|
+
|
|
146
246
|
const 정책 = 정책읽기({ env, 다시: true });
|
|
147
247
|
if (정책.값 && typeof 정책.값 === 'object') {
|
|
148
|
-
|
|
149
|
-
// 정책에 없는 칸이 「관리 정책에 이렇게 적혀 있습니다」 로 한 줄 올라온다 —
|
|
150
|
-
// 관리자에게 따지러 갈 근거가 통째로 거짓이 되는 자리다.
|
|
151
|
-
const 그값 = Object.hasOwn(정책.값, 끝칸) ? 정책.값[끝칸] : undefined;
|
|
248
|
+
const 그값 = 정책이얹는값(정책.값);
|
|
152
249
|
if (그값 !== undefined) {
|
|
153
250
|
층들.push({ 층: '관리 정책', 자리: 정책.곳 ?? 정책자리(env)[0], 읽나: true, 값: 지운값(끝칸, 그값) });
|
|
154
251
|
}
|
|
@@ -156,6 +253,15 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
156
253
|
|
|
157
254
|
// 이긴 층 — 뒤에 온 것이 이긴다. 다만 **읽는 층** 중에서만 고른다.
|
|
158
255
|
const 쓸수있는것 = 층들.filter((x) => x.읽나 && x.값 !== undefined);
|
|
256
|
+
/*
|
|
257
|
+
* 금지·허락은 이기고 지는 칸이 아니라 **더해지는** 칸이다 — 이 PC·저장소 금지는 겹치기가
|
|
258
|
+
* 합치고(config.js), 정책 금지·허락은 정책덮기·규칙모으기가 더한다(policy.js). 「한 곳이
|
|
259
|
+
* 이긴다」 로 그리면 사람은 제 금지가 풀린 줄 안다 (2.0.0 6회차 CX5).
|
|
260
|
+
*/
|
|
261
|
+
const 합치는칸 = 조각[0] === 'permissions'
|
|
262
|
+
&& (조각.length === 1 || (조각.length === 2 && (조각[1] === 'deny' || 조각[1] === 'allow')));
|
|
263
|
+
if (합치는칸) return { 칸, 층들, 이긴층: null, 합침: 쓸수있는것, 환경변수: 볼환경 };
|
|
264
|
+
|
|
159
265
|
const 이긴층 = 쓸수있는것.length ? 쓸수있는것[쓸수있는것.length - 1] : null;
|
|
160
266
|
|
|
161
267
|
return { 칸, 층들, 이긴층, 환경변수: 볼환경 };
|
|
@@ -164,17 +270,23 @@ export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
|
164
270
|
/** 화면에 그릴 줄들. 색은 부르는 쪽이 입힌다. */
|
|
165
271
|
export function 설명줄들(r) {
|
|
166
272
|
const 줄 = [];
|
|
167
|
-
|
|
273
|
+
// 더해지는 칸(금지·허락)은 이긴 한 곳이 아니라 걸리는 곳 전부를 적는다 (설명 의 합치는칸).
|
|
274
|
+
if (r.합침?.length) {
|
|
275
|
+
줄.push({ 갈래: '값', 글: `${r.합침.length}곳에 적힌 규칙이 합쳐져 다 걸립니다`, 곁: r.합침.map((x) => x.층).join(' + ') });
|
|
276
|
+
} else if (r.이긴층) 줄.push({ 갈래: '값', 글: `${값보이기(끝칸이름(r.칸), r.이긴층.값)}`, 곁: `${r.이긴층.층}` });
|
|
168
277
|
else 줄.push({ 갈래: '없음', 글: '아무 데도 안 적혀 있습니다 (기본값으로 돕니다)', 곁: null });
|
|
169
278
|
|
|
170
279
|
for (const 층 of r.층들) {
|
|
171
280
|
const 값 = 값보이기(끝칸이름(r.칸), 층.값);
|
|
172
281
|
줄.push({
|
|
173
|
-
갈래: 층 === r.이긴층 ? '이김' : (층.읽나 ? '아래' : '안읽음'),
|
|
282
|
+
갈래: 층 === r.이긴층 || r.합침?.includes(층) ? '이김' : (층.읽나 ? '아래' : '안읽음'),
|
|
174
283
|
글: `${층.층}`,
|
|
175
284
|
곁: [
|
|
176
285
|
층.자리,
|
|
177
|
-
값 !== null ? `= ${값}`
|
|
286
|
+
값 !== null ? `= ${값}`
|
|
287
|
+
: (층.걷힘
|
|
288
|
+
? `= ${값보이기(끝칸이름(r.칸), 층.걷힌값)} — 걷힙니다: 저장소가 못 정하는 칸이라 이 값은 안 씁니다`
|
|
289
|
+
: '(이 칸은 없음)'),
|
|
178
290
|
층.왜못읽나 ? `— ${층.왜못읽나}` : null,
|
|
179
291
|
층.탈 ? `— 못 읽었습니다: ${층.탈}` : null,
|
|
180
292
|
].filter(Boolean).join(' '),
|
package/src/doctor.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
//
|
|
21
21
|
// **비밀은 한 글자도 안 적는다.** 이 화면은 그대로 캡처되어 사내 메신저로
|
|
22
22
|
// 간다. 열쇠는 있나 없나와 어디서 왔나만, 인증서 암호는 어디서 왔나만 적는다.
|
|
23
|
-
import { existsSync,
|
|
23
|
+
import { existsSync, statSync } from 'node:fs';
|
|
24
24
|
import { join } from 'node:path';
|
|
25
25
|
import { req } from './backend/http.js';
|
|
26
26
|
import { 프록시설정, 프록시고르기 } from './backend/proxy.js';
|
|
@@ -30,21 +30,51 @@ import { 믿나 } from './safety/trust.js';
|
|
|
30
30
|
import { 훅읽기, 훅줄들 } from './safety/hooks.js';
|
|
31
31
|
import { 에이전트읽기, 에이전트줄들 } from './agent/agents.js';
|
|
32
32
|
import { headersFor } from './backend/http.js';
|
|
33
|
+
import { 열쇠출처, 집설정파일인가 } from './config.js';
|
|
34
|
+
import { 받기설정, 쓸수있나 as 받기쓸수있나 } from './safety/authcmd.js';
|
|
35
|
+
import { 정책읽기 } from './safety/policy.js';
|
|
36
|
+
import { 설정읽기 as MCP설정읽기 } from './backend/mcp.js';
|
|
33
37
|
|
|
34
38
|
/** 한 줄. `상태` 는 ok · warn · no · unknown 넷뿐이다. */
|
|
35
39
|
const 줄 = (상태, 이름, 값, 덧말 = null) => ({ 상태, 이름, 값, 덧말 });
|
|
36
40
|
|
|
41
|
+
/*
|
|
42
|
+
* ── 주소가 주소 꼴인가 ─────────────────────────────────────────────────
|
|
43
|
+
*
|
|
44
|
+
* 손으로 고친 프로필의 baseUrl 이 비었거나(`http://`) 빈칸이 들어 있으면, 부르는 쪽(bin/deel.js)은
|
|
45
|
+
* 그 주소를 자물쇠에 못 올려 「두드려도 되나」 가 거짓이 된다. 여기는 주소를 안 보고 「✓ 주소」 를
|
|
46
|
+
* 찍었고, 도달 줄에는 「자물쇠가 걸려 있거나 --offline」 이라고 적었다 — 둘 다 아니다. ✗ 가 없으니
|
|
47
|
+
* 종료코드도 0 이었다. 담당자에게 보낼 한 장이 「멀쩡하다」 고 말한 셈이다.
|
|
48
|
+
*
|
|
49
|
+
* @returns {string|null} 틀린 까닭. 멀쩡하면 null.
|
|
50
|
+
*/
|
|
51
|
+
function 주소살펴보기(주소) {
|
|
52
|
+
const s = String(주소 ?? '').trim();
|
|
53
|
+
if (!s) return '비어 있습니다 — 프로필에 baseUrl 을 적거나 deel setup 을 다시 하세요';
|
|
54
|
+
let u;
|
|
55
|
+
try { u = new URL(s); } catch { return '주소 꼴이 아닙니다 (빈칸·빠진 호스트) — 예: http://127.0.0.1:11434/v1'; }
|
|
56
|
+
if (!/^https?:$/.test(u.protocol)) return `http 나 https 로 시작해야 합니다 — 지금은 ${u.protocol}`;
|
|
57
|
+
/*
|
|
58
|
+
* 빈 호스트는 `u.hostname` 으로 못 잡는다 — http/https 는 호스트가 비면 위 `new URL` 이
|
|
59
|
+
* 먼저 던진다(13,500 조합을 다 재 봤다. 걸리는 주소가 하나도 없었다). 정작 새는 꼴은
|
|
60
|
+
* 슬래시가 셋인 주소다: 파서가 **경로를 호스트로 끌어올린다** (`http:///v1` → `http://v1/`).
|
|
61
|
+
* 사람은 호스트를 안 적었는데 화면에는 ✓ 가 찍히고, 두드리는 곳은 `v1` 이라는 엉뚱한
|
|
62
|
+
* 이름이 된다. 그래서 파서가 고쳐 놓은 값 말고 **적힌 글자**를 본다. (setup.js 의 주소꼴탈과 쌍둥이)
|
|
63
|
+
*/
|
|
64
|
+
if (/^https?:\/\/\//i.test(s)) return '호스트가 없습니다 — 예: http://127.0.0.1:11434/v1';
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
37
68
|
/**
|
|
38
69
|
* 이 자리의 조건을 다 본다. 화면 그리기는 안 한다 — 부르는 쪽이 그린다.
|
|
39
70
|
*
|
|
40
71
|
* @param {object} o
|
|
41
|
-
* @param {object|null} o.cfg 읽은 설정
|
|
42
72
|
* @param {object|null} o.prof 고른 프로필
|
|
43
73
|
* @param {string} o.root 작업 폴더
|
|
44
74
|
* @param {boolean} o.바깥가도되나 네트워크를 실제로 두드려도 되나
|
|
45
75
|
*/
|
|
46
76
|
export async function 진찰(o = {}) {
|
|
47
|
-
const {
|
|
77
|
+
const { prof = null, root = process.cwd(), 바깥가도되나 = true, 설정자리 = null, 설정탈 = null } = o;
|
|
48
78
|
const 줄들 = [];
|
|
49
79
|
|
|
50
80
|
// ── 1. 이 프로그램이 도는 자리 ───────────────────────────────────────
|
|
@@ -53,9 +83,26 @@ export async function 진찰(o = {}) {
|
|
|
53
83
|
major >= 20 ? null : '20 이상이 필요합니다'));
|
|
54
84
|
|
|
55
85
|
// ── 2. 설정 ─────────────────────────────────────────────────────────
|
|
56
|
-
|
|
86
|
+
/*
|
|
87
|
+
* ── 깨진 설정을 「✓ 설정 파일」 로 적고 있었다 ──────────────────────
|
|
88
|
+
*
|
|
89
|
+
* 부르는 쪽(bin/deel.js)이 읽기 오류를 `catch {}` 로 삼키고, 여기는 파일이
|
|
90
|
+
* **있나**만 봤다. 그래서 쉼표 하나 틀린 설정이 이렇게 나왔다:
|
|
91
|
+
*
|
|
92
|
+
* ✓ 설정 파일 …/config.json
|
|
93
|
+
* ✗ 프로필 (없음) — deel setup 을 먼저 하세요
|
|
94
|
+
*
|
|
95
|
+
* 프로필은 멀쩡히 적혀 있다. 이 화면을 받은 담당자는 setup 을 다시 시키고,
|
|
96
|
+
* setup 은 같은 파일에서 또 죽는다. 까닭(몇째 줄 무엇)은 어디에도 없었다.
|
|
97
|
+
* 이 파일의 규칙 그대로 — 못 읽은 것은 못 읽었다고, 왜인지까지 적는다.
|
|
98
|
+
*/
|
|
99
|
+
if (설정탈) {
|
|
100
|
+
줄들.push(줄('no', '설정 파일', 설정탈.자리 ?? 설정자리 ?? '(어느 파일인지 모름)',
|
|
101
|
+
`못 읽었습니다 — ${String(설정탈.까닭 ?? '').replace(/\s+/g, ' ').trim()}`));
|
|
102
|
+
} else if (설정자리) 줄들.push(줄(existsSync(설정자리) ? 'ok' : 'warn', '설정 파일', 설정자리));
|
|
57
103
|
const 프로젝트설정 = join(root, '.deel', 'config.json');
|
|
58
|
-
|
|
104
|
+
// 집 폴더에서 켜면 이 파일이 곧 이 PC 설정이다. 두 번 적으면 제 설정에 「deel trust」 를 권한다.
|
|
105
|
+
if (existsSync(프로젝트설정) && !집설정파일인가(프로젝트설정)) {
|
|
59
106
|
/*
|
|
60
107
|
* 프로젝트 설정은 **믿는 폴더에서만** 읽는다 (safety/trust.js). 여기서
|
|
61
108
|
* 읽었는지 안 읽었는지를 말해 주지 않으면, 적어 둔 사람은 자기 설정이
|
|
@@ -65,12 +112,20 @@ export async function 진찰(o = {}) {
|
|
|
65
112
|
줄들.push(줄(믿나결과 ? 'ok' : 'warn', '프로젝트 설정', 프로젝트설정,
|
|
66
113
|
믿나결과 ? '읽습니다' : '안 읽습니다 — 읽게 하려면 deel trust'));
|
|
67
114
|
}
|
|
115
|
+
if (!prof && 설정탈) {
|
|
116
|
+
// 없는 것이 아니라 못 읽은 것이다. 「deel setup 을 먼저」 는 같은 파일에서 또 죽는다.
|
|
117
|
+
줄들.push(줄('unknown', '프로필', '(모름)', '설정 파일을 못 읽어 알 수 없습니다 — 위 줄을 먼저 고치세요'));
|
|
118
|
+
return { 줄들, 붙어봤나: false };
|
|
119
|
+
}
|
|
68
120
|
if (!prof) {
|
|
69
121
|
줄들.push(줄('no', '프로필', '(없음)', 'deel setup 을 먼저 하세요'));
|
|
70
122
|
return { 줄들, 붙어봤나: false };
|
|
71
123
|
}
|
|
72
124
|
줄들.push(줄('ok', '프로필', `${prof.name ?? prof.id ?? '(이름 없음)'} · ${prof.model ?? '(모델 없음)'}`));
|
|
73
|
-
|
|
125
|
+
const 주소탈 = 주소살펴보기(prof.baseUrl);
|
|
126
|
+
줄들.push(주소탈
|
|
127
|
+
? 줄('no', '주소', 주소가리기(String(prof.baseUrl ?? '').trim() || '(없음)'), 주소탈)
|
|
128
|
+
: 줄('ok', '주소', 주소가리기(prof.baseUrl)));
|
|
74
129
|
|
|
75
130
|
// ── 3. 열쇠 ─────────────────────────────────────────────────────────
|
|
76
131
|
/*
|
|
@@ -78,18 +133,47 @@ export async function 진찰(o = {}) {
|
|
|
78
133
|
* 알아야 하는 것이 그 둘이다. 환경변수가 파일을 이기고 있는데 그걸 모르면
|
|
79
134
|
* 파일만 백 번 고친다.
|
|
80
135
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
136
|
+
/*
|
|
137
|
+
* 차례는 **여기서 정하지 않는다.** config.js 의 열쇠출처() 에게 물어본다.
|
|
138
|
+
*
|
|
139
|
+
* 여기가 제 차례를 따로 들고 있었다 — DEEL_API_KEY 를 맨 앞에 두고
|
|
140
|
+
* 열쇠받기를 맨 뒤에 뒀는데, 실제로 나가는 차례는 그 반대다. 그래서 둘
|
|
141
|
+
* 이상 넣어 둔 사람에게 **엉뚱한 자리를 가리켰다.** 자세한 것은
|
|
142
|
+
* config.js 의 열쇠출처() 머리말에 적어 뒀다.
|
|
143
|
+
*/
|
|
144
|
+
const 받기 = 받기설정(prof, { 정책값: 정책읽기().값 });
|
|
145
|
+
const 받기판단 = 받기 ? 받기쓸수있나(받기, { auth: prof.auth ?? 'bearer' }) : { 된다: false, 왜: null };
|
|
146
|
+
const 출처 = 열쇠출처(prof, { 받기쓰나: !!받기 && 받기판단.된다 });
|
|
147
|
+
const 열쇠어디 = 출처.말 || null;
|
|
89
148
|
const 열쇠있나 = !!(o.열쇠 ?? '');
|
|
149
|
+
// 열쇠를 여기서 안 받아 본 판. 아래 401 을 결함으로 부르지 않는 근거다.
|
|
150
|
+
const 열쇠를안돌려봤나 = !열쇠있나 && !!받기 && 받기판단.된다;
|
|
90
151
|
if (prof.auth === 'none') 줄들.push(줄('ok', '열쇠', '안 씁니다 (auth: none)'));
|
|
91
152
|
else if (열쇠있나) 줄들.push(줄('ok', '열쇠', `있습니다 · ${열쇠어디 ?? '어디서 왔는지 모름'}`));
|
|
92
|
-
|
|
153
|
+
/*
|
|
154
|
+
* ── 멀쩡한 구성에 고장 신고서를 써 주고 있었다 ──────────────────────
|
|
155
|
+
*
|
|
156
|
+
* 열쇠받기(SSO 명령) 프로필은 `apiKey` 도 환경변수도 없는 것이 **정상**
|
|
157
|
+
* 이다. 토큰은 요청 직전에 명령으로 받는다(adapter.js 머리말짓기). 그런데
|
|
158
|
+
* 여기는 `resolveKey` 가 준 빈 글자만 보고 이렇게 적었다:
|
|
159
|
+
*
|
|
160
|
+
* ✗ 열쇠 못 꺼냈습니다 — 명령으로 받아 옵니다 에서 읽으려 했습니다
|
|
161
|
+
* ✗ 인증 401
|
|
162
|
+
* 2군데가 막혀 있습니다. 위 줄을 그대로 담당자에게 보내시면 됩니다.
|
|
163
|
+
*
|
|
164
|
+
* 아무것도 안 망가진 상태다. `deel`·`deel run`·`deel acp` 가 다 붙는다.
|
|
165
|
+
* 이 화면의 뜻이 「그대로 담당자에게 보낼 질문을 만드는 것」 인데,
|
|
166
|
+
* 지어낸 결함을 담당자에게 보내게 만들었다. 종료코드도 1 이 된다.
|
|
167
|
+
*
|
|
168
|
+
* 이 파일의 규칙이 그 답이다 — **모르는 것은 모른다고 적는다.** 여기서는
|
|
169
|
+
* 명령을 안 돌려 봤으니 안 재 본 것이다.
|
|
170
|
+
*/
|
|
171
|
+
else if (받기 && 받기판단.된다) {
|
|
172
|
+
줄들.push(줄('unknown', '열쇠', '요청 직전에 명령으로 받아 옵니다',
|
|
173
|
+
`여기서는 안 돌려 봤습니다 — ${받기.곳}에 적힌 명령입니다`));
|
|
174
|
+
} else if (받기 && 받기판단.왜) {
|
|
175
|
+
줄들.push(줄('warn', '열쇠', '명령으로 받게 돼 있는데 지금은 안 부릅니다', 받기판단.왜));
|
|
176
|
+
} else 줄들.push(줄('no', '열쇠', '못 꺼냈습니다', 열쇠어디 ? `${열쇠어디} 에서 읽으려 했습니다` : '적어 둔 것이 없습니다'));
|
|
93
177
|
|
|
94
178
|
// ── 4. 우리 인증서 (mTLS) ───────────────────────────────────────────
|
|
95
179
|
const 인증 = 인증서설정(prof);
|
|
@@ -120,7 +204,9 @@ export async function 진찰(o = {}) {
|
|
|
120
204
|
const 프록 = 프록시설정();
|
|
121
205
|
const 이주소 = 프록시고르기(prof.baseUrl ?? '');
|
|
122
206
|
if (프록.탈) 줄들.push(줄('warn', '프록시', '설정을 못 읽었습니다', 프록.탈));
|
|
123
|
-
|
|
207
|
+
// 못 읽고서 「안 거칩니다」 라고 단정하면, 실제로는 프록시가 걸려 있는데 직접
|
|
208
|
+
// 간다고 적은 셈이 된다 — 이 화면은 모르는 것을 모른다고 적는 자리다.
|
|
209
|
+
else if (!프록.켜짐) 줄들.push(줄('ok', '프록시', '안 거칩니다 (직접)'));
|
|
124
210
|
else if (이주소) {
|
|
125
211
|
줄들.push(줄('ok', '프록시', `${주소가리기(이주소.url)} · ${이주소.출처}`,
|
|
126
212
|
이주소.auth ? '인증 있음' : null));
|
|
@@ -130,16 +216,51 @@ export async function 진찰(o = {}) {
|
|
|
130
216
|
}
|
|
131
217
|
|
|
132
218
|
// ── 7. MCP ──────────────────────────────────────────────────────────
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
219
|
+
/*
|
|
220
|
+
* ── 뜨지도 않을 서버를 세어서 초록으로 적었다 ───────────────────────
|
|
221
|
+
*
|
|
222
|
+
* 여기는 설정 읽기를 **따로 한 벌** 갖고 있었다 — `JSON.parse` 한 뒤
|
|
223
|
+
* 열쇠 개수를 세는 것. 그래서 실제로 붙는 자(backend/mcp.js 의 설정읽기)
|
|
224
|
+
* 가 거르는 것을 하나도 몰랐다:
|
|
225
|
+
*
|
|
226
|
+
* · `<root>/.mcp.json` 만 있는 경우 — 여기서는 받아 주지만 실제로
|
|
227
|
+
* 읽는 자리는 `<root>/.deel/mcp.json` 뿐이다. 0개가 붙는다.
|
|
228
|
+
* · `"disabled": true` · `"type": "sse"` · `url` 만 있는 항목 — 전부
|
|
229
|
+
* 여기서는 세고 실제로는 거절된다.
|
|
230
|
+
*
|
|
231
|
+
* 화면에는 `✓ MCP …/.mcp.json · 서버 3개` 가 뜨고 진찰은 초록으로
|
|
232
|
+
* 끝난다. 모델에게는 MCP 도구가 하나도 없다. 왜인지는 아무 데도 없다.
|
|
233
|
+
*
|
|
234
|
+
* 그래서 **붙이는 자에게 물어본다.** 못 받은 것은 그자가 이미 까닭까지
|
|
235
|
+
* 들고 있다(못받은것). 자리가 하나 늘면 그쪽만 고치면 된다.
|
|
236
|
+
*/
|
|
237
|
+
const mcp = MCP설정읽기(root);
|
|
238
|
+
const 남의자리 = join(root, '.mcp.json');
|
|
239
|
+
if (mcp.있음 || existsSync(남의자리)) {
|
|
240
|
+
if (mcp.오류) 줄들.push(줄('no', 'MCP', mcp.자리, mcp.오류));
|
|
241
|
+
else if (!mcp.있음) {
|
|
242
|
+
줄들.push(줄('warn', 'MCP', 남의자리,
|
|
243
|
+
`여기는 안 읽습니다 — ${mcp.자리} 로 옮기세요`));
|
|
244
|
+
} else {
|
|
245
|
+
const 못받은 = mcp.못받은것 ?? [];
|
|
246
|
+
// 안 믿는 폴더면 하나도 안 뜬다(아래 「MCP 신뢰」 줄). 그때 요약 줄까지 ✓ 로
|
|
247
|
+
// 적으면 한 화면에 ✓ 와 「하나도 안 띄웁니다」 가 같이 보인다 — 진찰은 그 자리에서
|
|
248
|
+
// 판단을 끝내 주는 화면이라 초록 하나가 그대로 결론이 된다.
|
|
249
|
+
const 띄울수있나 = 믿나(root);
|
|
250
|
+
줄들.push(줄(mcp.서버들.length && 띄울수있나 ? 'ok' : 'warn', 'MCP',
|
|
251
|
+
`${mcp.자리} · 붙일 서버 ${mcp.서버들.length}개`,
|
|
252
|
+
못받은.length
|
|
253
|
+
? `못 붙이는 것 ${못받은.length}개: ${못받은.map((x) => x.이름).join(' · ')}`
|
|
254
|
+
: '실제로 띄워 보려면 deel 을 켜고 /mcp'));
|
|
255
|
+
for (const x of 못받은) 줄들.push(줄('warn', ` ${x.이름}`, x.왜));
|
|
256
|
+
/*
|
|
257
|
+
* 붙는 조건이 둘 더 있다. 둘 다 「설정은 멀쩡한데 0개」 가 되는 길이라
|
|
258
|
+
* 여기서 말해 준다 — 안 말하면 설정 파일만 들여다보게 된다.
|
|
259
|
+
*/
|
|
260
|
+
if (mcp.서버들.length && !띄울수있나) {
|
|
261
|
+
줄들.push(줄('warn', ' MCP 신뢰', '안 믿는 폴더라 하나도 안 띄웁니다', 'deel trust 를 하세요'));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
143
264
|
}
|
|
144
265
|
|
|
145
266
|
// ── 7.5 훅 ───────────────────────────────────────────────────────────
|
|
@@ -154,6 +275,11 @@ export async function 진찰(o = {}) {
|
|
|
154
275
|
for (const 한줄 of 에이전트줄들(에이전트읽기(root))) 줄들.push(한줄);
|
|
155
276
|
|
|
156
277
|
// ── 8. 진짜로 두드려 본다 ───────────────────────────────────────────
|
|
278
|
+
// 주소가 틀렸으면 두드릴 곳이 없다. 자물쇠 탓으로 적으면 사람은 엉뚱한 설정을 뒤진다.
|
|
279
|
+
if (주소탈) {
|
|
280
|
+
줄들.push(줄('unknown', '도달', '안 두드렸습니다', '주소부터 고치세요 — 위 「주소」 줄'));
|
|
281
|
+
return { 줄들, 붙어봤나: false };
|
|
282
|
+
}
|
|
157
283
|
if (!바깥가도되나) {
|
|
158
284
|
줄들.push(줄('unknown', '도달', '안 두드렸습니다', '자물쇠가 걸려 있거나 --offline 입니다'));
|
|
159
285
|
return { 줄들, 붙어봤나: false };
|
|
@@ -187,7 +313,18 @@ export async function 진찰(o = {}) {
|
|
|
187
313
|
} else if (r.status === 401 || r.status === 403) {
|
|
188
314
|
// 닿기는 닿았다. 그 둘을 갈라 줘야 사람이 프록시가 아니라 열쇠를 본다.
|
|
189
315
|
줄들.push(줄('ok', '도달', `${r.status} · ${걸린}ms`, '주소는 닿습니다'));
|
|
190
|
-
|
|
316
|
+
/*
|
|
317
|
+
* 빈 열쇠로 두드린 401 은 **결함이 아니다.**
|
|
318
|
+
*
|
|
319
|
+
* 위 「열쇠」 줄 머리말이 이미 적어 둔 이야기인데(✗ 열쇠 · ✗ 인증 401 …
|
|
320
|
+
* 「지어낸 결함을 담당자에게 보내게 만들었다」), 고쳐진 것은 그 한 줄뿐이라
|
|
321
|
+
* 여기가 그대로 남아 종료코드까지 1 이 됐다. 열쇠받기(SSO) 프로필은 토큰을
|
|
322
|
+
* 요청 직전에 받는다 — 여기서는 안 돌려 봤으니 **안 재 본 것**이다.
|
|
323
|
+
*/
|
|
324
|
+
if (열쇠를안돌려봤나) {
|
|
325
|
+
줄들.push(줄('unknown', '인증', `${r.status}`,
|
|
326
|
+
'빈 열쇠로 두드린 결과입니다 — 토큰은 요청 직전에 명령으로 받습니다'));
|
|
327
|
+
} else 줄들.push(줄('no', '인증', `${r.status}`, '열쇠나 인증 방식(auth)을 보세요'));
|
|
191
328
|
} else {
|
|
192
329
|
줄들.push(줄('no', '도달', r.status ? `${r.status}` : '못 닿았습니다', r.error ?? null));
|
|
193
330
|
}
|
package/src/i18n/en.js
CHANGED
|
@@ -214,6 +214,8 @@ export const en = {
|
|
|
214
214
|
'undo.failed': '{n} file(s) could not be restored — they are still modified. The snapshots were kept, so try /undo again.',
|
|
215
215
|
'undo.talkKept': 'The conversation is left as is — that turn had already been folded away.',
|
|
216
216
|
'undo.talkKeptWhy': 'The model may still think the reverted work exists, so mention it in your next message.',
|
|
217
|
+
'undo.talkPartial': 'Of those, {n} turn(s) had already been folded into a summary, so their messages stay in the conversation.',
|
|
218
|
+
'undo.notesDropped': 'The {n} pinned "what you asked" note(s) from that turn were removed, though, so the reverted work is not requested again.',
|
|
217
219
|
|
|
218
220
|
// ── Bell ──────────────────────────────────────────────────────────────
|
|
219
221
|
'bell.stateOn': 'on',
|
|
@@ -299,6 +301,7 @@ export const en = {
|
|
|
299
301
|
'export.nothingRan': 'No commands were run.',
|
|
300
302
|
'export.unproven': 'Not proven',
|
|
301
303
|
'export.allProven': 'None — every change has a later check behind it.',
|
|
304
|
+
'export.evidenceFailed': 'Evidence could not be collected ({왜}) — so this report cannot claim everything was proven.',
|
|
302
305
|
'export.footer': 'Report generated by deel. No outbound links — opens on any network.',
|
|
303
306
|
'export.saved': 'Report saved:',
|
|
304
307
|
'export.openHint': 'Double-click to open. Attach it to a review or report as-is.',
|
|
@@ -341,6 +344,7 @@ export const en = {
|
|
|
341
344
|
'trust.removed': 'Removed from the trusted list',
|
|
342
345
|
'trust.notListed': 'That folder was not on the trusted list',
|
|
343
346
|
'trust.parentStill': 'Still trusted, through a parent folder',
|
|
347
|
+
'trust.stillTrustedEnv': "Still trusted, because of an environment setting on this machine",
|
|
344
348
|
'stats.title': 'What happened in this folder',
|
|
345
349
|
'stats.none': 'Nothing recorded yet — the log starts the first time deel does something here.',
|
|
346
350
|
'stats.period': 'Period',
|
|
@@ -350,6 +354,7 @@ export const en = {
|
|
|
350
354
|
'stats.sessions': '{n} sessions',
|
|
351
355
|
'stats.tools': 'Tools',
|
|
352
356
|
'stats.failed': '{n} failed',
|
|
357
|
+
'stats.unknown': '{n} unknown',
|
|
353
358
|
'stats.blocked': 'Blocked',
|
|
354
359
|
'stats.undos': 'Undos',
|
|
355
360
|
'stats.masked': 'Secrets masked',
|
|
@@ -358,11 +363,22 @@ export const en = {
|
|
|
358
363
|
'stats.noCost': 'Tokens and cost are not in this log. For the current conversation, use /cost.',
|
|
359
364
|
'trust.listTitle': 'Trusted folders',
|
|
360
365
|
'trust.listNone': 'No folder is trusted yet.',
|
|
366
|
+
'trust.listIgnoredWide': 'ignored — too wide to trust (run deel trust --off in that folder to remove it)',
|
|
361
367
|
'trust.blockedTitle': 'Keys a project config may never set, even when trusted',
|
|
362
368
|
'trust.saveFail': 'Could not write the trusted list',
|
|
369
|
+
'trust.refuseWide': 'Not trusting this folder',
|
|
370
|
+
'trust.refuseWideWhy': 'deel trust covers this folder and everything below it. Trusting a home folder or a drive root would read the config of every repository cloned underneath',
|
|
371
|
+
'trust.refuseWideHow': 'Change into the repository you mean and run deel trust there',
|
|
363
372
|
'trust.why.allow': 'It widens the approval rules. A deny, which narrows them, is still read',
|
|
364
373
|
'trust.why.apiKey': 'A key written into a repository is not a setting, it is a leak',
|
|
365
374
|
'trust.why.authCmd': 'It runs before the first request — ahead of tool approval, so nothing catches it',
|
|
375
|
+
'trust.why.connection': "Changing the address, API kind or auth of this PC's profile would send this PC's key where the repository chooses",
|
|
376
|
+
'trust.why.online': 'Permission to go outside is only granted where a person chooses it',
|
|
377
|
+
'trust.why.active': 'It would switch the connection to a profile the repository added. Picking a profile from this PC is still read',
|
|
378
|
+
'trust.why.offline': 'It lifts the seal. Turning it on (true) is still read',
|
|
379
|
+
'trust.why.proxy': 'Every request, key header included, would pass through that proxy',
|
|
380
|
+
'trust.why.shellEnv': 'It hands secret environment variables back to shell commands',
|
|
381
|
+
'trust.why.profilesNotArray': 'profiles is not a list ([ … ]), so none of it was read',
|
|
366
382
|
'level.beginner.name': 'beginner',
|
|
367
383
|
'level.beginner.hint': 'start on the recommended settings',
|
|
368
384
|
'level.developer.name': 'developer',
|
|
@@ -445,6 +461,7 @@ export const en = {
|
|
|
445
461
|
'tier.trail': 'ignoring trailing spaces and line endings',
|
|
446
462
|
'tier.indent': 'ignoring indentation',
|
|
447
463
|
'tier.space': 'ignoring all whitespace',
|
|
464
|
+
'tier.nfc': 'ignoring Unicode normalization (NFC/NFD)',
|
|
448
465
|
|
|
449
466
|
'sum.image': 'image',
|
|
450
467
|
'sum.imageDropped': 'dropped {크기} of images',
|
|
@@ -473,6 +490,9 @@ export const en = {
|
|
|
473
490
|
'run.hintHelp': '/help commands',
|
|
474
491
|
'run.hintEsc': 'ESC stop',
|
|
475
492
|
'run.hintQuit': 'Ctrl+C stop / quit',
|
|
493
|
+
'box.more': '{n} more',
|
|
494
|
+
'box.queuedHint': 'Press Enter to send it once the current task finishes',
|
|
495
|
+
'box.queued': '{n} queued',
|
|
476
496
|
'run.stopping': 'stopping…',
|
|
477
497
|
'run.errKept': 'What arrived before the failure was kept in the conversation — you can carry on from there.',
|
|
478
498
|
'ev.capped': 'The answer was cut off at {한계} tokens — raise the cap with /out, or have files written in parts',
|
|
@@ -498,6 +518,7 @@ export const en = {
|
|
|
498
518
|
'paste.pasted': 'Pasted {n} lines — Enter sends them all at once',
|
|
499
519
|
'run.thinking': 'thinking…',
|
|
500
520
|
'run.thinkingChars': 'thinking… {n} chars',
|
|
521
|
+
'run.thoughtChars': 'thought {n} chars',
|
|
501
522
|
'run.bye': 'Bye.',
|
|
502
523
|
'run.byeStats': '{n} model calls · {초}s in tools · ↑{입력} ↓{출력}',
|
|
503
524
|
'run.notMade': '— was not created',
|
|
@@ -563,6 +584,7 @@ export const en = {
|
|
|
563
584
|
'loop.limitAhead': 'Just rate-limited — spacing this out by {초}s',
|
|
564
585
|
'net.limit.guardrail': 'This is a guardrail limit on the gateway, not a model token limit. It counts text scanned per second (text units, roughly 1,000 characters each), so as the conversation grows every request gets bigger and past some point it fails every time. Prompt caching does not help here — the guardrail scans the full text every time regardless. Raising the model per-minute request/token limits (RPM/TPM) does nothing to it: different API, different quota. /compact shrinks the conversation for immediate relief; the real fix is for whoever runs the gateway to raise the guardrail quota or stop applying it to input.',
|
|
565
586
|
'net.limit.perMinute': 'This is a per-minute limit — it clears itself once the window rolls over. Waiting is the right answer here.',
|
|
587
|
+
'net.limit.oneTooBig': 'This single request is bigger than the per-minute limit — a new window will not help, because this one request still exceeds it. Shrink the prompt or attached files (/compact), lower the answer length cap, or raise the limit.',
|
|
566
588
|
'cost.quota': 'Server quota',
|
|
567
589
|
'cost.quotaAge': 'as of {초}s ago',
|
|
568
590
|
|
package/src/i18n/index.js
CHANGED
|
@@ -162,6 +162,36 @@ export function 조사붙이기(값, 갈래) {
|
|
|
162
162
|
return 짝[0];
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/*
|
|
166
|
+
* 자리를 찾는 무늬는 **한 벌**이다.
|
|
167
|
+
*
|
|
168
|
+
* 아래 말() 이 쓰는 것과, 「네 말의 자리 이름이 서로 같은가」 를 재는 검사가
|
|
169
|
+
* 쓰는 것이 따로였다. 그런데 검사 쪽은 `[^{}s:]` 라고 적혀 있었다 —
|
|
170
|
+
* 백슬래시 하나가 빠져서 **빈칸이 아니라 글자 `s` 를 뺐다.**
|
|
171
|
+
*
|
|
172
|
+
* 그래서 이름에 `s` 가 든 자리를 통째로 못 읽었다. 실제로 있는 말이다 —
|
|
173
|
+
*
|
|
174
|
+
* "{turns}개 턴, 파일 {files}개를 되돌렸습니다."
|
|
175
|
+
*
|
|
176
|
+
* 검사는 이 글에서 자리를 **0개**로 읽었다. 네 말 다 0개니 완벽한 짝이다.
|
|
177
|
+
* 누가 옮기면서 `{files}` 를 빠뜨려도 초록이고, 화면에는 「파일 個を元に
|
|
178
|
+
* 戻しました」 — 숫자가 소리 없이 사라진 문장이 나간다.
|
|
179
|
+
*
|
|
180
|
+
* 재는 쪽과 쓰는 쪽이 다른 무늬를 보면 검사는 무엇이든 통과시킨다. 그래서
|
|
181
|
+
* 무늬를 내보내고 양쪽이 같은 것을 쓴다. 이 저장소가 이미 밟은 함정이다 —
|
|
182
|
+
* 백슬래시 하나가 규칙을 통째로 바꿔 놓고 아무 데서도 안 터진다.
|
|
183
|
+
*/
|
|
184
|
+
const 자리무늬 = () => /\{([^{}\s:]+)(?::([^{}\s]+))?\}/g;
|
|
185
|
+
|
|
186
|
+
/** 이 글에 든 자리 이름들. 조사 표시(`~`)는 떼고, 같은 이름은 한 번만. */
|
|
187
|
+
export function 자리이름들(글) {
|
|
188
|
+
const 난것 = new Set();
|
|
189
|
+
for (const m of String(글 ?? '').matchAll(자리무늬())) {
|
|
190
|
+
난것.add(m[1].startsWith('~') ? m[1].slice(1) : m[1]);
|
|
191
|
+
}
|
|
192
|
+
return [...난것].sort();
|
|
193
|
+
}
|
|
194
|
+
|
|
165
195
|
/**
|
|
166
196
|
* 말 한 마디.
|
|
167
197
|
*
|
|
@@ -191,7 +221,7 @@ export function 말(열쇠, 채움 = null) {
|
|
|
191
221
|
* 안 준 자리는 그대로 둔다. 지워 버리면 "0개 턴" 처럼 틀린 말이 되고,
|
|
192
222
|
* 틀린 숫자는 안 적힌 것보다 나쁘다.
|
|
193
223
|
*/
|
|
194
|
-
return String(글).replace(
|
|
224
|
+
return String(글).replace(자리무늬(), (통째, 이름, 갈래) => {
|
|
195
225
|
const 조사만 = 이름.startsWith('~');
|
|
196
226
|
const 진짜이름 = 조사만 ? 이름.slice(1) : 이름;
|
|
197
227
|
if (!Object.prototype.hasOwnProperty.call(채움, 진짜이름)) return 통째;
|