deel-local-cli 1.16.0 → 1.17.2
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 +55 -9
- package/README.md +55 -8
- package/bin/deel.js +385 -5
- package/package.json +1 -1
- package/src/acp/serve.js +39 -1
- package/src/agent/agents.js +203 -0
- package/src/agent/loop.js +209 -17
- package/src/agent/models.js +4 -0
- package/src/agent/outschema.js +351 -0
- package/src/agent/session.js +13 -2
- package/src/backend/adapter.js +38 -2
- package/src/backend/clientcert.js +164 -0
- package/src/backend/http.js +232 -26
- package/src/backend/mcp.js +194 -30
- package/src/backend/probe.js +27 -1
- package/src/backend/wire.js +10 -0
- package/src/cmdnames.js +2 -0
- package/src/commands.js +139 -2
- package/src/completion.js +34 -4
- package/src/config.js +86 -9
- package/src/configexplain.js +177 -0
- package/src/doctor.js +195 -0
- package/src/i18n/en.js +37 -0
- package/src/i18n/ja.js +37 -0
- package/src/i18n/ko.js +37 -0
- package/src/i18n/zh.js +37 -0
- package/src/lsp/client.js +2 -2
- package/src/oneshot.js +181 -4
- package/src/pack/sbom.js +77 -3
- package/src/pack/selfpack.js +1 -1
- package/src/pack/sheet.en.js +1 -1
- package/src/repl.js +74 -3
- package/src/safety/hooks.js +389 -0
- package/src/safety/policy.js +74 -0
- package/src/safety/shellenv.js +116 -0
- package/src/safety/trust.js +230 -0
- package/src/setup.js +9 -0
- package/src/stats.js +162 -0
- package/src/tools/docs.js +12 -2
- package/src/tools/hwpxwrite.js +302 -0
- package/src/tools/index.js +128 -9
- package/src/tools/jobs.js +11 -6
- package/src/tools/spawn.js +19 -1
- package/src/tools/task.js +8 -2
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// `deel config explain <칸>` — 이 값은 **어디서 왔나**.
|
|
2
|
+
//
|
|
3
|
+
// ── 왜 필요한가 ────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// 설정을 읽는 자리가 넷이다 — 관리 정책 · 환경변수 · 프로젝트 설정 · 이 PC
|
|
6
|
+
// 설정. 겹치는 규칙은 문서에 적어 두었지만, 문서를 읽는 것과 **지금 이 자리에서
|
|
7
|
+
// 무엇이 이겼는지 아는 것**은 다른 일이다.
|
|
8
|
+
//
|
|
9
|
+
// 막히는 모양은 늘 같다. 파일에 분명히 적어 놨는데 안 먹는다. 그러면 사람은
|
|
10
|
+
// 그 파일을 고치고, 또 고치고, 결국 설정이라는 것을 안 믿게 된다. 실제로 값을
|
|
11
|
+
// 쥐고 있던 것은 환경변수 하나이거나, 프로젝트 설정을 안 믿는 폴더여서
|
|
12
|
+
// 읽지도 않은 것이다. 둘 다 **말해 주면 5초에 끝나는** 일이다.
|
|
13
|
+
//
|
|
14
|
+
// 그래서 층을 하나씩 열어서 「이 칸을 적어 둔 곳」 을 다 보여 주고, 어느 것이
|
|
15
|
+
// 이겼는지 표시한다. 값이 겹칠 때만 쓸모 있는 화면이 아니라, **아무 데도 안
|
|
16
|
+
// 적혀 있다**는 것을 보여 줄 때도 똑같이 쓸모 있다.
|
|
17
|
+
//
|
|
18
|
+
// ── 비밀은 안 보여 준다 ────────────────────────────────────────────────
|
|
19
|
+
//
|
|
20
|
+
// 열쇠 칸(apiKey)은 값을 안 적고 「적혀 있음」 만 적는다. 이 화면은 그대로
|
|
21
|
+
// 캡처되어 사내 메신저로 간다.
|
|
22
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { homeDir } from './config.js';
|
|
25
|
+
import { 믿나 } from './safety/trust.js';
|
|
26
|
+
import { 정책읽기, 정책자리 } from './safety/policy.js';
|
|
27
|
+
|
|
28
|
+
/** 값을 안 보여 줄 칸. 이름만 봐도 알 수 있는 것만 넣는다. */
|
|
29
|
+
const 가릴칸 = new Set(['apiKey', 'key', 'passphrase', '암호']);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 이 칸을 정할 수 있는 환경변수.
|
|
33
|
+
*
|
|
34
|
+
* 손으로 적는다. 자동으로 긁으면 「무엇이 이 칸을 이길 수 있나」 가 코드를
|
|
35
|
+
* 읽어야만 아는 것이 되는데, 그건 이 명령이 없애려는 바로 그 상태다.
|
|
36
|
+
*/
|
|
37
|
+
const 환경변수 = {
|
|
38
|
+
apiKey: ['DEEL_API_KEY', 'DEEL_KEY_<프로필ID>'],
|
|
39
|
+
shell: ['DEEL_SHELL'],
|
|
40
|
+
offline: [],
|
|
41
|
+
proxy: ['HTTPS_PROXY', 'HTTP_PROXY', 'ALL_PROXY', 'NO_PROXY'],
|
|
42
|
+
lang: ['DEEL_LANG'],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* 비밀 칸의 값은 **읽는 자리에서** 바꿔치기한다.
|
|
47
|
+
*
|
|
48
|
+
* 그리는 자리에서만 가리면 `--json` 이 날것을 그대로 낸다. 그 출력은
|
|
49
|
+
* 파이프를 타고 로그로, 사내 티켓으로 간다 — 화면만 가려 놓고 안전하다고
|
|
50
|
+
* 하는 것이 이런 자리에서 제일 흔한 사고다.
|
|
51
|
+
*/
|
|
52
|
+
export const 비밀표시 = '(적혀 있음 — 값은 안 보여 줍니다)';
|
|
53
|
+
const 지운값 = (칸, v) => (v !== undefined && 가릴칸.has(칸) ? 비밀표시 : v);
|
|
54
|
+
|
|
55
|
+
const 값보이기 = (칸, v) => {
|
|
56
|
+
if (v === undefined) return null;
|
|
57
|
+
if (v === null) return 'null';
|
|
58
|
+
if (typeof v === 'object') return JSON.stringify(v);
|
|
59
|
+
return String(v);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 한 칸이 어느 층에 적혀 있나.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} 칸 `offline` 처럼 맨 위 칸, 또는 `profiles.사내.model` 처럼
|
|
66
|
+
* 프로필 안의 칸
|
|
67
|
+
* @returns {{칸:string, 층들:Array, 이긴층:object|null, 환경변수:string[]}}
|
|
68
|
+
*/
|
|
69
|
+
export function 설명(칸, { root = process.cwd(), env = process.env } = {}) {
|
|
70
|
+
const 조각 = String(칸 ?? '').split('.').filter(Boolean);
|
|
71
|
+
const 층들 = [];
|
|
72
|
+
|
|
73
|
+
const 파일읽기 = (자리) => {
|
|
74
|
+
if (!existsSync(자리)) return { 있나: false };
|
|
75
|
+
try { return { 있나: true, 값: JSON.parse(readFileSync(자리, 'utf8')) }; }
|
|
76
|
+
catch (err) { return { 있나: true, 탈: err?.message ?? String(err) }; }
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const 파고들기 = (덩이) => {
|
|
80
|
+
let 여기 = 덩이;
|
|
81
|
+
for (let i = 0; i < 조각.length; i++) {
|
|
82
|
+
if (여기 == null || typeof 여기 !== 'object') return undefined;
|
|
83
|
+
const 이름 = 조각[i];
|
|
84
|
+
/*
|
|
85
|
+
* `profiles.<이름>.<칸>` 은 배열을 이름으로 뒤진다.
|
|
86
|
+
*
|
|
87
|
+
* 사람이 설정에서 프로필을 부르는 이름은 배열 번호가 아니라 name 이다.
|
|
88
|
+
* 번호로 적게 하면 프로필 하나를 지웠을 때 이 명령이 조용히 딴 것을
|
|
89
|
+
* 가리킨다.
|
|
90
|
+
*/
|
|
91
|
+
if (Array.isArray(여기)) {
|
|
92
|
+
여기 = 여기.find((x) => x?.name === 이름 || x?.id === 이름);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
여기 = 여기[이름];
|
|
96
|
+
}
|
|
97
|
+
return 여기;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const 끝칸이름지금 = 조각[조각.length - 1] ?? '';
|
|
101
|
+
|
|
102
|
+
// ── 1. 이 PC 설정 (제일 아래) ───────────────────────────────────────
|
|
103
|
+
const 집자리 = join(homeDir(), 'config.json');
|
|
104
|
+
const 집 = 파일읽기(집자리);
|
|
105
|
+
if (집.있나) {
|
|
106
|
+
층들.push({
|
|
107
|
+
층: '이 PC 설정', 자리: 집자리, 읽나: !집.탈,
|
|
108
|
+
값: 집.탈 ? undefined : 지운값(끝칸이름지금, 파고들기(집.값)), 탈: 집.탈 ?? null,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── 2. 프로젝트 설정 (믿는 폴더에서만) ──────────────────────────────
|
|
113
|
+
const 방자리 = join(root, '.deel', 'config.json');
|
|
114
|
+
if (existsSync(방자리)) {
|
|
115
|
+
const 믿나결과 = 믿나(root);
|
|
116
|
+
const 방 = 파일읽기(방자리);
|
|
117
|
+
층들.push({
|
|
118
|
+
층: '프로젝트 설정', 자리: 방자리, 읽나: 믿나결과 && !방.탈,
|
|
119
|
+
값: 방.탈 ? undefined : 지운값(끝칸이름지금, 파고들기(방.값)), 탈: 방.탈 ?? null,
|
|
120
|
+
// 읽지도 않는 파일에 적어 둔 것이 이 화면에서 제일 자주 나오는 답이다.
|
|
121
|
+
왜못읽나: 믿나결과 ? null : '믿는 폴더가 아닙니다 (deel trust)',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── 3. 환경변수 ─────────────────────────────────────────────────────
|
|
126
|
+
const 끝칸 = 조각[조각.length - 1] ?? '';
|
|
127
|
+
const 볼환경 = 환경변수[끝칸] ?? [];
|
|
128
|
+
const 켜진환경 = 볼환경.filter((이름) => !이름.includes('<') && env[이름]);
|
|
129
|
+
if (켜진환경.length) {
|
|
130
|
+
층들.push({
|
|
131
|
+
층: '환경변수', 자리: 켜진환경.join(' · '), 읽나: true,
|
|
132
|
+
값: 지운값(끝칸, env[켜진환경[0]]),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── 4. 관리 정책 (제일 위) ──────────────────────────────────────────
|
|
137
|
+
const 정책 = 정책읽기({ env, 다시: true });
|
|
138
|
+
if (정책.값 && typeof 정책.값 === 'object') {
|
|
139
|
+
const 그값 = 정책.값[끝칸];
|
|
140
|
+
if (그값 !== undefined) {
|
|
141
|
+
층들.push({ 층: '관리 정책', 자리: 정책.곳 ?? 정책자리(env)[0], 읽나: true, 값: 지운값(끝칸, 그값) });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 이긴 층 — 뒤에 온 것이 이긴다. 다만 **읽는 층** 중에서만 고른다.
|
|
146
|
+
const 쓸수있는것 = 층들.filter((x) => x.읽나 && x.값 !== undefined);
|
|
147
|
+
const 이긴층 = 쓸수있는것.length ? 쓸수있는것[쓸수있는것.length - 1] : null;
|
|
148
|
+
|
|
149
|
+
return { 칸, 층들, 이긴층, 환경변수: 볼환경 };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** 화면에 그릴 줄들. 색은 부르는 쪽이 입힌다. */
|
|
153
|
+
export function 설명줄들(r) {
|
|
154
|
+
const 줄 = [];
|
|
155
|
+
if (r.이긴층) 줄.push({ 갈래: '값', 글: `${값보이기(끝칸이름(r.칸), r.이긴층.값)}`, 곁: `${r.이긴층.층}` });
|
|
156
|
+
else 줄.push({ 갈래: '없음', 글: '아무 데도 안 적혀 있습니다 (기본값으로 돕니다)', 곁: null });
|
|
157
|
+
|
|
158
|
+
for (const 층 of r.층들) {
|
|
159
|
+
const 값 = 값보이기(끝칸이름(r.칸), 층.값);
|
|
160
|
+
줄.push({
|
|
161
|
+
갈래: 층 === r.이긴층 ? '이김' : (층.읽나 ? '아래' : '안읽음'),
|
|
162
|
+
글: `${층.층}`,
|
|
163
|
+
곁: [
|
|
164
|
+
층.자리,
|
|
165
|
+
값 !== null ? `= ${값}` : '(이 칸은 없음)',
|
|
166
|
+
층.왜못읽나 ? `— ${층.왜못읽나}` : null,
|
|
167
|
+
층.탈 ? `— 못 읽었습니다: ${층.탈}` : null,
|
|
168
|
+
].filter(Boolean).join(' '),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (r.환경변수.length) {
|
|
172
|
+
줄.push({ 갈래: '참고', 글: '이 칸을 이길 수 있는 환경변수', 곁: r.환경변수.join(' · ') });
|
|
173
|
+
}
|
|
174
|
+
return 줄;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const 끝칸이름 = (칸) => String(칸 ?? '').split('.').filter(Boolean).pop() ?? '';
|
package/src/doctor.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// `deel doctor` — **붙기 전에** 이 자리의 조건을 하나씩 본다.
|
|
2
|
+
//
|
|
3
|
+
// ── diagnose 와 무엇이 다른가 ──────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// `deel diagnose` 는 **모델이 일을 할 수 있나**를 잰다 — 도구를 부르나, 답을
|
|
6
|
+
// 흘려보내나, 창이 얼마나 넓나. 그건 붙은 다음의 이야기다.
|
|
7
|
+
//
|
|
8
|
+
// 그런데 사내에서 막히는 자리는 대부분 그 앞이다. 프록시를 안 거쳐서, 사내
|
|
9
|
+
// 루트가 없어서, 열쇠를 못 꺼내서, 인증서 경로에 오타가 나서. 그때 사람이
|
|
10
|
+
// 받는 것은 「fetch failed」 나 TLS 악수 실패 한 줄이고, 그 한 줄로는 여섯
|
|
11
|
+
// 가지 원인 중 어느 것인지 알 수가 없다. 담당자에게 물어볼 말조차 못 만든다.
|
|
12
|
+
//
|
|
13
|
+
// 그래서 여기서는 **조건을 하나씩 따로 재고, 각각 어디서 온 값인지 적는다.**
|
|
14
|
+
// 화면 한 장이 그대로 담당자에게 보낼 질문이 되게 하는 것이 목표다.
|
|
15
|
+
//
|
|
16
|
+
// ── 규칙 ───────────────────────────────────────────────────────────────
|
|
17
|
+
//
|
|
18
|
+
// **모르는 것은 모른다고 적는다.** 못 잰 것을 초록으로 세면 이 화면은 사람을
|
|
19
|
+
// 엉뚱한 데로 보낸다. 못 잰 것은 `?` 로 적고 왜 못 쟀는지 붙인다.
|
|
20
|
+
//
|
|
21
|
+
// **비밀은 한 글자도 안 적는다.** 이 화면은 그대로 캡처되어 사내 메신저로
|
|
22
|
+
// 간다. 열쇠는 있나 없나와 어디서 왔나만, 인증서 암호는 어디서 왔나만 적는다.
|
|
23
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import { req } from './backend/http.js';
|
|
26
|
+
import { 프록시설정, 프록시고르기 } from './backend/proxy.js';
|
|
27
|
+
import { 인증서설정, 인증서등록, 인증서찾기, 인증서말 } from './backend/clientcert.js';
|
|
28
|
+
import { 주소가리기 } from './safety/secrets.js';
|
|
29
|
+
import { 믿나 } from './safety/trust.js';
|
|
30
|
+
import { 훅읽기, 훅줄들 } from './safety/hooks.js';
|
|
31
|
+
import { 에이전트읽기, 에이전트줄들 } from './agent/agents.js';
|
|
32
|
+
import { headersFor } from './backend/http.js';
|
|
33
|
+
|
|
34
|
+
/** 한 줄. `상태` 는 ok · warn · no · unknown 넷뿐이다. */
|
|
35
|
+
const 줄 = (상태, 이름, 값, 덧말 = null) => ({ 상태, 이름, 값, 덧말 });
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 이 자리의 조건을 다 본다. 화면 그리기는 안 한다 — 부르는 쪽이 그린다.
|
|
39
|
+
*
|
|
40
|
+
* @param {object} o
|
|
41
|
+
* @param {object|null} o.cfg 읽은 설정
|
|
42
|
+
* @param {object|null} o.prof 고른 프로필
|
|
43
|
+
* @param {string} o.root 작업 폴더
|
|
44
|
+
* @param {boolean} o.바깥가도되나 네트워크를 실제로 두드려도 되나
|
|
45
|
+
*/
|
|
46
|
+
export async function 진찰(o = {}) {
|
|
47
|
+
const { cfg = null, prof = null, root = process.cwd(), 바깥가도되나 = true, 설정자리 = null } = o;
|
|
48
|
+
const 줄들 = [];
|
|
49
|
+
|
|
50
|
+
// ── 1. 이 프로그램이 도는 자리 ───────────────────────────────────────
|
|
51
|
+
const major = parseInt(String(process.versions.node).split('.')[0], 10);
|
|
52
|
+
줄들.push(줄(major >= 20 ? 'ok' : 'no', 'Node', `${process.versions.node} · ${process.platform}`,
|
|
53
|
+
major >= 20 ? null : '20 이상이 필요합니다'));
|
|
54
|
+
|
|
55
|
+
// ── 2. 설정 ─────────────────────────────────────────────────────────
|
|
56
|
+
if (설정자리) 줄들.push(줄(existsSync(설정자리) ? 'ok' : 'warn', '설정 파일', 설정자리));
|
|
57
|
+
const 프로젝트설정 = join(root, '.deel', 'config.json');
|
|
58
|
+
if (existsSync(프로젝트설정)) {
|
|
59
|
+
/*
|
|
60
|
+
* 프로젝트 설정은 **믿는 폴더에서만** 읽는다 (safety/trust.js). 여기서
|
|
61
|
+
* 읽었는지 안 읽었는지를 말해 주지 않으면, 적어 둔 사람은 자기 설정이
|
|
62
|
+
* 왜 안 먹는지 영영 모른다 — 파일은 분명히 거기 있기 때문이다.
|
|
63
|
+
*/
|
|
64
|
+
const 믿나결과 = 믿나(root);
|
|
65
|
+
줄들.push(줄(믿나결과 ? 'ok' : 'warn', '프로젝트 설정', 프로젝트설정,
|
|
66
|
+
믿나결과 ? '읽습니다' : '안 읽습니다 — 읽게 하려면 deel trust'));
|
|
67
|
+
}
|
|
68
|
+
if (!prof) {
|
|
69
|
+
줄들.push(줄('no', '프로필', '(없음)', 'deel setup 을 먼저 하세요'));
|
|
70
|
+
return { 줄들, 붙어봤나: false };
|
|
71
|
+
}
|
|
72
|
+
줄들.push(줄('ok', '프로필', `${prof.name ?? prof.id ?? '(이름 없음)'} · ${prof.model ?? '(모델 없음)'}`));
|
|
73
|
+
줄들.push(줄('ok', '주소', 주소가리기(prof.baseUrl ?? '(없음)')));
|
|
74
|
+
|
|
75
|
+
// ── 3. 열쇠 ─────────────────────────────────────────────────────────
|
|
76
|
+
/*
|
|
77
|
+
* 값은 절대 안 적는다. 있나 없나와 **어디서 왔나**만 적는다 — 고칠 사람이
|
|
78
|
+
* 알아야 하는 것이 그 둘이다. 환경변수가 파일을 이기고 있는데 그걸 모르면
|
|
79
|
+
* 파일만 백 번 고친다.
|
|
80
|
+
*/
|
|
81
|
+
const 열쇠어디 = (() => {
|
|
82
|
+
if (process.env.DEEL_API_KEY) return '환경변수 DEEL_API_KEY';
|
|
83
|
+
const 프로필키 = prof.id ? process.env[`DEEL_KEY_${String(prof.id).toUpperCase()}`] : null;
|
|
84
|
+
if (프로필키) return `환경변수 DEEL_KEY_${String(prof.id).toUpperCase()}`;
|
|
85
|
+
if (prof.열쇠받기 || prof.authCommand) return '명령으로 받아 옵니다';
|
|
86
|
+
if (prof.apiKey) return '설정 파일';
|
|
87
|
+
return null;
|
|
88
|
+
})();
|
|
89
|
+
const 열쇠있나 = !!(o.열쇠 ?? '');
|
|
90
|
+
if (prof.auth === 'none') 줄들.push(줄('ok', '열쇠', '안 씁니다 (auth: none)'));
|
|
91
|
+
else if (열쇠있나) 줄들.push(줄('ok', '열쇠', `있습니다 · ${열쇠어디 ?? '어디서 왔는지 모름'}`));
|
|
92
|
+
else 줄들.push(줄('no', '열쇠', '못 꺼냈습니다', 열쇠어디 ? `${열쇠어디} 에서 읽으려 했습니다` : '적어 둔 것이 없습니다'));
|
|
93
|
+
|
|
94
|
+
// ── 4. 우리 인증서 (mTLS) ───────────────────────────────────────────
|
|
95
|
+
const 인증 = 인증서설정(prof);
|
|
96
|
+
if (인증) {
|
|
97
|
+
인증서등록(prof.baseUrl, 인증);
|
|
98
|
+
try {
|
|
99
|
+
인증서찾기(prof.baseUrl); // 파일을 실제로 읽어 본다. 못 읽으면 던진다.
|
|
100
|
+
줄들.push(줄('ok', '내 인증서', 인증서말(인증)));
|
|
101
|
+
} catch (err) {
|
|
102
|
+
줄들.push(줄('no', '내 인증서', 인증서말(인증), String(err?.message ?? err).split('\n')[0]));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── 5. 사내 루트 (NODE_EXTRA_CA_CERTS) ──────────────────────────────
|
|
107
|
+
const ca = process.env.NODE_EXTRA_CA_CERTS;
|
|
108
|
+
if (ca) {
|
|
109
|
+
let 됐나 = false;
|
|
110
|
+
try { 됐나 = statSync(ca).isFile(); } catch { 됐나 = false; }
|
|
111
|
+
줄들.push(줄(됐나 ? 'ok' : 'no', '사내 루트', ca, 됐나 ? null : '그 자리에 파일이 없습니다'));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── 6. 프록시 ───────────────────────────────────────────────────────
|
|
115
|
+
/*
|
|
116
|
+
* 「프록시를 켰다」 와 「이 주소가 그 프록시를 거친다」 는 다른 말이다.
|
|
117
|
+
* NO_PROXY 하나로 거치지 않게 되는데, 그때 화면이 「프록시 켜짐」 만
|
|
118
|
+
* 말하면 사람은 거치는 줄 알고 프록시 담당자에게 묻는다.
|
|
119
|
+
*/
|
|
120
|
+
const 프록 = 프록시설정();
|
|
121
|
+
const 이주소 = 프록시고르기(prof.baseUrl ?? '');
|
|
122
|
+
if (프록.탈) 줄들.push(줄('warn', '프록시', '설정을 못 읽었습니다', 프록.탈));
|
|
123
|
+
if (!프록.켜짐) 줄들.push(줄('ok', '프록시', '안 거칩니다 (직접)'));
|
|
124
|
+
else if (이주소) {
|
|
125
|
+
줄들.push(줄('ok', '프록시', `${주소가리기(이주소.url)} · ${이주소.출처}`,
|
|
126
|
+
이주소.auth ? '인증 있음' : null));
|
|
127
|
+
} else {
|
|
128
|
+
줄들.push(줄('warn', '프록시', `${프록.프록시들.map((x) => `${주소가리기(x.url)}(${x.출처})`).join(' · ')}`,
|
|
129
|
+
'이 주소는 프록시를 안 거칩니다 (NO_PROXY 나 이 컴퓨터 안)'));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── 7. MCP ──────────────────────────────────────────────────────────
|
|
133
|
+
const mcp자리 = [join(root, '.deel', 'mcp.json'), join(root, '.mcp.json')].filter((p) => existsSync(p));
|
|
134
|
+
if (mcp자리.length) {
|
|
135
|
+
let 몇 = 0;
|
|
136
|
+
try {
|
|
137
|
+
const j = JSON.parse(readFileSync(mcp자리[0], 'utf8'));
|
|
138
|
+
몇 = Object.keys(j?.mcpServers ?? j?.servers ?? {}).length;
|
|
139
|
+
} catch { 몇 = -1; }
|
|
140
|
+
줄들.push(몇 < 0
|
|
141
|
+
? 줄('no', 'MCP', mcp자리[0], '읽지 못했습니다 (JSON 이 아닙니다)')
|
|
142
|
+
: 줄('ok', 'MCP', `${mcp자리[0]} · 서버 ${몇}개`, '실제로 띄워 보려면 deel 을 켜고 /mcp'));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── 7.5 훅 ───────────────────────────────────────────────────────────
|
|
146
|
+
//
|
|
147
|
+
// 훅은 사람이 적어 둔 명령을 돌리는 것이라, 안 도는 것도 도는 것도 둘 다 사고다.
|
|
148
|
+
// 안 도는 쪽이 더 나쁘다 — 사내 규칙을 걸어 둔 줄 알고 있는데 실제로는 안 걸린
|
|
149
|
+
// 상태이기 때문이다. 제일 흔한 까닭이 믿는 폴더가 아니어서인데, 그건 아무 데도 안
|
|
150
|
+
// 찍힌다. 여기서 말해 준다.
|
|
151
|
+
for (const 한줄 of 훅줄들(훅읽기(root))) 줄들.push(한줄);
|
|
152
|
+
|
|
153
|
+
// 이름 붙인 하위 작업 (agent/agents.js). 못 읽은 정의를 말해 주는 자리는 여기뿐이다.
|
|
154
|
+
for (const 한줄 of 에이전트줄들(에이전트읽기(root))) 줄들.push(한줄);
|
|
155
|
+
|
|
156
|
+
// ── 8. 진짜로 두드려 본다 ───────────────────────────────────────────
|
|
157
|
+
if (!바깥가도되나) {
|
|
158
|
+
줄들.push(줄('unknown', '도달', '안 두드렸습니다', '자물쇠가 걸려 있거나 --offline 입니다'));
|
|
159
|
+
return { 줄들, 붙어봤나: false };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const 주소 = String(prof.baseUrl ?? '').replace(/\/+$/, '');
|
|
163
|
+
const t0 = Date.now();
|
|
164
|
+
const r = await req(`${주소}/models`, {
|
|
165
|
+
headers: headersFor(prof.auth ?? 'bearer', o.열쇠 ?? ''),
|
|
166
|
+
timeout: 15000,
|
|
167
|
+
});
|
|
168
|
+
const 걸린 = Date.now() - t0;
|
|
169
|
+
|
|
170
|
+
if (r.ok) {
|
|
171
|
+
const 목록 = Array.isArray(r.json?.data) ? r.json.data : (Array.isArray(r.json?.models) ? r.json.models : []);
|
|
172
|
+
줄들.push(줄('ok', '도달', `${r.status} · ${걸린}ms`));
|
|
173
|
+
/*
|
|
174
|
+
* 모델 목록에 **그 모델이 있나**까지 본다.
|
|
175
|
+
*
|
|
176
|
+
* 붙기는 되는데 모델 이름이 한 글자 틀린 자리가 흔하다. 그때 서버가 주는
|
|
177
|
+
* 것은 404 나 400 이고, 그 화면은 「주소가 틀렸다」 와 구별이 안 된다.
|
|
178
|
+
*/
|
|
179
|
+
const 이름들 = 목록.map((m) => String(m?.id ?? m?.name ?? '')).filter(Boolean);
|
|
180
|
+
if (!이름들.length) 줄들.push(줄('unknown', '모델 목록', '안 알려 줍니다', '목록을 안 내는 서버도 있습니다'));
|
|
181
|
+
else if (이름들.includes(String(prof.model))) 줄들.push(줄('ok', '모델', `${prof.model} · 목록 ${이름들.length}개`));
|
|
182
|
+
else {
|
|
183
|
+
const 비슷 = 이름들.filter((n) => n.includes(String(prof.model ?? '')) || String(prof.model ?? '').includes(n));
|
|
184
|
+
줄들.push(줄('no', '모델', `${prof.model} 이 목록에 없습니다`,
|
|
185
|
+
(비슷.length ? `비슷한 것: ${비슷.slice(0, 3).join(' · ')}` : `있는 것: ${이름들.slice(0, 3).join(' · ')}${이름들.length > 3 ? ' …' : ''}`)));
|
|
186
|
+
}
|
|
187
|
+
} else if (r.status === 401 || r.status === 403) {
|
|
188
|
+
// 닿기는 닿았다. 그 둘을 갈라 줘야 사람이 프록시가 아니라 열쇠를 본다.
|
|
189
|
+
줄들.push(줄('ok', '도달', `${r.status} · ${걸린}ms`, '주소는 닿습니다'));
|
|
190
|
+
줄들.push(줄('no', '인증', `${r.status}`, '열쇠나 인증 방식(auth)을 보세요'));
|
|
191
|
+
} else {
|
|
192
|
+
줄들.push(줄('no', '도달', r.status ? `${r.status}` : '못 닿았습니다', r.error ?? null));
|
|
193
|
+
}
|
|
194
|
+
return { 줄들, 붙어봤나: true };
|
|
195
|
+
}
|
package/src/i18n/en.js
CHANGED
|
@@ -103,6 +103,8 @@ export const en = {
|
|
|
103
103
|
'cmd.recall.desc': 'search past sessions — by what was said, not by title',
|
|
104
104
|
'cmd.recall.arg': '<text>',
|
|
105
105
|
'cmd.mcp.desc': 'show connected MCP tool servers',
|
|
106
|
+
'cmd.hooks.desc': 'show configured hooks (where they came from, when they run)',
|
|
107
|
+
'cmd.agents.desc': 'show named subagent definitions',
|
|
106
108
|
'cmd.memory.desc': 'what survives the session — view and clear',
|
|
107
109
|
'cmd.memory.arg': '[forget <n>|clear|<text to remember>]',
|
|
108
110
|
'cmd.level.desc': 'user level (beginner/developer)',
|
|
@@ -327,6 +329,37 @@ export const en = {
|
|
|
327
329
|
'work.axis': 'A different axis from the approval policy ({명령}) — what you do / how much it asks.',
|
|
328
330
|
'work.autoOn': 'From here it moves to the right mode by itself, message by message.',
|
|
329
331
|
'work.pinned': 'You chose this, so it will not change by itself. To hand it back:',
|
|
332
|
+
'trust.ignored': "This folder's project config was not read — the folder is not trusted yet",
|
|
333
|
+
'trust.ignoredWhy': 'A config file ships with the repository. Reading a stranger\u2019s means it picks the endpoint and the command that fetches your key.',
|
|
334
|
+
'trust.howTo': 'To read it:',
|
|
335
|
+
'trust.filtered': 'Dropped the keys a project config may not set',
|
|
336
|
+
'trust.trusted': "This folder's project config is being read",
|
|
337
|
+
'trust.already': 'Already trusted',
|
|
338
|
+
'trust.removed': 'Removed from the trusted list',
|
|
339
|
+
'trust.notListed': 'That folder was not on the trusted list',
|
|
340
|
+
'trust.parentStill': 'Still trusted, through a parent folder',
|
|
341
|
+
'stats.title': 'What happened in this folder',
|
|
342
|
+
'stats.none': 'Nothing recorded yet — the log starts the first time deel does something here.',
|
|
343
|
+
'stats.period': 'Period',
|
|
344
|
+
'stats.window': 'last {n} days',
|
|
345
|
+
'stats.activeDays': '{n} active days',
|
|
346
|
+
'stats.turns': 'Turns',
|
|
347
|
+
'stats.sessions': '{n} sessions',
|
|
348
|
+
'stats.tools': 'Tools',
|
|
349
|
+
'stats.failed': '{n} failed',
|
|
350
|
+
'stats.blocked': 'Blocked',
|
|
351
|
+
'stats.undos': 'Undos',
|
|
352
|
+
'stats.masked': 'Secrets masked',
|
|
353
|
+
'stats.broken': '{n} lines could not be read — the numbers above are short by that much.',
|
|
354
|
+
'stats.older': '{n} more lines fall outside the window (--all for everything).',
|
|
355
|
+
'stats.noCost': 'Tokens and cost are not in this log. For the current conversation, use /cost.',
|
|
356
|
+
'trust.listTitle': 'Trusted folders',
|
|
357
|
+
'trust.listNone': 'No folder is trusted yet.',
|
|
358
|
+
'trust.blockedTitle': 'Keys a project config may never set, even when trusted',
|
|
359
|
+
'trust.saveFail': 'Could not write the trusted list',
|
|
360
|
+
'trust.why.allow': 'It widens the approval rules. A deny, which narrows them, is still read',
|
|
361
|
+
'trust.why.apiKey': 'A key written into a repository is not a setting, it is a leak',
|
|
362
|
+
'trust.why.authCmd': 'It runs before the first request — ahead of tool approval, so nothing catches it',
|
|
330
363
|
'level.beginner.name': 'beginner',
|
|
331
364
|
'level.beginner.hint': 'start on the recommended settings',
|
|
332
365
|
'level.developer.name': 'developer',
|
|
@@ -442,7 +475,10 @@ export const en = {
|
|
|
442
475
|
'run.errKept': 'What arrived before the failure was kept in the conversation — you can carry on from there.',
|
|
443
476
|
'ev.capped': 'The answer was cut off at {한계} tokens — raise the cap with /out, or have files written in parts',
|
|
444
477
|
'ev.cutoff': 'The server stopped the answer without saying it had finished — it may have been cut mid-way',
|
|
478
|
+
'ev.stalled': 'Nothing arrived for {n}s, so the connection was dropped — this is what came through',
|
|
445
479
|
'ev.nudgeMissed': 'It was about to finish leaving {n} of the things you asked for undone — pushed back once',
|
|
480
|
+
'ev.hookNote': 'hook: {말}',
|
|
481
|
+
'ev.hookBlock': 'A hook blocked this',
|
|
446
482
|
'ev.nudgeRead': 'It was about to finish having only read things — pushed back once',
|
|
447
483
|
'ev.learnedCtx': 'Setting the context limit to {한계} as the server reported, and calling again',
|
|
448
484
|
'ev.learnedOut': 'Setting the answer-length limit to {한계} as the server reported, and calling again',
|
|
@@ -476,6 +512,7 @@ export const en = {
|
|
|
476
512
|
'status.root': 'Work folder',
|
|
477
513
|
'status.shell': 'Shell',
|
|
478
514
|
'status.keyStore': 'Key store',
|
|
515
|
+
'status.clientCert': 'Client cert',
|
|
479
516
|
'status.rules': 'Rules',
|
|
480
517
|
'status.noRules': 'none (/init creates one)',
|
|
481
518
|
'status.rulesUnread': '{이름} exists but could not be read ({까닭}) — this folder\'s rules are not in effect',
|
package/src/i18n/ja.js
CHANGED
|
@@ -90,6 +90,8 @@ export const ja = {
|
|
|
90
90
|
'cmd.recall.desc': '過去の会話を探す — 題名ではなく中身で',
|
|
91
91
|
'cmd.recall.arg': '<文字列>',
|
|
92
92
|
'cmd.mcp.desc': 'つながっている MCP 道具サーバー',
|
|
93
|
+
'cmd.hooks.desc': '設定したフックを見る',
|
|
94
|
+
'cmd.agents.desc': '名前を付けた下位タスクを見る',
|
|
93
95
|
'cmd.memory.desc': '会話をまたいで残るもの — 見る・消す',
|
|
94
96
|
'cmd.memory.arg': '[forget <n>|clear|<覚えさせる文>]',
|
|
95
97
|
'cmd.level.desc': '利用者の段階 (かんたん/開発者)',
|
|
@@ -294,6 +296,37 @@ export const ja = {
|
|
|
294
296
|
'work.axis': '承認ポリシー({명령})とは別の軸です — 何をするか / どれだけ聞くか。',
|
|
295
297
|
'work.autoOn': 'これからは一言ごとに適したモードへ自動で移ります。',
|
|
296
298
|
'work.pinned': 'ご自分で選んだので自動では変わりません。任せ直すには',
|
|
299
|
+
'trust.ignored': 'このフォルダのプロジェクト設定は読みませんでした — まだ信頼していないフォルダです',
|
|
300
|
+
'trust.ignoredWhy': '設定ファイルはリポジトリに同梱されます。他人のものをそのまま読むと、接続先も鍵を取得するコマンドも向こうが決めます。',
|
|
301
|
+
'trust.howTo': '読ませるには',
|
|
302
|
+
'trust.filtered': 'プロジェクト設定が決められない項目を取り除きました',
|
|
303
|
+
'trust.trusted': 'このフォルダのプロジェクト設定を読みます',
|
|
304
|
+
'trust.already': 'すでに信頼しているフォルダです',
|
|
305
|
+
'trust.removed': '信頼リストから外しました',
|
|
306
|
+
'trust.notListed': '信頼リストに無いフォルダでした',
|
|
307
|
+
'trust.parentStill': '親フォルダのため、まだ信頼されています',
|
|
308
|
+
'stats.title': 'このフォルダでの作業',
|
|
309
|
+
'stats.none': 'まだ記録がありません — このフォルダで deel を使うと残りはじめます。',
|
|
310
|
+
'stats.period': '期間',
|
|
311
|
+
'stats.window': '直近 {n} 日',
|
|
312
|
+
'stats.activeDays': '{n} 日使用',
|
|
313
|
+
'stats.turns': '会話',
|
|
314
|
+
'stats.sessions': 'セッション {n} 件',
|
|
315
|
+
'stats.tools': 'ツール',
|
|
316
|
+
'stats.failed': '失敗 {n}',
|
|
317
|
+
'stats.blocked': 'ブロック',
|
|
318
|
+
'stats.undos': '取り消し',
|
|
319
|
+
'stats.masked': '秘密の伏字',
|
|
320
|
+
'stats.broken': '読めなかった行が {n} 件あります — 上の数はその分少なく数えています。',
|
|
321
|
+
'stats.older': '期間外にさらに {n} 行あります (--all で全部)。',
|
|
322
|
+
'stats.noCost': 'トークンと料金はこの記録にありません。今の会話ぶんは /cost で見られます。',
|
|
323
|
+
'trust.listTitle': '信頼しているフォルダ',
|
|
324
|
+
'trust.listNone': 'まだどのフォルダも信頼していません。',
|
|
325
|
+
'trust.blockedTitle': '信頼しても プロジェクト設定が決められない項目',
|
|
326
|
+
'trust.saveFail': '信頼リストを書けませんでした',
|
|
327
|
+
'trust.why.allow': '承認ルールを広げる項目です。狭める deny はそのまま読みます',
|
|
328
|
+
'trust.why.apiKey': 'リポジトリに書かれた鍵は設定ではなく漏洩です',
|
|
329
|
+
'trust.why.authCmd': '最初のリクエスト前にコマンドが走ります — ツール承認より前なので何も止められません',
|
|
297
330
|
'level.beginner.name': 'かんたん',
|
|
298
331
|
'level.beginner.hint': '勧められた設定のまま始めます',
|
|
299
332
|
'level.developer.name': '開発者',
|
|
@@ -400,7 +433,10 @@ export const ja = {
|
|
|
400
433
|
'run.errKept': 'ここまで届いた分は会話に残しました — このまま続けて話せます。',
|
|
401
434
|
'ev.capped': '答えが {한계} トークンで切れました — /out で上限を上げるか、ファイルは分けて書かせてください',
|
|
402
435
|
'ev.cutoff': 'サーバーが終わったとも言わずに答えを止めました — 途中で切れた可能性があります',
|
|
436
|
+
'ev.stalled': '{n}秒間なにも届かなかったので切りました — 届いたぶんだけ表示します',
|
|
403
437
|
'ev.nudgeMissed': '頼んだこと {n} 件を残したまま終えようとしたので、一度押し戻しました',
|
|
438
|
+
'ev.hookNote': 'フック: {말}',
|
|
439
|
+
'ev.hookBlock': 'フックが止めました',
|
|
404
440
|
'ev.nudgeRead': '読むだけで終えようとしたので、一度押し戻しました',
|
|
405
441
|
'ev.learnedCtx': 'サーバーが教えてくれたコンテキスト上限 {한계} に合わせて呼び直します',
|
|
406
442
|
'ev.learnedOut': 'サーバーが教えてくれた答えの長さ上限 {한계} に合わせて呼び直します',
|
|
@@ -433,6 +469,7 @@ export const ja = {
|
|
|
433
469
|
'status.root': '作業フォルダ',
|
|
434
470
|
'status.shell': 'シェル',
|
|
435
471
|
'status.keyStore': '鍵の保管',
|
|
472
|
+
'status.clientCert': '自分の証明書',
|
|
436
473
|
'status.rules': 'ルール',
|
|
437
474
|
'status.noRules': 'なし(/init で作れます)',
|
|
438
475
|
'status.rulesUnread': '{이름} はありますが読めませんでした({까닭})— このフォルダのルールは効いていません',
|
package/src/i18n/ko.js
CHANGED
|
@@ -92,6 +92,8 @@ export const ko = {
|
|
|
92
92
|
'cmd.recall.desc': '지난 대화에서 찾기 — 목록 말고 내용으로',
|
|
93
93
|
'cmd.recall.arg': '<찾을 말>',
|
|
94
94
|
'cmd.mcp.desc': '밖에서 붙인 도구(MCP) 서버 보기',
|
|
95
|
+
'cmd.hooks.desc': '적어 둔 훅 보기 (어디서 읽었고 언제 도는지)',
|
|
96
|
+
'cmd.agents.desc': '이름 붙인 하위 작업 보기',
|
|
95
97
|
'cmd.memory.desc': '대화가 끝나도 남는 기억 — 보기·지우기',
|
|
96
98
|
'cmd.memory.arg': '[forget <번호>|clear|<적을 말>]',
|
|
97
99
|
'cmd.level.desc': '사용자 수준 (쉬움/개발자)',
|
|
@@ -316,6 +318,37 @@ export const ko = {
|
|
|
316
318
|
'work.axis': '이건 승인 정책({명령})과 다른 축입니다 — 무엇을 하느냐 / 얼마나 물어보냐.',
|
|
317
319
|
'work.autoOn': '앞으로는 한마디마다 알맞은 모드로 저절로 옮겨 갑니다.',
|
|
318
320
|
'work.pinned': '직접 고르셨으므로 저절로 바뀌지 않습니다. 다시 맡기려면',
|
|
321
|
+
'trust.ignored': '이 폴더의 프로젝트 설정을 안 읽었습니다 — 아직 믿는 폴더가 아닙니다',
|
|
322
|
+
'trust.ignoredWhy': '설정 파일은 저장소에 딸려 옵니다. 남의 것을 그냥 읽으면 주소도 열쇠 받는 명령도 그쪽이 정합니다.',
|
|
323
|
+
'trust.howTo': '읽게 하려면',
|
|
324
|
+
'trust.filtered': '프로젝트 설정에서 못 정하는 칸을 걷어냈습니다',
|
|
325
|
+
'trust.trusted': '이 폴더의 프로젝트 설정을 읽습니다',
|
|
326
|
+
'trust.already': '이미 믿는 폴더입니다',
|
|
327
|
+
'trust.removed': '믿는 목록에서 뺐습니다',
|
|
328
|
+
'trust.notListed': '믿는 목록에 없던 폴더입니다',
|
|
329
|
+
'trust.parentStill': '그래도 위 폴더 때문에 아직 믿습니다',
|
|
330
|
+
'stats.title': '이 폴더에서 한 일',
|
|
331
|
+
'stats.none': '아직 기록이 없습니다 — 이 폴더에서 deel 로 무언가 하면 그때부터 남습니다.',
|
|
332
|
+
'stats.period': '기간',
|
|
333
|
+
'stats.window': '최근 {n}일',
|
|
334
|
+
'stats.activeDays': '{n}일 사용',
|
|
335
|
+
'stats.turns': '대화',
|
|
336
|
+
'stats.sessions': '세션 {n}개',
|
|
337
|
+
'stats.tools': '도구',
|
|
338
|
+
'stats.failed': '실패 {n}',
|
|
339
|
+
'stats.blocked': '막힘',
|
|
340
|
+
'stats.undos': '되돌리기',
|
|
341
|
+
'stats.masked': '비밀 가림',
|
|
342
|
+
'stats.broken': '읽지 못한 줄이 {n}개 있습니다 — 위 숫자는 그만큼 덜 셌습니다.',
|
|
343
|
+
'stats.older': '기간 밖에 {n}줄이 더 있습니다 (--all 로 전부).',
|
|
344
|
+
'stats.noCost': '토큰과 요금은 이 기록에 없습니다. 이번 대화에서 쓴 것은 /cost 로 보세요.',
|
|
345
|
+
'trust.listTitle': '믿는 폴더',
|
|
346
|
+
'trust.listNone': '아직 아무 폴더도 안 믿습니다.',
|
|
347
|
+
'trust.blockedTitle': '믿어도 프로젝트가 못 정하는 칸',
|
|
348
|
+
'trust.saveFail': '믿는 목록을 못 적었습니다',
|
|
349
|
+
'trust.why.allow': '승인 규칙을 넓히는 칸입니다. 좁히는 deny 는 그대로 읽습니다',
|
|
350
|
+
'trust.why.apiKey': '열쇠가 저장소에 적혀 있으면 설정이 아니라 유출입니다',
|
|
351
|
+
'trust.why.authCmd': '첫 요청 전에 명령이 돕니다 — 도구 승인보다 앞이라 안 걸립니다',
|
|
319
352
|
'level.beginner.name': '쉬움',
|
|
320
353
|
'level.beginner.hint': '권장값으로 바로 시작',
|
|
321
354
|
'level.developer.name': '개발자',
|
|
@@ -473,7 +506,10 @@ export const ko = {
|
|
|
473
506
|
*/
|
|
474
507
|
'ev.capped': '답이 {한계} 토큰에서 잘렸습니다 — /out 으로 상한을 올리거나, 파일은 나눠 쓰게 하세요',
|
|
475
508
|
'ev.cutoff': '서버가 끝났다는 말 없이 답을 멈췄습니다 — 중간에서 끊겼을 수 있습니다',
|
|
509
|
+
'ev.stalled': '{n}초 동안 아무것도 안 와서 끊었습니다 — 받은 데까지만 보여 드립니다',
|
|
476
510
|
'ev.nudgeMissed': '시킨 것 {n}가지를 빼놓고 끝내려고 해서 한 번 되밀었습니다',
|
|
511
|
+
'ev.hookNote': '훅: {말}',
|
|
512
|
+
'ev.hookBlock': '훅이 막았습니다',
|
|
477
513
|
'ev.nudgeRead': '읽기만 하고 끝내려고 해서 한 번 되밀었습니다',
|
|
478
514
|
'ev.learnedCtx': '서버가 알려 준 컨텍스트 한계 {한계} 로 맞추고 다시 부릅니다',
|
|
479
515
|
'ev.learnedOut': '서버가 알려 준 답 길이 한계 {한계} 로 맞추고 다시 부릅니다',
|
|
@@ -523,6 +559,7 @@ export const ko = {
|
|
|
523
559
|
'status.root': '작업 폴더',
|
|
524
560
|
'status.shell': '셸',
|
|
525
561
|
'status.keyStore': '열쇠 보관',
|
|
562
|
+
'status.clientCert': '내 인증서',
|
|
526
563
|
'status.rules': '규칙',
|
|
527
564
|
'status.noRules': '없음 (/init 으로 만들 수 있습니다)',
|
|
528
565
|
'status.rulesUnread': '{이름} 이 있는데 못 읽었습니다 ({까닭}) — 이 폴더 규칙이 안 걸려 있습니다',
|
package/src/i18n/zh.js
CHANGED
|
@@ -90,6 +90,8 @@ export const zh = {
|
|
|
90
90
|
'cmd.recall.desc': '搜索历史对话 — 按内容找,不是按标题',
|
|
91
91
|
'cmd.recall.arg': '<文字>',
|
|
92
92
|
'cmd.mcp.desc': '已连接的 MCP 工具服务',
|
|
93
|
+
'cmd.hooks.desc': '查看配置的钩子',
|
|
94
|
+
'cmd.agents.desc': '查看命名的子任务',
|
|
93
95
|
'cmd.memory.desc': '跨对话保留的东西 — 查看和清除',
|
|
94
96
|
'cmd.memory.arg': '[forget <n>|clear|<要记住的话>]',
|
|
95
97
|
'cmd.level.desc': '使用者级别 (新手/开发者)',
|
|
@@ -294,6 +296,37 @@ export const zh = {
|
|
|
294
296
|
'work.axis': '这与审批策略({명령})是两条不同的轴 — 做什么 / 问多少。',
|
|
295
297
|
'work.autoOn': '之后会按每句话自动切到合适的模式。',
|
|
296
298
|
'work.pinned': '你自己选的,不会自动改变。要交回自动请用',
|
|
299
|
+
'trust.ignored': '未读取此文件夹的项目配置 — 该文件夹尚未被信任',
|
|
300
|
+
'trust.ignoredWhy': '配置文件随仓库一同分发。直接读取他人的配置,意味着由对方决定连接地址和取密钥的命令。',
|
|
301
|
+
'trust.howTo': '要读取请运行',
|
|
302
|
+
'trust.filtered': '已移除项目配置不得设置的字段',
|
|
303
|
+
'trust.trusted': '正在读取此文件夹的项目配置',
|
|
304
|
+
'trust.already': '该文件夹已被信任',
|
|
305
|
+
'trust.removed': '已从信任列表中移除',
|
|
306
|
+
'trust.notListed': '该文件夹不在信任列表中',
|
|
307
|
+
'trust.parentStill': '仍因上级文件夹而被信任',
|
|
308
|
+
'stats.title': '这个文件夹里做过什么',
|
|
309
|
+
'stats.none': '还没有记录 — 在这个文件夹用过 deel 之后才会开始记。',
|
|
310
|
+
'stats.period': '时间段',
|
|
311
|
+
'stats.window': '最近 {n} 天',
|
|
312
|
+
'stats.activeDays': '{n} 天有使用',
|
|
313
|
+
'stats.turns': '对话',
|
|
314
|
+
'stats.sessions': '{n} 个会话',
|
|
315
|
+
'stats.tools': '工具',
|
|
316
|
+
'stats.failed': '失败 {n}',
|
|
317
|
+
'stats.blocked': '被拦下',
|
|
318
|
+
'stats.undos': '撤销',
|
|
319
|
+
'stats.masked': '已遮蔽的密钥',
|
|
320
|
+
'stats.broken': '有 {n} 行读不了 — 上面的数字少算了这些。',
|
|
321
|
+
'stats.older': '时间段之外还有 {n} 行(--all 看全部)。',
|
|
322
|
+
'stats.noCost': '这份记录里没有 token 和费用。本次对话的用量请看 /cost。',
|
|
323
|
+
'trust.listTitle': '已信任的文件夹',
|
|
324
|
+
'trust.listNone': '尚未信任任何文件夹。',
|
|
325
|
+
'trust.blockedTitle': '即使信任,项目配置也不得设置的字段',
|
|
326
|
+
'trust.saveFail': '无法写入信任列表',
|
|
327
|
+
'trust.why.allow': '该字段会放宽审批规则。收紧的 deny 仍会读取',
|
|
328
|
+
'trust.why.apiKey': '写在仓库里的密钥不是配置,而是泄露',
|
|
329
|
+
'trust.why.authCmd': '它在首次请求前执行 — 早于工具审批,因此无法被拦截',
|
|
297
330
|
'level.beginner.name': '新手',
|
|
298
331
|
'level.beginner.hint': '按推荐设置开始',
|
|
299
332
|
'level.developer.name': '开发者',
|
|
@@ -400,7 +433,10 @@ export const zh = {
|
|
|
400
433
|
'run.errKept': '出错前收到的内容已经留在对话里 — 可以直接接着说。',
|
|
401
434
|
'ev.capped': '回答在 {한계} 个 token 处被截断 — 用 /out 调高上限,或者让文件分次写',
|
|
402
435
|
'ev.cutoff': '服务端没说结束就停下了 — 回答可能是中途断的',
|
|
436
|
+
'ev.stalled': '{n} 秒没有任何数据,已断开 — 只显示已收到的部分',
|
|
403
437
|
'ev.nudgeMissed': '还剩你交代的 {n} 件事没做就要收尾,已经推回去一次',
|
|
438
|
+
'ev.hookNote': '钩子:{말}',
|
|
439
|
+
'ev.hookBlock': '钩子拦下了这一步',
|
|
404
440
|
'ev.nudgeRead': '只看不动就要收尾,已经推回去一次',
|
|
405
441
|
'ev.learnedCtx': '按服务端给出的上下文上限 {한계} 重新调用',
|
|
406
442
|
'ev.learnedOut': '按服务端给出的回答长度上限 {한계} 重新调用',
|
|
@@ -433,6 +469,7 @@ export const zh = {
|
|
|
433
469
|
'status.root': '工作目录',
|
|
434
470
|
'status.shell': 'Shell',
|
|
435
471
|
'status.keyStore': '密钥存放',
|
|
472
|
+
'status.clientCert': '客户端证书',
|
|
436
473
|
'status.rules': '规则',
|
|
437
474
|
'status.noRules': '无(可用 /init 建一个)',
|
|
438
475
|
'status.rulesUnread': '{이름} 存在但读不了({까닭})— 本文件夹的规则没有生效',
|