@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 +13 -0
- package/dist/diverge.mjs +48 -8
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
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
|
|
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