@norman-else/dsh-claude 0.1.7 → 0.1.8

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/index.mjs CHANGED
@@ -1,15 +1,16 @@
1
- import { a as latestClaudeTasks, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_PROJECTION_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 TASK_TOOL_NAMES } from "./events-DP0Ov_Re.mjs";
2
- import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-YMR2WMjn.mjs";
3
- import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-BKw-SdOm.mjs";
1
+ import { _ as CLAUDE_UPDATE_CHECK_PATH, a as latestClaudeTasks, b as TASK_TOOL_NAMES, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_PROJECTION_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_PATH } from "./events-BVQkPmjg.mjs";
2
+ import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-CQ9dBVyY.mjs";
3
+ import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-DUmmDvBm.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
- import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
8
- import { join } from "node:path";
9
- import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
7
+ import { chmod, mkdir, opendir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
8
+ import { dirname, join, resolve } from "node:path";
9
+ import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
10
10
  import { query } from "@anthropic-ai/claude-agent-sdk";
11
11
  import { EventEmitter } from "node:events";
12
12
  import { DSH_ENV_PREFIX, SENSITIVE_ENV_PATTERN } from "@deepseek-ai/dsh-subprocess";
13
+ import { fileURLToPath } from "node:url";
13
14
  //#region src/sidecar.ts
14
15
  const SIDECAR_SCHEMA_VERSION = 1;
15
16
  const MAX_ACTIVITIES = 1e4;
@@ -2064,7 +2065,7 @@ function json(res, status, value) {
2064
2065
  //#region src/doctor-routes.ts
2065
2066
  const CLAUDE_DOCTOR_PROBE_TIMEOUT_MS = 15e3;
2066
2067
  const claudeBridgeDiagnostics = /* @__PURE__ */ new WeakMap();
2067
- function safeMessage(error) {
2068
+ function safeMessage$1(error) {
2068
2069
  return redactText(error instanceof Error ? error.message : String(error), 1e3);
2069
2070
  }
2070
2071
  /** Live command-bridge diagnostics: which agents exist, their presets, and how
@@ -2080,14 +2081,14 @@ function commandDiagnostics(ctx) {
2080
2081
  const preset = ctx.agentPresets.composedPreset(agent.ctx);
2081
2082
  if (preset !== void 0) info.preset = preset;
2082
2083
  } catch (error) {
2083
- info.error = safeMessage(error);
2084
+ info.error = safeMessage$1(error);
2084
2085
  }
2085
2086
  try {
2086
2087
  const list = ctx.commands.list(agent);
2087
2088
  info.commandCount = list.length;
2088
2089
  info.sample = list.slice(0, 10).map((command) => command.name);
2089
2090
  } catch (error) {
2090
- info.error = info.error === void 0 ? safeMessage(error) : `${info.error}; ${safeMessage(error)}`;
2091
+ info.error = info.error === void 0 ? safeMessage$1(error) : `${info.error}; ${safeMessage$1(error)}`;
2091
2092
  }
2092
2093
  const bridge = claudeBridgeDiagnostics.get(agent);
2093
2094
  if (bridge !== void 0) info.bridge = bridge;
@@ -2095,7 +2096,7 @@ function commandDiagnostics(ctx) {
2095
2096
  })
2096
2097
  };
2097
2098
  } catch (error) {
2098
- return { error: safeMessage(error) };
2099
+ return { error: safeMessage$1(error) };
2099
2100
  }
2100
2101
  }
2101
2102
  function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolutionError) {
@@ -2119,7 +2120,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
2119
2120
  version: { status: "not-run" },
2120
2121
  authentication: { status: "not-run" },
2121
2122
  handshake: "not-run",
2122
- message: safeMessage(resolutionError),
2123
+ message: safeMessage$1(resolutionError),
2123
2124
  limits: {
2124
2125
  idleTimeoutMs: config.idleTimeoutMs,
2125
2126
  maxProcesses: config.maxProcesses
@@ -2149,7 +2150,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
2149
2150
  commandBridge: commandDiagnostics(ctx)
2150
2151
  });
2151
2152
  } catch (error) {
2152
- json(res, 500, { error: safeMessage(error) });
2153
+ json(res, 500, { error: safeMessage$1(error) });
2153
2154
  }
2154
2155
  }
2155
2156
  }), "dsh-claude: Doctor route");
@@ -2198,6 +2199,259 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession) {
2198
2199
  }), "dsh-claude: sidecar projection route");
2199
2200
  }
2200
2201
  //#endregion
2202
+ //#region src/update-routes.ts
2203
+ const PLUGIN_PACKAGE_NAME = "@norman-else/dsh-claude";
2204
+ const UPDATE_TIMEOUT_MS = 3e4;
2205
+ const CHECK_TIMEOUT_MS = 1e4;
2206
+ const MAX_MANIFEST_BYTES = 262144;
2207
+ const MAX_UPDATE_OUTPUT_BYTES = 32768;
2208
+ const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
2209
+ const PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
2210
+ function safeMessage(error) {
2211
+ return redactText(error instanceof Error ? error.message : String(error), 500);
2212
+ }
2213
+ async function readManifest(path) {
2214
+ const text = await readFile(path, "utf8");
2215
+ if (Buffer.byteLength(text) > MAX_MANIFEST_BYTES) throw new Error("package manifest is too large");
2216
+ const value = JSON.parse(text);
2217
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("package manifest is invalid");
2218
+ return value;
2219
+ }
2220
+ function dependencySpec(manifest) {
2221
+ if (typeof manifest.dependencies !== "object" || manifest.dependencies === null || Array.isArray(manifest.dependencies)) return void 0;
2222
+ const value = manifest.dependencies[PLUGIN_PACKAGE_NAME];
2223
+ return typeof value === "string" && value.length <= 2e3 ? value : void 0;
2224
+ }
2225
+ function classifyInstallSpec(spec) {
2226
+ if (/^(?:link|file|workspace):/i.test(spec)) return "link";
2227
+ if (/^(?:git(?:\+[^:]+)?:|github:|https?:|npm:)/i.test(spec) || /\.git(?:#|$)/i.test(spec)) return "unsupported";
2228
+ return /^(?:\^|~|>=?|<=?|=)?\s*v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\s*\|\|\s*(?:\^|~|>=?|<=?|=)?\s*v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)*$/.test(spec.trim()) ? "registry" : "unsupported";
2229
+ }
2230
+ async function samePath(left, right) {
2231
+ try {
2232
+ const [a, b] = await Promise.all([realpath(left), realpath(right)]);
2233
+ return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
2234
+ } catch {
2235
+ return false;
2236
+ }
2237
+ }
2238
+ function linkedTarget(profileDir, spec) {
2239
+ const match = /^(?:link|file):(.*)$/i.exec(spec);
2240
+ return match?.[1] === void 0 ? void 0 : resolve(profileDir, match[1]);
2241
+ }
2242
+ async function discoverInstallation(dshHome, packageDir) {
2243
+ const profilesDir = join(dshHome, "profiles");
2244
+ const matches = [];
2245
+ let profiles;
2246
+ try {
2247
+ profiles = await opendir(profilesDir);
2248
+ } catch (error) {
2249
+ if (error.code === "ENOENT") return void 0;
2250
+ throw error;
2251
+ }
2252
+ for await (const entry of profiles) {
2253
+ if (!entry.isDirectory() || !PROFILE_NAME.test(entry.name)) continue;
2254
+ const profileDir = join(profilesDir, entry.name);
2255
+ let manifest;
2256
+ try {
2257
+ manifest = await readManifest(join(profileDir, "package.json"));
2258
+ } catch {
2259
+ continue;
2260
+ }
2261
+ const spec = dependencySpec(manifest);
2262
+ if (spec === void 0) continue;
2263
+ const source = classifyInstallSpec(spec);
2264
+ if (source === "link" ? linkedTarget(profileDir, spec) !== void 0 && await samePath(linkedTarget(profileDir, spec), packageDir) : await samePath(join(profileDir, "node_modules", ..."@norman-else/dsh-claude".split("/")), packageDir)) matches.push({
2265
+ profile: entry.name,
2266
+ profileDir,
2267
+ source,
2268
+ spec
2269
+ });
2270
+ }
2271
+ return matches.length === 1 ? matches[0] : void 0;
2272
+ }
2273
+ function parseVersion(version) {
2274
+ const match = SEMVER.exec(version);
2275
+ if (match === null) throw new Error("package version is not valid semver");
2276
+ return match;
2277
+ }
2278
+ function compareVersions(left, right) {
2279
+ const a = parseVersion(left);
2280
+ const b = parseVersion(right);
2281
+ for (const index of [
2282
+ 1,
2283
+ 2,
2284
+ 3
2285
+ ]) {
2286
+ const difference = Number(a[index]) - Number(b[index]);
2287
+ if (difference !== 0) return Math.sign(difference);
2288
+ }
2289
+ const aPre = a[4];
2290
+ const bPre = b[4];
2291
+ if (aPre === void 0) return bPre === void 0 ? 0 : 1;
2292
+ if (bPre === void 0) return -1;
2293
+ return aPre.localeCompare(bPre, "en", { numeric: true });
2294
+ }
2295
+ async function registryLatest(signal) {
2296
+ const response = await fetch("https://registry.npmjs.org/%40norman-else%2Fdsh-claude", {
2297
+ headers: { accept: "application/vnd.npm.install-v1+json" },
2298
+ signal
2299
+ });
2300
+ if (!response.ok) throw new Error(`npm registry returned HTTP ${response.status}`);
2301
+ const latest = (await response.json())["dist-tags"]?.latest;
2302
+ if (typeof latest !== "string") throw new Error("npm registry response has no latest version");
2303
+ parseVersion(latest);
2304
+ return latest;
2305
+ }
2306
+ async function resolvePackageContextDir(configured) {
2307
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
2308
+ const candidates = configured === void 0 ? [moduleDir, dirname(moduleDir)] : [configured];
2309
+ for (const packageDir of candidates) try {
2310
+ const manifest = await readManifest(join(packageDir, "package.json"));
2311
+ if (manifest.name === "@norman-else/dsh-claude") return {
2312
+ packageDir,
2313
+ manifest
2314
+ };
2315
+ } catch {}
2316
+ throw new Error("plugin package manifest is invalid");
2317
+ }
2318
+ async function packageContext(deps) {
2319
+ const { packageDir, manifest } = await resolvePackageContextDir(deps.packageDir);
2320
+ if (typeof manifest.version !== "string") throw new Error("plugin package manifest is invalid");
2321
+ parseVersion(manifest.version);
2322
+ const installation = await discoverInstallation(deps.dshHome ?? resolveDshHome(), packageDir);
2323
+ return {
2324
+ version: manifest.version,
2325
+ ...installation === void 0 ? {} : { installation }
2326
+ };
2327
+ }
2328
+ async function checkPluginUpdate(deps = {}) {
2329
+ try {
2330
+ const { version, installation } = await packageContext(deps);
2331
+ if (installation === void 0) return {
2332
+ currentVersion: version,
2333
+ source: "unknown",
2334
+ state: "unavailable",
2335
+ canUpdate: false,
2336
+ restartRequired: false,
2337
+ message: "Active DSH profile could not be identified uniquely"
2338
+ };
2339
+ if (installation.source === "link") return {
2340
+ currentVersion: version,
2341
+ source: "link",
2342
+ state: "linked",
2343
+ canUpdate: false,
2344
+ restartRequired: false,
2345
+ message: "Local development link; updates come from the linked checkout"
2346
+ };
2347
+ if (installation.source !== "registry") return {
2348
+ currentVersion: version,
2349
+ source: installation.source,
2350
+ state: "unsupported",
2351
+ canUpdate: false,
2352
+ restartRequired: false,
2353
+ message: "This installation source cannot be updated from the npm registry"
2354
+ };
2355
+ const latest = await (deps.fetchLatest ?? registryLatest)(AbortSignal.timeout(CHECK_TIMEOUT_MS));
2356
+ const comparison = compareVersions(version, latest);
2357
+ return {
2358
+ currentVersion: version,
2359
+ latestVersion: latest,
2360
+ source: "registry",
2361
+ state: comparison < 0 ? "available" : "current",
2362
+ canUpdate: comparison < 0,
2363
+ restartRequired: comparison < 0
2364
+ };
2365
+ } catch (error) {
2366
+ return {
2367
+ currentVersion: "unknown",
2368
+ source: "unknown",
2369
+ state: "error",
2370
+ canUpdate: false,
2371
+ restartRequired: false,
2372
+ message: safeMessage(error)
2373
+ };
2374
+ }
2375
+ }
2376
+ async function updatePlugin(deps = {}) {
2377
+ const { version, installation } = await packageContext(deps);
2378
+ if (installation === void 0 || installation.source !== "registry") throw new Error("Plugin update is unavailable for this installation");
2379
+ const latest = await (deps.fetchLatest ?? registryLatest)(AbortSignal.timeout(CHECK_TIMEOUT_MS));
2380
+ if (compareVersions(version, latest) >= 0) return {
2381
+ currentVersion: version,
2382
+ latestVersion: latest,
2383
+ source: "registry",
2384
+ state: "current",
2385
+ canUpdate: false,
2386
+ restartRequired: false
2387
+ };
2388
+ const resolveExecutable = deps.resolveExecutable;
2389
+ const spawn = deps.spawn;
2390
+ if (resolveExecutable === void 0 || spawn === void 0) throw new Error("DSH update runtime is unavailable");
2391
+ const signal = AbortSignal.timeout(UPDATE_TIMEOUT_MS);
2392
+ const handle = spawn({
2393
+ argv: [
2394
+ await resolveExecutable("dsh", {}, signal),
2395
+ "plugin",
2396
+ "--profile",
2397
+ installation.profile,
2398
+ "update",
2399
+ PLUGIN_PACKAGE_NAME
2400
+ ],
2401
+ cwd: installation.profileDir,
2402
+ env: {},
2403
+ stdio: {
2404
+ stdin: "ignore",
2405
+ stdout: { maxBytes: MAX_UPDATE_OUTPUT_BYTES },
2406
+ stderr: { maxBytes: MAX_UPDATE_OUTPUT_BYTES }
2407
+ },
2408
+ graceMs: 2e3,
2409
+ signal
2410
+ });
2411
+ const outcome = await handle.done;
2412
+ if (outcome.exitCode !== 0) {
2413
+ const detail = handle.collected.stderr?.readFrom(0).text ?? "";
2414
+ throw new Error(`DSH plugin update failed (${outcome.exitCode ?? outcome.signal ?? "unknown exit"}): ${safeMessage(detail)}`);
2415
+ }
2416
+ return {
2417
+ currentVersion: version,
2418
+ latestVersion: latest,
2419
+ source: "registry",
2420
+ state: "current",
2421
+ canUpdate: false,
2422
+ restartRequired: true,
2423
+ message: "Update installed; restart DSH Desktop to load it"
2424
+ };
2425
+ }
2426
+ function registerClaudeUpdateRoutes(ctx, runtime, deps = {}) {
2427
+ const shared = {
2428
+ ...deps,
2429
+ resolveExecutable: runtime.resolveExecutable.bind(runtime),
2430
+ spawn: runtime.spawn.bind(runtime)
2431
+ };
2432
+ for (const route of [{
2433
+ path: CLAUDE_UPDATE_CHECK_PATH,
2434
+ method: "GET",
2435
+ run: () => checkPluginUpdate(shared)
2436
+ }, {
2437
+ path: CLAUDE_UPDATE_PATH,
2438
+ method: "POST",
2439
+ run: () => updatePlugin(shared)
2440
+ }]) ctx.effect(() => ctx.webServer.register({
2441
+ kind: "exact",
2442
+ path: route.path,
2443
+ handler: async (req, res) => {
2444
+ if (req.method !== route.method) return json(res, 405, { error: "method not allowed" });
2445
+ if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
2446
+ try {
2447
+ json(res, 200, await route.run());
2448
+ } catch (error) {
2449
+ json(res, 500, { error: safeMessage(error) });
2450
+ }
2451
+ }
2452
+ }), `dsh-claude: ${route.method} ${route.path}`);
2453
+ }
2454
+ //#endregion
2201
2455
  //#region src/index.ts
2202
2456
  const name = "llm-claude";
2203
2457
  const inject = [
@@ -2409,6 +2663,7 @@ async function apply(ctx, config) {
2409
2663
  ctx.effect(() => () => supervisor.dispose(), "dsh-claude: process supervisor");
2410
2664
  ctx.inject(["webServer"], (webCtx) => {
2411
2665
  registerClaudeDoctorRoutes(webCtx, webCtx.subprocess, supervisor, supervisorConfig, resolutionError);
2666
+ registerClaudeUpdateRoutes(webCtx, webCtx.subprocess);
2412
2667
  registerClaudeProjectionRoute(webCtx, sidecar, (sessionId) => {
2413
2668
  const agent = webCtx.agents.get(sessionId);
2414
2669
  return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";