@divergeai/cli 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/README.md +7 -4
- package/dist/diverge.mjs +122 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,10 +79,13 @@ Anthropic or OpenAI. Every other model, dsh included, searches through the
|
|
|
79
79
|
platform's `search_online` tool: one key on the platform, metered and audited
|
|
80
80
|
per organization. The dsh overlay wires it in as `mcp__divergent__search_online`
|
|
81
81
|
(dsh's DeepSeek-backed `web_search` needs a vendor key and is off; `web_fetch`
|
|
82
|
-
stays).
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
stays). dsh's web profile composes the model's tools per agent preset, where a
|
|
83
|
+
patch cannot reach, so `diverge agents setup dsh` and `run dsh` also derive a
|
|
84
|
+
**Divergent** preset from the installed dsh's `standard` one, with the vendor
|
|
85
|
+
search left out of the model's tools, under `$DSH_HOME/.agent-presets/`, and
|
|
86
|
+
make it the default for new web sessions once in `settings.yaml` (a preset you
|
|
87
|
+
pick yourself there stays picked). To give Claude Code or Codex the same tool
|
|
88
|
+
for a non-native upstream, register the bridge once:
|
|
86
89
|
|
|
87
90
|
```bash
|
|
88
91
|
claude mcp add --scope user divergent -- diverge tools bridge --tools search_online
|
package/dist/diverge.mjs
CHANGED
|
@@ -6915,14 +6915,14 @@ var require_parser = __commonJS({
|
|
|
6915
6915
|
case "scalar":
|
|
6916
6916
|
case "single-quoted-scalar":
|
|
6917
6917
|
case "double-quoted-scalar": {
|
|
6918
|
-
const
|
|
6918
|
+
const fs9 = this.flowScalar(this.type);
|
|
6919
6919
|
if (atNextItem || it.value) {
|
|
6920
|
-
map.items.push({ start, key:
|
|
6920
|
+
map.items.push({ start, key: fs9, sep: [] });
|
|
6921
6921
|
this.onKeyLine = true;
|
|
6922
6922
|
} else if (it.sep) {
|
|
6923
|
-
this.stack.push(
|
|
6923
|
+
this.stack.push(fs9);
|
|
6924
6924
|
} else {
|
|
6925
|
-
Object.assign(it, { key:
|
|
6925
|
+
Object.assign(it, { key: fs9, sep: [] });
|
|
6926
6926
|
this.onKeyLine = true;
|
|
6927
6927
|
}
|
|
6928
6928
|
return;
|
|
@@ -7050,13 +7050,13 @@ var require_parser = __commonJS({
|
|
|
7050
7050
|
case "scalar":
|
|
7051
7051
|
case "single-quoted-scalar":
|
|
7052
7052
|
case "double-quoted-scalar": {
|
|
7053
|
-
const
|
|
7053
|
+
const fs9 = this.flowScalar(this.type);
|
|
7054
7054
|
if (!it || it.value)
|
|
7055
|
-
fc.items.push({ start: [], key:
|
|
7055
|
+
fc.items.push({ start: [], key: fs9, sep: [] });
|
|
7056
7056
|
else if (it.sep)
|
|
7057
|
-
this.stack.push(
|
|
7057
|
+
this.stack.push(fs9);
|
|
7058
7058
|
else
|
|
7059
|
-
Object.assign(it, { key:
|
|
7059
|
+
Object.assign(it, { key: fs9, sep: [] });
|
|
7060
7060
|
return;
|
|
7061
7061
|
}
|
|
7062
7062
|
case "flow-map-end":
|
|
@@ -7382,12 +7382,12 @@ var CliError = class extends Error {
|
|
|
7382
7382
|
};
|
|
7383
7383
|
|
|
7384
7384
|
// src/commands/agents.ts
|
|
7385
|
-
import
|
|
7385
|
+
import fs7 from "node:fs";
|
|
7386
7386
|
import path10 from "node:path";
|
|
7387
7387
|
import { parseArgs } from "node:util";
|
|
7388
7388
|
|
|
7389
7389
|
// src/version.ts
|
|
7390
|
-
var VERSION = true ? "0.1.
|
|
7390
|
+
var VERSION = true ? "0.1.10" : "dev";
|
|
7391
7391
|
var USER_AGENT = `diverge/${VERSION}`;
|
|
7392
7392
|
|
|
7393
7393
|
// src/http.ts
|
|
@@ -7916,6 +7916,8 @@ function mergeCodexConfig(existing, block) {
|
|
|
7916
7916
|
|
|
7917
7917
|
// src/agents/dsh.ts
|
|
7918
7918
|
var import_yaml = __toESM(require_dist(), 1);
|
|
7919
|
+
import fs4 from "node:fs";
|
|
7920
|
+
import { createRequire } from "node:module";
|
|
7919
7921
|
import os4 from "node:os";
|
|
7920
7922
|
import path5 from "node:path";
|
|
7921
7923
|
var DSH_SHIPPED_DEFAULT_MODEL = "deepseek-flash";
|
|
@@ -8028,6 +8030,83 @@ function dshSettingsDefaultModel(settingsText, surface) {
|
|
|
8028
8030
|
function stalePinMessage(settingsPath, pinned, surface, served) {
|
|
8029
8031
|
return `${settingsPath} pins ${pinned} on divergent-${surface}, which the gateway does not serve; set agent-default-model.model there to one of: ${served.join(", ")} (or remove it to use ${served[0]})`;
|
|
8030
8032
|
}
|
|
8033
|
+
var DIVERGENT_PRESET_ID = "divergent";
|
|
8034
|
+
var DSH_USER_PRESETS_DIR = ".agent-presets";
|
|
8035
|
+
function dshPackageDir(binary) {
|
|
8036
|
+
if (/\.(cmd|bat)$/i.test(binary)) {
|
|
8037
|
+
const dir2 = path5.join(path5.dirname(binary), "node_modules", "@deepseek-ai", "dsh");
|
|
8038
|
+
return fs4.existsSync(path5.join(dir2, "package.json")) ? dir2 : null;
|
|
8039
|
+
}
|
|
8040
|
+
let dir;
|
|
8041
|
+
try {
|
|
8042
|
+
dir = path5.dirname(fs4.realpathSync(binary));
|
|
8043
|
+
} catch {
|
|
8044
|
+
return null;
|
|
8045
|
+
}
|
|
8046
|
+
for (; ; ) {
|
|
8047
|
+
try {
|
|
8048
|
+
if (JSON.parse(fs4.readFileSync(path5.join(dir, "package.json"), "utf8")).name === "@deepseek-ai/dsh") return dir;
|
|
8049
|
+
} catch {
|
|
8050
|
+
}
|
|
8051
|
+
const parent = path5.dirname(dir);
|
|
8052
|
+
if (parent === dir) return null;
|
|
8053
|
+
dir = parent;
|
|
8054
|
+
}
|
|
8055
|
+
}
|
|
8056
|
+
function shippedPresetDir(packageDir, id = "standard") {
|
|
8057
|
+
try {
|
|
8058
|
+
const require2 = createRequire(path5.join(packageDir, "package.json"));
|
|
8059
|
+
const dir = path5.join(path5.dirname(require2.resolve("@deepseek-ai/dsh-agent-presets/package.json")), "presets", id);
|
|
8060
|
+
return fs4.existsSync(path5.join(dir, "agent.cordis.yml")) ? dir : null;
|
|
8061
|
+
} catch {
|
|
8062
|
+
return null;
|
|
8063
|
+
}
|
|
8064
|
+
}
|
|
8065
|
+
function divergentPreset(shippedComposition) {
|
|
8066
|
+
const lines = shippedComposition.split("\n");
|
|
8067
|
+
const start = lines.findIndex((line) => /^- id: ['"]?tool-web['"]?\s*$/.test(line));
|
|
8068
|
+
if (start === -1) throw new Error("the shipped preset composes no tool-web row");
|
|
8069
|
+
let end = lines.findIndex((line, index) => index > start && /^- /.test(line));
|
|
8070
|
+
if (end === -1) end = lines.length;
|
|
8071
|
+
const row = lines.slice(start, end);
|
|
8072
|
+
const search = row.findIndex((line) => /^\s+search:/.test(line));
|
|
8073
|
+
if (search !== -1) {
|
|
8074
|
+
row[search] = row[search].replace(/:.*$/, ": false");
|
|
8075
|
+
} else {
|
|
8076
|
+
const config = row.findIndex((line) => /^ config:\s*$/.test(line));
|
|
8077
|
+
if (config !== -1) row.splice(config + 1, 0, " search: false");
|
|
8078
|
+
else row.splice(1, 0, " config:", " search: false");
|
|
8079
|
+
}
|
|
8080
|
+
return [
|
|
8081
|
+
`# The Divergent agent preset: dsh's shipped "standard" preset with the vendor web`,
|
|
8082
|
+
"# search left out of the model's tools; the platform's search_online arrives over the",
|
|
8083
|
+
"# bridge (row mcp-divergent). Derived from the installed dsh by `diverge agents",
|
|
8084
|
+
"# setup dsh` and `diverge agents run dsh` on every run; edits do not survive.",
|
|
8085
|
+
...lines.slice(0, start),
|
|
8086
|
+
...row,
|
|
8087
|
+
...lines.slice(end)
|
|
8088
|
+
].join("\n");
|
|
8089
|
+
}
|
|
8090
|
+
var DIVERGENT_PRESET_METADATA = [
|
|
8091
|
+
"name: Divergent",
|
|
8092
|
+
"description: The standard coding agent, searching the web through the platform's search_online tool instead of a vendor key.",
|
|
8093
|
+
"order: 0",
|
|
8094
|
+
""
|
|
8095
|
+
].join("\n");
|
|
8096
|
+
function writeDivergentPreset(home, shippedDir) {
|
|
8097
|
+
const dir = path5.join(home, DSH_USER_PRESETS_DIR, DIVERGENT_PRESET_ID);
|
|
8098
|
+
writeTextAtomic(path5.join(dir, "agent.cordis.yml"), divergentPreset(fs4.readFileSync(path5.join(shippedDir, "agent.cordis.yml"), "utf8")), 420);
|
|
8099
|
+
writeTextAtomic(path5.join(dir, "preset.yml"), DIVERGENT_PRESET_METADATA, 420);
|
|
8100
|
+
return dir;
|
|
8101
|
+
}
|
|
8102
|
+
function withDivergentPresetDefault(settingsText) {
|
|
8103
|
+
const parsed = settingsText?.trim() ? (0, import_yaml.parse)(settingsText) : null;
|
|
8104
|
+
const settings = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { ...parsed } : {};
|
|
8105
|
+
const presets = settings["agent-presets"];
|
|
8106
|
+
if (presets && typeof presets === "object" && presets.default) return null;
|
|
8107
|
+
settings["agent-presets"] = { ...presets ?? {}, default: DIVERGENT_PRESET_ID };
|
|
8108
|
+
return (0, import_yaml.stringify)(settings, { lineWidth: 0 });
|
|
8109
|
+
}
|
|
8031
8110
|
|
|
8032
8111
|
// src/agents/opencode.ts
|
|
8033
8112
|
import os5 from "node:os";
|
|
@@ -8049,7 +8128,7 @@ function mergeOpencodeConfig(existing, options) {
|
|
|
8049
8128
|
|
|
8050
8129
|
// src/agents/run-dsh.ts
|
|
8051
8130
|
import { spawn as spawn2 } from "node:child_process";
|
|
8052
|
-
import
|
|
8131
|
+
import fs5 from "node:fs";
|
|
8053
8132
|
import path7 from "node:path";
|
|
8054
8133
|
function findOnPath(name, env = process.env, platform = process.platform) {
|
|
8055
8134
|
const dirs = (env.PATH ?? "").split(path7.delimiter).filter(Boolean);
|
|
@@ -8059,8 +8138,8 @@ function findOnPath(name, env = process.env, platform = process.platform) {
|
|
|
8059
8138
|
for (const candidate of candidates) {
|
|
8060
8139
|
const full = path7.join(dir, candidate);
|
|
8061
8140
|
try {
|
|
8062
|
-
|
|
8063
|
-
if (
|
|
8141
|
+
fs5.accessSync(full, fs5.constants.X_OK);
|
|
8142
|
+
if (fs5.statSync(full).isFile()) return full;
|
|
8064
8143
|
} catch {
|
|
8065
8144
|
}
|
|
8066
8145
|
}
|
|
@@ -8068,7 +8147,7 @@ function findOnPath(name, env = process.env, platform = process.platform) {
|
|
|
8068
8147
|
return null;
|
|
8069
8148
|
}
|
|
8070
8149
|
function writeDshToken(credentialsPath, accessToken) {
|
|
8071
|
-
const existing =
|
|
8150
|
+
const existing = fs5.existsSync(credentialsPath) ? fs5.readFileSync(credentialsPath, "utf8") : null;
|
|
8072
8151
|
let merged;
|
|
8073
8152
|
try {
|
|
8074
8153
|
merged = mergeDshCredentials(existing, accessToken);
|
|
@@ -8086,7 +8165,7 @@ function dshLaunch(binary, args, platform = process.platform, execPath = process
|
|
|
8086
8165
|
const packageDir = path7.join(path7.dirname(binary), "node_modules", "@deepseek-ai", "dsh");
|
|
8087
8166
|
let bin;
|
|
8088
8167
|
try {
|
|
8089
|
-
bin = JSON.parse(
|
|
8168
|
+
bin = JSON.parse(fs5.readFileSync(path7.join(packageDir, "package.json"), "utf8")).bin;
|
|
8090
8169
|
} catch {
|
|
8091
8170
|
throw new CliError(`${binary} is not an npm shim for @deepseek-ai/dsh (no ${packageDir}); reinstall: npm install -g @deepseek-ai/dsh`);
|
|
8092
8171
|
}
|
|
@@ -8177,19 +8256,19 @@ async function rotateTokenFile(loop) {
|
|
|
8177
8256
|
}
|
|
8178
8257
|
|
|
8179
8258
|
// src/agents/self-command.ts
|
|
8180
|
-
import
|
|
8259
|
+
import fs6 from "node:fs";
|
|
8181
8260
|
import path9 from "node:path";
|
|
8182
8261
|
function resolveSelfCommand(argv = process.argv, platform = process.platform, execPath = process.execPath) {
|
|
8183
8262
|
const script = argv[1];
|
|
8184
8263
|
if (!script) return { command: execPath, args: [] };
|
|
8185
8264
|
if (platform !== "win32") {
|
|
8186
8265
|
try {
|
|
8187
|
-
|
|
8266
|
+
fs6.accessSync(script, fs6.constants.X_OK);
|
|
8188
8267
|
return { command: path9.resolve(script), args: [] };
|
|
8189
8268
|
} catch {
|
|
8190
8269
|
}
|
|
8191
8270
|
}
|
|
8192
|
-
return { command: execPath, args: [
|
|
8271
|
+
return { command: execPath, args: [fs6.realpathSync(script)] };
|
|
8193
8272
|
}
|
|
8194
8273
|
function shellCommand(self, ...trailing) {
|
|
8195
8274
|
return [self.command, ...self.args, ...trailing].map((part) => /[\s"]/.test(part) ? `"${part.replace(/"/g, '\\"')}"` : part).join(" ");
|
|
@@ -8216,12 +8295,29 @@ function surfaceOf(value, fallback) {
|
|
|
8216
8295
|
throw new CliError(`--surface must be one of: ${SURFACES.join(", ")}`);
|
|
8217
8296
|
}
|
|
8218
8297
|
function readJson(file) {
|
|
8219
|
-
if (!
|
|
8220
|
-
return JSON.parse(
|
|
8298
|
+
if (!fs7.existsSync(file)) return null;
|
|
8299
|
+
return JSON.parse(fs7.readFileSync(file, "utf8"));
|
|
8221
8300
|
}
|
|
8222
8301
|
async function servedModels(apiUrl, token, surface) {
|
|
8223
8302
|
return (await listModels(apiUrl, token.access_token, surface)).map((entry) => entry.id);
|
|
8224
8303
|
}
|
|
8304
|
+
function syncDivergentPreset(io, binary, home) {
|
|
8305
|
+
if (!binary) {
|
|
8306
|
+
io.stderr("dsh is not on PATH; the Divergent agent preset is derived when `diverge agents run dsh` first runs.");
|
|
8307
|
+
return;
|
|
8308
|
+
}
|
|
8309
|
+
const packageDir = dshPackageDir(binary);
|
|
8310
|
+
const shipped = packageDir ? shippedPresetDir(packageDir) : null;
|
|
8311
|
+
if (!shipped) {
|
|
8312
|
+
io.stderr(`warning: no shipped "standard" preset found behind ${binary}; dsh's own web_search stays in its web sessions and fails without a vendor key`);
|
|
8313
|
+
return;
|
|
8314
|
+
}
|
|
8315
|
+
const dir = writeDivergentPreset(home, shipped);
|
|
8316
|
+
const settingsPath = path10.join(home, "settings.yaml");
|
|
8317
|
+
const settings = withDivergentPresetDefault(fs7.existsSync(settingsPath) ? fs7.readFileSync(settingsPath, "utf8") : null);
|
|
8318
|
+
if (settings !== null) writeTextAtomic(settingsPath, settings);
|
|
8319
|
+
io.stderr(`Agent preset "${DIVERGENT_PRESET_ID}" (${dir}): web sessions search through the platform${settings !== null ? "; set as the default preset in settings.yaml" : ""}.`);
|
|
8320
|
+
}
|
|
8225
8321
|
async function overlayAndDefault(apiUrl, token, surface, io, requested) {
|
|
8226
8322
|
const models = await servedModels(apiUrl, token, surface);
|
|
8227
8323
|
if (models.length === 0) throw new CliError(`the gateway serves no models on the ${surface} surface for this organization`);
|
|
@@ -8277,7 +8373,7 @@ async function setup(args, io) {
|
|
|
8277
8373
|
}
|
|
8278
8374
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
8279
8375
|
const file = values.config ?? codexConfigPath();
|
|
8280
|
-
const existing =
|
|
8376
|
+
const existing = fs7.existsSync(file) ? fs7.readFileSync(file, "utf8") : null;
|
|
8281
8377
|
writeTextAtomic(file, mergeCodexConfig(existing, block));
|
|
8282
8378
|
io.stdout(`Codex provider divergent \u2192 ${openaiBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
|
|
8283
8379
|
io.stdout('Select it per run with: codex -c model_provider=divergent (or set model_provider = "divergent" in the config)');
|
|
@@ -8296,6 +8392,7 @@ async function setup(args, io) {
|
|
|
8296
8392
|
writeTextAtomic(file, overlay);
|
|
8297
8393
|
io.stdout(`dsh route divergent-${surface} \u2192 ${surfaceBaseUrl(apiUrl, surface)} as you, organization ${tokenClaims(token.access_token).organizationId}, default model ${defaultModel}. Wrote ${file}.`);
|
|
8298
8394
|
io.stdout("Web search is the platform tool search_online over `diverge tools bridge` (row mcp-divergent); dsh's DeepSeek search is off.");
|
|
8395
|
+
syncDivergentPreset(io, findOnPath("dsh", io.env), dshHome(io.env));
|
|
8299
8396
|
io.stdout("Boot a profile with the overlay and the token in its environment (user tokens expire; the login refreshes):");
|
|
8300
8397
|
io.stdout(` DIVERGENT_TOKEN="$(diverge token)" dsh --profile headless --patch ${file} "<task>"`);
|
|
8301
8398
|
io.stdout(`or let the CLI keep the token fresh for the whole session: diverge agents run dsh --surface ${surface} -- --profile web --port 3131 --no-open`);
|
|
@@ -8326,7 +8423,7 @@ ${agentsUsage}`);
|
|
|
8326
8423
|
const settingsPath = path10.join(home, "settings.yaml");
|
|
8327
8424
|
let pinned;
|
|
8328
8425
|
try {
|
|
8329
|
-
pinned = dshSettingsDefaultModel(
|
|
8426
|
+
pinned = dshSettingsDefaultModel(fs7.existsSync(settingsPath) ? fs7.readFileSync(settingsPath, "utf8") : null, surface);
|
|
8330
8427
|
} catch (error) {
|
|
8331
8428
|
throw new CliError(`${settingsPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
8332
8429
|
}
|
|
@@ -8334,6 +8431,7 @@ ${agentsUsage}`);
|
|
|
8334
8431
|
if (pinned && !served.includes(pinned)) throw new CliError(stalePinMessage(settingsPath, pinned, surface, served));
|
|
8335
8432
|
const overlayPath = path10.join(home, `divergent-${surface}.yml`);
|
|
8336
8433
|
writeTextAtomic(overlayPath, overlay);
|
|
8434
|
+
syncDivergentPreset(io, binary, home);
|
|
8337
8435
|
io.stderr(`dsh on divergent-${surface} as you (organization ${tokenClaims(token.access_token).organizationId}), default model ${defaultModel}`);
|
|
8338
8436
|
if (passthrough.length === 0) io.stderr('Starting the web UI; open the URL dsh prints. For one task without the UI: diverge agents run dsh -- "<task>"');
|
|
8339
8437
|
const source = { tokenFile: tokenFile(io.env), baseUrl: apiUrl, clientId: clientId(io.env) };
|
|
@@ -9277,7 +9375,7 @@ ${toolsUsage}`);
|
|
|
9277
9375
|
}
|
|
9278
9376
|
|
|
9279
9377
|
// src/commands/plugins.ts
|
|
9280
|
-
import
|
|
9378
|
+
import fs8 from "node:fs";
|
|
9281
9379
|
import { parseArgs as parseArgs5 } from "node:util";
|
|
9282
9380
|
var pluginsUsage = `diverge plugins <validate|publish|list|retire> [--organization-id <id>] ...
|
|
9283
9381
|
|
|
@@ -9304,7 +9402,7 @@ function readManifest(file) {
|
|
|
9304
9402
|
${pluginsUsage}`);
|
|
9305
9403
|
let text;
|
|
9306
9404
|
try {
|
|
9307
|
-
text =
|
|
9405
|
+
text = fs8.readFileSync(file, "utf8");
|
|
9308
9406
|
} catch (error) {
|
|
9309
9407
|
throw new CliError(`Cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9310
9408
|
}
|
package/package.json
CHANGED