devez-vibe 1.9.33 → 1.9.35
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/LICENSE +21 -21
- package/README.md +73 -73
- package/bin/dvz.exe +0 -0
- package/bin/dvz.js +35 -35
- package/bridge/claude-agent-sdk-bridge.mjs +116 -54
- package/install-skills.mjs +11 -11
- package/package.json +1 -1
- package/prepare-search-deps.mjs +42 -42
- package/skills/insane-search/SKILL.md +36 -36
- package/skills/insane-search/engine/__main__.py +44 -44
- package/skills/insane-search/engine/executor.py +246 -246
- package/skills/insane-search/engine/fetch_chain.py +443 -443
- package/skills/insane-search/engine/network.py +108 -108
- package/skills/insane-search/engine/phase0.py +380 -380
- package/skills/insane-search/engine/session_input.py +120 -120
- package/skills/insane-search/engine/templates/invisible_playwright_fetch.py +57 -57
- package/skills/insane-search/engine/templates/nodriver_fetch.py +9 -9
- package/skills/insane-search/engine/templates/patchright_fetch.py +9 -9
- package/skills/insane-search/engine/templates/playwright_mobile_chrome.js +9 -9
- package/skills/insane-search/engine/templates/playwright_real_chrome.js +7 -7
- package/skills/insane-search/engine/templates/scrapling_fetch.py +46 -46
- package/skills/insane-search/engine/tests/test_smoke.py +8 -8
- package/skills/insane-search/engine/tests/test_t10_artifact_routes.py +126 -126
- package/skills/insane-search/engine/tests/test_t11_session_handoff.py +157 -157
- package/skills/insane-search/engine/tests/test_t5_pdfplumber.py +11 -11
- package/skills/insane-search/engine/tests/test_t7_browser_gate.py +3 -3
- package/skills/insane-search/engine/tests/test_t8_network_routes.py +207 -207
- package/skills/insane-search/engine/tests/test_t9_evidence_bundle.py +89 -89
- package/skills/insane-search/engine/tests/test_u5.py +9 -9
- package/skills/insane-search/engine/transport.py +94 -94
- package/skills/insane-search/engine/waf_profiles.yaml +38 -38
- package/skills/insane-search/references/cache-archive.md +13 -13
- package/skills/insane-search/references/media.md +14 -14
- package/skills/insane-search/references/playwright.md +27 -27
- package/skills/insane-search/references/research-evidence.md +36 -36
- package/skills/insane-search/references/session-handoff.md +26 -26
- package/skills/insane-search/tests/test_x_search.py +3 -3
- package/skills/luna-loop/agents/openai.yaml +7 -7
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Devez Vibe contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Devez Vibe contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
# Devez Vibe
|
|
2
|
-
|
|
3
|
-
공식 `codex app-server`와 Claude Agent SDK를 위한 차분한 터미널 클라이언트입니다.
|
|
4
|
-
각 공식 런타임이 인증·도구·스킬·프로젝트 지침을 담당하고, 이 프로젝트는 공통 화면과
|
|
5
|
-
입력 계층을 소유합니다.
|
|
6
|
-
|
|
7
|
-
## 설치
|
|
8
|
-
|
|
9
|
-
```powershell
|
|
10
|
-
npm install -g devez-vibe
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
설치하면 `dvz` 명령을 사용할 수 있습니다.
|
|
14
|
-
|
|
15
|
-
| 요건 | 값 |
|
|
16
|
-
| --- | --- |
|
|
17
|
-
| OS | Windows x64 |
|
|
18
|
-
| Node.js | 18 이상 (Claude Agent SDK 런타임 포함) |
|
|
19
|
-
| Codex 전제 | Codex CLI 설치 및 로그인 완료 |
|
|
20
|
-
| Claude 전제 | Claude Code 설치 후 `claude` 구독 로그인 완료 |
|
|
21
|
-
|
|
22
|
-
현재 Windows x64 빌드만 배포합니다. 다른 플랫폼에서는 `EBADPLATFORM`으로 설치가 거부됩니다.
|
|
23
|
-
|
|
24
|
-
## 사용
|
|
25
|
-
|
|
26
|
-
Codex CLI가 설치되고 로그인된 환경에서:
|
|
27
|
-
|
|
28
|
-
```powershell
|
|
29
|
-
dvz
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
개인 Claude 구독 로그인 재사용:
|
|
33
|
-
|
|
34
|
-
```powershell
|
|
35
|
-
dvz --model claude
|
|
36
|
-
dvz --model sonnet --effort high
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Claude SDK 자식 프로세스는 API 키 환경변수를 제거하고 설치된 Claude Code 로그인만 사용합니다.
|
|
40
|
-
본인 계정의 로컬 개인 사용만 대상으로 하며 로그인 공유나 제3자 인증은 제공하지 않습니다.
|
|
41
|
-
|
|
42
|
-
주요 옵션:
|
|
43
|
-
|
|
44
|
-
```text
|
|
45
|
-
dvz [--resume [SESSION] | --continue] [--model MODEL] [--effort EFFORT]
|
|
46
|
-
[--cwd PATH] [--codex PATH] [--claude PATH] [--theme THEME]
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
`--resume`만 입력하면 검색 가능한 세션 피커를 열고, `--continue`는 현재 폴더의 가장 최근
|
|
50
|
-
세션을 바로 이어갑니다. 실행 중 전체 명령은 `/help`에서 확인합니다.
|
|
51
|
-
|
|
52
|
-
설치 상태와 제공자 연결 준비 상태는 다음 명령으로 점검합니다.
|
|
53
|
-
|
|
54
|
-
```powershell
|
|
55
|
-
dvz doctor
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## 업데이트
|
|
59
|
-
|
|
60
|
-
새 버전이 배포되면 시작 시 안내 배너가 표시됩니다. 배너의 안내대로 실행하면 됩니다.
|
|
61
|
-
|
|
62
|
-
```powershell
|
|
63
|
-
dvz update
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
새 버전을 별도 경로에 내려받아 실행 파일을 검증한 뒤 다음 실행 버전으로 전환합니다.
|
|
67
|
-
실행 중인 `dvz` 세션은 이전 버전을 유지하고, 새로 시작하는 세션부터 새 버전을 사용합니다.
|
|
68
|
-
|
|
69
|
-
업데이트 확인을 끄려면 `DEVEZ_VIBE_NO_UPDATE_CHECK` 환경변수를 설정합니다.
|
|
70
|
-
|
|
71
|
-
## 라이선스
|
|
72
|
-
|
|
73
|
-
MIT. 자세한 내용은 [저장소](https://github.com/MrHoje/Devez-vibe)를 참고하세요.
|
|
1
|
+
# Devez Vibe
|
|
2
|
+
|
|
3
|
+
공식 `codex app-server`와 Claude Agent SDK를 위한 차분한 터미널 클라이언트입니다.
|
|
4
|
+
각 공식 런타임이 인증·도구·스킬·프로젝트 지침을 담당하고, 이 프로젝트는 공통 화면과
|
|
5
|
+
입력 계층을 소유합니다.
|
|
6
|
+
|
|
7
|
+
## 설치
|
|
8
|
+
|
|
9
|
+
```powershell
|
|
10
|
+
npm install -g devez-vibe
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
설치하면 `dvz` 명령을 사용할 수 있습니다.
|
|
14
|
+
|
|
15
|
+
| 요건 | 값 |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| OS | Windows x64 |
|
|
18
|
+
| Node.js | 18 이상 (Claude Agent SDK 런타임 포함) |
|
|
19
|
+
| Codex 전제 | Codex CLI 설치 및 로그인 완료 |
|
|
20
|
+
| Claude 전제 | Claude Code 설치 후 `claude` 구독 로그인 완료 |
|
|
21
|
+
|
|
22
|
+
현재 Windows x64 빌드만 배포합니다. 다른 플랫폼에서는 `EBADPLATFORM`으로 설치가 거부됩니다.
|
|
23
|
+
|
|
24
|
+
## 사용
|
|
25
|
+
|
|
26
|
+
Codex CLI가 설치되고 로그인된 환경에서:
|
|
27
|
+
|
|
28
|
+
```powershell
|
|
29
|
+
dvz
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
개인 Claude 구독 로그인 재사용:
|
|
33
|
+
|
|
34
|
+
```powershell
|
|
35
|
+
dvz --model claude
|
|
36
|
+
dvz --model sonnet --effort high
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Claude SDK 자식 프로세스는 API 키 환경변수를 제거하고 설치된 Claude Code 로그인만 사용합니다.
|
|
40
|
+
본인 계정의 로컬 개인 사용만 대상으로 하며 로그인 공유나 제3자 인증은 제공하지 않습니다.
|
|
41
|
+
|
|
42
|
+
주요 옵션:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
dvz [--resume [SESSION] | --continue] [--model MODEL] [--effort EFFORT]
|
|
46
|
+
[--cwd PATH] [--codex PATH] [--claude PATH] [--theme THEME]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`--resume`만 입력하면 검색 가능한 세션 피커를 열고, `--continue`는 현재 폴더의 가장 최근
|
|
50
|
+
세션을 바로 이어갑니다. 실행 중 전체 명령은 `/help`에서 확인합니다.
|
|
51
|
+
|
|
52
|
+
설치 상태와 제공자 연결 준비 상태는 다음 명령으로 점검합니다.
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
dvz doctor
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 업데이트
|
|
59
|
+
|
|
60
|
+
새 버전이 배포되면 시작 시 안내 배너가 표시됩니다. 배너의 안내대로 실행하면 됩니다.
|
|
61
|
+
|
|
62
|
+
```powershell
|
|
63
|
+
dvz update
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
새 버전을 별도 경로에 내려받아 실행 파일을 검증한 뒤 다음 실행 버전으로 전환합니다.
|
|
67
|
+
실행 중인 `dvz` 세션은 이전 버전을 유지하고, 새로 시작하는 세션부터 새 버전을 사용합니다.
|
|
68
|
+
|
|
69
|
+
업데이트 확인을 끄려면 `DEVEZ_VIBE_NO_UPDATE_CHECK` 환경변수를 설정합니다.
|
|
70
|
+
|
|
71
|
+
## 라이선스
|
|
72
|
+
|
|
73
|
+
MIT. 자세한 내용은 [저장소](https://github.com/MrHoje/Devez-vibe)를 참고하세요.
|
package/bin/dvz.exe
CHANGED
|
Binary file
|
package/bin/dvz.js
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
|
-
import { dirname, isAbsolute, join } from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
|
|
8
|
-
const packageExecutable = join(dirname(fileURLToPath(import.meta.url)), "dvz.exe");
|
|
9
|
-
const pointer = process.env.LOCALAPPDATA
|
|
10
|
-
? join(process.env.LOCALAPPDATA, "DevezVibe", "current-executable.txt")
|
|
11
|
-
: "";
|
|
12
|
-
|
|
13
|
-
let executable = packageExecutable;
|
|
14
|
-
try {
|
|
15
|
-
const candidate = pointer ? readFileSync(pointer, "utf8").trim() : "";
|
|
16
|
-
if (isAbsolute(candidate) && existsSync(candidate)) executable = candidate;
|
|
17
|
-
} catch {
|
|
18
|
-
// A missing or incomplete pointer falls back to the npm-packaged executable.
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const child = spawn(executable, process.argv.slice(2), { stdio: "inherit" });
|
|
22
|
-
child.on("error", (error) => {
|
|
23
|
-
console.error(`Devez Vibe 실행 실패: ${error.message}`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
});
|
|
26
|
-
child.on("exit", (code, signal) => {
|
|
27
|
-
if (signal) process.kill(process.pid, signal);
|
|
28
|
-
else process.exit(code ?? 1);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
32
|
-
process.on(signal, () => {
|
|
33
|
-
if (!child.killed) child.kill(signal);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const packageExecutable = join(dirname(fileURLToPath(import.meta.url)), "dvz.exe");
|
|
9
|
+
const pointer = process.env.LOCALAPPDATA
|
|
10
|
+
? join(process.env.LOCALAPPDATA, "DevezVibe", "current-executable.txt")
|
|
11
|
+
: "";
|
|
12
|
+
|
|
13
|
+
let executable = packageExecutable;
|
|
14
|
+
try {
|
|
15
|
+
const candidate = pointer ? readFileSync(pointer, "utf8").trim() : "";
|
|
16
|
+
if (isAbsolute(candidate) && existsSync(candidate)) executable = candidate;
|
|
17
|
+
} catch {
|
|
18
|
+
// A missing or incomplete pointer falls back to the npm-packaged executable.
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const child = spawn(executable, process.argv.slice(2), { stdio: "inherit" });
|
|
22
|
+
child.on("error", (error) => {
|
|
23
|
+
console.error(`Devez Vibe 실행 실패: ${error.message}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
child.on("exit", (code, signal) => {
|
|
27
|
+
if (signal) process.kill(process.pid, signal);
|
|
28
|
+
else process.exit(code ?? 1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
32
|
+
process.on(signal, () => {
|
|
33
|
+
if (!child.killed) child.kill(signal);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -330,10 +330,40 @@ function catalogEntry(model, defaultResolvedModel) {
|
|
|
330
330
|
};
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
function compareVersions(left, right) {
|
|
334
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
335
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
336
|
+
if (difference) return difference;
|
|
337
|
+
}
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Each family's newest model in CLAUDE_MODEL_ORDER first, then older ones by family and version. */
|
|
342
|
+
function sortClaudeModels(models) {
|
|
343
|
+
const ranked = models.map((model) => {
|
|
344
|
+
const [family = "", version = ""] = compactClaudeModelName(model).toLowerCase().split(" ");
|
|
345
|
+
return { model, family, version: version.match(/\d+/g)?.map(Number) ?? [] };
|
|
346
|
+
});
|
|
347
|
+
const newest = new Map();
|
|
348
|
+
for (const { family, version } of ranked) {
|
|
349
|
+
if (!newest.has(family) || compareVersions(version, newest.get(family)) > 0) newest.set(family, version);
|
|
350
|
+
}
|
|
351
|
+
const tier = (entry) => (compareVersions(entry.version, newest.get(entry.family)) === 0 ? 0 : 1);
|
|
352
|
+
const familyOrder = ({ family }) => {
|
|
353
|
+
const index = CLAUDE_MODEL_ORDER.indexOf(family);
|
|
354
|
+
return index < 0 ? CLAUDE_MODEL_ORDER.length : index;
|
|
355
|
+
};
|
|
356
|
+
return ranked
|
|
357
|
+
.sort((left, right) => tier(left) - tier(right)
|
|
358
|
+
|| familyOrder(left) - familyOrder(right)
|
|
359
|
+
|| compareVersions(right.version, left.version))
|
|
360
|
+
.map(({ model }) => model);
|
|
361
|
+
}
|
|
362
|
+
|
|
333
363
|
function claudeCatalogEntries(models, defaultResolvedModel) {
|
|
334
|
-
const catalogModels = models.filter((model) => model.value && model.value !== "default"
|
|
364
|
+
const catalogModels = sortClaudeModels(models.filter((model) => model.value && model.value !== "default"
|
|
335
365
|
&& ![model.value, model.resolvedModel]
|
|
336
|
-
.some((name) => stripClaudeModel(String(name || "")).startsWith(RETIRED_OPUS_MODEL)));
|
|
366
|
+
.some((name) => stripClaudeModel(String(name || "")).startsWith(RETIRED_OPUS_MODEL))));
|
|
337
367
|
const entries = catalogModels.map((model) => catalogEntry(model, defaultResolvedModel));
|
|
338
368
|
const existingIndex = entries.findIndex((entry) =>
|
|
339
369
|
stripClaudeModel(entry.model) === PREVIOUS_OPUS_MODEL
|
|
@@ -401,14 +431,6 @@ async function loadModelCatalog(params) {
|
|
|
401
431
|
const defaultResolvedModel = String(
|
|
402
432
|
models.find((model) => model.value === "default")?.resolvedModel || "",
|
|
403
433
|
);
|
|
404
|
-
models.sort((left, right) => {
|
|
405
|
-
const leftFamily = String(left.value || "").match(/(fable|opus|sonnet|haiku)/i)?.[1]?.toLowerCase();
|
|
406
|
-
const rightFamily = String(right.value || "").match(/(fable|opus|sonnet|haiku)/i)?.[1]?.toLowerCase();
|
|
407
|
-
const leftOrder = CLAUDE_MODEL_ORDER.indexOf(leftFamily);
|
|
408
|
-
const rightOrder = CLAUDE_MODEL_ORDER.indexOf(rightFamily);
|
|
409
|
-
return (leftOrder < 0 ? CLAUDE_MODEL_ORDER.length : leftOrder)
|
|
410
|
-
- (rightOrder < 0 ? CLAUDE_MODEL_ORDER.length : rightOrder);
|
|
411
|
-
});
|
|
412
434
|
return {
|
|
413
435
|
data: claudeCatalogEntries(models, defaultResolvedModel),
|
|
414
436
|
};
|
|
@@ -902,14 +924,14 @@ async function requestToolPermission(toolName, input, permission) {
|
|
|
902
924
|
// decide that a human answer is needed. Forward every such request to the host;
|
|
903
925
|
// auto-allowing the ordinary cases here would silently defeat default/auto mode.
|
|
904
926
|
const planApproval = toolName === "ExitPlanMode";
|
|
905
|
-
const persistentApprovalLabel = permission.suppressAlwaysAllowRule
|
|
906
|
-
? "" : permissionSuggestionLabel(permission.suggestions);
|
|
907
|
-
const common = {
|
|
908
|
-
claudePermission: true,
|
|
909
|
-
title: permission.title || permission.displayName,
|
|
910
|
-
persistentApprovalLabel,
|
|
911
|
-
defaultToNo: Boolean(permission.defaultToNo),
|
|
912
|
-
suppressAlwaysAllowRule: Boolean(permission.suppressAlwaysAllowRule),
|
|
927
|
+
const persistentApprovalLabel = permission.suppressAlwaysAllowRule
|
|
928
|
+
? "" : permissionSuggestionLabel(permission.suggestions);
|
|
929
|
+
const common = {
|
|
930
|
+
claudePermission: true,
|
|
931
|
+
title: permission.title || permission.displayName,
|
|
932
|
+
persistentApprovalLabel,
|
|
933
|
+
defaultToNo: Boolean(permission.defaultToNo),
|
|
934
|
+
suppressAlwaysAllowRule: Boolean(permission.suppressAlwaysAllowRule),
|
|
913
935
|
};
|
|
914
936
|
|
|
915
937
|
let method = "item/permissions/requestApproval";
|
|
@@ -947,10 +969,10 @@ async function requestToolPermission(toolName, input, permission) {
|
|
|
947
969
|
return {
|
|
948
970
|
behavior: "allow",
|
|
949
971
|
updatedInput: input,
|
|
950
|
-
...(!permission.suppressAlwaysAllowRule
|
|
951
|
-
&& (response?.decision === "acceptForSession" || response?.scope === "session")
|
|
952
|
-
? { updatedPermissions: permission.suggestions }
|
|
953
|
-
: {}),
|
|
972
|
+
...(!permission.suppressAlwaysAllowRule
|
|
973
|
+
&& (response?.decision === "acceptForSession" || response?.scope === "session")
|
|
974
|
+
? { updatedPermissions: permission.suggestions }
|
|
975
|
+
: {}),
|
|
954
976
|
};
|
|
955
977
|
}
|
|
956
978
|
|
|
@@ -2627,6 +2649,12 @@ function processUser(session, message) {
|
|
|
2627
2649
|
path: String(message.tool_use_result.path || pending.input?.file_path || ""),
|
|
2628
2650
|
});
|
|
2629
2651
|
}
|
|
2652
|
+
// Worktree tools move the session's folder; reuse Codex's settings notice so the branch badge follows.
|
|
2653
|
+
const worktreeCwd = pending.name === "EnterWorktree" ? message.tool_use_result?.worktreePath
|
|
2654
|
+
: pending.name === "ExitWorktree" ? message.tool_use_result?.originalCwd : null;
|
|
2655
|
+
if (!block.is_error && typeof worktreeCwd === "string") {
|
|
2656
|
+
notify("thread/settings/updated", { threadId: session.id, threadSettings: { cwd: worktreeCwd } });
|
|
2657
|
+
}
|
|
2630
2658
|
pending.toolUseId = block.tool_use_id;
|
|
2631
2659
|
if (pending.suppressed) {
|
|
2632
2660
|
updatePlanFromToolResult(session, pending, message);
|
|
@@ -3895,7 +3923,7 @@ async function dispatch(method, params = {}) {
|
|
|
3895
3923
|
throw new Error(`지원하지 않는 Claude 브리지 메서드: ${method}`);
|
|
3896
3924
|
}
|
|
3897
3925
|
|
|
3898
|
-
async function runPermissionModeSelfTest() {
|
|
3926
|
+
async function runPermissionModeSelfTest() {
|
|
3899
3927
|
const fakeSession = (setPermissionMode) => ({
|
|
3900
3928
|
id: "permission-mode-self-test",
|
|
3901
3929
|
permissionMode: BOOTSTRAP_PERMISSION_MODE,
|
|
@@ -3927,35 +3955,35 @@ async function runPermissionModeSelfTest() {
|
|
|
3927
3955
|
|| rejectedCalls.join(",") !== "auto" || rejected.permissionModeVerified) {
|
|
3928
3956
|
throw new Error(`Claude permission double rejection self-test failed: ${doubleRejection}`);
|
|
3929
3957
|
}
|
|
3930
|
-
}
|
|
3931
|
-
|
|
3932
|
-
async function runGuardedPermissionSelfTest() {
|
|
3933
|
-
const captured = [];
|
|
3934
|
-
const originalWrite = process.stdout.write;
|
|
3935
|
-
process.stdout.write = (chunk) => { captured.push(JSON.parse(String(chunk))); return true; };
|
|
3936
|
-
try {
|
|
3937
|
-
const pending = requestToolPermission("Bash", { command: "npm test" }, {
|
|
3938
|
-
defaultToNo: true,
|
|
3939
|
-
suppressAlwaysAllowRule: true,
|
|
3940
|
-
suggestions: [{ destination: "projectSettings", rules: [{ toolName: "Bash" }] }],
|
|
3941
|
-
});
|
|
3942
|
-
const request = captured[0];
|
|
3943
|
-
if (request.params.defaultToNo !== true
|
|
3944
|
-
|| request.params.suppressAlwaysAllowRule !== true
|
|
3945
|
-
|| request.params.persistentApprovalLabel) {
|
|
3946
|
-
throw new Error("Claude guarded permission request self-test failed");
|
|
3947
|
-
}
|
|
3948
|
-
const reply = pendingHostRequests.get(request.id);
|
|
3949
|
-
pendingHostRequests.delete(request.id);
|
|
3950
|
-
reply.resolve({ decision: "acceptForSession" });
|
|
3951
|
-
const result = await pending;
|
|
3952
|
-
if (result.behavior !== "allow" || "updatedPermissions" in result) {
|
|
3953
|
-
throw new Error("Claude guarded permission response self-test failed");
|
|
3954
|
-
}
|
|
3955
|
-
} finally {
|
|
3956
|
-
process.stdout.write = originalWrite;
|
|
3957
|
-
}
|
|
3958
|
-
}
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
async function runGuardedPermissionSelfTest() {
|
|
3961
|
+
const captured = [];
|
|
3962
|
+
const originalWrite = process.stdout.write;
|
|
3963
|
+
process.stdout.write = (chunk) => { captured.push(JSON.parse(String(chunk))); return true; };
|
|
3964
|
+
try {
|
|
3965
|
+
const pending = requestToolPermission("Bash", { command: "npm test" }, {
|
|
3966
|
+
defaultToNo: true,
|
|
3967
|
+
suppressAlwaysAllowRule: true,
|
|
3968
|
+
suggestions: [{ destination: "projectSettings", rules: [{ toolName: "Bash" }] }],
|
|
3969
|
+
});
|
|
3970
|
+
const request = captured[0];
|
|
3971
|
+
if (request.params.defaultToNo !== true
|
|
3972
|
+
|| request.params.suppressAlwaysAllowRule !== true
|
|
3973
|
+
|| request.params.persistentApprovalLabel) {
|
|
3974
|
+
throw new Error("Claude guarded permission request self-test failed");
|
|
3975
|
+
}
|
|
3976
|
+
const reply = pendingHostRequests.get(request.id);
|
|
3977
|
+
pendingHostRequests.delete(request.id);
|
|
3978
|
+
reply.resolve({ decision: "acceptForSession" });
|
|
3979
|
+
const result = await pending;
|
|
3980
|
+
if (result.behavior !== "allow" || "updatedPermissions" in result) {
|
|
3981
|
+
throw new Error("Claude guarded permission response self-test failed");
|
|
3982
|
+
}
|
|
3983
|
+
} finally {
|
|
3984
|
+
process.stdout.write = originalWrite;
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3959
3987
|
|
|
3960
3988
|
function runToolPolicySelfTest() {
|
|
3961
3989
|
const cwd = process.platform === "win32" ? "C:\\repo" : "/repo";
|
|
@@ -4051,8 +4079,8 @@ async function runSelfTest() {
|
|
|
4051
4079
|
if (startupGaps.length) {
|
|
4052
4080
|
throw new Error(`Claude 기동 실패 안내 누락: ${startupGaps.join(", ")}`);
|
|
4053
4081
|
}
|
|
4054
|
-
await runPermissionModeSelfTest();
|
|
4055
|
-
await runGuardedPermissionSelfTest();
|
|
4082
|
+
await runPermissionModeSelfTest();
|
|
4083
|
+
await runGuardedPermissionSelfTest();
|
|
4056
4084
|
runToolPolicySelfTest();
|
|
4057
4085
|
await runCommandTimeoutSelfTest();
|
|
4058
4086
|
// A turn that ends with no answer must reach the user: refusal as an error
|
|
@@ -4569,6 +4597,19 @@ async function runSelfTest() {
|
|
|
4569
4597
|
) !== "max") {
|
|
4570
4598
|
throw new Error(`Claude pinned model self-test failed: ${JSON.stringify(catalog)}`);
|
|
4571
4599
|
}
|
|
4600
|
+
const orderedNames = claudeCatalogEntries([
|
|
4601
|
+
{ value: "haiku", displayName: "Haiku 4.5" },
|
|
4602
|
+
{ value: "claude-sonnet-4-6", displayName: "Sonnet 4.6" },
|
|
4603
|
+
{ value: "fable", displayName: "Fable 5" },
|
|
4604
|
+
{ value: "opus", resolvedModel: "claude-opus-5-5", displayName: "Opus" },
|
|
4605
|
+
{ value: "sonnet", displayName: "Sonnet 5" },
|
|
4606
|
+
{ value: "claude-opus-6", displayName: "Opus 6" },
|
|
4607
|
+
{ value: "claude-fable-5-1", displayName: "Fable 5.1" },
|
|
4608
|
+
{ value: "nova", displayName: "Nova 1" },
|
|
4609
|
+
].map((model) => ({ resolvedModel: model.value, ...model })), "").filter((entry) => !entry.hidden).map((entry) => entry.displayName).join(",");
|
|
4610
|
+
if (orderedNames !== "Fable 5.1,Opus 6,Sonnet 5,Haiku 4.5,Nova 1,Fable 5,Opus 5.5,Sonnet 4.6") {
|
|
4611
|
+
throw new Error(`Claude model order self-test failed: ${orderedNames}`);
|
|
4612
|
+
}
|
|
4572
4613
|
const inheritedFork = forkSessionSelection(
|
|
4573
4614
|
{ model: "claude:sonnet", effort: "high" },
|
|
4574
4615
|
{ model: "claude:opus", effort: "max" },
|
|
@@ -4988,6 +5029,27 @@ async function runSelfTest() {
|
|
|
4988
5029
|
}
|
|
4989
5030
|
finishTurn(lifecycleSession, null, 1);
|
|
4990
5031
|
|
|
5032
|
+
beginTurn(lifecycleSession);
|
|
5033
|
+
const beforeWorktree = captured.length;
|
|
5034
|
+
for (const [id, name, result] of [
|
|
5035
|
+
["toolu_wt1", "EnterWorktree", { worktreePath: "D:\\repo\\.claude\\worktrees\\task", message: "" }],
|
|
5036
|
+
["toolu_wt2", "ExitWorktree", { action: "keep", originalCwd: "D:\\repo", worktreePath: "D:\\repo\\.claude\\worktrees\\task", message: "" }],
|
|
5037
|
+
]) {
|
|
5038
|
+
processToolUse(lifecycleSession, { type: "tool_use", id, name, input: {} });
|
|
5039
|
+
processUser(lifecycleSession, {
|
|
5040
|
+
message: { content: [{ type: "tool_result", tool_use_id: id, content: "" }] },
|
|
5041
|
+
tool_use_result: result,
|
|
5042
|
+
});
|
|
5043
|
+
}
|
|
5044
|
+
const worktreeCwds = captured.slice(beforeWorktree).join("").trim().split("\n")
|
|
5045
|
+
.map((line) => JSON.parse(line))
|
|
5046
|
+
.filter((line) => line.method === "thread/settings/updated")
|
|
5047
|
+
.map((line) => line.params.threadSettings.cwd);
|
|
5048
|
+
if (JSON.stringify(worktreeCwds) !== JSON.stringify(["D:\\repo\\.claude\\worktrees\\task", "D:\\repo"])) {
|
|
5049
|
+
throw new Error(`Claude worktree folder self-test failed: ${JSON.stringify(worktreeCwds)}`);
|
|
5050
|
+
}
|
|
5051
|
+
finishTurn(lifecycleSession, null, 1);
|
|
5052
|
+
|
|
4991
5053
|
const structuredSession = {
|
|
4992
5054
|
id: "structured-self-test",
|
|
4993
5055
|
turn: null,
|
package/install-skills.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
copyFileSync,
|
|
3
3
|
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
rmSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
} from "node:fs";
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
|
|
13
13
|
const packageRoot = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const skillNames = ["luna-loop", "insane-search"];
|
|
15
|
-
const bundledMarker = ".devez-vibe-bundled-skill";
|
|
14
|
+
const skillNames = ["luna-loop", "insane-search"];
|
|
15
|
+
const bundledMarker = ".devez-vibe-bundled-skill";
|
|
16
16
|
const userHome = homedir();
|
|
17
17
|
const codexHome = process.env.CODEX_HOME?.trim() || join(userHome, ".codex");
|
|
18
18
|
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(userHome, ".claude");
|
|
@@ -61,10 +61,10 @@ for (const skill of skillNames) {
|
|
|
61
61
|
for (const home of homes) {
|
|
62
62
|
const target = join(home.root, skill);
|
|
63
63
|
try {
|
|
64
|
-
copyTree(sourceRoot, target);
|
|
65
|
-
pruneTree(sourceRoot, target);
|
|
66
|
-
writeFileSync(join(target, bundledMarker), `${skill}\n`, "utf8");
|
|
67
|
-
console.log(`스킬 설치 완료 (${home.name}/${skill}): ${target}`);
|
|
64
|
+
copyTree(sourceRoot, target);
|
|
65
|
+
pruneTree(sourceRoot, target);
|
|
66
|
+
writeFileSync(join(target, bundledMarker), `${skill}\n`, "utf8");
|
|
67
|
+
console.log(`스킬 설치 완료 (${home.name}/${skill}): ${target}`);
|
|
68
68
|
} catch (error) {
|
|
69
69
|
failed = true;
|
|
70
70
|
console.error(`스킬 설치 실패 (${home.name}/${skill}): ${error instanceof Error ? error.message : error}`);
|
package/package.json
CHANGED
package/prepare-search-deps.mjs
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
// insane-search 스킬이 첫 호출에서 받는 파이썬 패키지를 설치 시점에 미리 깔아 둔다.
|
|
2
|
-
// 준비는 어디까지나 선택적이다. 파이썬이 없거나 설치가 실패해도 안내만 남기고 정상 종료해서
|
|
3
|
-
// devezVibe 설치 자체가 깨지지 않게 한다. 스킬은 첫 사용 때 다시 설치를 시도하므로 손해가 없다.
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
|
|
6
|
-
// SKILL.md의 의존성 가드와 같은 조합이다. 한쪽만 바뀌면 첫 호출에서 다시 설치가 돈다.
|
|
7
|
-
const guard = 'import curl_cffi,bs4,yaml,pypdf,markdownify; v=curl_cffi.__version__.split("."); assert (int(v[0]),int(v[1]))>=(0,15)';
|
|
8
|
-
const packages = ["curl_cffi>=0.15.0", "beautifulsoup4", "pyyaml", "pypdf", "markdownify"];
|
|
9
|
-
|
|
10
|
-
function run(command, args) {
|
|
11
|
-
return spawnSync(command, args, { encoding: "utf8", windowsHide: true, timeout: 300000 });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Windows에는 python3가 없는 대신 실행하면 스토어를 여는 별칭이 잡혀 있을 수 있다.
|
|
15
|
-
// 버전 출력을 실제로 확인해서 쓸 수 있는 실행기만 고른다.
|
|
16
|
-
function findPython() {
|
|
17
|
-
for (const candidate of ["python3", "python", "py"]) {
|
|
18
|
-
const probe = run(candidate, ["-c", "import sys; print(sys.version_info[0])"]);
|
|
19
|
-
if (probe.status === 0 && probe.stdout.trim() === "3") return candidate;
|
|
20
|
-
}
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const python = findPython();
|
|
25
|
-
if (!python) {
|
|
26
|
-
console.log("insane-search 준비 건너뜀: 파이썬 3을 찾지 못했습니다. 설치하면 첫 사용 때 자동으로 준비됩니다.");
|
|
27
|
-
process.exit(0);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (run(python, ["-c", guard]).status === 0) {
|
|
31
|
-
console.log("insane-search 의존성 준비 완료: 이미 설치되어 있습니다.");
|
|
32
|
-
process.exit(0);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// --user는 관리자 권한 없이도 설치되게 하고, 가상환경에서는 pip가 알아서 무시한다.
|
|
36
|
-
const install = run(python, ["-m", "pip", "install", "-U", "--user", "-q", ...packages]);
|
|
37
|
-
if (install.status === 0 && run(python, ["-c", guard]).status === 0) {
|
|
38
|
-
console.log("insane-search 의존성 준비 완료");
|
|
39
|
-
} else {
|
|
40
|
-
const detail = (install.stderr || install.stdout || "").trim().split("\n").pop() || "원인 미상";
|
|
41
|
-
console.log(`insane-search 준비 건너뜀 (${detail}). 첫 사용 때 다시 시도합니다.`);
|
|
42
|
-
}
|
|
1
|
+
// insane-search 스킬이 첫 호출에서 받는 파이썬 패키지를 설치 시점에 미리 깔아 둔다.
|
|
2
|
+
// 준비는 어디까지나 선택적이다. 파이썬이 없거나 설치가 실패해도 안내만 남기고 정상 종료해서
|
|
3
|
+
// devezVibe 설치 자체가 깨지지 않게 한다. 스킬은 첫 사용 때 다시 설치를 시도하므로 손해가 없다.
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
// SKILL.md의 의존성 가드와 같은 조합이다. 한쪽만 바뀌면 첫 호출에서 다시 설치가 돈다.
|
|
7
|
+
const guard = 'import curl_cffi,bs4,yaml,pypdf,markdownify; v=curl_cffi.__version__.split("."); assert (int(v[0]),int(v[1]))>=(0,15)';
|
|
8
|
+
const packages = ["curl_cffi>=0.15.0", "beautifulsoup4", "pyyaml", "pypdf", "markdownify"];
|
|
9
|
+
|
|
10
|
+
function run(command, args) {
|
|
11
|
+
return spawnSync(command, args, { encoding: "utf8", windowsHide: true, timeout: 300000 });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Windows에는 python3가 없는 대신 실행하면 스토어를 여는 별칭이 잡혀 있을 수 있다.
|
|
15
|
+
// 버전 출력을 실제로 확인해서 쓸 수 있는 실행기만 고른다.
|
|
16
|
+
function findPython() {
|
|
17
|
+
for (const candidate of ["python3", "python", "py"]) {
|
|
18
|
+
const probe = run(candidate, ["-c", "import sys; print(sys.version_info[0])"]);
|
|
19
|
+
if (probe.status === 0 && probe.stdout.trim() === "3") return candidate;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const python = findPython();
|
|
25
|
+
if (!python) {
|
|
26
|
+
console.log("insane-search 준비 건너뜀: 파이썬 3을 찾지 못했습니다. 설치하면 첫 사용 때 자동으로 준비됩니다.");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (run(python, ["-c", guard]).status === 0) {
|
|
31
|
+
console.log("insane-search 의존성 준비 완료: 이미 설치되어 있습니다.");
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --user는 관리자 권한 없이도 설치되게 하고, 가상환경에서는 pip가 알아서 무시한다.
|
|
36
|
+
const install = run(python, ["-m", "pip", "install", "-U", "--user", "-q", ...packages]);
|
|
37
|
+
if (install.status === 0 && run(python, ["-c", guard]).status === 0) {
|
|
38
|
+
console.log("insane-search 의존성 준비 완료");
|
|
39
|
+
} else {
|
|
40
|
+
const detail = (install.stderr || install.stdout || "").trim().split("\n").pop() || "원인 미상";
|
|
41
|
+
console.log(`insane-search 준비 건너뜀 (${detail}). 첫 사용 때 다시 시도합니다.`);
|
|
42
|
+
}
|