@divergeai/cli 0.1.9 → 0.1.11

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 +13 -4
  2. package/dist/diverge.mjs +255 -95
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -79,10 +79,13 @@ Anthropic or OpenAI. Every other model, dsh included, searches through the
79
79
  platform's `search_online` tool: one key on the platform, metered and audited
80
80
  per organization. The dsh overlay wires it in as `mcp__divergent__search_online`
81
81
  (dsh's DeepSeek-backed `web_search` needs a vendor key and is off; `web_fetch`
82
- stays). In dsh's web profile the shipped agent presets still list `web_search`,
83
- where a patch cannot reach; it fails loudly and the model uses the bridge's
84
- tool listed beside it. To give Claude Code or Codex the same tool for a
85
- non-native upstream, register the bridge once:
82
+ stays). dsh's web profile composes the model's tools per agent preset, where a
83
+ patch cannot reach, so `diverge agents setup dsh` and `run dsh` also derive a
84
+ **Divergent** preset from the installed dsh's `standard` one, with the vendor
85
+ search left out of the model's tools, under `$DSH_HOME/.agent-presets/`, and
86
+ make it the default for new web sessions once in `settings.yaml` (a preset you
87
+ pick yourself there stays picked). To give Claude Code or Codex the same tool
88
+ for a non-native upstream, register the bridge once:
86
89
 
87
90
  ```bash
88
91
  claude mcp add --scope user divergent -- diverge tools bridge --tools search_online
@@ -149,6 +152,11 @@ on the binding when the endpoint is a tailnet or LAN address.
149
152
  ## Updating
150
153
 
151
154
  `diverge update` installs the latest release with the npm that installed it.
155
+ Once a day, after a command finishes, the CLI asks the registry for the latest
156
+ version in the background and caches the answer under `$DIVERGE_HOME`; the
157
+ next command prints one line on stderr when a newer release exists. Nothing
158
+ waits on the network for it, and it stays quiet in the bridge, in
159
+ non-interactive shells, under `CI`, and with `DIVERGE_NO_UPDATE_CHECK=1`.
152
160
 
153
161
  ## Environment
154
162
 
@@ -159,6 +167,7 @@ on the binding when the endpoint is a tailnet or LAN address.
159
167
  | `DIVERGENT_ORG_ID`, `DIVERGENT_PROJECT_ID` | tool scope when the flags are omitted; without either, the login's organization |
160
168
  | `DIVERGENT_CLIENT_ID`, `DIVERGENT_CLIENT_SECRET`, `DIVERGENT_USER_ID` | unattended tool execution with service credentials, delegated to that person |
161
169
  | `DSH_HOME` | where dsh keeps its files, default `~/.dsh` |
170
+ | `DIVERGE_NO_UPDATE_CHECK` | set to skip the once-a-day check for a newer release |
162
171
 
163
172
  ## Security
164
173
 
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, path11) {
118
- const ctrl = callVisitor(key, node, visitor, path11);
117
+ function visit_(key, node, visitor, path12) {
118
+ const ctrl = callVisitor(key, node, visitor, path12);
119
119
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
120
- replaceNode(key, path11, ctrl);
121
- return visit_(key, ctrl, visitor, path11);
120
+ replaceNode(key, path12, ctrl);
121
+ return visit_(key, ctrl, visitor, path12);
122
122
  }
123
123
  if (typeof ctrl !== "symbol") {
124
124
  if (identity.isCollection(node)) {
125
- path11 = Object.freeze(path11.concat(node));
125
+ path12 = Object.freeze(path12.concat(node));
126
126
  for (let i = 0; i < node.items.length; ++i) {
127
- const ci = visit_(i, node.items[i], visitor, path11);
127
+ const ci = visit_(i, node.items[i], visitor, path12);
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
- path11 = Object.freeze(path11.concat(node));
139
- const ck = visit_("key", node.key, visitor, path11);
138
+ path12 = Object.freeze(path12.concat(node));
139
+ const ck = visit_("key", node.key, visitor, path12);
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, path11);
144
+ const cv = visit_("value", node.value, visitor, path12);
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, path11) {
166
- const ctrl = await callVisitor(key, node, visitor, path11);
165
+ async function visitAsync_(key, node, visitor, path12) {
166
+ const ctrl = await callVisitor(key, node, visitor, path12);
167
167
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
168
- replaceNode(key, path11, ctrl);
169
- return visitAsync_(key, ctrl, visitor, path11);
168
+ replaceNode(key, path12, ctrl);
169
+ return visitAsync_(key, ctrl, visitor, path12);
170
170
  }
171
171
  if (typeof ctrl !== "symbol") {
172
172
  if (identity.isCollection(node)) {
173
- path11 = Object.freeze(path11.concat(node));
173
+ path12 = Object.freeze(path12.concat(node));
174
174
  for (let i = 0; i < node.items.length; ++i) {
175
- const ci = await visitAsync_(i, node.items[i], visitor, path11);
175
+ const ci = await visitAsync_(i, node.items[i], visitor, path12);
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
- path11 = Object.freeze(path11.concat(node));
187
- const ck = await visitAsync_("key", node.key, visitor, path11);
186
+ path12 = Object.freeze(path12.concat(node));
187
+ const ck = await visitAsync_("key", node.key, visitor, path12);
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, path11);
192
+ const cv = await visitAsync_("value", node.value, visitor, path12);
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, path11) {
219
+ function callVisitor(key, node, visitor, path12) {
220
220
  if (typeof visitor === "function")
221
- return visitor(key, node, path11);
221
+ return visitor(key, node, path12);
222
222
  if (identity.isMap(node))
223
- return visitor.Map?.(key, node, path11);
223
+ return visitor.Map?.(key, node, path12);
224
224
  if (identity.isSeq(node))
225
- return visitor.Seq?.(key, node, path11);
225
+ return visitor.Seq?.(key, node, path12);
226
226
  if (identity.isPair(node))
227
- return visitor.Pair?.(key, node, path11);
227
+ return visitor.Pair?.(key, node, path12);
228
228
  if (identity.isScalar(node))
229
- return visitor.Scalar?.(key, node, path11);
229
+ return visitor.Scalar?.(key, node, path12);
230
230
  if (identity.isAlias(node))
231
- return visitor.Alias?.(key, node, path11);
231
+ return visitor.Alias?.(key, node, path12);
232
232
  return void 0;
233
233
  }
234
- function replaceNode(key, path11, node) {
235
- const parent = path11[path11.length - 1];
234
+ function replaceNode(key, path12, node) {
235
+ const parent = path12[path12.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, path11, value) {
845
+ function collectionFromPath(schema, path12, value) {
846
846
  let v = value;
847
- for (let i = path11.length - 1; i >= 0; --i) {
848
- const k = path11[i];
847
+ for (let i = path12.length - 1; i >= 0; --i) {
848
+ const k = path12[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 = (path11) => path11 == null || typeof path11 === "object" && !!path11[Symbol.iterator]().next().done;
867
+ var isEmptyPath = (path12) => path12 == null || typeof path12 === "object" && !!path12[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(path11, value) {
898
- if (isEmptyPath(path11))
897
+ addIn(path12, value) {
898
+ if (isEmptyPath(path12))
899
899
  this.add(value);
900
900
  else {
901
- const [key, ...rest] = path11;
901
+ const [key, ...rest] = path12;
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(path11) {
916
- const [key, ...rest] = path11;
915
+ deleteIn(path12) {
916
+ const [key, ...rest] = path12;
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(path11, keepScalar) {
931
- const [key, ...rest] = path11;
930
+ getIn(path12, keepScalar) {
931
+ const [key, ...rest] = path12;
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(path11) {
950
- const [key, ...rest] = path11;
949
+ hasIn(path12) {
950
+ const [key, ...rest] = path12;
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(path11, value) {
961
- const [key, ...rest] = path11;
960
+ setIn(path12, value) {
961
+ const [key, ...rest] = path12;
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(path11, value) {
3476
+ addIn(path12, value) {
3477
3477
  if (assertCollection(this.contents))
3478
- this.contents.addIn(path11, value);
3478
+ this.contents.addIn(path12, 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(path11) {
3554
- if (Collection.isEmptyPath(path11)) {
3553
+ deleteIn(path12) {
3554
+ if (Collection.isEmptyPath(path12)) {
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(path11) : false;
3560
+ return assertCollection(this.contents) ? this.contents.deleteIn(path12) : 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(path11, keepScalar) {
3576
- if (Collection.isEmptyPath(path11))
3575
+ getIn(path12, keepScalar) {
3576
+ if (Collection.isEmptyPath(path12))
3577
3577
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
3578
- return identity.isCollection(this.contents) ? this.contents.getIn(path11, keepScalar) : void 0;
3578
+ return identity.isCollection(this.contents) ? this.contents.getIn(path12, 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(path11) {
3590
- if (Collection.isEmptyPath(path11))
3589
+ hasIn(path12) {
3590
+ if (Collection.isEmptyPath(path12))
3591
3591
  return this.contents !== void 0;
3592
- return identity.isCollection(this.contents) ? this.contents.hasIn(path11) : false;
3592
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path12) : 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(path11, value) {
3610
- if (Collection.isEmptyPath(path11)) {
3609
+ setIn(path12, value) {
3610
+ if (Collection.isEmptyPath(path12)) {
3611
3611
  this.contents = value;
3612
3612
  } else if (this.contents == null) {
3613
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path11), value);
3613
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path12), value);
3614
3614
  } else if (assertCollection(this.contents)) {
3615
- this.contents.setIn(path11, value);
3615
+ this.contents.setIn(path12, 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, path11) => {
5575
+ visit.itemAtPath = (cst, path12) => {
5576
5576
  let item = cst;
5577
- for (const [field, index] of path11) {
5577
+ for (const [field, index] of path12) {
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, path11) => {
5587
- const parent = visit.itemAtPath(cst, path11.slice(0, -1));
5588
- const field = path11[path11.length - 1][0];
5586
+ visit.parentCollection = (cst, path12) => {
5587
+ const parent = visit.itemAtPath(cst, path12.slice(0, -1));
5588
+ const field = path12[path12.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(path11, item, visitor) {
5595
- let ctrl = visitor(item, path11);
5594
+ function _visit(path12, item, visitor) {
5595
+ let ctrl = visitor(item, path12);
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(path11.concat([[field, i]])), token.items[i], visitor);
5602
+ const ci = _visit(Object.freeze(path12.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, path11);
5613
+ ctrl = ctrl(item, path12);
5614
5614
  }
5615
5615
  }
5616
- return typeof ctrl === "function" ? ctrl(item, path11) : ctrl;
5616
+ return typeof ctrl === "function" ? ctrl(item, path12) : 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 fs8 = this.flowScalar(this.type);
6918
+ const fs10 = this.flowScalar(this.type);
6919
6919
  if (atNextItem || it.value) {
6920
- map.items.push({ start, key: fs8, sep: [] });
6920
+ map.items.push({ start, key: fs10, sep: [] });
6921
6921
  this.onKeyLine = true;
6922
6922
  } else if (it.sep) {
6923
- this.stack.push(fs8);
6923
+ this.stack.push(fs10);
6924
6924
  } else {
6925
- Object.assign(it, { key: fs8, sep: [] });
6925
+ Object.assign(it, { key: fs10, 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 fs8 = this.flowScalar(this.type);
7053
+ const fs10 = this.flowScalar(this.type);
7054
7054
  if (!it || it.value)
7055
- fc.items.push({ start: [], key: fs8, sep: [] });
7055
+ fc.items.push({ start: [], key: fs10, sep: [] });
7056
7056
  else if (it.sep)
7057
- this.stack.push(fs8);
7057
+ this.stack.push(fs10);
7058
7058
  else
7059
- Object.assign(it, { key: fs8, sep: [] });
7059
+ Object.assign(it, { key: fs10, 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 fs6 from "node:fs";
7385
+ import fs7 from "node:fs";
7386
7386
  import path10 from "node:path";
7387
7387
  import { parseArgs } from "node:util";
7388
7388
 
7389
7389
  // src/version.ts
7390
- var VERSION = true ? "0.1.9" : "dev";
7390
+ var VERSION = true ? "0.1.11" : "dev";
7391
7391
  var USER_AGENT = `diverge/${VERSION}`;
7392
7392
 
7393
7393
  // src/http.ts
@@ -7916,6 +7916,8 @@ function mergeCodexConfig(existing, block) {
7916
7916
 
7917
7917
  // src/agents/dsh.ts
7918
7918
  var import_yaml = __toESM(require_dist(), 1);
7919
+ import fs4 from "node:fs";
7920
+ import { createRequire } from "node:module";
7919
7921
  import os4 from "node:os";
7920
7922
  import path5 from "node:path";
7921
7923
  var DSH_SHIPPED_DEFAULT_MODEL = "deepseek-flash";
@@ -8028,6 +8030,83 @@ function dshSettingsDefaultModel(settingsText, surface) {
8028
8030
  function stalePinMessage(settingsPath, pinned, surface, served) {
8029
8031
  return `${settingsPath} pins ${pinned} on divergent-${surface}, which the gateway does not serve; set agent-default-model.model there to one of: ${served.join(", ")} (or remove it to use ${served[0]})`;
8030
8032
  }
8033
+ var DIVERGENT_PRESET_ID = "divergent";
8034
+ var DSH_USER_PRESETS_DIR = ".agent-presets";
8035
+ function dshPackageDir(binary) {
8036
+ if (/\.(cmd|bat)$/i.test(binary)) {
8037
+ const dir2 = path5.join(path5.dirname(binary), "node_modules", "@deepseek-ai", "dsh");
8038
+ return fs4.existsSync(path5.join(dir2, "package.json")) ? dir2 : null;
8039
+ }
8040
+ let dir;
8041
+ try {
8042
+ dir = path5.dirname(fs4.realpathSync(binary));
8043
+ } catch {
8044
+ return null;
8045
+ }
8046
+ for (; ; ) {
8047
+ try {
8048
+ if (JSON.parse(fs4.readFileSync(path5.join(dir, "package.json"), "utf8")).name === "@deepseek-ai/dsh") return dir;
8049
+ } catch {
8050
+ }
8051
+ const parent = path5.dirname(dir);
8052
+ if (parent === dir) return null;
8053
+ dir = parent;
8054
+ }
8055
+ }
8056
+ function shippedPresetDir(packageDir, id = "standard") {
8057
+ try {
8058
+ const require2 = createRequire(path5.join(packageDir, "package.json"));
8059
+ const dir = path5.join(path5.dirname(require2.resolve("@deepseek-ai/dsh-agent-presets/package.json")), "presets", id);
8060
+ return fs4.existsSync(path5.join(dir, "agent.cordis.yml")) ? dir : null;
8061
+ } catch {
8062
+ return null;
8063
+ }
8064
+ }
8065
+ function divergentPreset(shippedComposition) {
8066
+ const lines = shippedComposition.split("\n");
8067
+ const start = lines.findIndex((line) => /^- id: ['"]?tool-web['"]?\s*$/.test(line));
8068
+ if (start === -1) throw new Error("the shipped preset composes no tool-web row");
8069
+ let end = lines.findIndex((line, index) => index > start && /^- /.test(line));
8070
+ if (end === -1) end = lines.length;
8071
+ const row = lines.slice(start, end);
8072
+ const search = row.findIndex((line) => /^\s+search:/.test(line));
8073
+ if (search !== -1) {
8074
+ row[search] = row[search].replace(/:.*$/, ": false");
8075
+ } else {
8076
+ const config = row.findIndex((line) => /^ config:\s*$/.test(line));
8077
+ if (config !== -1) row.splice(config + 1, 0, " search: false");
8078
+ else row.splice(1, 0, " config:", " search: false");
8079
+ }
8080
+ return [
8081
+ `# The Divergent agent preset: dsh's shipped "standard" preset with the vendor web`,
8082
+ "# search left out of the model's tools; the platform's search_online arrives over the",
8083
+ "# bridge (row mcp-divergent). Derived from the installed dsh by `diverge agents",
8084
+ "# setup dsh` and `diverge agents run dsh` on every run; edits do not survive.",
8085
+ ...lines.slice(0, start),
8086
+ ...row,
8087
+ ...lines.slice(end)
8088
+ ].join("\n");
8089
+ }
8090
+ var DIVERGENT_PRESET_METADATA = [
8091
+ "name: Divergent",
8092
+ "description: The standard coding agent, searching the web through the platform's search_online tool instead of a vendor key.",
8093
+ "order: 0",
8094
+ ""
8095
+ ].join("\n");
8096
+ function writeDivergentPreset(home, shippedDir) {
8097
+ const dir = path5.join(home, DSH_USER_PRESETS_DIR, DIVERGENT_PRESET_ID);
8098
+ writeTextAtomic(path5.join(dir, "agent.cordis.yml"), divergentPreset(fs4.readFileSync(path5.join(shippedDir, "agent.cordis.yml"), "utf8")), 420);
8099
+ writeTextAtomic(path5.join(dir, "preset.yml"), DIVERGENT_PRESET_METADATA, 420);
8100
+ return dir;
8101
+ }
8102
+ function withDivergentPresetDefault(settingsText) {
8103
+ const parsed = settingsText?.trim() ? (0, import_yaml.parse)(settingsText) : null;
8104
+ const settings = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { ...parsed } : {};
8105
+ const presets = settings["agent-presets"];
8106
+ if (presets && typeof presets === "object" && presets.default) return null;
8107
+ settings["agent-presets"] = { ...presets ?? {}, default: DIVERGENT_PRESET_ID };
8108
+ return (0, import_yaml.stringify)(settings, { lineWidth: 0 });
8109
+ }
8031
8110
 
8032
8111
  // src/agents/opencode.ts
8033
8112
  import os5 from "node:os";
@@ -8049,7 +8128,7 @@ function mergeOpencodeConfig(existing, options) {
8049
8128
 
8050
8129
  // src/agents/run-dsh.ts
8051
8130
  import { spawn as spawn2 } from "node:child_process";
8052
- import fs4 from "node:fs";
8131
+ import fs5 from "node:fs";
8053
8132
  import path7 from "node:path";
8054
8133
  function findOnPath(name, env = process.env, platform = process.platform) {
8055
8134
  const dirs = (env.PATH ?? "").split(path7.delimiter).filter(Boolean);
@@ -8059,8 +8138,8 @@ function findOnPath(name, env = process.env, platform = process.platform) {
8059
8138
  for (const candidate of candidates) {
8060
8139
  const full = path7.join(dir, candidate);
8061
8140
  try {
8062
- fs4.accessSync(full, fs4.constants.X_OK);
8063
- if (fs4.statSync(full).isFile()) return full;
8141
+ fs5.accessSync(full, fs5.constants.X_OK);
8142
+ if (fs5.statSync(full).isFile()) return full;
8064
8143
  } catch {
8065
8144
  }
8066
8145
  }
@@ -8068,7 +8147,7 @@ function findOnPath(name, env = process.env, platform = process.platform) {
8068
8147
  return null;
8069
8148
  }
8070
8149
  function writeDshToken(credentialsPath, accessToken) {
8071
- const existing = fs4.existsSync(credentialsPath) ? fs4.readFileSync(credentialsPath, "utf8") : null;
8150
+ const existing = fs5.existsSync(credentialsPath) ? fs5.readFileSync(credentialsPath, "utf8") : null;
8072
8151
  let merged;
8073
8152
  try {
8074
8153
  merged = mergeDshCredentials(existing, accessToken);
@@ -8086,7 +8165,7 @@ function dshLaunch(binary, args, platform = process.platform, execPath = process
8086
8165
  const packageDir = path7.join(path7.dirname(binary), "node_modules", "@deepseek-ai", "dsh");
8087
8166
  let bin;
8088
8167
  try {
8089
- bin = JSON.parse(fs4.readFileSync(path7.join(packageDir, "package.json"), "utf8")).bin;
8168
+ bin = JSON.parse(fs5.readFileSync(path7.join(packageDir, "package.json"), "utf8")).bin;
8090
8169
  } catch {
8091
8170
  throw new CliError(`${binary} is not an npm shim for @deepseek-ai/dsh (no ${packageDir}); reinstall: npm install -g @deepseek-ai/dsh`);
8092
8171
  }
@@ -8177,19 +8256,19 @@ async function rotateTokenFile(loop) {
8177
8256
  }
8178
8257
 
8179
8258
  // src/agents/self-command.ts
8180
- import fs5 from "node:fs";
8259
+ import fs6 from "node:fs";
8181
8260
  import path9 from "node:path";
8182
8261
  function resolveSelfCommand(argv = process.argv, platform = process.platform, execPath = process.execPath) {
8183
8262
  const script = argv[1];
8184
8263
  if (!script) return { command: execPath, args: [] };
8185
8264
  if (platform !== "win32") {
8186
8265
  try {
8187
- fs5.accessSync(script, fs5.constants.X_OK);
8266
+ fs6.accessSync(script, fs6.constants.X_OK);
8188
8267
  return { command: path9.resolve(script), args: [] };
8189
8268
  } catch {
8190
8269
  }
8191
8270
  }
8192
- return { command: execPath, args: [fs5.realpathSync(script)] };
8271
+ return { command: execPath, args: [fs6.realpathSync(script)] };
8193
8272
  }
8194
8273
  function shellCommand(self, ...trailing) {
8195
8274
  return [self.command, ...self.args, ...trailing].map((part) => /[\s"]/.test(part) ? `"${part.replace(/"/g, '\\"')}"` : part).join(" ");
@@ -8216,12 +8295,29 @@ function surfaceOf(value, fallback) {
8216
8295
  throw new CliError(`--surface must be one of: ${SURFACES.join(", ")}`);
8217
8296
  }
8218
8297
  function readJson(file) {
8219
- if (!fs6.existsSync(file)) return null;
8220
- return JSON.parse(fs6.readFileSync(file, "utf8"));
8298
+ if (!fs7.existsSync(file)) return null;
8299
+ return JSON.parse(fs7.readFileSync(file, "utf8"));
8221
8300
  }
8222
8301
  async function servedModels(apiUrl, token, surface) {
8223
8302
  return (await listModels(apiUrl, token.access_token, surface)).map((entry) => entry.id);
8224
8303
  }
8304
+ function syncDivergentPreset(io, binary, home) {
8305
+ if (!binary) {
8306
+ io.stderr("dsh is not on PATH; the Divergent agent preset is derived when `diverge agents run dsh` first runs.");
8307
+ return;
8308
+ }
8309
+ const packageDir = dshPackageDir(binary);
8310
+ const shipped = packageDir ? shippedPresetDir(packageDir) : null;
8311
+ if (!shipped) {
8312
+ io.stderr(`warning: no shipped "standard" preset found behind ${binary}; dsh's own web_search stays in its web sessions and fails without a vendor key`);
8313
+ return;
8314
+ }
8315
+ const dir = writeDivergentPreset(home, shipped);
8316
+ const settingsPath = path10.join(home, "settings.yaml");
8317
+ const settings = withDivergentPresetDefault(fs7.existsSync(settingsPath) ? fs7.readFileSync(settingsPath, "utf8") : null);
8318
+ if (settings !== null) writeTextAtomic(settingsPath, settings);
8319
+ io.stderr(`Agent preset "${DIVERGENT_PRESET_ID}" (${dir}): web sessions search through the platform${settings !== null ? "; set as the default preset in settings.yaml" : ""}.`);
8320
+ }
8225
8321
  async function overlayAndDefault(apiUrl, token, surface, io, requested) {
8226
8322
  const models = await servedModels(apiUrl, token, surface);
8227
8323
  if (models.length === 0) throw new CliError(`the gateway serves no models on the ${surface} surface for this organization`);
@@ -8277,7 +8373,7 @@ async function setup(args, io) {
8277
8373
  }
8278
8374
  const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
8279
8375
  const file = values.config ?? codexConfigPath();
8280
- const existing = fs6.existsSync(file) ? fs6.readFileSync(file, "utf8") : null;
8376
+ const existing = fs7.existsSync(file) ? fs7.readFileSync(file, "utf8") : null;
8281
8377
  writeTextAtomic(file, mergeCodexConfig(existing, block));
8282
8378
  io.stdout(`Codex provider divergent \u2192 ${openaiBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
8283
8379
  io.stdout('Select it per run with: codex -c model_provider=divergent (or set model_provider = "divergent" in the config)');
@@ -8296,6 +8392,7 @@ async function setup(args, io) {
8296
8392
  writeTextAtomic(file, overlay);
8297
8393
  io.stdout(`dsh route divergent-${surface} \u2192 ${surfaceBaseUrl(apiUrl, surface)} as you, organization ${tokenClaims(token.access_token).organizationId}, default model ${defaultModel}. Wrote ${file}.`);
8298
8394
  io.stdout("Web search is the platform tool search_online over `diverge tools bridge` (row mcp-divergent); dsh's DeepSeek search is off.");
8395
+ syncDivergentPreset(io, findOnPath("dsh", io.env), dshHome(io.env));
8299
8396
  io.stdout("Boot a profile with the overlay and the token in its environment (user tokens expire; the login refreshes):");
8300
8397
  io.stdout(` DIVERGENT_TOKEN="$(diverge token)" dsh --profile headless --patch ${file} "<task>"`);
8301
8398
  io.stdout(`or let the CLI keep the token fresh for the whole session: diverge agents run dsh --surface ${surface} -- --profile web --port 3131 --no-open`);
@@ -8326,7 +8423,7 @@ ${agentsUsage}`);
8326
8423
  const settingsPath = path10.join(home, "settings.yaml");
8327
8424
  let pinned;
8328
8425
  try {
8329
- pinned = dshSettingsDefaultModel(fs6.existsSync(settingsPath) ? fs6.readFileSync(settingsPath, "utf8") : null, surface);
8426
+ pinned = dshSettingsDefaultModel(fs7.existsSync(settingsPath) ? fs7.readFileSync(settingsPath, "utf8") : null, surface);
8330
8427
  } catch (error) {
8331
8428
  throw new CliError(`${settingsPath}: ${error instanceof Error ? error.message : String(error)}`);
8332
8429
  }
@@ -8334,6 +8431,7 @@ ${agentsUsage}`);
8334
8431
  if (pinned && !served.includes(pinned)) throw new CliError(stalePinMessage(settingsPath, pinned, surface, served));
8335
8432
  const overlayPath = path10.join(home, `divergent-${surface}.yml`);
8336
8433
  writeTextAtomic(overlayPath, overlay);
8434
+ syncDivergentPreset(io, binary, home);
8337
8435
  io.stderr(`dsh on divergent-${surface} as you (organization ${tokenClaims(token.access_token).organizationId}), default model ${defaultModel}`);
8338
8436
  if (passthrough.length === 0) io.stderr('Starting the web UI; open the URL dsh prints. For one task without the UI: diverge agents run dsh -- "<task>"');
8339
8437
  const source = { tokenFile: tokenFile(io.env), baseUrl: apiUrl, clientId: clientId(io.env) };
@@ -8793,10 +8891,10 @@ var ToolsClient = class {
8793
8891
  if (this.settings.mode === "service") headers["x-divergent-user-id"] = this.settings.delegatedUserId;
8794
8892
  return headers;
8795
8893
  }
8796
- scoped(path11) {
8894
+ scoped(path12) {
8797
8895
  const query = new URLSearchParams({ organizationId: this.settings.organizationId });
8798
8896
  if (this.settings.projectId) query.set("projectId", this.settings.projectId);
8799
- return `${this.settings.apiUrl}${path11}?${query.toString()}`;
8897
+ return `${this.settings.apiUrl}${path12}?${query.toString()}`;
8800
8898
  }
8801
8899
  async send(method, url, body) {
8802
8900
  return this.request(method, url, body, void 0);
@@ -9277,7 +9375,7 @@ ${toolsUsage}`);
9277
9375
  }
9278
9376
 
9279
9377
  // src/commands/plugins.ts
9280
- import fs7 from "node:fs";
9378
+ import fs8 from "node:fs";
9281
9379
  import { parseArgs as parseArgs5 } from "node:util";
9282
9380
  var pluginsUsage = `diverge plugins <validate|publish|list|retire> [--organization-id <id>] ...
9283
9381
 
@@ -9304,7 +9402,7 @@ function readManifest(file) {
9304
9402
  ${pluginsUsage}`);
9305
9403
  let text;
9306
9404
  try {
9307
- text = fs7.readFileSync(file, "utf8");
9405
+ text = fs8.readFileSync(file, "utf8");
9308
9406
  } catch (error) {
9309
9407
  throw new CliError(`Cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`);
9310
9408
  }
@@ -9403,7 +9501,57 @@ ${pluginsUsage}`);
9403
9501
  }
9404
9502
 
9405
9503
  // src/commands/update.ts
9504
+ import { spawn as spawn4 } from "node:child_process";
9505
+
9506
+ // src/update-notice.ts
9406
9507
  import { spawn as spawn3 } from "node:child_process";
9508
+ import fs9 from "node:fs";
9509
+ import path11 from "node:path";
9510
+ var UPDATE_CHECK_FILE = "update-check.json";
9511
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
9512
+ function updateCheckFile(env) {
9513
+ return path11.join(configDir(env), UPDATE_CHECK_FILE);
9514
+ }
9515
+ function readUpdateCheck(env) {
9516
+ try {
9517
+ const parsed = JSON.parse(fs9.readFileSync(updateCheckFile(env), "utf8"));
9518
+ if (parsed && typeof parsed === "object" && typeof parsed.checkedAt === "number" && typeof parsed.latest === "string") return parsed;
9519
+ } catch {
9520
+ }
9521
+ return null;
9522
+ }
9523
+ function writeUpdateCheck(env, check) {
9524
+ writeJsonAtomic(updateCheckFile(env), check);
9525
+ }
9526
+ function noticeFor(check, version = VERSION) {
9527
+ if (!check || !isNewer(check.latest, version)) return null;
9528
+ return `diverge ${check.latest} is available (this is ${version}). Update with: diverge update (or run npx @divergeai/cli@latest)`;
9529
+ }
9530
+ function noticeApplies(argv, io, version = VERSION) {
9531
+ if (version === "dev" || !io.interactive) return false;
9532
+ if (io.env.DIVERGE_NO_UPDATE_CHECK?.trim() || io.env.CI?.trim()) return false;
9533
+ if (argv[0] === "update") return false;
9534
+ if (argv[0] === "tools" && argv[1] === "bridge") return false;
9535
+ return true;
9536
+ }
9537
+ function scheduleUpdateCheck(spawnImpl = spawn3) {
9538
+ try {
9539
+ const child = spawnImpl(process.execPath, [process.argv[1], "update", "--record"], { detached: true, stdio: "ignore" });
9540
+ child.on("error", () => {
9541
+ });
9542
+ child.unref();
9543
+ } catch {
9544
+ }
9545
+ }
9546
+ function updateNotice(argv, io, deps = { now: Date.now, schedule: scheduleUpdateCheck, read: readUpdateCheck, version: VERSION }) {
9547
+ if (!noticeApplies(argv, io, deps.version)) return;
9548
+ const check = deps.read(io.env);
9549
+ const notice = noticeFor(check, deps.version);
9550
+ if (notice) io.stderr(notice);
9551
+ if (!check || deps.now() - check.checkedAt > UPDATE_CHECK_INTERVAL_MS) deps.schedule();
9552
+ }
9553
+
9554
+ // src/commands/update.ts
9407
9555
  var PACKAGE_NAME = "@divergeai/cli";
9408
9556
  var updateUsage = "diverge update\n\nInstall the latest release with the npm that installed this one.";
9409
9557
  async function latestVersion(registry = "https://registry.npmjs.org") {
@@ -9428,7 +9576,14 @@ function isNewer(a, b) {
9428
9576
  if (!y.pre) return false;
9429
9577
  return x.pre > y.pre;
9430
9578
  }
9431
- async function runUpdate(_args, io, deps = { latestVersion, install: installLatest }) {
9579
+ async function runUpdate(args, io, deps = { latestVersion, install: installLatest, record: writeUpdateCheck, now: Date.now }) {
9580
+ if (args[0] === "--record") {
9581
+ try {
9582
+ deps.record(io.env, { checkedAt: deps.now(), latest: await deps.latestVersion() });
9583
+ } catch {
9584
+ }
9585
+ return ExitCode.Ok;
9586
+ }
9432
9587
  if (VERSION === "dev") throw new CliError("This is a source checkout, not an installed release; pull the repository instead.");
9433
9588
  const latest = await deps.latestVersion();
9434
9589
  if (!isNewer(latest, VERSION)) {
@@ -9444,7 +9599,7 @@ async function runUpdate(_args, io, deps = { latestVersion, install: installLate
9444
9599
  function installLatest(version) {
9445
9600
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
9446
9601
  return new Promise((resolve, reject) => {
9447
- const child = spawn3(npm, ["install", "-g", `${PACKAGE_NAME}@${version}`], { stdio: "inherit" });
9602
+ const child = spawn4(npm, ["install", "-g", `${PACKAGE_NAME}@${version}`], { stdio: "inherit" });
9448
9603
  child.on("error", reject);
9449
9604
  child.on("exit", (code2) => resolve(code2 ?? 1));
9450
9605
  });
@@ -9499,6 +9654,11 @@ ${usage()}`);
9499
9654
  io.stdout(command.usage);
9500
9655
  return ExitCode.Ok;
9501
9656
  }
9657
+ const code2 = await runCommand(command, rest, io);
9658
+ updateNotice(argv, io);
9659
+ return code2;
9660
+ }
9661
+ async function runCommand(command, rest, io) {
9502
9662
  try {
9503
9663
  return await command.run(rest, io);
9504
9664
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divergeai/cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
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",