@divergeai/cli 0.1.28 → 0.1.29

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.md CHANGED
@@ -222,6 +222,19 @@ command in a fresh sandbox. Its tool definitions ride on every request
222
222
  (about 5k tokens, cached after the first). Without a Chrome on the port the
223
223
  server waits; the first tool call connects.
224
224
 
225
+ dsh runs the repository's hooks. Started inside a git checkout whose root
226
+ holds a Claude Code hook config (`.claude/settings.json`), `diverge dsh`
227
+ mounts dsh's own hook bridge (`@deepseek-ai/dsh-hooks-claude-code`, installed
228
+ beside the harness) on that file, and says so at launch; anywhere else
229
+ nothing is mounted. The bridge reads the file once at start and runs each
230
+ command hook at the matching moment: session start, prompt, tool use, stop.
231
+ Three things differ from Claude Code, and a hook meant for both allows for
232
+ them: a hook runs inside dsh's sandbox (the working directory is writable,
233
+ the home directory is not, `/tmp` does not outlive the command); context is
234
+ read only as JSON (`hookSpecificOutput.additionalContext`), never as plain
235
+ output; and a stop hook is never told the stop was already continued, so it
236
+ keeps its own record of what it held for.
237
+
225
238
  ## Platform tools
226
239
 
227
240
  Every command takes the scope it acts in: `--organization-id` (or
package/dist/diverge.mjs CHANGED
@@ -7553,7 +7553,7 @@ function tokenSource(env, baseUrl) {
7553
7553
  }
7554
7554
 
7555
7555
  // src/version.ts
7556
- var VERSION = true ? "0.1.28" : "dev";
7556
+ var VERSION = true ? "0.1.29" : "dev";
7557
7557
  var USER_AGENT = `diverge/${VERSION}`;
7558
7558
 
7559
7559
  // src/http.ts
@@ -8600,9 +8600,28 @@ function dshPatchYaml(options) {
8600
8600
  ...dshSearchRows(),
8601
8601
  ...dshShellRows(),
8602
8602
  ...dshBrowserRows(),
8603
+ ...options.hooks ? dshHookRows(options.hooks) : [],
8603
8604
  ""
8604
8605
  ].join("\n");
8605
8606
  }
8607
+ function repositoryHookConfig(cwd) {
8608
+ for (let dir = path4.resolve(cwd); ; dir = path4.dirname(dir)) {
8609
+ if (fs5.existsSync(path4.join(dir, ".git"))) {
8610
+ const file = path4.join(dir, ".claude", "settings.json");
8611
+ return fs5.existsSync(file) ? file : null;
8612
+ }
8613
+ if (dir === path4.dirname(dir)) return null;
8614
+ }
8615
+ }
8616
+ function dshHookRows(hooks) {
8617
+ return [
8618
+ "- insert:",
8619
+ " - id: hooks-claude-code",
8620
+ ` name: ${JSON.stringify(hooks.bridge)}`,
8621
+ " config:",
8622
+ ` configPath: ${JSON.stringify(hooks.configPath)}`
8623
+ ];
8624
+ }
8606
8625
  var DSH_LAUNCH_ID_HEADER = "x-divergent-launch-id";
8607
8626
  var DSH_CLIENT_HEADER = "x-divergent-client";
8608
8627
  function dshLaunchHeaderRows(launch) {
@@ -9550,6 +9569,7 @@ var TESTED_DSH_VERSION = "0.2.1-rc.5";
9550
9569
 
9551
9570
  // src/agents/dsh-install.ts
9552
9571
  var DSH_PACKAGE = "@deepseek-ai/dsh";
9572
+ var DSH_HOOK_BRIDGE_PACKAGE = "@deepseek-ai/dsh-hooks-claude-code";
9553
9573
  var REGISTRY = "https://registry.npmjs.org";
9554
9574
  var RETIRED_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
9555
9575
  function dshInstallRoot(env) {
@@ -9558,6 +9578,15 @@ function dshInstallRoot(env) {
9558
9578
  function dshPackageIn(prefix) {
9559
9579
  return path13.join(prefix, "node_modules", "@deepseek-ai", "dsh");
9560
9580
  }
9581
+ function dshHookBridgeIn(prefix) {
9582
+ const dir = path13.join(prefix, "node_modules", ...DSH_HOOK_BRIDGE_PACKAGE.split("/"));
9583
+ try {
9584
+ const manifest = JSON.parse(fs10.readFileSync(path13.join(dir, "package.json"), "utf8"));
9585
+ return path13.join(dir, manifest.main ?? "lib/index.js");
9586
+ } catch {
9587
+ return null;
9588
+ }
9589
+ }
9561
9590
  function readManifest(prefix) {
9562
9591
  try {
9563
9592
  return JSON.parse(fs10.readFileSync(path13.join(dshPackageIn(prefix), "package.json"), "utf8"));
@@ -9586,9 +9615,12 @@ var realDeps = (env) => ({
9586
9615
  // start; the rest check prebuilt binaries or print a notice. An older npm
9587
9616
  // ignores the field and runs them all. stdout goes to stderr: a headless
9588
9617
  // run's stdout is the answer.
9589
- npmInstall: (prefix, spec) => {
9590
- fs10.writeFileSync(path13.join(prefix, "package.json"), JSON.stringify({ private: true, allowScripts: { "@deepseek-ai/dsh-subprocess-local": true } }));
9591
- return runNpm(["install", "--prefix", prefix, "--no-audit", "--no-fund", "--loglevel=error", spec], env, process.platform, ["ignore", 2, 2]);
9618
+ // A prefix that holds a version already keeps its manifest: npm removes what
9619
+ // a manifest stops naming.
9620
+ npmInstall: (prefix, specs) => {
9621
+ const manifest = path13.join(prefix, "package.json");
9622
+ if (!fs10.existsSync(manifest)) fs10.writeFileSync(manifest, JSON.stringify({ private: true, allowScripts: { "@deepseek-ai/dsh-subprocess-local": true } }));
9623
+ return runNpm(["install", "--prefix", prefix, "--no-audit", "--no-fund", "--loglevel=error", ...specs], env, process.platform, ["ignore", 2, 2]);
9592
9624
  },
9593
9625
  engines: async (version) => (await fetchJson(`${REGISTRY}/${DSH_PACKAGE}/${version}`, { timeoutMs: 15e3 })).engines?.node,
9594
9626
  nodeVersion: process.version,
@@ -9600,6 +9632,10 @@ async function ensureDsh(io, version = TESTED_DSH_VERSION, deps = realDeps(io.en
9600
9632
  let manifest = readManifest(prefix);
9601
9633
  if (manifest) {
9602
9634
  requireNode(version, manifest.engines?.node, deps.nodeVersion);
9635
+ if (!dshHookBridgeIn(prefix)) {
9636
+ const code2 = await deps.npmInstall(prefix, [`${DSH_HOOK_BRIDGE_PACKAGE}@${version}`]).catch(() => 1);
9637
+ if (code2 !== 0) io.stderr(`warning: could not add dsh's hook bridge (npm exited with ${code2}); this session runs without the repository's hooks.`);
9638
+ }
9603
9639
  } else {
9604
9640
  let engines;
9605
9641
  try {
@@ -9611,7 +9647,7 @@ async function ensureDsh(io, version = TESTED_DSH_VERSION, deps = realDeps(io.en
9611
9647
  const staging = `${prefix}.installing-${process.pid}`;
9612
9648
  fs10.mkdirSync(staging, { recursive: true });
9613
9649
  try {
9614
- const code2 = await deps.npmInstall(staging, `${DSH_PACKAGE}@${version}`);
9650
+ const code2 = await deps.npmInstall(staging, [`${DSH_PACKAGE}@${version}`, `${DSH_HOOK_BRIDGE_PACKAGE}@${version}`]);
9615
9651
  if (code2 !== 0) throw new CliError(`Installing dsh ${version} failed: npm exited with ${code2}. Check the network and run diverge dsh again.`);
9616
9652
  try {
9617
9653
  fs10.renameSync(staging, prefix);
@@ -10291,7 +10327,7 @@ function savedModel(home, surface) {
10291
10327
  throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
10292
10328
  }
10293
10329
  }
10294
- async function overlayAndDefault(io, apiUrl, token, surface, requested, saved, launch) {
10330
+ async function overlayAndDefault(io, apiUrl, token, surface, requested, saved, launch, hooks) {
10295
10331
  const served = (await listModels(apiUrl, token.access_token, surface)).map(servedModel);
10296
10332
  const models = served.map((model) => model.id);
10297
10333
  if (models.length === 0) throw new CliError(`the gateway serves no models on the ${surface} surface for this organization`);
@@ -10299,7 +10335,7 @@ async function overlayAndDefault(io, apiUrl, token, surface, requested, saved, l
10299
10335
  const usable = !requested && saved && models.includes(saved.model) ? saved : null;
10300
10336
  const defaultModel = dshDefaultModel(models, requested ?? usable?.model);
10301
10337
  if (!requested && saved && !usable) io.stderr(`The model you last chose, ${saved.model}, is no longer served here; using ${defaultModel}.`);
10302
- return { overlay: dshPatchYaml({ baseUrl: surfaceBaseUrl(apiUrl, surface), surface, models: served, defaultModel, reasoningEffort: usable?.reasoningEffort, launch }), defaultModel };
10338
+ return { overlay: dshPatchYaml({ baseUrl: surfaceBaseUrl(apiUrl, surface), surface, models: served, defaultModel, reasoningEffort: usable?.reasoningEffort, launch, hooks }), defaultModel };
10303
10339
  }
10304
10340
  async function runDshCommand(args, io) {
10305
10341
  const terminator = args.indexOf("--");
@@ -10321,10 +10357,14 @@ async function runDshCommand(args, io) {
10321
10357
  await skillsFor(io, apiUrl, "dsh", values["organization-id"]);
10322
10358
  const launch = { id: randomUUID(), client: `dsh/${TESTED_DSH_VERSION}` };
10323
10359
  const home = dshHome(io.env);
10324
- const { overlay, defaultModel } = await overlayAndDefault(io, apiUrl, token, surface, values.model, savedModel(home, surface), launch);
10360
+ const bridge = dshHookBridgeIn(prefix);
10361
+ const configPath = bridge ? repositoryHookConfig(process.cwd()) : null;
10362
+ const hooks = bridge && configPath ? { bridge, configPath } : null;
10363
+ const { overlay, defaultModel } = await overlayAndDefault(io, apiUrl, token, surface, values.model, savedModel(home, surface), launch, hooks);
10325
10364
  const overlayPath = path18.join(home, `divergent-${surface}.yml`);
10326
10365
  writeTextAtomic(overlayPath, `${overlay}${dshPresetOverrides(prefix)}`);
10327
10366
  io.stderr(`dsh ${TESTED_DSH_VERSION} on divergent-${surface} as you (organization ${tokenClaims(token.access_token).organizationId}), default model ${defaultModel}`);
10367
+ if (hooks) io.stderr(`Running this repository's hooks (${hooks.configPath}).`);
10328
10368
  if (passthrough.length === 0) io.stderr('Starting the web UI; open the URL dsh prints. For one task without the UI: diverge dsh -- "<task>"');
10329
10369
  const source = tokenSource(io.env, apiUrl);
10330
10370
  const organizationId = tokenClaims(token.access_token).organizationId ?? values["organization-id"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divergeai/cli",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Divergent platform CLI: sign in, wire coding agents to the inference gateway, call platform tools",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",