@divergeai/cli 0.1.28 → 0.1.30
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 +28 -2
- package/dist/diverge.mjs +182 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -117,7 +117,8 @@ after `--` goes to dsh as given. Each launch, in order:
|
|
|
117
117
|
`$DIVERGE_HOME/harness/dsh/<version>`: about a minute and nearly 1 GB, and
|
|
118
118
|
Node 24.9 or later. A dsh you installed yourself is neither used nor touched.
|
|
119
119
|
A version no launch has used for a week is deleted.
|
|
120
|
-
4. brings
|
|
120
|
+
4. brings the launch organization's skills and the CLI's up to date (see
|
|
121
|
+
[Skills](#skills)) and names both roots in the overlay.
|
|
121
122
|
5. writes the overlay `$DSH_HOME/divergent-<surface>.yml`: the models the
|
|
122
123
|
gateway serves, each with the input, context window and output cap the
|
|
123
124
|
gateway lists for it; dsh's DeepSeek adapter, telemetry upload and vendor
|
|
@@ -178,7 +179,7 @@ a shell.
|
|
|
178
179
|
|
|
179
180
|
```bash
|
|
180
181
|
diverge skills list # what the platform serves you, per organization
|
|
181
|
-
diverge skills install # into ~/.claude/skills, ~/.codex/skills and $
|
|
182
|
+
diverge skills install # into ~/.claude/skills, ~/.codex/skills and $DIVERGE_HOME/skills/dsh
|
|
182
183
|
diverge skills refresh # the same, best effort and quiet: what sign-in and a session start run
|
|
183
184
|
diverge skills status # what each folder holds against the platform
|
|
184
185
|
```
|
|
@@ -213,6 +214,18 @@ shell with `diverge tools`, which costs nothing until a tool is called, and
|
|
|
213
214
|
the `diverge` skill shows it how, arguments piped through stdin and batches
|
|
214
215
|
included.
|
|
215
216
|
|
|
217
|
+
A dsh session is bound to one organization by the launch that starts it, so
|
|
218
|
+
dsh's skills do not sit side by side: the CLI writes them under its own root,
|
|
219
|
+
`$DIVERGE_HOME/skills/dsh/<organization id>/<folder>/SKILL.md`, and the CLI's
|
|
220
|
+
`diverge` and `drive-chrome` under `$DIVERGE_HOME/skills/dsh/cli/`. A `diverge
|
|
221
|
+
dsh` launch refreshes its organization's folder and names it and the CLI's as
|
|
222
|
+
`customSkillDirs` on dsh's skill provider, in the host row and in every
|
|
223
|
+
shipped agent preset that mounts one, so its catalog offers only the skills
|
|
224
|
+
its token can run. dsh's default roots stay on: your own `$DSH_HOME/skills`
|
|
225
|
+
and a project's `.agents/skills` still load. A CLI before 0.1.30 wrote every
|
|
226
|
+
organization's skills into `$DSH_HOME/skills`; the first install or launch
|
|
227
|
+
after it removes exactly the folders it recorded there, and nothing of yours.
|
|
228
|
+
|
|
216
229
|
dsh also gets the browser. The overlay mounts Chrome DevTools MCP, attached
|
|
217
230
|
to a Chrome the person starts in debug mode (`--remote-debugging-port=9222`,
|
|
218
231
|
on a profile of its own), and the CLI's `drive-chrome` skill says how to use
|
|
@@ -222,6 +235,19 @@ command in a fresh sandbox. Its tool definitions ride on every request
|
|
|
222
235
|
(about 5k tokens, cached after the first). Without a Chrome on the port the
|
|
223
236
|
server waits; the first tool call connects.
|
|
224
237
|
|
|
238
|
+
dsh runs the repository's hooks. Started inside a git checkout whose root
|
|
239
|
+
holds a Claude Code hook config (`.claude/settings.json`), `diverge dsh`
|
|
240
|
+
mounts dsh's own hook bridge (`@deepseek-ai/dsh-hooks-claude-code`, installed
|
|
241
|
+
beside the harness) on that file, and says so at launch; anywhere else
|
|
242
|
+
nothing is mounted. The bridge reads the file once at start and runs each
|
|
243
|
+
command hook at the matching moment: session start, prompt, tool use, stop.
|
|
244
|
+
Three things differ from Claude Code, and a hook meant for both allows for
|
|
245
|
+
them: a hook runs inside dsh's sandbox (the working directory is writable,
|
|
246
|
+
the home directory is not, `/tmp` does not outlive the command); context is
|
|
247
|
+
read only as JSON (`hookSpecificOutput.additionalContext`), never as plain
|
|
248
|
+
output; and a stop hook is never told the stop was already continued, so it
|
|
249
|
+
keeps its own record of what it held for.
|
|
250
|
+
|
|
225
251
|
## Platform tools
|
|
226
252
|
|
|
227
253
|
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.30" : "dev";
|
|
7557
7557
|
var USER_AGENT = `diverge/${VERSION}`;
|
|
7558
7558
|
|
|
7559
7559
|
// src/http.ts
|
|
@@ -8600,9 +8600,37 @@ function dshPatchYaml(options) {
|
|
|
8600
8600
|
...dshSearchRows(),
|
|
8601
8601
|
...dshShellRows(),
|
|
8602
8602
|
...dshBrowserRows(),
|
|
8603
|
+
...options.hooks ? dshHookRows(options.hooks) : [],
|
|
8604
|
+
...options.skillDirs?.length ? dshSkillRows(options.skillDirs) : [],
|
|
8603
8605
|
""
|
|
8604
8606
|
].join("\n");
|
|
8605
8607
|
}
|
|
8608
|
+
function dshSkillRows(skillDirs) {
|
|
8609
|
+
return [
|
|
8610
|
+
"- id: skill-filesystem",
|
|
8611
|
+
" config:",
|
|
8612
|
+
" customSkillDirs:",
|
|
8613
|
+
...skillDirs.map((dir) => ` - ${JSON.stringify(dir)}`)
|
|
8614
|
+
];
|
|
8615
|
+
}
|
|
8616
|
+
function repositoryHookConfig(cwd) {
|
|
8617
|
+
for (let dir = path4.resolve(cwd); ; dir = path4.dirname(dir)) {
|
|
8618
|
+
if (fs5.existsSync(path4.join(dir, ".git"))) {
|
|
8619
|
+
const file = path4.join(dir, ".claude", "settings.json");
|
|
8620
|
+
return fs5.existsSync(file) ? file : null;
|
|
8621
|
+
}
|
|
8622
|
+
if (dir === path4.dirname(dir)) return null;
|
|
8623
|
+
}
|
|
8624
|
+
}
|
|
8625
|
+
function dshHookRows(hooks) {
|
|
8626
|
+
return [
|
|
8627
|
+
"- insert:",
|
|
8628
|
+
" - id: hooks-claude-code",
|
|
8629
|
+
` name: ${JSON.stringify(hooks.bridge)}`,
|
|
8630
|
+
" config:",
|
|
8631
|
+
` configPath: ${JSON.stringify(hooks.configPath)}`
|
|
8632
|
+
];
|
|
8633
|
+
}
|
|
8606
8634
|
var DSH_LAUNCH_ID_HEADER = "x-divergent-launch-id";
|
|
8607
8635
|
var DSH_CLIENT_HEADER = "x-divergent-client";
|
|
8608
8636
|
function dshLaunchHeaderRows(launch) {
|
|
@@ -8669,48 +8697,77 @@ function dshSavedModel(patchText, surface) {
|
|
|
8669
8697
|
return { model: String(config.model), ...config.reasoningEffort ? { reasoningEffort: String(config.reasoningEffort) } : {} };
|
|
8670
8698
|
}
|
|
8671
8699
|
var dshPresetsDir = (prefix) => path4.join(prefix, "node_modules", "@deepseek-ai", "dsh-web-app", "presets");
|
|
8672
|
-
function
|
|
8673
|
-
const
|
|
8674
|
-
|
|
8675
|
-
if (insert === -1) throw new Error("a shipped preset is not one inserted row");
|
|
8676
|
-
const row = lines.slice(insert + 1).map((line) => line.replace(/^ {4}/, ""));
|
|
8677
|
-
const start = row.findIndex((line) => /^\s+- id: ['"]?tool-web['"]?\s*$/.test(line));
|
|
8678
|
-
if (start === -1) return null;
|
|
8700
|
+
function editPresetPlugin(row, id, edit) {
|
|
8701
|
+
const start = row.findIndex((line) => new RegExp(`^\\s+- id: ['"]?${id}['"]?\\s*$`).test(line));
|
|
8702
|
+
if (start === -1) return false;
|
|
8679
8703
|
const indent = row[start].indexOf("-");
|
|
8680
8704
|
const body = " ".repeat(indent + 2);
|
|
8681
8705
|
let end = row.findIndex((line, index) => index > start && line.trim() !== "" && line.search(/\S/) <= indent);
|
|
8682
8706
|
if (end === -1) end = row.length;
|
|
8683
8707
|
const block = row.slice(start, end);
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8708
|
+
edit(block, body);
|
|
8709
|
+
row.splice(start, end - start, ...block);
|
|
8710
|
+
return true;
|
|
8711
|
+
}
|
|
8712
|
+
function dshPresetOverride(shipped, skillDirs = []) {
|
|
8713
|
+
const lines = shipped.split("\n");
|
|
8714
|
+
const insert = lines.indexOf("- insert:");
|
|
8715
|
+
if (insert === -1) throw new Error("a shipped preset is not one inserted row");
|
|
8716
|
+
const row = lines.slice(insert + 1).map((line) => line.replace(/^ {4}/, ""));
|
|
8717
|
+
const web = editPresetPlugin(row, "tool-web", (block, body) => {
|
|
8718
|
+
const search = block.findIndex((line) => line.startsWith(`${body} search:`));
|
|
8719
|
+
if (search !== -1) {
|
|
8720
|
+
block[search] = `${body} search: false`;
|
|
8721
|
+
} else {
|
|
8722
|
+
const config = block.indexOf(`${body}config:`);
|
|
8723
|
+
if (config !== -1) block.splice(config + 1, 0, `${body} search: false`);
|
|
8724
|
+
else block.splice(1, 0, `${body}config:`, `${body} search: false`);
|
|
8725
|
+
}
|
|
8726
|
+
});
|
|
8727
|
+
const skills = skillDirs.length > 0 && editPresetPlugin(row, "skill-filesystem", (block, body) => {
|
|
8728
|
+
const items = skillDirs.map((dir) => `${body} - ${JSON.stringify(dir)}`);
|
|
8729
|
+
const list = block.indexOf(`${body} customSkillDirs:`);
|
|
8730
|
+
if (list !== -1) {
|
|
8731
|
+
let last = list + 1;
|
|
8732
|
+
while (last < block.length && block[last].startsWith(`${body} `)) last += 1;
|
|
8733
|
+
block.splice(last, 0, ...items);
|
|
8734
|
+
} else {
|
|
8735
|
+
const config = block.indexOf(`${body}config:`);
|
|
8736
|
+
if (config !== -1) block.splice(config + 1, 0, `${body} customSkillDirs:`, ...items);
|
|
8737
|
+
else block.splice(1, 0, `${body}config:`, `${body} customSkillDirs:`, ...items);
|
|
8738
|
+
}
|
|
8739
|
+
});
|
|
8740
|
+
if (!web && !skills) return null;
|
|
8741
|
+
return row.join("\n").trimEnd() + "\n";
|
|
8693
8742
|
}
|
|
8694
|
-
function dshPresetOverrides(prefix) {
|
|
8743
|
+
function dshPresetOverrides(prefix, skillDirs = []) {
|
|
8695
8744
|
const dir = dshPresetsDir(prefix);
|
|
8696
|
-
return fs5.readdirSync(dir).filter((name) => name.endsWith(".patch.yml")).sort().flatMap((name) => dshPresetOverride(fs5.readFileSync(path4.join(dir, name), "utf8")) ?? []).join("");
|
|
8745
|
+
return fs5.readdirSync(dir).filter((name) => name.endsWith(".patch.yml")).sort().flatMap((name) => dshPresetOverride(fs5.readFileSync(path4.join(dir, name), "utf8"), skillDirs) ?? []).join("");
|
|
8697
8746
|
}
|
|
8698
8747
|
|
|
8699
8748
|
// src/skills/install.ts
|
|
8700
8749
|
var HARNESSES = ["claude", "codex", "dsh"];
|
|
8701
8750
|
var SKILLS_MANIFEST = ".diverge-skills.json";
|
|
8702
|
-
|
|
8751
|
+
var DSH_CLI_SKILLS = "cli";
|
|
8752
|
+
function harnessHome(harness, env) {
|
|
8703
8753
|
switch (harness) {
|
|
8704
8754
|
case "claude":
|
|
8705
|
-
return
|
|
8755
|
+
return env.CLAUDE_CONFIG_DIR?.trim() || path5.join(os3.homedir(), ".claude");
|
|
8706
8756
|
case "codex":
|
|
8707
|
-
return
|
|
8757
|
+
return env.CODEX_HOME?.trim() || path5.join(os3.homedir(), ".codex");
|
|
8708
8758
|
case "dsh":
|
|
8709
|
-
return
|
|
8759
|
+
return dshHome(env);
|
|
8710
8760
|
}
|
|
8711
8761
|
}
|
|
8762
|
+
function skillsRoot(harness, env = process.env) {
|
|
8763
|
+
return harness === "dsh" ? path5.join(configDir(env), "skills", "dsh") : path5.join(harnessHome(harness, env), "skills");
|
|
8764
|
+
}
|
|
8765
|
+
function dshSkillDirs(organizationId, env = process.env) {
|
|
8766
|
+
const root = skillsRoot("dsh", env);
|
|
8767
|
+
return [path5.join(root, organizationId), path5.join(root, DSH_CLI_SKILLS)];
|
|
8768
|
+
}
|
|
8712
8769
|
function presentHarnesses(env = process.env) {
|
|
8713
|
-
return HARNESSES.filter((harness) => fs6.existsSync(
|
|
8770
|
+
return HARNESSES.filter((harness) => fs6.existsSync(harnessHome(harness, env)));
|
|
8714
8771
|
}
|
|
8715
8772
|
function isRecord(value) {
|
|
8716
8773
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -8847,6 +8904,66 @@ function skillsStatus(root, set2) {
|
|
|
8847
8904
|
const retired = [...new Set(retirable(manifest, set2))].filter((folder) => !servedFolders.has(folder));
|
|
8848
8905
|
return { root, installedAt: manifest?.installedAt || null, current, behind, missing, retired, skipped };
|
|
8849
8906
|
}
|
|
8907
|
+
function isOrganizationFolder(id) {
|
|
8908
|
+
return id !== DSH_CLI_SKILLS && /^[A-Za-z0-9_-]{1,64}$/.test(id);
|
|
8909
|
+
}
|
|
8910
|
+
function targets(harness, root, set2) {
|
|
8911
|
+
if (harness !== "dsh") return [{ root, set: set2 }];
|
|
8912
|
+
return [
|
|
8913
|
+
...set2.organizations.filter((organization) => isOrganizationFolder(organization.id)).map((organization) => ({ root: path5.join(root, organization.id), set: { cli: [], organizations: [organization], complete: true } })),
|
|
8914
|
+
{ root: path5.join(root, DSH_CLI_SKILLS), set: { cli: set2.cli, organizations: [], complete: true } }
|
|
8915
|
+
];
|
|
8916
|
+
}
|
|
8917
|
+
function retiredOrganizations(root, set2) {
|
|
8918
|
+
if (!set2.complete || !fs6.existsSync(root)) return [];
|
|
8919
|
+
const listed = new Set(set2.organizations.map((organization) => organization.id));
|
|
8920
|
+
return fs6.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isOrganizationFolder(entry.name) && !listed.has(entry.name) && readSkillsManifest(path5.join(root, entry.name))).map((entry) => path5.join(root, entry.name));
|
|
8921
|
+
}
|
|
8922
|
+
function legacyDshFolders(env) {
|
|
8923
|
+
const root = path5.join(dshHome(env), "skills");
|
|
8924
|
+
return { root, folders: [...heldFolders(readSkillsManifest(root)).keys()].filter(isSkillFolder) };
|
|
8925
|
+
}
|
|
8926
|
+
function installHarness(harness, root, set2, source, env = process.env, now = () => /* @__PURE__ */ new Date()) {
|
|
8927
|
+
const results = targets(harness, root, set2).map((target) => installSkills(target.root, target.set, source, now));
|
|
8928
|
+
const merged = {
|
|
8929
|
+
root,
|
|
8930
|
+
written: results.flatMap((result) => result.written),
|
|
8931
|
+
added: results.flatMap((result) => result.added),
|
|
8932
|
+
updated: results.flatMap((result) => result.updated),
|
|
8933
|
+
removed: results.flatMap((result) => result.removed),
|
|
8934
|
+
skipped: results.flatMap((result) => result.skipped),
|
|
8935
|
+
counts: results.flatMap((result) => result.counts)
|
|
8936
|
+
};
|
|
8937
|
+
if (harness !== "dsh") return merged;
|
|
8938
|
+
for (const folder of retiredOrganizations(root, set2)) {
|
|
8939
|
+
merged.removed.push(...heldFolders(readSkillsManifest(folder)).keys());
|
|
8940
|
+
fs6.rmSync(folder, { recursive: true, force: true });
|
|
8941
|
+
}
|
|
8942
|
+
if (root !== skillsRoot("dsh", env)) return merged;
|
|
8943
|
+
const legacy = legacyDshFolders(env);
|
|
8944
|
+
for (const folder of legacy.folders) fs6.rmSync(path5.join(legacy.root, folder), { recursive: true, force: true });
|
|
8945
|
+
fs6.rmSync(path5.join(legacy.root, SKILLS_MANIFEST), { force: true });
|
|
8946
|
+
merged.removed.push(...legacy.folders);
|
|
8947
|
+
return merged;
|
|
8948
|
+
}
|
|
8949
|
+
function harnessStatus(harness, root, set2, env = process.env) {
|
|
8950
|
+
const statuses = targets(harness, root, set2).map((target) => skillsStatus(target.root, target.set));
|
|
8951
|
+
const retired = statuses.flatMap((status2) => status2.retired);
|
|
8952
|
+
if (harness === "dsh") {
|
|
8953
|
+
for (const folder of retiredOrganizations(root, set2)) retired.push(...heldFolders(readSkillsManifest(folder)).keys());
|
|
8954
|
+
if (root === skillsRoot("dsh", env)) retired.push(...legacyDshFolders(env).folders);
|
|
8955
|
+
}
|
|
8956
|
+
const installed = statuses.map((status2) => status2.installedAt).filter((at) => at !== null).sort();
|
|
8957
|
+
return {
|
|
8958
|
+
root,
|
|
8959
|
+
installedAt: installed[0] ?? null,
|
|
8960
|
+
current: statuses.flatMap((status2) => status2.current),
|
|
8961
|
+
behind: statuses.flatMap((status2) => status2.behind),
|
|
8962
|
+
missing: statuses.flatMap((status2) => status2.missing),
|
|
8963
|
+
retired,
|
|
8964
|
+
skipped: statuses.flatMap((status2) => status2.skipped)
|
|
8965
|
+
};
|
|
8966
|
+
}
|
|
8850
8967
|
|
|
8851
8968
|
// src/skills/installable.ts
|
|
8852
8969
|
async function fetchInstallableSkills(baseUrl, accessToken) {
|
|
@@ -8894,9 +9011,11 @@ async function collectLoginSkills(deps, options = {}) {
|
|
|
8894
9011
|
var skillsUsage = `diverge skills <list|install|refresh|status> [--organization-id <id|name>] [--harness claude,codex,dsh] [--into <dir>]
|
|
8895
9012
|
|
|
8896
9013
|
list the skills the platform serves you: in every organization you can act in, the skills whose tools you can run
|
|
8897
|
-
install write them, and the CLI's own skills ("diverge"; "drive-chrome" for dsh), into each harness's
|
|
8898
|
-
|
|
8899
|
-
|
|
9014
|
+
install write them, and the CLI's own skills ("diverge"; "drive-chrome" for dsh), into each harness's skill folder:
|
|
9015
|
+
~/.claude/skills and ~/.codex/skills side by side under each organization's prefix; for dsh one folder
|
|
9016
|
+
per organization and one for the CLI's under ~/.diverge/skills/dsh, of which a diverge dsh launch names
|
|
9017
|
+
its organization's and the CLI's; retires what an organization stopped serving; reports what changed \u2014
|
|
9018
|
+
the full list is \`skills list\`
|
|
8900
9019
|
refresh the same, best effort: no login, no network or nothing changed prints nothing and never fails.
|
|
8901
9020
|
Runs at sign-in and at every session start, so the folders hold what the organizations serve now
|
|
8902
9021
|
status what each folder holds against what the platform serves now
|
|
@@ -8961,7 +9080,7 @@ function installReport(harness, result) {
|
|
|
8961
9080
|
return `${harness}: ${result.written.length} skills in ${result.root} (${holdings}${changes.length ? `; ${changes.join("; ")}` : "; unchanged"})`;
|
|
8962
9081
|
}
|
|
8963
9082
|
function installForHarness(io, harness, skills, apiUrl, into) {
|
|
8964
|
-
const result =
|
|
9083
|
+
const result = installHarness(harness, into ?? skillsRoot(harness, io.env), installSet(harness, skills), apiUrl, io.env);
|
|
8965
9084
|
io.stdout(installReport(harness, result));
|
|
8966
9085
|
return result;
|
|
8967
9086
|
}
|
|
@@ -8980,7 +9099,7 @@ async function refreshSkills(io, apiUrl, options = {}) {
|
|
|
8980
9099
|
}
|
|
8981
9100
|
const results = [];
|
|
8982
9101
|
for (const harness of harnesses) {
|
|
8983
|
-
const result =
|
|
9102
|
+
const result = installHarness(harness, skillsRoot(harness, io.env), installSet(harness, skills), apiUrl, io.env);
|
|
8984
9103
|
if (!options.quiet || result.added.length || result.updated.length || result.removed.length) io.stdout(installReport(harness, result));
|
|
8985
9104
|
results.push(result);
|
|
8986
9105
|
}
|
|
@@ -9018,7 +9137,7 @@ async function runSkills(args, io) {
|
|
|
9018
9137
|
const skills = await loginSkillSet(apiUrl, io.env, organizationId);
|
|
9019
9138
|
let behind = false;
|
|
9020
9139
|
for (const harness of harnessesOf(values.harness, io.env)) {
|
|
9021
|
-
const status2 =
|
|
9140
|
+
const status2 = harnessStatus(harness, values.into ?? skillsRoot(harness, io.env), installSet(harness, skills), io.env);
|
|
9022
9141
|
const parts = [`${status2.current.length} current`];
|
|
9023
9142
|
if (status2.behind.length) parts.push(`${status2.behind.length} behind (${few(status2.behind)})`);
|
|
9024
9143
|
if (status2.missing.length) parts.push(`${status2.missing.length} missing${status2.current.length || status2.behind.length ? ` (${few(status2.missing)})` : ""}`);
|
|
@@ -9550,6 +9669,7 @@ var TESTED_DSH_VERSION = "0.2.1-rc.5";
|
|
|
9550
9669
|
|
|
9551
9670
|
// src/agents/dsh-install.ts
|
|
9552
9671
|
var DSH_PACKAGE = "@deepseek-ai/dsh";
|
|
9672
|
+
var DSH_HOOK_BRIDGE_PACKAGE = "@deepseek-ai/dsh-hooks-claude-code";
|
|
9553
9673
|
var REGISTRY = "https://registry.npmjs.org";
|
|
9554
9674
|
var RETIRED_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
9555
9675
|
function dshInstallRoot(env) {
|
|
@@ -9558,6 +9678,15 @@ function dshInstallRoot(env) {
|
|
|
9558
9678
|
function dshPackageIn(prefix) {
|
|
9559
9679
|
return path13.join(prefix, "node_modules", "@deepseek-ai", "dsh");
|
|
9560
9680
|
}
|
|
9681
|
+
function dshHookBridgeIn(prefix) {
|
|
9682
|
+
const dir = path13.join(prefix, "node_modules", ...DSH_HOOK_BRIDGE_PACKAGE.split("/"));
|
|
9683
|
+
try {
|
|
9684
|
+
const manifest = JSON.parse(fs10.readFileSync(path13.join(dir, "package.json"), "utf8"));
|
|
9685
|
+
return path13.join(dir, manifest.main ?? "lib/index.js");
|
|
9686
|
+
} catch {
|
|
9687
|
+
return null;
|
|
9688
|
+
}
|
|
9689
|
+
}
|
|
9561
9690
|
function readManifest(prefix) {
|
|
9562
9691
|
try {
|
|
9563
9692
|
return JSON.parse(fs10.readFileSync(path13.join(dshPackageIn(prefix), "package.json"), "utf8"));
|
|
@@ -9586,9 +9715,12 @@ var realDeps = (env) => ({
|
|
|
9586
9715
|
// start; the rest check prebuilt binaries or print a notice. An older npm
|
|
9587
9716
|
// ignores the field and runs them all. stdout goes to stderr: a headless
|
|
9588
9717
|
// run's stdout is the answer.
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9718
|
+
// A prefix that holds a version already keeps its manifest: npm removes what
|
|
9719
|
+
// a manifest stops naming.
|
|
9720
|
+
npmInstall: (prefix, specs) => {
|
|
9721
|
+
const manifest = path13.join(prefix, "package.json");
|
|
9722
|
+
if (!fs10.existsSync(manifest)) fs10.writeFileSync(manifest, JSON.stringify({ private: true, allowScripts: { "@deepseek-ai/dsh-subprocess-local": true } }));
|
|
9723
|
+
return runNpm(["install", "--prefix", prefix, "--no-audit", "--no-fund", "--loglevel=error", ...specs], env, process.platform, ["ignore", 2, 2]);
|
|
9592
9724
|
},
|
|
9593
9725
|
engines: async (version) => (await fetchJson(`${REGISTRY}/${DSH_PACKAGE}/${version}`, { timeoutMs: 15e3 })).engines?.node,
|
|
9594
9726
|
nodeVersion: process.version,
|
|
@@ -9600,6 +9732,10 @@ async function ensureDsh(io, version = TESTED_DSH_VERSION, deps = realDeps(io.en
|
|
|
9600
9732
|
let manifest = readManifest(prefix);
|
|
9601
9733
|
if (manifest) {
|
|
9602
9734
|
requireNode(version, manifest.engines?.node, deps.nodeVersion);
|
|
9735
|
+
if (!dshHookBridgeIn(prefix)) {
|
|
9736
|
+
const code2 = await deps.npmInstall(prefix, [`${DSH_HOOK_BRIDGE_PACKAGE}@${version}`]).catch(() => 1);
|
|
9737
|
+
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.`);
|
|
9738
|
+
}
|
|
9603
9739
|
} else {
|
|
9604
9740
|
let engines;
|
|
9605
9741
|
try {
|
|
@@ -9611,7 +9747,7 @@ async function ensureDsh(io, version = TESTED_DSH_VERSION, deps = realDeps(io.en
|
|
|
9611
9747
|
const staging = `${prefix}.installing-${process.pid}`;
|
|
9612
9748
|
fs10.mkdirSync(staging, { recursive: true });
|
|
9613
9749
|
try {
|
|
9614
|
-
const code2 = await deps.npmInstall(staging, `${DSH_PACKAGE}@${version}`);
|
|
9750
|
+
const code2 = await deps.npmInstall(staging, [`${DSH_PACKAGE}@${version}`, `${DSH_HOOK_BRIDGE_PACKAGE}@${version}`]);
|
|
9615
9751
|
if (code2 !== 0) throw new CliError(`Installing dsh ${version} failed: npm exited with ${code2}. Check the network and run diverge dsh again.`);
|
|
9616
9752
|
try {
|
|
9617
9753
|
fs10.renameSync(staging, prefix);
|
|
@@ -10291,7 +10427,7 @@ function savedModel(home, surface) {
|
|
|
10291
10427
|
throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10292
10428
|
}
|
|
10293
10429
|
}
|
|
10294
|
-
async function overlayAndDefault(io, apiUrl, token, surface, requested, saved, launch) {
|
|
10430
|
+
async function overlayAndDefault(io, apiUrl, token, surface, requested, saved, launch, hooks, skillDirs) {
|
|
10295
10431
|
const served = (await listModels(apiUrl, token.access_token, surface)).map(servedModel);
|
|
10296
10432
|
const models = served.map((model) => model.id);
|
|
10297
10433
|
if (models.length === 0) throw new CliError(`the gateway serves no models on the ${surface} surface for this organization`);
|
|
@@ -10299,7 +10435,7 @@ async function overlayAndDefault(io, apiUrl, token, surface, requested, saved, l
|
|
|
10299
10435
|
const usable = !requested && saved && models.includes(saved.model) ? saved : null;
|
|
10300
10436
|
const defaultModel = dshDefaultModel(models, requested ?? usable?.model);
|
|
10301
10437
|
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 };
|
|
10438
|
+
return { overlay: dshPatchYaml({ baseUrl: surfaceBaseUrl(apiUrl, surface), surface, models: served, defaultModel, reasoningEffort: usable?.reasoningEffort, launch, hooks, skillDirs }), defaultModel };
|
|
10303
10439
|
}
|
|
10304
10440
|
async function runDshCommand(args, io) {
|
|
10305
10441
|
const terminator = args.indexOf("--");
|
|
@@ -10318,17 +10454,22 @@ async function runDshCommand(args, io) {
|
|
|
10318
10454
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10319
10455
|
const dsh = await ensureDsh(io);
|
|
10320
10456
|
const prefix = path18.join(dshInstallRoot(io.env), TESTED_DSH_VERSION);
|
|
10321
|
-
|
|
10457
|
+
const organizationId = tokenClaims(token.access_token).organizationId ?? values["organization-id"];
|
|
10458
|
+
if (!organizationId) throw new CliError("the gateway token names no organization; pass --organization-id");
|
|
10459
|
+
await skillsFor(io, apiUrl, "dsh", organizationId);
|
|
10460
|
+
const skillDirs = dshSkillDirs(organizationId, io.env);
|
|
10322
10461
|
const launch = { id: randomUUID(), client: `dsh/${TESTED_DSH_VERSION}` };
|
|
10323
10462
|
const home = dshHome(io.env);
|
|
10324
|
-
const
|
|
10463
|
+
const bridge = dshHookBridgeIn(prefix);
|
|
10464
|
+
const configPath = bridge ? repositoryHookConfig(process.cwd()) : null;
|
|
10465
|
+
const hooks = bridge && configPath ? { bridge, configPath } : null;
|
|
10466
|
+
const { overlay, defaultModel } = await overlayAndDefault(io, apiUrl, token, surface, values.model, savedModel(home, surface), launch, hooks, skillDirs);
|
|
10325
10467
|
const overlayPath = path18.join(home, `divergent-${surface}.yml`);
|
|
10326
|
-
writeTextAtomic(overlayPath, `${overlay}${dshPresetOverrides(prefix)}`);
|
|
10468
|
+
writeTextAtomic(overlayPath, `${overlay}${dshPresetOverrides(prefix, skillDirs)}`);
|
|
10327
10469
|
io.stderr(`dsh ${TESTED_DSH_VERSION} on divergent-${surface} as you (organization ${tokenClaims(token.access_token).organizationId}), default model ${defaultModel}`);
|
|
10470
|
+
if (hooks) io.stderr(`Running this repository's hooks (${hooks.configPath}).`);
|
|
10328
10471
|
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
10472
|
const source = tokenSource(io.env, apiUrl);
|
|
10330
|
-
const organizationId = tokenClaims(token.access_token).organizationId ?? values["organization-id"];
|
|
10331
|
-
if (!organizationId) throw new CliError("the gateway token names no organization; pass --organization-id");
|
|
10332
10473
|
io.stderr(`This session is organization ${organizationId}'s own record at the level its administrators set \u2014 off, metadata (tools, tokens, models; no content) or the whole conversation \u2014 named once its record opens. Yours to read: diverge tools call ${AGENT_SESSION_LIST_TOOL} (ADR 0121).`);
|
|
10333
10474
|
const tools = new ToolsClient({ apiUrl, organizationId, mode: "operator", clientId: clientId(io.env) }, io.env);
|
|
10334
10475
|
const publisher = new DshSessionPublisher({
|
package/package.json
CHANGED