@divergeai/cli 0.1.11 → 0.1.14

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.
Files changed (3) hide show
  1. package/README.md +35 -15
  2. package/dist/diverge.mjs +505 -169
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -57,7 +57,7 @@ the gateway, keeping everything else in the file:
57
57
  | `claude` | `~/.claude/settings.json` (or `./.claude/settings.local.json` with `--scope project`): the gateway base URL and `diverge token` as the key helper; `--project-id` adds the project header, `--model-discovery` lets Claude Code list the gateway's models |
58
58
  | `opencode` | `~/.config/opencode/opencode.json`: a `divergent-<surface>` provider reading `DIVERGENT_TOKEN` |
59
59
  | `codex` | `~/.codex/config.toml`: a `divergent` provider block with `diverge token` as its auth command; `--print` shows it instead |
60
- | `dsh` | `$DSH_HOME/divergent-<surface>.yml`: a patch overlay with the models the gateway serves, dsh's DeepSeek adapter, telemetry upload and search turned off, and the platform's web search served over `diverge tools bridge` (row `mcp-divergent`) |
60
+ | `dsh` | `$DSH_HOME/divergent-<surface>.yml`: a patch overlay with the models the gateway serves and dsh's DeepSeek adapter, telemetry upload and vendor search turned off; the Divergent agent preset; and your skills in `$DSH_HOME/skills`, through which the agent reaches every platform tool, web search included, from dsh's shell with `diverge tools` |
61
61
 
62
62
  `diverge agents run dsh` launches DeepSeek Harness on the overlay and keeps
63
63
  its token fresh in dsh's own credentials file until it exits. With nothing
@@ -77,27 +77,39 @@ Claude Code and Codex search through their model vendor's own server tool,
77
77
  which the gateway forwards unchanged when the organization's upstream is
78
78
  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
- per organization. The dsh overlay wires it in as `mcp__divergent__search_online`
81
- (dsh's DeepSeek-backed `web_search` needs a vendor key and is off; `web_fetch`
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:
80
+ per organization. In dsh the agent reaches it the way it reaches every
81
+ platform tool, from the shell: `diverge tools call search_online`, which the
82
+ installed `diverge` skill names. dsh's DeepSeek-backed `web_search` needs a
83
+ vendor key and is off; `web_fetch` stays. To give Claude Code or Codex the
84
+ same tool for a non-native upstream, the stdio bridge is the opt-in for a
85
+ client that wants it as a native tool:
89
86
 
90
87
  ```bash
91
88
  claude mcp add --scope user divergent -- diverge tools bridge --tools search_online
92
89
  ```
93
90
 
94
- ```toml
95
- # ~/.codex/config.toml
96
- [mcp_servers.divergent]
97
- command = "diverge"
98
- args = ["tools", "bridge", "--tools", "search_online"]
91
+ ## Skills
92
+
93
+ A skill is a procedure over the tools it names, in the Agent Skills format
94
+ every harness reads (`<name>/SKILL.md`). The organization authors its skills
95
+ on the platform; you get the ones whose every named tool you can run, plus
96
+ the CLI's own `diverge` skill, which tells an agent how to reach the platform's
97
+ tools from a shell.
98
+
99
+ ```bash
100
+ diverge skills list # what the platform serves you
101
+ diverge skills install # into ~/.claude/skills, ~/.codex/skills and $DSH_HOME/skills
102
+ diverge skills status # what each folder holds against the platform
99
103
  ```
100
104
 
105
+ `agents setup claude|codex|dsh` and `agents run dsh` install them as well, so
106
+ a harness the CLI set up has the skills from its first session. A refresh
107
+ replaces the CLI's own folders, retires the ones the organization retired, and
108
+ never touches a skill you authored beside them. The skills are the tool
109
+ surface: an agent reaches every platform tool from its shell with `diverge
110
+ tools`, which costs nothing until a tool is called, and the `diverge` skill
111
+ shows it how, file-based arguments and batches included.
112
+
101
113
  ## Platform tools
102
114
 
103
115
  Every command takes the scope it acts in: `--organization-id` (or
@@ -108,8 +120,16 @@ resource inside a project, `--project-id` (or `DIVERGENT_PROJECT_ID`).
108
120
  diverge tools list --filter rbac # find candidates
109
121
  diverge tools describe rbac_assign_role # one schema, on demand
110
122
  diverge tools call rbac_list_roles --args-json '{}'
123
+ diverge tools call inventory_lookup_stock --args-file args.json # or --args-json - to read stdin
124
+ diverge tools run calls.json --wait # several calls from one file, one JSON line of outcome each
111
125
  ```
112
126
 
127
+ Put arguments in a file or on stdin when a shell may touch them: Windows
128
+ PowerShell strips the double quotes inside an inline argument. `run` takes a
129
+ JSON array of `{"tool": "...", "args": {...}}` (or one per line), runs them in
130
+ order, and exits with the worst outcome: 1 failed, 4 rejected, 5 expired,
131
+ 3 pending, else 0.
132
+
113
133
  A write that needs a human is not refused: it is persisted as a pending
114
134
  invocation, decided in the platform's approval inbox, and read back here by
115
135
  id. Branch on the exit code, never on text:
package/dist/diverge.mjs CHANGED
@@ -114,17 +114,17 @@ var require_visit = __commonJS({
114
114
  visit.BREAK = BREAK;
115
115
  visit.SKIP = SKIP;
116
116
  visit.REMOVE = REMOVE;
117
- function visit_(key, node, visitor, path12) {
118
- const ctrl = callVisitor(key, node, visitor, path12);
117
+ function visit_(key, node, visitor, path13) {
118
+ const ctrl = callVisitor(key, node, visitor, path13);
119
119
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
120
- replaceNode(key, path12, ctrl);
121
- return visit_(key, ctrl, visitor, path12);
120
+ replaceNode(key, path13, ctrl);
121
+ return visit_(key, ctrl, visitor, path13);
122
122
  }
123
123
  if (typeof ctrl !== "symbol") {
124
124
  if (identity.isCollection(node)) {
125
- path12 = Object.freeze(path12.concat(node));
125
+ path13 = Object.freeze(path13.concat(node));
126
126
  for (let i = 0; i < node.items.length; ++i) {
127
- const ci = visit_(i, node.items[i], visitor, path12);
127
+ const ci = visit_(i, node.items[i], visitor, path13);
128
128
  if (typeof ci === "number")
129
129
  i = ci - 1;
130
130
  else if (ci === BREAK)
@@ -135,13 +135,13 @@ var require_visit = __commonJS({
135
135
  }
136
136
  }
137
137
  } else if (identity.isPair(node)) {
138
- path12 = Object.freeze(path12.concat(node));
139
- const ck = visit_("key", node.key, visitor, path12);
138
+ path13 = Object.freeze(path13.concat(node));
139
+ const ck = visit_("key", node.key, visitor, path13);
140
140
  if (ck === BREAK)
141
141
  return BREAK;
142
142
  else if (ck === REMOVE)
143
143
  node.key = null;
144
- const cv = visit_("value", node.value, visitor, path12);
144
+ const cv = visit_("value", node.value, visitor, path13);
145
145
  if (cv === BREAK)
146
146
  return BREAK;
147
147
  else if (cv === REMOVE)
@@ -162,17 +162,17 @@ var require_visit = __commonJS({
162
162
  visitAsync.BREAK = BREAK;
163
163
  visitAsync.SKIP = SKIP;
164
164
  visitAsync.REMOVE = REMOVE;
165
- async function visitAsync_(key, node, visitor, path12) {
166
- const ctrl = await callVisitor(key, node, visitor, path12);
165
+ async function visitAsync_(key, node, visitor, path13) {
166
+ const ctrl = await callVisitor(key, node, visitor, path13);
167
167
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
168
- replaceNode(key, path12, ctrl);
169
- return visitAsync_(key, ctrl, visitor, path12);
168
+ replaceNode(key, path13, ctrl);
169
+ return visitAsync_(key, ctrl, visitor, path13);
170
170
  }
171
171
  if (typeof ctrl !== "symbol") {
172
172
  if (identity.isCollection(node)) {
173
- path12 = Object.freeze(path12.concat(node));
173
+ path13 = Object.freeze(path13.concat(node));
174
174
  for (let i = 0; i < node.items.length; ++i) {
175
- const ci = await visitAsync_(i, node.items[i], visitor, path12);
175
+ const ci = await visitAsync_(i, node.items[i], visitor, path13);
176
176
  if (typeof ci === "number")
177
177
  i = ci - 1;
178
178
  else if (ci === BREAK)
@@ -183,13 +183,13 @@ var require_visit = __commonJS({
183
183
  }
184
184
  }
185
185
  } else if (identity.isPair(node)) {
186
- path12 = Object.freeze(path12.concat(node));
187
- const ck = await visitAsync_("key", node.key, visitor, path12);
186
+ path13 = Object.freeze(path13.concat(node));
187
+ const ck = await visitAsync_("key", node.key, visitor, path13);
188
188
  if (ck === BREAK)
189
189
  return BREAK;
190
190
  else if (ck === REMOVE)
191
191
  node.key = null;
192
- const cv = await visitAsync_("value", node.value, visitor, path12);
192
+ const cv = await visitAsync_("value", node.value, visitor, path13);
193
193
  if (cv === BREAK)
194
194
  return BREAK;
195
195
  else if (cv === REMOVE)
@@ -216,23 +216,23 @@ var require_visit = __commonJS({
216
216
  }
217
217
  return visitor;
218
218
  }
219
- function callVisitor(key, node, visitor, path12) {
219
+ function callVisitor(key, node, visitor, path13) {
220
220
  if (typeof visitor === "function")
221
- return visitor(key, node, path12);
221
+ return visitor(key, node, path13);
222
222
  if (identity.isMap(node))
223
- return visitor.Map?.(key, node, path12);
223
+ return visitor.Map?.(key, node, path13);
224
224
  if (identity.isSeq(node))
225
- return visitor.Seq?.(key, node, path12);
225
+ return visitor.Seq?.(key, node, path13);
226
226
  if (identity.isPair(node))
227
- return visitor.Pair?.(key, node, path12);
227
+ return visitor.Pair?.(key, node, path13);
228
228
  if (identity.isScalar(node))
229
- return visitor.Scalar?.(key, node, path12);
229
+ return visitor.Scalar?.(key, node, path13);
230
230
  if (identity.isAlias(node))
231
- return visitor.Alias?.(key, node, path12);
231
+ return visitor.Alias?.(key, node, path13);
232
232
  return void 0;
233
233
  }
234
- function replaceNode(key, path12, node) {
235
- const parent = path12[path12.length - 1];
234
+ function replaceNode(key, path13, node) {
235
+ const parent = path13[path13.length - 1];
236
236
  if (identity.isCollection(parent)) {
237
237
  parent.items[key] = node;
238
238
  } else if (identity.isPair(parent)) {
@@ -842,10 +842,10 @@ var require_Collection = __commonJS({
842
842
  var createNode = require_createNode();
843
843
  var identity = require_identity();
844
844
  var Node = require_Node();
845
- function collectionFromPath(schema, path12, value) {
845
+ function collectionFromPath(schema, path13, value) {
846
846
  let v = value;
847
- for (let i = path12.length - 1; i >= 0; --i) {
848
- const k = path12[i];
847
+ for (let i = path13.length - 1; i >= 0; --i) {
848
+ const k = path13[i];
849
849
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
850
850
  const a = [];
851
851
  a[k] = v;
@@ -864,7 +864,7 @@ var require_Collection = __commonJS({
864
864
  sourceObjects: /* @__PURE__ */ new Map()
865
865
  });
866
866
  }
867
- var isEmptyPath = (path12) => path12 == null || typeof path12 === "object" && !!path12[Symbol.iterator]().next().done;
867
+ var isEmptyPath = (path13) => path13 == null || typeof path13 === "object" && !!path13[Symbol.iterator]().next().done;
868
868
  var Collection = class extends Node.NodeBase {
869
869
  constructor(type, schema) {
870
870
  super(type);
@@ -894,11 +894,11 @@ var require_Collection = __commonJS({
894
894
  * be a Pair instance or a `{ key, value }` object, which may not have a key
895
895
  * that already exists in the map.
896
896
  */
897
- addIn(path12, value) {
898
- if (isEmptyPath(path12))
897
+ addIn(path13, value) {
898
+ if (isEmptyPath(path13))
899
899
  this.add(value);
900
900
  else {
901
- const [key, ...rest] = path12;
901
+ const [key, ...rest] = path13;
902
902
  const node = this.get(key, true);
903
903
  if (identity.isCollection(node))
904
904
  node.addIn(rest, value);
@@ -912,8 +912,8 @@ var require_Collection = __commonJS({
912
912
  * Removes a value from the collection.
913
913
  * @returns `true` if the item was found and removed.
914
914
  */
915
- deleteIn(path12) {
916
- const [key, ...rest] = path12;
915
+ deleteIn(path13) {
916
+ const [key, ...rest] = path13;
917
917
  if (rest.length === 0)
918
918
  return this.delete(key);
919
919
  const node = this.get(key, true);
@@ -927,8 +927,8 @@ var require_Collection = __commonJS({
927
927
  * scalar values from their surrounding node; to disable set `keepScalar` to
928
928
  * `true` (collections are always returned intact).
929
929
  */
930
- getIn(path12, keepScalar) {
931
- const [key, ...rest] = path12;
930
+ getIn(path13, keepScalar) {
931
+ const [key, ...rest] = path13;
932
932
  const node = this.get(key, true);
933
933
  if (rest.length === 0)
934
934
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -946,8 +946,8 @@ var require_Collection = __commonJS({
946
946
  /**
947
947
  * Checks if the collection includes a value with the key `key`.
948
948
  */
949
- hasIn(path12) {
950
- const [key, ...rest] = path12;
949
+ hasIn(path13) {
950
+ const [key, ...rest] = path13;
951
951
  if (rest.length === 0)
952
952
  return this.has(key);
953
953
  const node = this.get(key, true);
@@ -957,8 +957,8 @@ var require_Collection = __commonJS({
957
957
  * Sets a value in this collection. For `!!set`, `value` needs to be a
958
958
  * boolean to add/remove the item from the set.
959
959
  */
960
- setIn(path12, value) {
961
- const [key, ...rest] = path12;
960
+ setIn(path13, value) {
961
+ const [key, ...rest] = path13;
962
962
  if (rest.length === 0) {
963
963
  this.set(key, value);
964
964
  } else {
@@ -3473,9 +3473,9 @@ var require_Document = __commonJS({
3473
3473
  this.contents.add(value);
3474
3474
  }
3475
3475
  /** Adds a value to the document. */
3476
- addIn(path12, value) {
3476
+ addIn(path13, value) {
3477
3477
  if (assertCollection(this.contents))
3478
- this.contents.addIn(path12, value);
3478
+ this.contents.addIn(path13, value);
3479
3479
  }
3480
3480
  /**
3481
3481
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -3550,14 +3550,14 @@ var require_Document = __commonJS({
3550
3550
  * Removes a value from the document.
3551
3551
  * @returns `true` if the item was found and removed.
3552
3552
  */
3553
- deleteIn(path12) {
3554
- if (Collection.isEmptyPath(path12)) {
3553
+ deleteIn(path13) {
3554
+ if (Collection.isEmptyPath(path13)) {
3555
3555
  if (this.contents == null)
3556
3556
  return false;
3557
3557
  this.contents = null;
3558
3558
  return true;
3559
3559
  }
3560
- return assertCollection(this.contents) ? this.contents.deleteIn(path12) : false;
3560
+ return assertCollection(this.contents) ? this.contents.deleteIn(path13) : false;
3561
3561
  }
3562
3562
  /**
3563
3563
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -3572,10 +3572,10 @@ var require_Document = __commonJS({
3572
3572
  * scalar values from their surrounding node; to disable set `keepScalar` to
3573
3573
  * `true` (collections are always returned intact).
3574
3574
  */
3575
- getIn(path12, keepScalar) {
3576
- if (Collection.isEmptyPath(path12))
3575
+ getIn(path13, keepScalar) {
3576
+ if (Collection.isEmptyPath(path13))
3577
3577
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
3578
- return identity.isCollection(this.contents) ? this.contents.getIn(path12, keepScalar) : void 0;
3578
+ return identity.isCollection(this.contents) ? this.contents.getIn(path13, keepScalar) : void 0;
3579
3579
  }
3580
3580
  /**
3581
3581
  * Checks if the document includes a value with the key `key`.
@@ -3586,10 +3586,10 @@ var require_Document = __commonJS({
3586
3586
  /**
3587
3587
  * Checks if the document includes a value at `path`.
3588
3588
  */
3589
- hasIn(path12) {
3590
- if (Collection.isEmptyPath(path12))
3589
+ hasIn(path13) {
3590
+ if (Collection.isEmptyPath(path13))
3591
3591
  return this.contents !== void 0;
3592
- return identity.isCollection(this.contents) ? this.contents.hasIn(path12) : false;
3592
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path13) : false;
3593
3593
  }
3594
3594
  /**
3595
3595
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -3606,13 +3606,13 @@ var require_Document = __commonJS({
3606
3606
  * Sets a value in this document. For `!!set`, `value` needs to be a
3607
3607
  * boolean to add/remove the item from the set.
3608
3608
  */
3609
- setIn(path12, value) {
3610
- if (Collection.isEmptyPath(path12)) {
3609
+ setIn(path13, value) {
3610
+ if (Collection.isEmptyPath(path13)) {
3611
3611
  this.contents = value;
3612
3612
  } else if (this.contents == null) {
3613
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path12), value);
3613
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path13), value);
3614
3614
  } else if (assertCollection(this.contents)) {
3615
- this.contents.setIn(path12, value);
3615
+ this.contents.setIn(path13, value);
3616
3616
  }
3617
3617
  }
3618
3618
  /**
@@ -5572,9 +5572,9 @@ var require_cst_visit = __commonJS({
5572
5572
  visit.BREAK = BREAK;
5573
5573
  visit.SKIP = SKIP;
5574
5574
  visit.REMOVE = REMOVE;
5575
- visit.itemAtPath = (cst, path12) => {
5575
+ visit.itemAtPath = (cst, path13) => {
5576
5576
  let item = cst;
5577
- for (const [field, index] of path12) {
5577
+ for (const [field, index] of path13) {
5578
5578
  const tok = item?.[field];
5579
5579
  if (tok && "items" in tok) {
5580
5580
  item = tok.items[index];
@@ -5583,23 +5583,23 @@ var require_cst_visit = __commonJS({
5583
5583
  }
5584
5584
  return item;
5585
5585
  };
5586
- visit.parentCollection = (cst, path12) => {
5587
- const parent = visit.itemAtPath(cst, path12.slice(0, -1));
5588
- const field = path12[path12.length - 1][0];
5586
+ visit.parentCollection = (cst, path13) => {
5587
+ const parent = visit.itemAtPath(cst, path13.slice(0, -1));
5588
+ const field = path13[path13.length - 1][0];
5589
5589
  const coll = parent?.[field];
5590
5590
  if (coll && "items" in coll)
5591
5591
  return coll;
5592
5592
  throw new Error("Parent collection not found");
5593
5593
  };
5594
- function _visit(path12, item, visitor) {
5595
- let ctrl = visitor(item, path12);
5594
+ function _visit(path13, item, visitor) {
5595
+ let ctrl = visitor(item, path13);
5596
5596
  if (typeof ctrl === "symbol")
5597
5597
  return ctrl;
5598
5598
  for (const field of ["key", "value"]) {
5599
5599
  const token = item[field];
5600
5600
  if (token && "items" in token) {
5601
5601
  for (let i = 0; i < token.items.length; ++i) {
5602
- const ci = _visit(Object.freeze(path12.concat([[field, i]])), token.items[i], visitor);
5602
+ const ci = _visit(Object.freeze(path13.concat([[field, i]])), token.items[i], visitor);
5603
5603
  if (typeof ci === "number")
5604
5604
  i = ci - 1;
5605
5605
  else if (ci === BREAK)
@@ -5610,10 +5610,10 @@ var require_cst_visit = __commonJS({
5610
5610
  }
5611
5611
  }
5612
5612
  if (typeof ctrl === "function" && field === "key")
5613
- ctrl = ctrl(item, path12);
5613
+ ctrl = ctrl(item, path13);
5614
5614
  }
5615
5615
  }
5616
- return typeof ctrl === "function" ? ctrl(item, path12) : ctrl;
5616
+ return typeof ctrl === "function" ? ctrl(item, path13) : ctrl;
5617
5617
  }
5618
5618
  exports.visit = visit;
5619
5619
  }
@@ -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 fs10 = this.flowScalar(this.type);
6918
+ const fs13 = this.flowScalar(this.type);
6919
6919
  if (atNextItem || it.value) {
6920
- map.items.push({ start, key: fs10, sep: [] });
6920
+ map.items.push({ start, key: fs13, sep: [] });
6921
6921
  this.onKeyLine = true;
6922
6922
  } else if (it.sep) {
6923
- this.stack.push(fs10);
6923
+ this.stack.push(fs13);
6924
6924
  } else {
6925
- Object.assign(it, { key: fs10, sep: [] });
6925
+ Object.assign(it, { key: fs13, 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 fs10 = this.flowScalar(this.type);
7053
+ const fs13 = this.flowScalar(this.type);
7054
7054
  if (!it || it.value)
7055
- fc.items.push({ start: [], key: fs10, sep: [] });
7055
+ fc.items.push({ start: [], key: fs13, sep: [] });
7056
7056
  else if (it.sep)
7057
- this.stack.push(fs10);
7057
+ this.stack.push(fs13);
7058
7058
  else
7059
- Object.assign(it, { key: fs10, sep: [] });
7059
+ Object.assign(it, { key: fs13, 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 fs7 from "node:fs";
7386
- import path10 from "node:path";
7387
- import { parseArgs } from "node:util";
7385
+ import fs8 from "node:fs";
7386
+ import path11 from "node:path";
7387
+ import { parseArgs as parseArgs2 } from "node:util";
7388
7388
 
7389
7389
  // src/version.ts
7390
- var VERSION = true ? "0.1.11" : "dev";
7390
+ var VERSION = true ? "0.1.14" : "dev";
7391
7391
  var USER_AGENT = `diverge/${VERSION}`;
7392
7392
 
7393
7393
  // src/http.ts
@@ -7928,7 +7928,6 @@ function dshDefaultModel(models, requested) {
7928
7928
  if (requested) return requested;
7929
7929
  return models.includes(DSH_SHIPPED_DEFAULT_MODEL) ? DSH_SHIPPED_DEFAULT_MODEL : models[0];
7930
7930
  }
7931
- var DSH_BRIDGE_TOOLS = ["search_online"];
7932
7931
  function dshPatchYaml(options) {
7933
7932
  const tokenEnv = options.tokenEnv ?? "DIVERGENT_TOKEN";
7934
7933
  const route = `divergent-${options.surface}`;
@@ -7961,42 +7960,20 @@ function dshPatchYaml(options) {
7961
7960
  " disabled: true",
7962
7961
  "- id: session-telemetry-otel",
7963
7962
  " disabled: true",
7964
- ...options.bridge ? dshSearchRows(options.bridge) : [],
7963
+ ...dshSearchRows(),
7965
7964
  ""
7966
7965
  ].join("\n");
7967
7966
  }
7968
- function dshSearchRows(bridge) {
7969
- const rows = [
7967
+ function dshSearchRows() {
7968
+ return [
7970
7969
  "- id: web-search-deepseek",
7971
7970
  " disabled: true",
7972
7971
  "- id: tool-web",
7973
7972
  " config:",
7974
7973
  " search: false",
7975
7974
  " fetch: true",
7976
- " searchTimeoutMs: 60000",
7977
- "- insert:",
7978
- " - id: mcp-divergent",
7979
- " name: '@deepseek-ai/dsh-mcp-client'",
7980
- " config:",
7981
- " serverName: divergent",
7982
- " transport: stdio",
7983
- ` command: ${JSON.stringify(bridge.command)}`,
7984
- ` args: [${bridge.args.map((arg) => JSON.stringify(arg)).join(", ")}]`
7975
+ " searchTimeoutMs: 60000"
7985
7976
  ];
7986
- const env = Object.entries(bridge.env ?? {});
7987
- if (env.length) {
7988
- rows.push(" env:");
7989
- for (const [key, value] of env) rows.push(` ${key}: ${JSON.stringify(value)}`);
7990
- }
7991
- return rows;
7992
- }
7993
- function dshBridge(self, env = process.env) {
7994
- const passthrough = {};
7995
- for (const key of ["DIVERGE_HOME", "DIVERGENT_API_URL", "DIVERGENT_DIVERGE_CLIENT_ID"]) {
7996
- const value = env[key]?.trim();
7997
- if (value) passthrough[key] = value;
7998
- }
7999
- return { command: self.command, args: [...self.args, "tools", "bridge", "--tools", DSH_BRIDGE_TOOLS.join(",")], env: Object.keys(passthrough).length ? passthrough : void 0 };
8000
7977
  }
8001
7978
  function servedIds(overlay) {
8002
7979
  const rows = (0, import_yaml.parse)(overlay) ?? [];
@@ -8079,8 +8056,8 @@ function divergentPreset(shippedComposition) {
8079
8056
  }
8080
8057
  return [
8081
8058
  `# 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",
8059
+ "# search left out of the model's tools; platform tools, search included, are reached",
8060
+ "# from the shell through `diverge tools`. Derived from the installed dsh by `diverge agents",
8084
8061
  "# setup dsh` and `diverge agents run dsh` on every run; edits do not survive.",
8085
8062
  ...lines.slice(0, start),
8086
8063
  ...row,
@@ -8237,11 +8214,242 @@ function runDsh(options) {
8237
8214
  });
8238
8215
  }
8239
8216
 
8240
- // src/agents/rotate.ts
8217
+ // src/commands/skills.ts
8218
+ import { parseArgs } from "node:util";
8219
+
8220
+ // src/skills/bundled.ts
8221
+ var DIVERGE_SKILL = {
8222
+ name: "diverge",
8223
+ description: "Reach the Divergent platform's tools from a shell with the diverge CLI: discover, read one schema, call, and read back a call that waits on a person. Use when a task needs the organization's records, diagnostics, documents or governed actions, which the platform holds and this skill's siblings name.",
8224
+ tools: [],
8225
+ updatedAt: `diverge/${VERSION}`,
8226
+ content: `---
8227
+ name: diverge
8228
+ description: Reach the Divergent platform's tools from a shell with the diverge CLI - discover, read one schema, call, and read back a call that waits on a person. Use when a task needs the organization's records, diagnostics, documents or governed actions, which the platform holds and this skill's siblings name.
8229
+ ---
8230
+
8231
+ # diverge: the platform's tools from a shell
8232
+
8233
+ The organization's tools run on the platform, as the signed-in person, with
8234
+ their permissions and audit trail. Nothing runs locally; the CLI is the
8235
+ transport. Keep the catalog out of context: pull the one schema you need.
8236
+
8237
+ \`\`\`bash
8238
+ diverge tools list --filter <word> # find candidates by name or capability
8239
+ diverge tools describe <tool> # the one schema, on demand
8240
+ diverge tools call <tool> --args-file args.json # arguments from a file (see below)
8241
+ diverge tools call search_online --args-json '{"query": "..."}' # web search is a platform tool too
8242
+ \`\`\`
8243
+
8244
+ The other skills installed beside this one are the organization's own
8245
+ procedures; each names the tools it is about. Prefer a procedure over
8246
+ guessing a tool.
8247
+
8248
+ ## Arguments: write them to a file
8249
+
8250
+ Shells mangle JSON. Windows PowerShell strips the double quotes inside an
8251
+ inline argument, so \`--args-json '{"a":"b"}'\` can arrive as \`{a:b}\`.
8252
+ Write the object to a file and pass \`--args-file\`, or pipe it with
8253
+ \`--args-json -\`; both are exact on every shell. A worked call:
8254
+
8255
+ \`\`\`bash
8256
+ diverge tools describe divix-mcp_implementation_search_source
8257
+ cat > /tmp/args.json <<'JSON'
8258
+ {"query": "cr9b5_serial", "limit": 20}
8259
+ JSON
8260
+ diverge tools call divix-mcp_implementation_search_source --args-file /tmp/args.json
8261
+ \`\`\`
8262
+
8263
+ ## Several calls at once
8264
+
8265
+ When a task needs a sequence of calls, write them to one file and run it
8266
+ once instead of one round trip each: \`diverge tools run calls.json\`, a JSON
8267
+ array of \`{"tool": "...", "args": {...}}\` (or one per line). Calls run in
8268
+ order; each prints one JSON line of outcome; the exit code is the worst of
8269
+ them.
8270
+
8271
+ ## Exit codes are the contract
8272
+
8273
+ | Code | Meaning | Do |
8274
+ | --- | --- | --- |
8275
+ | 0 | executed | use the result |
8276
+ | 3 | pending: a person must decide it in Compass | hand over the printed link, keep working, read it back later |
8277
+ | 4 | rejected, with the person's reason | re-plan; the reason is data |
8278
+ | 5 | expired: nobody decided in time | raise a fresh call if still needed |
8279
+ | 1 | executed but failed, or a transport error | read the message; do not re-raise blindly |
8280
+
8281
+ A pending call prints its invocation id and a link. Read the outcome with
8282
+ \`diverge tools invocation <id>\` (add \`--wait\` to poll at the platform's
8283
+ cadence). Never decide your own invocation from any surface you can drive.
8284
+
8285
+ ## Session
8286
+
8287
+ - \`diverge status\` says who is signed in and at which organization; every
8288
+ call runs there. \`diverge org use <id|name>\` moves the session.
8289
+ - A missing login or scope prints the command to run; \`diverge login\` opens
8290
+ the browser.
8291
+ - \`--json\` on \`list\`, \`describe\` and \`invocation\` gives raw records.
8292
+ `
8293
+ };
8294
+
8295
+ // src/skills/install.ts
8296
+ import fs6 from "node:fs";
8241
8297
  import os6 from "node:os";
8242
8298
  import path8 from "node:path";
8299
+ var HARNESSES = ["claude", "codex", "dsh"];
8300
+ var SKILLS_MANIFEST = ".diverge-skills.json";
8301
+ function skillsRoot(harness, env = process.env) {
8302
+ switch (harness) {
8303
+ case "claude":
8304
+ return path8.join(env.CLAUDE_CONFIG_DIR?.trim() || path8.join(os6.homedir(), ".claude"), "skills");
8305
+ case "codex":
8306
+ return path8.join(env.CODEX_HOME?.trim() || path8.join(os6.homedir(), ".codex"), "skills");
8307
+ case "dsh":
8308
+ return path8.join(dshHome(env), "skills");
8309
+ }
8310
+ }
8311
+ function readSkillsManifest(root) {
8312
+ try {
8313
+ const parsed = JSON.parse(fs6.readFileSync(path8.join(root, SKILLS_MANIFEST), "utf8"));
8314
+ if (parsed && typeof parsed === "object" && typeof parsed.skills === "object") return parsed;
8315
+ } catch {
8316
+ }
8317
+ return null;
8318
+ }
8319
+ function few(names, limit = 6) {
8320
+ return names.length <= limit ? names.join(", ") : `${names.slice(0, limit).join(", ")} and ${names.length - limit} more`;
8321
+ }
8322
+ function installSkills(root, skills, source, now = () => /* @__PURE__ */ new Date()) {
8323
+ const previous = readSkillsManifest(root);
8324
+ const held = previous?.skills ?? {};
8325
+ const written = [];
8326
+ const added = [];
8327
+ const updated = [];
8328
+ for (const skill of skills) {
8329
+ if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(skill.name)) throw new Error(`skill name ${JSON.stringify(skill.name)} is not a folder name`);
8330
+ writeTextAtomic(path8.join(root, skill.name, "SKILL.md"), skill.content, 420);
8331
+ written.push(skill.name);
8332
+ if (!(skill.name in held)) added.push(skill.name);
8333
+ else if (held[skill.name] !== skill.updatedAt) updated.push(skill.name);
8334
+ }
8335
+ const removed = [];
8336
+ for (const name of Object.keys(previous?.skills ?? {})) {
8337
+ if (skills.some((skill) => skill.name === name)) continue;
8338
+ fs6.rmSync(path8.join(root, name), { recursive: true, force: true });
8339
+ removed.push(name);
8340
+ }
8341
+ const manifest = {
8342
+ installedAt: now().toISOString(),
8343
+ source,
8344
+ skills: Object.fromEntries(skills.map((skill) => [skill.name, skill.updatedAt]))
8345
+ };
8346
+ writeJsonAtomic(path8.join(root, SKILLS_MANIFEST), manifest, 420);
8347
+ return { root, written, added, updated, removed };
8348
+ }
8349
+ function skillsStatus(root, skills) {
8350
+ const manifest = readSkillsManifest(root);
8351
+ const held = manifest?.skills ?? {};
8352
+ const current = [];
8353
+ const behind = [];
8354
+ const missing = [];
8355
+ for (const skill of skills) {
8356
+ if (!(skill.name in held) || !fs6.existsSync(path8.join(root, skill.name, "SKILL.md"))) missing.push(skill.name);
8357
+ else if (held[skill.name] !== skill.updatedAt) behind.push(skill.name);
8358
+ else current.push(skill.name);
8359
+ }
8360
+ const retired = Object.keys(held).filter((name) => !skills.some((skill) => skill.name === name));
8361
+ return { root, installedAt: manifest?.installedAt ?? null, current, behind, missing, retired };
8362
+ }
8363
+
8364
+ // src/skills/installable.ts
8365
+ async function fetchInstallableSkills(baseUrl, accessToken) {
8366
+ const body = await fetchJson(
8367
+ `${baseUrl.replace(/\/+$/, "")}/api/plugins/symphony/skills/installable`,
8368
+ { headers: { Authorization: `Bearer ${accessToken}` } }
8369
+ );
8370
+ if (!body.success || !body.data) throw new CliError(body.error ?? "The platform answered without skills");
8371
+ return body.data;
8372
+ }
8373
+
8374
+ // src/commands/skills.ts
8375
+ var skillsUsage = `diverge skills <list|install|status> [--harness claude,codex,dsh] [--into <dir>]
8376
+
8377
+ list the skills the platform serves you: your organization's skills whose tools you can run
8378
+ install write them, and the CLI's own "diverge" skill, into each harness's user skill folder
8379
+ (~/.claude/skills, ~/.codex/skills, $DSH_HOME/skills); retires the ones the organization retired;
8380
+ reports what changed \u2014 the full list is \`skills list\`
8381
+ status what each folder holds against what the platform serves now
8382
+
8383
+ A skill is a procedure over the tools it names; you get the ones whose every
8384
+ tool you may run. Re-run install after the organization changes its skills.`;
8385
+ async function installableSkillSet(apiUrl, env) {
8386
+ const source = { tokenFile: tokenFile(env), baseUrl: apiUrl, clientId: clientId(env) };
8387
+ const token = await getUserToken(source);
8388
+ const organizationId = tokenClaims(token.access_token).organizationId;
8389
+ if (!organizationId) throw new CliError("The login is not bound to an organization; run: diverge org use <id|name>");
8390
+ const served = await fetchInstallableSkills(apiUrl, token.access_token);
8391
+ return { skills: [DIVERGE_SKILL, ...served.skills], organizationId };
8392
+ }
8393
+ function harnessesOf(flag) {
8394
+ if (!flag) return [...HARNESSES];
8395
+ const chosen = flag.split(",").map((name) => name.trim()).filter(Boolean);
8396
+ for (const name of chosen) if (!HARNESSES.includes(name)) throw new CliError(`--harness must name some of: ${HARNESSES.join(", ")}`);
8397
+ return chosen;
8398
+ }
8399
+ function installForHarness(io, harness, skills, apiUrl, into) {
8400
+ const root = into ?? skillsRoot(harness, io.env);
8401
+ const result = installSkills(root, skills, apiUrl);
8402
+ const changes = [
8403
+ ...result.added.length ? [`${result.added.length} new: ${few(result.added)}`] : [],
8404
+ ...result.updated.length ? [`${result.updated.length} updated: ${few(result.updated)}`] : [],
8405
+ ...result.removed.length ? [`${result.removed.length} retired: ${few(result.removed)}`] : []
8406
+ ];
8407
+ io.stdout(`${harness}: ${result.written.length} skills in ${root}${changes.length ? ` (${changes.join("; ")})` : " (unchanged)"}`);
8408
+ }
8409
+ async function runSkills(args, io) {
8410
+ const [verb, ...rest] = args;
8411
+ const { values } = parseArgs({ args: rest, options: { harness: { type: "string" }, into: { type: "string" } }, strict: true });
8412
+ const apiUrl = resolveApiUrl(void 0, io.env);
8413
+ switch (verb) {
8414
+ case "list": {
8415
+ const { skills, organizationId } = await installableSkillSet(apiUrl, io.env);
8416
+ io.stdout(`Skills the platform serves you at organization ${organizationId}:`);
8417
+ for (const skill of skills) io.stdout(` ${skill.name.padEnd(36)} ${skill.tools.length ? skill.tools.join(", ") : "(the CLI itself)"}`);
8418
+ return ExitCode.Ok;
8419
+ }
8420
+ case "install": {
8421
+ const { skills } = await installableSkillSet(apiUrl, io.env);
8422
+ const harnesses = values.into ? [harnessesOf(values.harness)[0]] : harnessesOf(values.harness);
8423
+ for (const harness of harnesses) installForHarness(io, harness, skills, apiUrl, values.into);
8424
+ return ExitCode.Ok;
8425
+ }
8426
+ case "status": {
8427
+ const { skills } = await installableSkillSet(apiUrl, io.env);
8428
+ let behind = false;
8429
+ for (const harness of harnessesOf(values.harness)) {
8430
+ const status2 = skillsStatus(values.into ?? skillsRoot(harness, io.env), skills);
8431
+ const parts = [`${status2.current.length} current`];
8432
+ if (status2.behind.length) parts.push(`${status2.behind.length} behind (${few(status2.behind)})`);
8433
+ if (status2.missing.length) parts.push(`${status2.missing.length} missing${status2.current.length || status2.behind.length ? ` (${few(status2.missing)})` : ""}`);
8434
+ if (status2.retired.length) parts.push(`${status2.retired.length} retired (${few(status2.retired)})`);
8435
+ behind ||= status2.behind.length + status2.missing.length + status2.retired.length > 0;
8436
+ io.stdout(`${harness}: ${parts.join(", ")} in ${status2.root}${status2.installedAt ? ` (installed ${status2.installedAt})` : " (never installed)"}`);
8437
+ }
8438
+ if (behind) io.stdout("Run: diverge skills install");
8439
+ return ExitCode.Ok;
8440
+ }
8441
+ default:
8442
+ throw new CliError(`skills needs one of: list, install, status
8443
+
8444
+ ${skillsUsage}`);
8445
+ }
8446
+ }
8447
+
8448
+ // src/agents/rotate.ts
8449
+ import os7 from "node:os";
8450
+ import path9 from "node:path";
8243
8451
  function defaultTokenFilePath() {
8244
- return path8.join(os6.homedir(), ".divergent", "token");
8452
+ return path9.join(os7.homedir(), ".divergent", "token");
8245
8453
  }
8246
8454
  var MIN_ROTATE_INTERVAL_SECONDS = 60;
8247
8455
  async function rotateTokenFile(loop) {
@@ -8256,19 +8464,19 @@ async function rotateTokenFile(loop) {
8256
8464
  }
8257
8465
 
8258
8466
  // src/agents/self-command.ts
8259
- import fs6 from "node:fs";
8260
- import path9 from "node:path";
8467
+ import fs7 from "node:fs";
8468
+ import path10 from "node:path";
8261
8469
  function resolveSelfCommand(argv = process.argv, platform = process.platform, execPath = process.execPath) {
8262
8470
  const script = argv[1];
8263
8471
  if (!script) return { command: execPath, args: [] };
8264
8472
  if (platform !== "win32") {
8265
8473
  try {
8266
- fs6.accessSync(script, fs6.constants.X_OK);
8267
- return { command: path9.resolve(script), args: [] };
8474
+ fs7.accessSync(script, fs7.constants.X_OK);
8475
+ return { command: path10.resolve(script), args: [] };
8268
8476
  } catch {
8269
8477
  }
8270
8478
  }
8271
- return { command: execPath, args: [fs6.realpathSync(script)] };
8479
+ return { command: execPath, args: [fs7.realpathSync(script)] };
8272
8480
  }
8273
8481
  function shellCommand(self, ...trailing) {
8274
8482
  return [self.command, ...self.args, ...trailing].map((part) => /[\s"]/.test(part) ? `"${part.replace(/"/g, '\\"')}"` : part).join(" ");
@@ -8295,8 +8503,8 @@ function surfaceOf(value, fallback) {
8295
8503
  throw new CliError(`--surface must be one of: ${SURFACES.join(", ")}`);
8296
8504
  }
8297
8505
  function readJson(file) {
8298
- if (!fs7.existsSync(file)) return null;
8299
- return JSON.parse(fs7.readFileSync(file, "utf8"));
8506
+ if (!fs8.existsSync(file)) return null;
8507
+ return JSON.parse(fs8.readFileSync(file, "utf8"));
8300
8508
  }
8301
8509
  async function servedModels(apiUrl, token, surface) {
8302
8510
  return (await listModels(apiUrl, token.access_token, surface)).map((entry) => entry.id);
@@ -8313,25 +8521,32 @@ function syncDivergentPreset(io, binary, home) {
8313
8521
  return;
8314
8522
  }
8315
8523
  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);
8524
+ const settingsPath = path11.join(home, "settings.yaml");
8525
+ const settings = withDivergentPresetDefault(fs8.existsSync(settingsPath) ? fs8.readFileSync(settingsPath, "utf8") : null);
8318
8526
  if (settings !== null) writeTextAtomic(settingsPath, settings);
8319
8527
  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
8528
  }
8529
+ async function skillsFor(io, apiUrl, harness) {
8530
+ try {
8531
+ const { skills } = await installableSkillSet(apiUrl, io.env);
8532
+ installForHarness(io, harness, skills, apiUrl);
8533
+ } catch (error) {
8534
+ io.stderr(`warning: skills not installed (${error instanceof Error ? error.message : String(error)}); run: diverge skills install`);
8535
+ }
8536
+ }
8321
8537
  async function overlayAndDefault(apiUrl, token, surface, io, requested) {
8322
8538
  const models = await servedModels(apiUrl, token, surface);
8323
8539
  if (models.length === 0) throw new CliError(`the gateway serves no models on the ${surface} surface for this organization`);
8324
8540
  if (requested && !models.includes(requested)) throw new CliError(`${requested} is not served on the ${surface} surface; choose one of: ${models.join(", ")}`);
8325
8541
  const defaultModel = dshDefaultModel(models, requested);
8326
- const bridge = dshBridge(resolveSelfCommand(), io.env);
8327
- return { overlay: dshPatchYaml({ baseUrl: surfaceBaseUrl(apiUrl, surface), surface, models, defaultModel, bridge }), defaultModel, models };
8542
+ return { overlay: dshPatchYaml({ baseUrl: surfaceBaseUrl(apiUrl, surface), surface, models, defaultModel }), defaultModel, models };
8328
8543
  }
8329
8544
  async function setup(args, io) {
8330
8545
  const [agent, ...rest] = args;
8331
8546
  const apiUrl = resolveApiUrl(void 0, io.env);
8332
8547
  switch (agent) {
8333
8548
  case "claude": {
8334
- const { values } = parseArgs({ args: rest, options: { scope: { type: "string", default: "user" }, "project-id": { type: "string" }, "organization-id": { type: "string" }, "model-discovery": { type: "boolean", default: false } }, strict: true });
8549
+ const { values } = parseArgs2({ args: rest, options: { scope: { type: "string", default: "user" }, "project-id": { type: "string" }, "organization-id": { type: "string" }, "model-discovery": { type: "boolean", default: false } }, strict: true });
8335
8550
  if (values.scope !== "user" && values.scope !== "project") throw new CliError("--scope must be user or project");
8336
8551
  const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
8337
8552
  const file = claudeSettingsPath(values.scope);
@@ -8344,10 +8559,11 @@ async function setup(args, io) {
8344
8559
  writeJsonAtomic(file, settings);
8345
8560
  io.stdout(`Claude Code \u2192 ${anthropicBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
8346
8561
  for (const warning of warnings) io.stderr(`warning: ${warning}`);
8562
+ await skillsFor(io, apiUrl, "claude");
8347
8563
  return ExitCode.Ok;
8348
8564
  }
8349
8565
  case "opencode": {
8350
- const { values } = parseArgs({ args: rest, options: { surface: { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" } }, strict: true });
8566
+ const { values } = parseArgs2({ args: rest, options: { surface: { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" } }, strict: true });
8351
8567
  const surface = surfaceOf(values.surface, "anthropic");
8352
8568
  const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
8353
8569
  const baseUrl = surfaceBaseUrl(apiUrl, surface);
@@ -8365,7 +8581,7 @@ async function setup(args, io) {
8365
8581
  return ExitCode.Ok;
8366
8582
  }
8367
8583
  case "codex": {
8368
- const { values } = parseArgs({ args: rest, options: { "project-id": { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" }, print: { type: "boolean", default: false } }, strict: true });
8584
+ const { values } = parseArgs2({ args: rest, options: { "project-id": { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" }, print: { type: "boolean", default: false } }, strict: true });
8369
8585
  const block = codexProviderToml({ openaiBaseUrl: openaiBaseUrl(apiUrl), self: resolveSelfCommand(), projectId: values["project-id"] });
8370
8586
  if (values.print) {
8371
8587
  io.stdout(block.trimEnd());
@@ -8373,25 +8589,27 @@ async function setup(args, io) {
8373
8589
  }
8374
8590
  const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
8375
8591
  const file = values.config ?? codexConfigPath();
8376
- const existing = fs7.existsSync(file) ? fs7.readFileSync(file, "utf8") : null;
8592
+ const existing = fs8.existsSync(file) ? fs8.readFileSync(file, "utf8") : null;
8377
8593
  writeTextAtomic(file, mergeCodexConfig(existing, block));
8378
8594
  io.stdout(`Codex provider divergent \u2192 ${openaiBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
8379
8595
  io.stdout('Select it per run with: codex -c model_provider=divergent (or set model_provider = "divergent" in the config)');
8596
+ await skillsFor(io, apiUrl, "codex");
8380
8597
  return ExitCode.Ok;
8381
8598
  }
8382
8599
  case "dsh": {
8383
- const { values } = parseArgs({ args: rest, options: { surface: { type: "string" }, model: { type: "string" }, "organization-id": { type: "string" }, into: { type: "string" }, print: { type: "boolean", default: false } }, strict: true });
8600
+ const { values } = parseArgs2({ args: rest, options: { surface: { type: "string" }, model: { type: "string" }, "organization-id": { type: "string" }, into: { type: "string" }, print: { type: "boolean", default: false } }, strict: true });
8384
8601
  const surface = surfaceOf(values.surface, "openai");
8385
8602
  const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
8603
+ if (!values.print) await skillsFor(io, apiUrl, "dsh");
8386
8604
  const { overlay, defaultModel } = await overlayAndDefault(apiUrl, token, surface, io, values.model);
8387
8605
  if (values.print) {
8388
8606
  io.stdout(overlay.trimEnd());
8389
8607
  return ExitCode.Ok;
8390
8608
  }
8391
- const file = values.into ?? path10.join(dshHome(io.env), `divergent-${surface}.yml`);
8609
+ const file = values.into ?? path11.join(dshHome(io.env), `divergent-${surface}.yml`);
8392
8610
  writeTextAtomic(file, overlay);
8393
8611
  io.stdout(`dsh route divergent-${surface} \u2192 ${surfaceBaseUrl(apiUrl, surface)} as you, organization ${tokenClaims(token.access_token).organizationId}, default model ${defaultModel}. Wrote ${file}.`);
8394
- io.stdout("Web search is the platform tool search_online over `diverge tools bridge` (row mcp-divergent); dsh's DeepSeek search is off.");
8612
+ io.stdout("Platform tools, web search included, are reached from dsh's shell through `diverge tools`; the installed skills say when and how. dsh's DeepSeek search is off.");
8395
8613
  syncDivergentPreset(io, findOnPath("dsh", io.env), dshHome(io.env));
8396
8614
  io.stdout("Boot a profile with the overlay and the token in its environment (user tokens expire; the login refreshes):");
8397
8615
  io.stdout(` DIVERGENT_TOKEN="$(diverge token)" dsh --profile headless --patch ${file} "<task>"`);
@@ -8412,24 +8630,25 @@ ${agentsUsage}`);
8412
8630
  const terminator = rest.indexOf("--");
8413
8631
  const flags = terminator === -1 ? rest : rest.slice(0, terminator);
8414
8632
  const passthrough = terminator === -1 ? [] : rest.slice(terminator + 1);
8415
- const { values } = parseArgs({ args: flags, options: { surface: { type: "string" }, model: { type: "string" }, "organization-id": { type: "string" } }, strict: true });
8633
+ const { values } = parseArgs2({ args: flags, options: { surface: { type: "string" }, model: { type: "string" }, "organization-id": { type: "string" } }, strict: true });
8416
8634
  const surface = surfaceOf(values.surface, "openai");
8417
8635
  const apiUrl = resolveApiUrl(void 0, io.env);
8418
8636
  const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
8419
8637
  const binary = findOnPath("dsh", io.env);
8420
8638
  if (!binary) throw new CliError("dsh is not on PATH (install: npm install -g @deepseek-ai/dsh)");
8639
+ await skillsFor(io, apiUrl, "dsh");
8421
8640
  const { overlay, defaultModel } = await overlayAndDefault(apiUrl, token, surface, io, values.model);
8422
8641
  const home = dshHome(io.env);
8423
- const settingsPath = path10.join(home, "settings.yaml");
8642
+ const settingsPath = path11.join(home, "settings.yaml");
8424
8643
  let pinned;
8425
8644
  try {
8426
- pinned = dshSettingsDefaultModel(fs7.existsSync(settingsPath) ? fs7.readFileSync(settingsPath, "utf8") : null, surface);
8645
+ pinned = dshSettingsDefaultModel(fs8.existsSync(settingsPath) ? fs8.readFileSync(settingsPath, "utf8") : null, surface);
8427
8646
  } catch (error) {
8428
8647
  throw new CliError(`${settingsPath}: ${error instanceof Error ? error.message : String(error)}`);
8429
8648
  }
8430
8649
  const served = servedIds(overlay);
8431
8650
  if (pinned && !served.includes(pinned)) throw new CliError(stalePinMessage(settingsPath, pinned, surface, served));
8432
- const overlayPath = path10.join(home, `divergent-${surface}.yml`);
8651
+ const overlayPath = path11.join(home, `divergent-${surface}.yml`);
8433
8652
  writeTextAtomic(overlayPath, overlay);
8434
8653
  syncDivergentPreset(io, binary, home);
8435
8654
  io.stderr(`dsh on divergent-${surface} as you (organization ${tokenClaims(token.access_token).organizationId}), default model ${defaultModel}`);
@@ -8438,7 +8657,7 @@ ${agentsUsage}`);
8438
8657
  return runDsh({
8439
8658
  binary,
8440
8659
  overlayPath,
8441
- credentialsPath: path10.join(home, ".credentials.yaml"),
8660
+ credentialsPath: path11.join(home, ".credentials.yaml"),
8442
8661
  args: passthrough,
8443
8662
  env: io.env,
8444
8663
  initialToken: token.access_token,
@@ -8453,7 +8672,7 @@ ${agentsUsage}`);
8453
8672
  });
8454
8673
  }
8455
8674
  async function status(args, io) {
8456
- const { values } = parseArgs({ args, options: { "organization-id": { type: "string" } }, strict: true });
8675
+ const { values } = parseArgs2({ args, options: { "organization-id": { type: "string" } }, strict: true });
8457
8676
  const apiUrl = resolveApiUrl(void 0, io.env);
8458
8677
  const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
8459
8678
  const claims = tokenClaims(token.access_token);
@@ -8467,7 +8686,7 @@ async function status(args, io) {
8467
8686
  return ExitCode.Ok;
8468
8687
  }
8469
8688
  async function rotate(args, io) {
8470
- const { values } = parseArgs({ args, options: { into: { type: "string" }, interval: { type: "string", default: "600" }, once: { type: "boolean", default: false }, "organization-id": { type: "string" } }, strict: true });
8689
+ const { values } = parseArgs2({ args, options: { into: { type: "string" }, interval: { type: "string", default: "600" }, once: { type: "boolean", default: false }, "organization-id": { type: "string" } }, strict: true });
8471
8690
  const interval = Number(values.interval);
8472
8691
  if (!Number.isFinite(interval) || interval <= 0) throw new CliError("--interval must be a positive number of seconds");
8473
8692
  const apiUrl = resolveApiUrl(void 0, io.env);
@@ -8513,7 +8732,7 @@ ${agentsUsage}`);
8513
8732
  }
8514
8733
 
8515
8734
  // src/commands/login.ts
8516
- import { parseArgs as parseArgs2 } from "node:util";
8735
+ import { parseArgs as parseArgs3 } from "node:util";
8517
8736
 
8518
8737
  // src/auth/organizations.ts
8519
8738
  function chooseOrganization(input) {
@@ -8597,7 +8816,7 @@ function resolveLoginHint(flag, config) {
8597
8816
  return remembered || void 0;
8598
8817
  }
8599
8818
  async function runLogin(args, io) {
8600
- const { values } = parseArgs2({
8819
+ const { values } = parseArgs3({
8601
8820
  args,
8602
8821
  options: {
8603
8822
  "organization-id": { type: "string" },
@@ -8667,7 +8886,7 @@ ${organizationMenu(choice.options).join("\n")}`);
8667
8886
  }
8668
8887
 
8669
8888
  // src/commands/org.ts
8670
- import { parseArgs as parseArgs3 } from "node:util";
8889
+ import { parseArgs as parseArgs4 } from "node:util";
8671
8890
  var orgUsage = `diverge org [use <id|name>]
8672
8891
 
8673
8892
  Without a verb: the organizations you may act in, the bound one marked.
@@ -8675,7 +8894,7 @@ use <id|name>: bind the login to that organization on the platform, without a
8675
8894
  browser; every later command acts there. A platform actor may name an
8676
8895
  organization they are not a member of.`;
8677
8896
  async function runOrg(args, io) {
8678
- const { positionals } = parseArgs3({ args, options: {}, strict: true, allowPositionals: true });
8897
+ const { positionals } = parseArgs4({ args, options: {}, strict: true, allowPositionals: true });
8679
8898
  const apiUrl = resolveApiUrl(void 0, io.env);
8680
8899
  const source = { tokenFile: tokenFile(io.env), baseUrl: apiUrl, clientId: clientId(io.env) };
8681
8900
  const token = await getUserToken(source);
@@ -8835,7 +9054,7 @@ async function runToken(_args, io) {
8835
9054
  }
8836
9055
 
8837
9056
  // src/commands/tools.ts
8838
- import { parseArgs as parseArgs4 } from "node:util";
9057
+ import { parseArgs as parseArgs5 } from "node:util";
8839
9058
 
8840
9059
  // src/mcp/bridge.ts
8841
9060
  import readline2 from "node:readline";
@@ -8891,10 +9110,10 @@ var ToolsClient = class {
8891
9110
  if (this.settings.mode === "service") headers["x-divergent-user-id"] = this.settings.delegatedUserId;
8892
9111
  return headers;
8893
9112
  }
8894
- scoped(path12) {
9113
+ scoped(path13) {
8895
9114
  const query = new URLSearchParams({ organizationId: this.settings.organizationId });
8896
9115
  if (this.settings.projectId) query.set("projectId", this.settings.projectId);
8897
- return `${this.settings.apiUrl}${path12}?${query.toString()}`;
9116
+ return `${this.settings.apiUrl}${path13}?${query.toString()}`;
8898
9117
  }
8899
9118
  async send(method, url, body) {
8900
9119
  return this.request(method, url, body, void 0);
@@ -9161,6 +9380,98 @@ async function waitForInvocation(id, options, deps) {
9161
9380
  }
9162
9381
  }
9163
9382
 
9383
+ // src/tools/call-args.ts
9384
+ import fs9 from "node:fs";
9385
+ function resolveCallArgs(options, readStdin = () => fs9.readFileSync(0, "utf8")) {
9386
+ if (options.argsJson !== void 0 && options.argsJson !== "{}" && options.argsFile !== void 0) {
9387
+ throw new CliError("give the arguments once: --args-json or --args-file");
9388
+ }
9389
+ let text;
9390
+ let source;
9391
+ if (options.argsFile !== void 0) {
9392
+ source = `--args-file ${options.argsFile}`;
9393
+ try {
9394
+ text = fs9.readFileSync(options.argsFile, "utf8");
9395
+ } catch (error) {
9396
+ throw new CliError(`${source}: ${error instanceof Error ? error.message : String(error)}`);
9397
+ }
9398
+ } else if (options.argsJson === "-") {
9399
+ source = "--args-json - (stdin)";
9400
+ text = readStdin();
9401
+ } else {
9402
+ source = "--args-json";
9403
+ text = options.argsJson ?? "{}";
9404
+ }
9405
+ let parsed;
9406
+ try {
9407
+ parsed = JSON.parse(text.trim() === "" ? "{}" : text);
9408
+ } catch (error) {
9409
+ throw new CliError(`${source} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
9410
+ }
9411
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError(`${source} must be a JSON object`);
9412
+ return parsed;
9413
+ }
9414
+
9415
+ // src/tools/batch.ts
9416
+ function parseBatch(text) {
9417
+ const trimmed = text.trim();
9418
+ if (!trimmed) throw new Error("the batch file is empty");
9419
+ const raw = trimmed.startsWith("[") ? JSON.parse(trimmed) : trimmed.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
9420
+ if (!Array.isArray(raw)) throw new Error("the batch file must hold a JSON array or one JSON object per line");
9421
+ return raw.map((entry, index) => {
9422
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`entry ${index + 1} is not an object`);
9423
+ const { tool, args } = entry;
9424
+ if (typeof tool !== "string" || !tool) throw new Error(`entry ${index + 1} names no tool`);
9425
+ if (args !== void 0 && (!args || typeof args !== "object" || Array.isArray(args))) throw new Error(`entry ${index + 1}: args must be an object`);
9426
+ return { tool, args: args ?? {} };
9427
+ });
9428
+ }
9429
+ async function runBatch(entries, deps) {
9430
+ const outcomes = [];
9431
+ for (const entry of entries) {
9432
+ const outcome = await runOne(entry, deps);
9433
+ outcomes.push(outcome);
9434
+ deps.onOutcome?.(outcome);
9435
+ }
9436
+ return { outcomes, exitCode: worstExitCode(outcomes) };
9437
+ }
9438
+ async function runOne(entry, deps) {
9439
+ let body;
9440
+ try {
9441
+ body = await deps.callTool(entry.tool, entry.args);
9442
+ } catch (error) {
9443
+ return { tool: entry.tool, status: "failed", error: error instanceof Error ? error.message : String(error) };
9444
+ }
9445
+ if (body.success === false) return { tool: entry.tool, status: "failed", error: body.error || "Tool execution failed" };
9446
+ if (!isPending(body.result)) return { tool: entry.tool, status: "executed", result: body.result ?? null };
9447
+ const pending = { tool: entry.tool, status: "pending", invocationId: body.result.invocationId, decideAt: deps.decideAt(body.result) };
9448
+ if (!deps.waitFor) return pending;
9449
+ const settled = await deps.waitFor(body.result);
9450
+ switch (settled?.status) {
9451
+ case "succeeded":
9452
+ return { tool: entry.tool, status: "executed", result: settled.result ?? null, invocationId: pending.invocationId };
9453
+ case "rejected":
9454
+ return { tool: entry.tool, status: "rejected", invocationId: pending.invocationId, error: settled.decisionReason ?? void 0 };
9455
+ case "expired":
9456
+ return { tool: entry.tool, status: "expired", invocationId: pending.invocationId };
9457
+ case "failed":
9458
+ return { tool: entry.tool, status: "failed", invocationId: pending.invocationId, error: settled.errorMessage || "execution failed" };
9459
+ default:
9460
+ return pending;
9461
+ }
9462
+ }
9463
+ function worstExitCode(outcomes) {
9464
+ const has = (status2) => outcomes.some((outcome) => outcome.status === status2);
9465
+ if (has("failed")) return ExitCode.Failure;
9466
+ if (has("rejected")) return ExitCode.Rejected;
9467
+ if (has("expired")) return ExitCode.Expired;
9468
+ if (has("pending")) return ExitCode.Pending;
9469
+ return ExitCode.Ok;
9470
+ }
9471
+
9472
+ // src/commands/tools.ts
9473
+ import fs10 from "node:fs";
9474
+
9164
9475
  // src/tools/print.ts
9165
9476
  function matchesCapability(selector, capability) {
9166
9477
  if (!capability) return false;
@@ -9241,12 +9552,16 @@ function settledOutcome(invocation) {
9241
9552
  }
9242
9553
 
9243
9554
  // src/commands/tools.ts
9244
- var toolsUsage = `diverge tools <list|capabilities|describe|call|invocation|bridge> [--organization-id <id>] [--project-id <id>] ...
9555
+ var toolsUsage = `diverge tools <list|capabilities|describe|call|run|invocation|bridge> [--organization-id <id>] [--project-id <id>] ...
9245
9556
 
9246
9557
  list [--filter <substring>] [--capability <selector>] [--json]
9247
9558
  capabilities [--json]
9248
9559
  describe <name> [--json]
9249
- call <name> [--args-json '{...}'] [--wait] [--timeout <seconds>]
9560
+ call <name> [--args-json '{...}' | --args-json - | --args-file <path>] [--wait] [--timeout <seconds>]
9561
+ arguments inline, from stdin (-), or from a file: PowerShell strips quotes inside inline JSON
9562
+ run <file> [--wait] [--timeout <seconds>]
9563
+ several calls from one file (a JSON array of {tool, args}, or one per line), in order,
9564
+ one JSON line of outcome each; exit code is the worst: 1 failed, 4 rejected, 5 expired, 3 pending
9250
9565
  invocation <id> [--wait] [--timeout <seconds>] [--json]
9251
9566
  bridge [--tools <name,name>] (stdio MCP server; the allowlist keeps a harness to the tools it was given)
9252
9567
 
@@ -9282,7 +9597,7 @@ async function runTools(args, io) {
9282
9597
  }
9283
9598
  switch (verb) {
9284
9599
  case "list": {
9285
- const { values } = parseArgs4({ args: rest, options: { ...SCOPE_OPTIONS, filter: { type: "string" }, capability: { type: "string" }, json: { type: "boolean", default: false } }, strict: true });
9600
+ const { values } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, filter: { type: "string" }, capability: { type: "string" }, json: { type: "boolean", default: false } }, strict: true });
9286
9601
  const { client: tools } = await client(values, io);
9287
9602
  let list = await tools.listTools();
9288
9603
  if (values.filter) list = list.filter((tool) => tool.name.includes(values.filter));
@@ -9292,7 +9607,7 @@ async function runTools(args, io) {
9292
9607
  return ExitCode.Ok;
9293
9608
  }
9294
9609
  case "capabilities": {
9295
- const { values } = parseArgs4({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true });
9610
+ const { values } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true });
9296
9611
  const { client: tools } = await client(values, io);
9297
9612
  const list = await tools.listTools();
9298
9613
  if (values.json) io.stdout(JSON.stringify(capabilityClusters(list), null, 2));
@@ -9300,7 +9615,7 @@ async function runTools(args, io) {
9300
9615
  return ExitCode.Ok;
9301
9616
  }
9302
9617
  case "describe": {
9303
- const { values, positionals } = parseArgs4({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true, allowPositionals: true });
9618
+ const { values, positionals } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true, allowPositionals: true });
9304
9619
  const name = positionals[0];
9305
9620
  if (!name) throw new CliError("describe needs a tool name");
9306
9621
  const { settings, client: tools } = await client(values, io);
@@ -9315,16 +9630,10 @@ async function runTools(args, io) {
9315
9630
  return ExitCode.Ok;
9316
9631
  }
9317
9632
  case "call": {
9318
- const { values, positionals } = parseArgs4({ args: rest, options: { ...SCOPE_OPTIONS, "args-json": { type: "string", default: "{}" }, wait: { type: "boolean", default: false }, timeout: { type: "string", default: "600" } }, strict: true, allowPositionals: true });
9633
+ const { values, positionals } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, "args-json": { type: "string" }, "args-file": { type: "string" }, wait: { type: "boolean", default: false }, timeout: { type: "string", default: "600" } }, strict: true, allowPositionals: true });
9319
9634
  const name = positionals[0];
9320
9635
  if (!name) throw new CliError("call needs a tool name");
9321
- let parsedArgs;
9322
- try {
9323
- parsedArgs = JSON.parse(values["args-json"]);
9324
- } catch (error) {
9325
- throw new CliError(`--args-json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
9326
- }
9327
- if (!parsedArgs || typeof parsedArgs !== "object" || Array.isArray(parsedArgs)) throw new CliError("--args-json must be a JSON object");
9636
+ const parsedArgs = resolveCallArgs({ argsJson: values["args-json"], argsFile: values["args-file"] });
9328
9637
  const timeoutMs = Number(values.timeout) * 1e3;
9329
9638
  if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new CliError("--timeout must be a number of seconds");
9330
9639
  const { settings, client: tools } = await client(values, io);
@@ -9343,8 +9652,34 @@ async function runTools(args, io) {
9343
9652
  }
9344
9653
  return emit(io, settledOutcome(invocation));
9345
9654
  }
9655
+ case "run": {
9656
+ const { values, positionals } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, wait: { type: "boolean", default: false }, timeout: { type: "string", default: "600" } }, strict: true, allowPositionals: true });
9657
+ const file = positionals[0];
9658
+ if (!file) throw new CliError("run needs a file of calls");
9659
+ let entries;
9660
+ try {
9661
+ entries = parseBatch(fs10.readFileSync(file, "utf8"));
9662
+ } catch (error) {
9663
+ throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
9664
+ }
9665
+ const timeoutMs = Number(values.timeout) * 1e3;
9666
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new CliError("--timeout must be a number of seconds");
9667
+ const { settings, client: tools } = await client(values, io);
9668
+ const { exitCode } = await runBatch(entries, {
9669
+ callTool: (tool, args2) => tools.callTool(tool, args2),
9670
+ decideAt: (pending) => `${settings.apiUrl}${pending.approvalUrl ?? ""}`,
9671
+ onOutcome: (outcome) => io.stdout(JSON.stringify(outcome)),
9672
+ ...values.wait ? {
9673
+ waitFor: async (pending) => {
9674
+ const invocation = await waitForInvocation(pending.invocationId ?? "", { pollAfterMs: pending.pollAfterMs, timeoutMs }, { getInvocation: (id) => tools.getInvocation(id), sleep: realSleep, now: Date.now });
9675
+ return PENDING_INVOCATION_STATUSES.includes(invocation.status ?? "") ? null : invocation;
9676
+ }
9677
+ } : {}
9678
+ });
9679
+ return exitCode;
9680
+ }
9346
9681
  case "invocation": {
9347
- const { values, positionals } = parseArgs4({ args: rest, options: { ...SCOPE_OPTIONS, wait: { type: "boolean", default: false }, timeout: { type: "string", default: "600" }, json: { type: "boolean", default: false } }, strict: true, allowPositionals: true });
9682
+ const { values, positionals } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, wait: { type: "boolean", default: false }, timeout: { type: "string", default: "600" }, json: { type: "boolean", default: false } }, strict: true, allowPositionals: true });
9348
9683
  const id = positionals[0];
9349
9684
  if (!id) throw new CliError("invocation needs an id");
9350
9685
  const { client: tools } = await client(values, io);
@@ -9362,21 +9697,21 @@ async function runTools(args, io) {
9362
9697
  return emit(io, settledOutcome(invocation));
9363
9698
  }
9364
9699
  case "bridge": {
9365
- const { values } = parseArgs4({ args: rest, options: { ...SCOPE_OPTIONS, tools: { type: "string" } }, strict: true });
9700
+ const { values } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, tools: { type: "string" } }, strict: true });
9366
9701
  const { client: tools } = await client(values, io);
9367
9702
  const allow = (values.tools ?? "").split(",").map((name) => name.trim()).filter(Boolean);
9368
9703
  return serveBridge(new BridgeServer(tools, { tools: allow }), process.stdin, process.stdout);
9369
9704
  }
9370
9705
  default:
9371
- throw new CliError(`tools needs one of: list, capabilities, describe, call, invocation, bridge
9706
+ throw new CliError(`tools needs one of: list, capabilities, describe, call, run, invocation, bridge
9372
9707
 
9373
9708
  ${toolsUsage}`);
9374
9709
  }
9375
9710
  }
9376
9711
 
9377
9712
  // src/commands/plugins.ts
9378
- import fs8 from "node:fs";
9379
- import { parseArgs as parseArgs5 } from "node:util";
9713
+ import fs11 from "node:fs";
9714
+ import { parseArgs as parseArgs6 } from "node:util";
9380
9715
  var pluginsUsage = `diverge plugins <validate|publish|list|retire> [--organization-id <id>] ...
9381
9716
 
9382
9717
  validate <manifest.json> (dry run: the platform's full validation, nothing stored)
@@ -9402,7 +9737,7 @@ function readManifest(file) {
9402
9737
  ${pluginsUsage}`);
9403
9738
  let text;
9404
9739
  try {
9405
- text = fs8.readFileSync(file, "utf8");
9740
+ text = fs11.readFileSync(file, "utf8");
9406
9741
  } catch (error) {
9407
9742
  throw new CliError(`Cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`);
9408
9743
  }
@@ -9439,7 +9774,7 @@ async function runPlugins(args, io) {
9439
9774
  }
9440
9775
  switch (verb) {
9441
9776
  case "validate": {
9442
- const { values, positionals } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
9777
+ const { values, positionals } = parseArgs6({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
9443
9778
  const manifest = readManifest(positionals[0]);
9444
9779
  const tools = await client2(values, io);
9445
9780
  const outcome = await tools.publishPlugin(manifest, { dryRun: true });
@@ -9456,7 +9791,7 @@ async function runPlugins(args, io) {
9456
9791
  return ExitCode.Ok;
9457
9792
  }
9458
9793
  case "publish": {
9459
- const { values, positionals } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
9794
+ const { values, positionals } = parseArgs6({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
9460
9795
  const manifest = readManifest(positionals[0]);
9461
9796
  const tools = await client2(values, io);
9462
9797
  const outcome = await tools.publishPlugin(manifest, { dryRun: false });
@@ -9474,7 +9809,7 @@ async function runPlugins(args, io) {
9474
9809
  return ExitCode.Ok;
9475
9810
  }
9476
9811
  case "list": {
9477
- const { values } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, strict: true });
9812
+ const { values } = parseArgs6({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, strict: true });
9478
9813
  const tools = await client2(values, io);
9479
9814
  const plugins = await tools.listPlugins();
9480
9815
  if (values.json) io.stdout(JSON.stringify(plugins, null, 2));
@@ -9483,7 +9818,7 @@ async function runPlugins(args, io) {
9483
9818
  return ExitCode.Ok;
9484
9819
  }
9485
9820
  case "retire": {
9486
- const { values, positionals } = parseArgs5({ args: rest, options: SCOPE_OPTIONS2, allowPositionals: true, strict: true });
9821
+ const { values, positionals } = parseArgs6({ args: rest, options: SCOPE_OPTIONS2, allowPositionals: true, strict: true });
9487
9822
  const pluginId = positionals[0];
9488
9823
  if (!pluginId) throw new CliError(`A plugin id is required.
9489
9824
 
@@ -9505,16 +9840,16 @@ import { spawn as spawn4 } from "node:child_process";
9505
9840
 
9506
9841
  // src/update-notice.ts
9507
9842
  import { spawn as spawn3 } from "node:child_process";
9508
- import fs9 from "node:fs";
9509
- import path11 from "node:path";
9843
+ import fs12 from "node:fs";
9844
+ import path12 from "node:path";
9510
9845
  var UPDATE_CHECK_FILE = "update-check.json";
9511
9846
  var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
9512
9847
  function updateCheckFile(env) {
9513
- return path11.join(configDir(env), UPDATE_CHECK_FILE);
9848
+ return path12.join(configDir(env), UPDATE_CHECK_FILE);
9514
9849
  }
9515
9850
  function readUpdateCheck(env) {
9516
9851
  try {
9517
- const parsed = JSON.parse(fs9.readFileSync(updateCheckFile(env), "utf8"));
9852
+ const parsed = JSON.parse(fs12.readFileSync(updateCheckFile(env), "utf8"));
9518
9853
  if (parsed && typeof parsed === "object" && typeof parsed.checkedAt === "number" && typeof parsed.latest === "string") return parsed;
9519
9854
  } catch {
9520
9855
  }
@@ -9615,6 +9950,7 @@ var COMMANDS = [
9615
9950
  { name: "agents", summary: "Point a coding agent at the model gateway (setup, run, status)", usage: agentsUsage, run: runAgents },
9616
9951
  { name: "tools", summary: "Discover and execute platform tools; raise and read approvals", usage: toolsUsage, run: runTools },
9617
9952
  { name: "plugins", summary: "Validate, publish, list and retire tenant plugins for your organization", usage: pluginsUsage, run: runPlugins },
9953
+ { name: "skills", summary: "Install your organization's skills into Claude Code, Codex and dsh", usage: skillsUsage, run: runSkills },
9618
9954
  { name: "update", summary: "Install the latest release", usage: updateUsage, run: (args, io) => runUpdate(args, io) }
9619
9955
  ];
9620
9956
  function usage() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divergeai/cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.14",
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",