@norman-else/dsh-claude 0.1.9 → 0.1.10
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/lib/bin.mjs +1 -1
- package/lib/client.d.ts +6 -0
- package/lib/client.js +107 -0
- package/lib/client.js.map +1 -1
- package/lib/{events-BVQkPmjg.mjs → events-6xTApIQw.mjs} +3 -2
- package/lib/events-6xTApIQw.mjs.map +1 -0
- package/lib/index.mjs +203 -7
- package/lib/index.mjs.map +1 -1
- package/lib/{presenters-CQ9dBVyY.mjs → presenters-C8Yx_rNY.mjs} +2 -2
- package/lib/{presenters-CQ9dBVyY.mjs.map → presenters-C8Yx_rNY.mjs.map} +1 -1
- package/lib/{preset-installer-DUmmDvBm.mjs → preset-installer-B0NrA4Hi.mjs} +2 -2
- package/lib/{preset-installer-DUmmDvBm.mjs.map → preset-installer-B0NrA4Hi.mjs.map} +1 -1
- package/lib/preset-route.mjs +2 -2
- package/package.json +1 -1
- package/lib/events-BVQkPmjg.mjs.map +0 -1
package/lib/index.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-
|
|
3
|
-
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-
|
|
1
|
+
import { _ as CLAUDE_PROJECTION_PATH, a as latestClaudeTasks, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_GLOBAL_SETTINGS_PATH, h as CLAUDE_DOCTOR_PATH, i as latestClaudeSessionBinding, l as redactText, m as CLAUDE_CODE_PROVIDER_IDS, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PROVIDER, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_UPDATE_CHECK_PATH, x as TASK_TOOL_NAMES, y as CLAUDE_UPDATE_PATH } from "./events-6xTApIQw.mjs";
|
|
2
|
+
import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-C8Yx_rNY.mjs";
|
|
3
|
+
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-B0NrA4Hi.mjs";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
import { CallId, LlmAdapter, ReasoningEffortId, createToolResultMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { chmod, mkdir, opendir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
8
|
-
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { dirname, extname, join, resolve } from "node:path";
|
|
9
9
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
10
|
+
import { homedir } from "node:os";
|
|
10
11
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
11
12
|
import { EventEmitter } from "node:events";
|
|
12
13
|
import { DSH_ENV_PREFIX, SENSITIVE_ENV_PATTERN } from "@deepseek-ai/dsh-subprocess";
|
|
@@ -2457,6 +2458,11 @@ async function checkPluginUpdate(deps = {}) {
|
|
|
2457
2458
|
};
|
|
2458
2459
|
}
|
|
2459
2460
|
}
|
|
2461
|
+
async function verifyInstalledVersion(installation, expectedVersion) {
|
|
2462
|
+
if (dependencySpec(await readManifest(join(installation.profileDir, "package.json"))) !== expectedVersion) throw new Error("DSH plugin update completed without updating the profile dependency");
|
|
2463
|
+
const installedManifest = await readManifest(join(installation.profileDir, "node_modules", ...PLUGIN_PACKAGE_NAME.split("/"), "package.json"));
|
|
2464
|
+
if (installedManifest.name !== "@norman-else/dsh-claude" || installedManifest.version !== expectedVersion) throw new Error("DSH plugin update completed without installing the requested version");
|
|
2465
|
+
}
|
|
2460
2466
|
async function updatePlugin(deps = {}) {
|
|
2461
2467
|
const { version, installation } = await packageContext(deps);
|
|
2462
2468
|
if (installation === void 0 || installation.source !== "registry") throw new Error("Plugin update is unavailable for this installation");
|
|
@@ -2479,8 +2485,8 @@ async function updatePlugin(deps = {}) {
|
|
|
2479
2485
|
"plugin",
|
|
2480
2486
|
"--profile",
|
|
2481
2487
|
installation.profile,
|
|
2482
|
-
"
|
|
2483
|
-
PLUGIN_PACKAGE_NAME
|
|
2488
|
+
"add",
|
|
2489
|
+
`${PLUGIN_PACKAGE_NAME}@${latest}`
|
|
2484
2490
|
],
|
|
2485
2491
|
cwd: installation.profileDir,
|
|
2486
2492
|
env: {},
|
|
@@ -2497,8 +2503,9 @@ async function updatePlugin(deps = {}) {
|
|
|
2497
2503
|
const detail = handle.collected.stderr?.readFrom(0).text ?? "";
|
|
2498
2504
|
throw new Error(`DSH plugin update failed (${outcome.exitCode ?? outcome.signal ?? "unknown exit"}): ${safeMessage(detail)}`);
|
|
2499
2505
|
}
|
|
2506
|
+
await verifyInstalledVersion(installation, latest);
|
|
2500
2507
|
return {
|
|
2501
|
-
currentVersion:
|
|
2508
|
+
currentVersion: latest,
|
|
2502
2509
|
latestVersion: latest,
|
|
2503
2510
|
source: "registry",
|
|
2504
2511
|
state: "current",
|
|
@@ -2536,6 +2543,194 @@ function registerClaudeUpdateRoutes(ctx, runtime, deps = {}) {
|
|
|
2536
2543
|
}), `dsh-claude: ${route.method} ${route.path}`);
|
|
2537
2544
|
}
|
|
2538
2545
|
//#endregion
|
|
2546
|
+
//#region src/global-settings.ts
|
|
2547
|
+
const MAX_SETTINGS_BYTES = 262144;
|
|
2548
|
+
const MAX_REQUEST_BYTES = 8192;
|
|
2549
|
+
const MAX_STYLE_BYTES = 65536;
|
|
2550
|
+
const MAX_STYLE_FILES = 256;
|
|
2551
|
+
const BUILTIN_OUTPUT_STYLES = [
|
|
2552
|
+
"Default",
|
|
2553
|
+
"Proactive",
|
|
2554
|
+
"Concise",
|
|
2555
|
+
"Explanatory",
|
|
2556
|
+
"Learning"
|
|
2557
|
+
];
|
|
2558
|
+
const STYLE_NAME = /^[\p{L}\p{N}][\p{L}\p{N} ._()\[\]-]{0,127}$/u;
|
|
2559
|
+
function pathsFor(deps) {
|
|
2560
|
+
const root = join(homedir(), ".claude");
|
|
2561
|
+
return {
|
|
2562
|
+
settingsFile: deps.paths?.settingsFile ?? join(root, "settings.json"),
|
|
2563
|
+
outputStylesDir: deps.paths?.outputStylesDir ?? join(root, "output-styles")
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
function object(value) {
|
|
2567
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2568
|
+
}
|
|
2569
|
+
async function readDocument(path) {
|
|
2570
|
+
try {
|
|
2571
|
+
const text = await readFile(path, "utf8");
|
|
2572
|
+
if (Buffer.byteLength(text) > MAX_SETTINGS_BYTES) throw new Error("Claude Code settings file is too large");
|
|
2573
|
+
const parsed = object(JSON.parse(text));
|
|
2574
|
+
if (parsed === void 0) throw new Error("Claude Code settings file must contain a JSON object");
|
|
2575
|
+
return parsed;
|
|
2576
|
+
} catch (error) {
|
|
2577
|
+
if (error.code === "ENOENT") return {};
|
|
2578
|
+
throw error;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
function frontmatterName(text) {
|
|
2582
|
+
if (!text.startsWith("---\n") && !text.startsWith("---\r\n")) return void 0;
|
|
2583
|
+
const normalized = text.replaceAll("\r\n", "\n");
|
|
2584
|
+
const end = normalized.indexOf("\n---\n", 4);
|
|
2585
|
+
if (end < 0) return void 0;
|
|
2586
|
+
for (const line of normalized.slice(4, end).split("\n")) {
|
|
2587
|
+
const match = /^name:\s*(.+?)\s*$/.exec(line);
|
|
2588
|
+
if (match?.[1] === void 0) continue;
|
|
2589
|
+
const raw = match[1];
|
|
2590
|
+
const value = raw.startsWith("\"") && raw.endsWith("\"") || raw.startsWith("'") && raw.endsWith("'") ? raw.slice(1, -1) : raw;
|
|
2591
|
+
return STYLE_NAME.test(value) ? value : void 0;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
async function userOutputStyleOptions(directory) {
|
|
2595
|
+
const options = [];
|
|
2596
|
+
let entries;
|
|
2597
|
+
try {
|
|
2598
|
+
entries = await opendir(directory);
|
|
2599
|
+
} catch (error) {
|
|
2600
|
+
if (error.code === "ENOENT") return options;
|
|
2601
|
+
throw error;
|
|
2602
|
+
}
|
|
2603
|
+
for await (const entry of entries) {
|
|
2604
|
+
if (options.length >= MAX_STYLE_FILES) break;
|
|
2605
|
+
if (!entry.isFile() || extname(entry.name).toLowerCase() !== ".md") continue;
|
|
2606
|
+
try {
|
|
2607
|
+
const text = await readFile(join(directory, entry.name), "utf8");
|
|
2608
|
+
if (Buffer.byteLength(text) > MAX_STYLE_BYTES) continue;
|
|
2609
|
+
const name = frontmatterName(text) ?? entry.name.slice(0, -3);
|
|
2610
|
+
if (STYLE_NAME.test(name)) options.push({
|
|
2611
|
+
value: name,
|
|
2612
|
+
label: name,
|
|
2613
|
+
source: "user"
|
|
2614
|
+
});
|
|
2615
|
+
} catch {}
|
|
2616
|
+
}
|
|
2617
|
+
return options;
|
|
2618
|
+
}
|
|
2619
|
+
const DESCRIPTORS = [{
|
|
2620
|
+
key: "outputStyle",
|
|
2621
|
+
kind: "select",
|
|
2622
|
+
effect: "new-session",
|
|
2623
|
+
async options(paths) {
|
|
2624
|
+
const builtIn = BUILTIN_OUTPUT_STYLES.map((value) => ({
|
|
2625
|
+
value,
|
|
2626
|
+
label: value,
|
|
2627
|
+
source: "built-in"
|
|
2628
|
+
}));
|
|
2629
|
+
const user = await userOutputStyleOptions(paths.outputStylesDir);
|
|
2630
|
+
const seen = new Set(builtIn.map((option) => option.value));
|
|
2631
|
+
return [...builtIn, ...user.filter((option) => !seen.has(option.value)).sort((a, b) => a.label.localeCompare(b.label))];
|
|
2632
|
+
},
|
|
2633
|
+
read(document, options) {
|
|
2634
|
+
const value = document.outputStyle;
|
|
2635
|
+
return typeof value === "string" && STYLE_NAME.test(value) ? value : "Default";
|
|
2636
|
+
},
|
|
2637
|
+
apply(document, value, options) {
|
|
2638
|
+
if (typeof value !== "string" || !options.some((option) => option.value === value)) throw new Error("Invalid value for global setting outputStyle");
|
|
2639
|
+
if (value === "Default") delete document.outputStyle;
|
|
2640
|
+
else document.outputStyle = value;
|
|
2641
|
+
}
|
|
2642
|
+
}];
|
|
2643
|
+
const DESCRIPTOR_BY_KEY = new Map(DESCRIPTORS.map((descriptor) => [descriptor.key, descriptor]));
|
|
2644
|
+
let pendingWrite = Promise.resolve();
|
|
2645
|
+
async function views(document, paths) {
|
|
2646
|
+
return { settings: await Promise.all(DESCRIPTORS.map(async (descriptor) => {
|
|
2647
|
+
const discovered = await descriptor.options(paths);
|
|
2648
|
+
const value = descriptor.read(document, discovered);
|
|
2649
|
+
const options = discovered.some((option) => option.value === value) ? discovered : [...discovered, {
|
|
2650
|
+
value,
|
|
2651
|
+
label: value,
|
|
2652
|
+
source: "configured"
|
|
2653
|
+
}];
|
|
2654
|
+
return {
|
|
2655
|
+
key: descriptor.key,
|
|
2656
|
+
kind: descriptor.kind,
|
|
2657
|
+
value,
|
|
2658
|
+
options,
|
|
2659
|
+
effect: descriptor.effect
|
|
2660
|
+
};
|
|
2661
|
+
})) };
|
|
2662
|
+
}
|
|
2663
|
+
async function readGlobalSettings(deps = {}) {
|
|
2664
|
+
const paths = pathsFor(deps);
|
|
2665
|
+
return views(await readDocument(paths.settingsFile), paths);
|
|
2666
|
+
}
|
|
2667
|
+
async function atomicWrite(path, document) {
|
|
2668
|
+
await mkdir(dirname(path), {
|
|
2669
|
+
recursive: true,
|
|
2670
|
+
mode: 448
|
|
2671
|
+
});
|
|
2672
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
2673
|
+
try {
|
|
2674
|
+
await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, {
|
|
2675
|
+
encoding: "utf8",
|
|
2676
|
+
mode: 384,
|
|
2677
|
+
flag: "wx"
|
|
2678
|
+
});
|
|
2679
|
+
await chmod(temporary, 384);
|
|
2680
|
+
await rename(temporary, path);
|
|
2681
|
+
await chmod(path, 384);
|
|
2682
|
+
} finally {
|
|
2683
|
+
await rm(temporary, { force: true }).catch(() => void 0);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
function updateGlobalSettings(changes, deps = {}) {
|
|
2687
|
+
const changeObject = object(changes);
|
|
2688
|
+
if (changeObject === void 0 || Object.keys(changeObject).length === 0) return Promise.reject(/* @__PURE__ */ new Error("Global settings changes must be a non-empty object"));
|
|
2689
|
+
for (const key of Object.keys(changeObject)) if (!DESCRIPTOR_BY_KEY.has(key)) return Promise.reject(/* @__PURE__ */ new Error(`Unsupported global setting: ${key}`));
|
|
2690
|
+
const paths = pathsFor(deps);
|
|
2691
|
+
const operation = pendingWrite.catch(() => void 0).then(async () => {
|
|
2692
|
+
const document = await readDocument(paths.settingsFile);
|
|
2693
|
+
for (const [key, value] of Object.entries(changeObject)) {
|
|
2694
|
+
const descriptor = DESCRIPTOR_BY_KEY.get(key);
|
|
2695
|
+
descriptor.apply(document, value, await descriptor.options(paths));
|
|
2696
|
+
}
|
|
2697
|
+
await atomicWrite(paths.settingsFile, document);
|
|
2698
|
+
return views(document, paths);
|
|
2699
|
+
});
|
|
2700
|
+
pendingWrite = operation;
|
|
2701
|
+
return operation;
|
|
2702
|
+
}
|
|
2703
|
+
async function requestJson(req) {
|
|
2704
|
+
const declared = Number(req.headers["content-length"] ?? 0);
|
|
2705
|
+
if (!Number.isFinite(declared) || declared < 0 || declared > MAX_REQUEST_BYTES) throw new Error("Request body is too large");
|
|
2706
|
+
const chunks = [];
|
|
2707
|
+
let bytes = 0;
|
|
2708
|
+
for await (const chunk of req) {
|
|
2709
|
+
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2710
|
+
bytes += data.byteLength;
|
|
2711
|
+
if (bytes > MAX_REQUEST_BYTES) throw new Error("Request body is too large");
|
|
2712
|
+
chunks.push(data);
|
|
2713
|
+
}
|
|
2714
|
+
const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
2715
|
+
if (body === void 0 || Object.keys(body).some((key) => key !== "changes")) throw new Error("Invalid global settings request");
|
|
2716
|
+
return body.changes;
|
|
2717
|
+
}
|
|
2718
|
+
function registerClaudeGlobalSettingsRoute(ctx, deps = {}) {
|
|
2719
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2720
|
+
kind: "exact",
|
|
2721
|
+
path: CLAUDE_GLOBAL_SETTINGS_PATH,
|
|
2722
|
+
handler: async (req, res) => {
|
|
2723
|
+
if (req.method !== "GET" && req.method !== "PATCH") return json(res, 405, { error: "method not allowed" });
|
|
2724
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
2725
|
+
try {
|
|
2726
|
+
json(res, 200, req.method === "GET" ? await readGlobalSettings(deps) : await updateGlobalSettings(await requestJson(req), deps));
|
|
2727
|
+
} catch (error) {
|
|
2728
|
+
json(res, 400, { error: error instanceof Error ? error.message : "Invalid global settings request" });
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
}), "dsh-claude: global settings");
|
|
2732
|
+
}
|
|
2733
|
+
//#endregion
|
|
2539
2734
|
//#region src/index.ts
|
|
2540
2735
|
const name = "llm-claude";
|
|
2541
2736
|
const inject = [
|
|
@@ -2749,6 +2944,7 @@ async function apply(ctx, config) {
|
|
|
2749
2944
|
ctx.inject(["webServer"], (webCtx) => {
|
|
2750
2945
|
registerClaudeDoctorRoutes(webCtx, webCtx.subprocess, supervisor, supervisorConfig, resolutionError);
|
|
2751
2946
|
registerClaudeUpdateRoutes(webCtx, webCtx.subprocess);
|
|
2947
|
+
registerClaudeGlobalSettingsRoute(webCtx);
|
|
2752
2948
|
registerClaudeProjectionRoute(webCtx, sidecar, (sessionId) => {
|
|
2753
2949
|
const agent = webCtx.agents.get(sessionId);
|
|
2754
2950
|
return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";
|