@divergeai/cli 0.1.27 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -21
- package/dist/diverge.mjs +1025 -944
- package/package.json +3 -2
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,
|
|
118
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
117
|
+
function visit_(key, node, visitor, path20) {
|
|
118
|
+
const ctrl = callVisitor(key, node, visitor, path20);
|
|
119
119
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
120
|
-
replaceNode(key,
|
|
121
|
-
return visit_(key, ctrl, visitor,
|
|
120
|
+
replaceNode(key, path20, ctrl);
|
|
121
|
+
return visit_(key, ctrl, visitor, path20);
|
|
122
122
|
}
|
|
123
123
|
if (typeof ctrl !== "symbol") {
|
|
124
124
|
if (identity.isCollection(node)) {
|
|
125
|
-
|
|
125
|
+
path20 = Object.freeze(path20.concat(node));
|
|
126
126
|
for (let i = 0; i < node.items.length; ++i) {
|
|
127
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
127
|
+
const ci = visit_(i, node.items[i], visitor, path20);
|
|
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
|
-
|
|
139
|
-
const ck = visit_("key", node.key, visitor,
|
|
138
|
+
path20 = Object.freeze(path20.concat(node));
|
|
139
|
+
const ck = visit_("key", node.key, visitor, path20);
|
|
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,
|
|
144
|
+
const cv = visit_("value", node.value, visitor, path20);
|
|
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,
|
|
166
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
165
|
+
async function visitAsync_(key, node, visitor, path20) {
|
|
166
|
+
const ctrl = await callVisitor(key, node, visitor, path20);
|
|
167
167
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
168
|
-
replaceNode(key,
|
|
169
|
-
return visitAsync_(key, ctrl, visitor,
|
|
168
|
+
replaceNode(key, path20, ctrl);
|
|
169
|
+
return visitAsync_(key, ctrl, visitor, path20);
|
|
170
170
|
}
|
|
171
171
|
if (typeof ctrl !== "symbol") {
|
|
172
172
|
if (identity.isCollection(node)) {
|
|
173
|
-
|
|
173
|
+
path20 = Object.freeze(path20.concat(node));
|
|
174
174
|
for (let i = 0; i < node.items.length; ++i) {
|
|
175
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
175
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path20);
|
|
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
|
-
|
|
187
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
186
|
+
path20 = Object.freeze(path20.concat(node));
|
|
187
|
+
const ck = await visitAsync_("key", node.key, visitor, path20);
|
|
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,
|
|
192
|
+
const cv = await visitAsync_("value", node.value, visitor, path20);
|
|
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,
|
|
219
|
+
function callVisitor(key, node, visitor, path20) {
|
|
220
220
|
if (typeof visitor === "function")
|
|
221
|
-
return visitor(key, node,
|
|
221
|
+
return visitor(key, node, path20);
|
|
222
222
|
if (identity.isMap(node))
|
|
223
|
-
return visitor.Map?.(key, node,
|
|
223
|
+
return visitor.Map?.(key, node, path20);
|
|
224
224
|
if (identity.isSeq(node))
|
|
225
|
-
return visitor.Seq?.(key, node,
|
|
225
|
+
return visitor.Seq?.(key, node, path20);
|
|
226
226
|
if (identity.isPair(node))
|
|
227
|
-
return visitor.Pair?.(key, node,
|
|
227
|
+
return visitor.Pair?.(key, node, path20);
|
|
228
228
|
if (identity.isScalar(node))
|
|
229
|
-
return visitor.Scalar?.(key, node,
|
|
229
|
+
return visitor.Scalar?.(key, node, path20);
|
|
230
230
|
if (identity.isAlias(node))
|
|
231
|
-
return visitor.Alias?.(key, node,
|
|
231
|
+
return visitor.Alias?.(key, node, path20);
|
|
232
232
|
return void 0;
|
|
233
233
|
}
|
|
234
|
-
function replaceNode(key,
|
|
235
|
-
const parent =
|
|
234
|
+
function replaceNode(key, path20, node) {
|
|
235
|
+
const parent = path20[path20.length - 1];
|
|
236
236
|
if (identity.isCollection(parent)) {
|
|
237
237
|
parent.items[key] = node;
|
|
238
238
|
} else if (identity.isPair(parent)) {
|
|
@@ -844,10 +844,10 @@ var require_Collection = __commonJS({
|
|
|
844
844
|
var createNode = require_createNode();
|
|
845
845
|
var identity = require_identity();
|
|
846
846
|
var Node = require_Node();
|
|
847
|
-
function collectionFromPath(schema,
|
|
847
|
+
function collectionFromPath(schema, path20, value) {
|
|
848
848
|
let v = value;
|
|
849
|
-
for (let i =
|
|
850
|
-
const k =
|
|
849
|
+
for (let i = path20.length - 1; i >= 0; --i) {
|
|
850
|
+
const k = path20[i];
|
|
851
851
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
852
852
|
const a = [];
|
|
853
853
|
a[k] = v;
|
|
@@ -866,7 +866,7 @@ var require_Collection = __commonJS({
|
|
|
866
866
|
sourceObjects: /* @__PURE__ */ new Map()
|
|
867
867
|
});
|
|
868
868
|
}
|
|
869
|
-
var isEmptyPath = (
|
|
869
|
+
var isEmptyPath = (path20) => path20 == null || typeof path20 === "object" && !!path20[Symbol.iterator]().next().done;
|
|
870
870
|
var Collection = class extends Node.NodeBase {
|
|
871
871
|
constructor(type, schema) {
|
|
872
872
|
super(type);
|
|
@@ -896,11 +896,11 @@ var require_Collection = __commonJS({
|
|
|
896
896
|
* be a Pair instance or a `{ key, value }` object, which may not have a key
|
|
897
897
|
* that already exists in the map.
|
|
898
898
|
*/
|
|
899
|
-
addIn(
|
|
900
|
-
if (isEmptyPath(
|
|
899
|
+
addIn(path20, value) {
|
|
900
|
+
if (isEmptyPath(path20))
|
|
901
901
|
this.add(value);
|
|
902
902
|
else {
|
|
903
|
-
const [key, ...rest] =
|
|
903
|
+
const [key, ...rest] = path20;
|
|
904
904
|
const node = this.get(key, true);
|
|
905
905
|
if (identity.isCollection(node))
|
|
906
906
|
node.addIn(rest, value);
|
|
@@ -914,8 +914,8 @@ var require_Collection = __commonJS({
|
|
|
914
914
|
* Removes a value from the collection.
|
|
915
915
|
* @returns `true` if the item was found and removed.
|
|
916
916
|
*/
|
|
917
|
-
deleteIn(
|
|
918
|
-
const [key, ...rest] =
|
|
917
|
+
deleteIn(path20) {
|
|
918
|
+
const [key, ...rest] = path20;
|
|
919
919
|
if (rest.length === 0)
|
|
920
920
|
return this.delete(key);
|
|
921
921
|
const node = this.get(key, true);
|
|
@@ -929,8 +929,8 @@ var require_Collection = __commonJS({
|
|
|
929
929
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
930
930
|
* `true` (collections are always returned intact).
|
|
931
931
|
*/
|
|
932
|
-
getIn(
|
|
933
|
-
const [key, ...rest] =
|
|
932
|
+
getIn(path20, keepScalar) {
|
|
933
|
+
const [key, ...rest] = path20;
|
|
934
934
|
const node = this.get(key, true);
|
|
935
935
|
if (rest.length === 0)
|
|
936
936
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -948,8 +948,8 @@ var require_Collection = __commonJS({
|
|
|
948
948
|
/**
|
|
949
949
|
* Checks if the collection includes a value with the key `key`.
|
|
950
950
|
*/
|
|
951
|
-
hasIn(
|
|
952
|
-
const [key, ...rest] =
|
|
951
|
+
hasIn(path20) {
|
|
952
|
+
const [key, ...rest] = path20;
|
|
953
953
|
if (rest.length === 0)
|
|
954
954
|
return this.has(key);
|
|
955
955
|
const node = this.get(key, true);
|
|
@@ -959,8 +959,8 @@ var require_Collection = __commonJS({
|
|
|
959
959
|
* Sets a value in this collection. For `!!set`, `value` needs to be a
|
|
960
960
|
* boolean to add/remove the item from the set.
|
|
961
961
|
*/
|
|
962
|
-
setIn(
|
|
963
|
-
const [key, ...rest] =
|
|
962
|
+
setIn(path20, value) {
|
|
963
|
+
const [key, ...rest] = path20;
|
|
964
964
|
if (rest.length === 0) {
|
|
965
965
|
this.set(key, value);
|
|
966
966
|
} else {
|
|
@@ -3475,9 +3475,9 @@ var require_Document = __commonJS({
|
|
|
3475
3475
|
this.contents.add(value);
|
|
3476
3476
|
}
|
|
3477
3477
|
/** Adds a value to the document. */
|
|
3478
|
-
addIn(
|
|
3478
|
+
addIn(path20, value) {
|
|
3479
3479
|
if (assertCollection(this.contents))
|
|
3480
|
-
this.contents.addIn(
|
|
3480
|
+
this.contents.addIn(path20, value);
|
|
3481
3481
|
}
|
|
3482
3482
|
/**
|
|
3483
3483
|
* Create a new `Alias` node, ensuring that the target `node` has the required anchor.
|
|
@@ -3552,14 +3552,14 @@ var require_Document = __commonJS({
|
|
|
3552
3552
|
* Removes a value from the document.
|
|
3553
3553
|
* @returns `true` if the item was found and removed.
|
|
3554
3554
|
*/
|
|
3555
|
-
deleteIn(
|
|
3556
|
-
if (Collection.isEmptyPath(
|
|
3555
|
+
deleteIn(path20) {
|
|
3556
|
+
if (Collection.isEmptyPath(path20)) {
|
|
3557
3557
|
if (this.contents == null)
|
|
3558
3558
|
return false;
|
|
3559
3559
|
this.contents = null;
|
|
3560
3560
|
return true;
|
|
3561
3561
|
}
|
|
3562
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
3562
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path20) : false;
|
|
3563
3563
|
}
|
|
3564
3564
|
/**
|
|
3565
3565
|
* Returns item at `key`, or `undefined` if not found. By default unwraps
|
|
@@ -3574,10 +3574,10 @@ var require_Document = __commonJS({
|
|
|
3574
3574
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
3575
3575
|
* `true` (collections are always returned intact).
|
|
3576
3576
|
*/
|
|
3577
|
-
getIn(
|
|
3578
|
-
if (Collection.isEmptyPath(
|
|
3577
|
+
getIn(path20, keepScalar) {
|
|
3578
|
+
if (Collection.isEmptyPath(path20))
|
|
3579
3579
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
3580
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
3580
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path20, keepScalar) : void 0;
|
|
3581
3581
|
}
|
|
3582
3582
|
/**
|
|
3583
3583
|
* Checks if the document includes a value with the key `key`.
|
|
@@ -3588,10 +3588,10 @@ var require_Document = __commonJS({
|
|
|
3588
3588
|
/**
|
|
3589
3589
|
* Checks if the document includes a value at `path`.
|
|
3590
3590
|
*/
|
|
3591
|
-
hasIn(
|
|
3592
|
-
if (Collection.isEmptyPath(
|
|
3591
|
+
hasIn(path20) {
|
|
3592
|
+
if (Collection.isEmptyPath(path20))
|
|
3593
3593
|
return this.contents !== void 0;
|
|
3594
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
3594
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path20) : false;
|
|
3595
3595
|
}
|
|
3596
3596
|
/**
|
|
3597
3597
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
@@ -3608,13 +3608,13 @@ var require_Document = __commonJS({
|
|
|
3608
3608
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
3609
3609
|
* boolean to add/remove the item from the set.
|
|
3610
3610
|
*/
|
|
3611
|
-
setIn(
|
|
3612
|
-
if (Collection.isEmptyPath(
|
|
3611
|
+
setIn(path20, value) {
|
|
3612
|
+
if (Collection.isEmptyPath(path20)) {
|
|
3613
3613
|
this.contents = value;
|
|
3614
3614
|
} else if (this.contents == null) {
|
|
3615
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
3615
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path20), value);
|
|
3616
3616
|
} else if (assertCollection(this.contents)) {
|
|
3617
|
-
this.contents.setIn(
|
|
3617
|
+
this.contents.setIn(path20, value);
|
|
3618
3618
|
}
|
|
3619
3619
|
}
|
|
3620
3620
|
/**
|
|
@@ -5575,9 +5575,9 @@ var require_cst_visit = __commonJS({
|
|
|
5575
5575
|
visit.BREAK = BREAK;
|
|
5576
5576
|
visit.SKIP = SKIP;
|
|
5577
5577
|
visit.REMOVE = REMOVE;
|
|
5578
|
-
visit.itemAtPath = (cst,
|
|
5578
|
+
visit.itemAtPath = (cst, path20) => {
|
|
5579
5579
|
let item = cst;
|
|
5580
|
-
for (const [field, index] of
|
|
5580
|
+
for (const [field, index] of path20) {
|
|
5581
5581
|
const tok = item?.[field];
|
|
5582
5582
|
if (tok && "items" in tok) {
|
|
5583
5583
|
item = tok.items[index];
|
|
@@ -5586,23 +5586,23 @@ var require_cst_visit = __commonJS({
|
|
|
5586
5586
|
}
|
|
5587
5587
|
return item;
|
|
5588
5588
|
};
|
|
5589
|
-
visit.parentCollection = (cst,
|
|
5590
|
-
const parent = visit.itemAtPath(cst,
|
|
5591
|
-
const field =
|
|
5589
|
+
visit.parentCollection = (cst, path20) => {
|
|
5590
|
+
const parent = visit.itemAtPath(cst, path20.slice(0, -1));
|
|
5591
|
+
const field = path20[path20.length - 1][0];
|
|
5592
5592
|
const coll = parent?.[field];
|
|
5593
5593
|
if (coll && "items" in coll)
|
|
5594
5594
|
return coll;
|
|
5595
5595
|
throw new Error("Parent collection not found");
|
|
5596
5596
|
};
|
|
5597
|
-
function _visit(
|
|
5598
|
-
let ctrl = visitor(item,
|
|
5597
|
+
function _visit(path20, item, visitor) {
|
|
5598
|
+
let ctrl = visitor(item, path20);
|
|
5599
5599
|
if (typeof ctrl === "symbol")
|
|
5600
5600
|
return ctrl;
|
|
5601
5601
|
for (const field of ["key", "value"]) {
|
|
5602
5602
|
const token = item[field];
|
|
5603
5603
|
if (token && "items" in token) {
|
|
5604
5604
|
for (let i = 0; i < token.items.length; ++i) {
|
|
5605
|
-
const ci = _visit(Object.freeze(
|
|
5605
|
+
const ci = _visit(Object.freeze(path20.concat([[field, i]])), token.items[i], visitor);
|
|
5606
5606
|
if (typeof ci === "number")
|
|
5607
5607
|
i = ci - 1;
|
|
5608
5608
|
else if (ci === BREAK)
|
|
@@ -5613,10 +5613,10 @@ var require_cst_visit = __commonJS({
|
|
|
5613
5613
|
}
|
|
5614
5614
|
}
|
|
5615
5615
|
if (typeof ctrl === "function" && field === "key")
|
|
5616
|
-
ctrl = ctrl(item,
|
|
5616
|
+
ctrl = ctrl(item, path20);
|
|
5617
5617
|
}
|
|
5618
5618
|
}
|
|
5619
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5619
|
+
return typeof ctrl === "function" ? ctrl(item, path20) : ctrl;
|
|
5620
5620
|
}
|
|
5621
5621
|
exports.visit = visit;
|
|
5622
5622
|
}
|
|
@@ -6918,14 +6918,14 @@ var require_parser = __commonJS({
|
|
|
6918
6918
|
case "scalar":
|
|
6919
6919
|
case "single-quoted-scalar":
|
|
6920
6920
|
case "double-quoted-scalar": {
|
|
6921
|
-
const
|
|
6921
|
+
const fs19 = this.flowScalar(this.type);
|
|
6922
6922
|
if (atNextItem || it.value) {
|
|
6923
|
-
map.items.push({ start, key:
|
|
6923
|
+
map.items.push({ start, key: fs19, sep: [] });
|
|
6924
6924
|
this.onKeyLine = true;
|
|
6925
6925
|
} else if (it.sep) {
|
|
6926
|
-
this.stack.push(
|
|
6926
|
+
this.stack.push(fs19);
|
|
6927
6927
|
} else {
|
|
6928
|
-
Object.assign(it, { key:
|
|
6928
|
+
Object.assign(it, { key: fs19, sep: [] });
|
|
6929
6929
|
this.onKeyLine = true;
|
|
6930
6930
|
}
|
|
6931
6931
|
return;
|
|
@@ -7053,13 +7053,13 @@ var require_parser = __commonJS({
|
|
|
7053
7053
|
case "scalar":
|
|
7054
7054
|
case "single-quoted-scalar":
|
|
7055
7055
|
case "double-quoted-scalar": {
|
|
7056
|
-
const
|
|
7056
|
+
const fs19 = this.flowScalar(this.type);
|
|
7057
7057
|
if (!it || it.value)
|
|
7058
|
-
fc.items.push({ start: [], key:
|
|
7058
|
+
fc.items.push({ start: [], key: fs19, sep: [] });
|
|
7059
7059
|
else if (it.sep)
|
|
7060
|
-
this.stack.push(
|
|
7060
|
+
this.stack.push(fs19);
|
|
7061
7061
|
else
|
|
7062
|
-
Object.assign(it, { key:
|
|
7062
|
+
Object.assign(it, { key: fs19, sep: [] });
|
|
7063
7063
|
return;
|
|
7064
7064
|
}
|
|
7065
7065
|
case "flow-map-end":
|
|
@@ -7392,10 +7392,7 @@ var NotGrantedError = class extends CliError {
|
|
|
7392
7392
|
};
|
|
7393
7393
|
|
|
7394
7394
|
// src/commands/agents.ts
|
|
7395
|
-
import
|
|
7396
|
-
import fs11 from "node:fs";
|
|
7397
|
-
import os8 from "node:os";
|
|
7398
|
-
import path14 from "node:path";
|
|
7395
|
+
import fs8 from "node:fs";
|
|
7399
7396
|
import { parseArgs as parseArgs3 } from "node:util";
|
|
7400
7397
|
|
|
7401
7398
|
// src/auth/oauth-client.ts
|
|
@@ -7556,7 +7553,7 @@ function tokenSource(env, baseUrl) {
|
|
|
7556
7553
|
}
|
|
7557
7554
|
|
|
7558
7555
|
// src/version.ts
|
|
7559
|
-
var VERSION = true ? "0.1.
|
|
7556
|
+
var VERSION = true ? "0.1.28" : "dev";
|
|
7560
7557
|
var USER_AGENT = `diverge/${VERSION}`;
|
|
7561
7558
|
|
|
7562
7559
|
// src/http.ts
|
|
@@ -7939,11 +7936,11 @@ function verbPath(usage2, args) {
|
|
|
7939
7936
|
function groupOptions(usage2) {
|
|
7940
7937
|
return usage2.split("\n", 1)[0].match(/\[--[^\]]+\]/g) ?? [];
|
|
7941
7938
|
}
|
|
7942
|
-
function verbUsage(command, usage2,
|
|
7943
|
-
const lines = usageEntries(usage2).filter((entry) => startsWith(entry.verbs,
|
|
7944
|
-
if (
|
|
7939
|
+
function verbUsage(command, usage2, path20) {
|
|
7940
|
+
const lines = usageEntries(usage2).filter((entry) => startsWith(entry.verbs, path20)).flatMap((entry) => entry.lines);
|
|
7941
|
+
if (path20.length === 0 || lines.length === 0) return usage2;
|
|
7945
7942
|
const shared = groupOptions(usage2);
|
|
7946
|
-
return [`diverge ${command} ${
|
|
7943
|
+
return [`diverge ${command} ${path20.join(" ")} ...`, "", ...lines, ...shared.length ? ["", `Every ${command} command also takes ${shared.join(" ")}.`] : []].join("\n");
|
|
7947
7944
|
}
|
|
7948
7945
|
var COMMON_WORDS = /* @__PURE__ */ new Set(["the", "and", "you", "for", "its", "with", "not", "one", "are", "all", "any", "from", "that", "this", "own", "per", "into", "out", "else", "each", "what", "when", "your"]);
|
|
7949
7946
|
function distance(a, b) {
|
|
@@ -7995,12 +7992,12 @@ function didYouMean(matches) {
|
|
|
7995
7992
|
return ` Did you mean one of:
|
|
7996
7993
|
${matches.map((match) => ` ${shown(match)}`).join("\n")}`;
|
|
7997
7994
|
}
|
|
7998
|
-
function verbCandidates(command, usage2,
|
|
7995
|
+
function verbCandidates(command, usage2, path20) {
|
|
7999
7996
|
const byName = /* @__PURE__ */ new Map();
|
|
8000
7997
|
for (const entry of usageEntries(usage2)) {
|
|
8001
|
-
if (!startsWith(entry.verbs,
|
|
8002
|
-
const name = entry.verbs[
|
|
8003
|
-
const invocation = ["diverge", command, ...
|
|
7998
|
+
if (!startsWith(entry.verbs, path20)) continue;
|
|
7999
|
+
const name = entry.verbs[path20.length] ?? "";
|
|
8000
|
+
const invocation = ["diverge", command, ...path20, ...name ? [name] : []].join(" ");
|
|
8004
8001
|
const lines = [entry.summary, ...entry.lines.slice(1).map((line) => line.trim())].filter(Boolean);
|
|
8005
8002
|
const known = byName.get(name);
|
|
8006
8003
|
if (known) known.lines.push(...lines);
|
|
@@ -8008,9 +8005,9 @@ function verbCandidates(command, usage2, path18) {
|
|
|
8008
8005
|
}
|
|
8009
8006
|
return [...byName.values()];
|
|
8010
8007
|
}
|
|
8011
|
-
function unknownVerb(command, usage2,
|
|
8012
|
-
const candidates = verbCandidates(command, usage2,
|
|
8013
|
-
const where = [command, ...
|
|
8008
|
+
function unknownVerb(command, usage2, path20, verb, rest = []) {
|
|
8009
|
+
const candidates = verbCandidates(command, usage2, path20);
|
|
8010
|
+
const where = [command, ...path20].join(" ");
|
|
8014
8011
|
if (!verb) return new CliError(`${where} needs one of: ${candidates.map((candidate) => candidate.name).filter(Boolean).join(", ")}
|
|
8015
8012
|
|
|
8016
8013
|
${usage2}`);
|
|
@@ -8018,10 +8015,10 @@ ${usage2}`);
|
|
|
8018
8015
|
|
|
8019
8016
|
${usage2}`);
|
|
8020
8017
|
}
|
|
8021
|
-
function optionCandidates(usage2,
|
|
8018
|
+
function optionCandidates(usage2, path20) {
|
|
8022
8019
|
const entries = usageEntries(usage2);
|
|
8023
8020
|
const [header = "", ...body] = usage2.split("\n");
|
|
8024
|
-
const described = entries.length === 0 ||
|
|
8021
|
+
const described = entries.length === 0 || path20.length === 0 ? body : entries.filter((entry) => startsWith(entry.verbs, path20)).flatMap((entry) => entry.lines);
|
|
8025
8022
|
const byName = /* @__PURE__ */ new Map();
|
|
8026
8023
|
const add = (line, describes) => {
|
|
8027
8024
|
for (const [flag] of line.matchAll(/--[a-z][a-z-]*/g)) {
|
|
@@ -8038,8 +8035,8 @@ function unknownOption(error, usage2, args) {
|
|
|
8038
8035
|
if (!(error instanceof Error) || error.name !== "TypeError") return null;
|
|
8039
8036
|
const option = /^Unknown option '([^']+)'/.exec(error.message)?.[1];
|
|
8040
8037
|
if (!option) return null;
|
|
8041
|
-
const
|
|
8042
|
-
return `Unknown option: ${option}.${didYouMean(closest(option, optionCandidates(usage2,
|
|
8038
|
+
const path20 = verbPath(usage2, args);
|
|
8039
|
+
return `Unknown option: ${option}.${didYouMean(closest(option, optionCandidates(usage2, path20)))}`;
|
|
8043
8040
|
}
|
|
8044
8041
|
|
|
8045
8042
|
// src/commands/login.ts
|
|
@@ -8158,19 +8155,23 @@ unwraps the platform's envelope and nothing inside. Its shape is the tool's
|
|
|
8158
8155
|
own and differs by tool and plugin \u2014 one returns its records under \`data\`,
|
|
8159
8156
|
another at the top level \u2014 so read a tool's shape from its own answer, not
|
|
8160
8157
|
from the last tool's. Parse stdout alone; stderr only carries the scope and
|
|
8161
|
-
identity lines.
|
|
8158
|
+
identity lines. A shell tool that shows both streams in one view prints those
|
|
8159
|
+
two lines above the JSON, but a pipe or \`$(...)\` never sees them: pipe
|
|
8160
|
+
straight to \`jq\` and strip nothing. Without \`--json\`, errors are text on
|
|
8161
|
+
stderr.
|
|
8162
8162
|
|
|
8163
8163
|
## Several related writes: one set, one decision
|
|
8164
8164
|
|
|
8165
|
-
When two or more writes serve one intent \u2014
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8165
|
+
When two or more writes serve one intent \u2014 deactivate several users, set
|
|
8166
|
+
several notification routes \u2014 raise them as one set, never one call per
|
|
8167
|
+
write: the person approves the set once instead of clicking through one card
|
|
8168
|
+
each. A change to one thing's access is already one call: a role's holders,
|
|
8169
|
+
a group's members, a person's groups.
|
|
8169
8170
|
|
|
8170
8171
|
\`\`\`bash
|
|
8171
|
-
diverge tools set - --summary "Coordinators
|
|
8172
|
-
[{"tool": "
|
|
8173
|
-
{"tool": "
|
|
8172
|
+
diverge tools set - --summary "Coordinators who left" --json <<'JSON'
|
|
8173
|
+
[{"tool": "rbac_deactivate_user", "args": {"userId": "u1"}},
|
|
8174
|
+
{"tool": "rbac_deactivate_user", "args": {"userId": "u2"}}]
|
|
8174
8175
|
JSON
|
|
8175
8176
|
\`\`\`
|
|
8176
8177
|
|
|
@@ -8276,7 +8277,7 @@ var DRIVE_CHROME_SKILL = {
|
|
|
8276
8277
|
"tool call connects; there is nothing to restart.",
|
|
8277
8278
|
"",
|
|
8278
8279
|
"If no `mcp__chrome-devtools__` tool is listed at all, dsh was started without",
|
|
8279
|
-
"the Divergent overlay: `diverge
|
|
8280
|
+
"the Divergent overlay: `diverge dsh` starts it with one.",
|
|
8280
8281
|
""
|
|
8281
8282
|
].join("\n")
|
|
8282
8283
|
};
|
|
@@ -8292,7 +8293,6 @@ import path5 from "node:path";
|
|
|
8292
8293
|
// src/agents/dsh.ts
|
|
8293
8294
|
var import_yaml = __toESM(require_dist(), 1);
|
|
8294
8295
|
import fs5 from "node:fs";
|
|
8295
|
-
import { createRequire } from "node:module";
|
|
8296
8296
|
import os2 from "node:os";
|
|
8297
8297
|
import path4 from "node:path";
|
|
8298
8298
|
|
|
@@ -8361,18 +8361,24 @@ function describeToolExecution(env = process.env) {
|
|
|
8361
8361
|
}
|
|
8362
8362
|
|
|
8363
8363
|
// src/tools/client.ts
|
|
8364
|
-
var AUTHORITY_CHANGE_CODE = "REMOTE_PLUGIN_AUTHORITY_CHANGE";
|
|
8365
8364
|
var INVOCATION_SET_TOOL = "functions_invocation_set";
|
|
8366
8365
|
var PENDING_INVOCATION_STATUSES = ["pending", "approved", "executing"];
|
|
8367
|
-
function
|
|
8366
|
+
function isPending(result) {
|
|
8367
|
+
return !!result && typeof result === "object" && result.status === "pending_approval";
|
|
8368
|
+
}
|
|
8369
|
+
function devTaskContinuation(env) {
|
|
8370
|
+
const raw = env.DIVERGENT_DEV_TASK?.trim();
|
|
8371
|
+
if (!raw) return void 0;
|
|
8372
|
+
let task;
|
|
8368
8373
|
try {
|
|
8369
|
-
|
|
8374
|
+
task = JSON.parse(raw);
|
|
8370
8375
|
} catch {
|
|
8371
|
-
|
|
8376
|
+
throw new CliError('DIVERGENT_DEV_TASK is not JSON; the launcher writes {"organizationId","host","worktreePath"}');
|
|
8372
8377
|
}
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8378
|
+
if (!task || typeof task !== "object" || Array.isArray(task)) {
|
|
8379
|
+
throw new CliError('DIVERGENT_DEV_TASK names no task; the launcher writes {"organizationId","host","worktreePath"}');
|
|
8380
|
+
}
|
|
8381
|
+
return { kind: "forge.dev-board.task-resume", payload: task };
|
|
8376
8382
|
}
|
|
8377
8383
|
var REQUEST_TIMEOUT_MS = 63e4;
|
|
8378
8384
|
var TOKEN_REFRESH_MARGIN_SECONDS = 60;
|
|
@@ -8420,10 +8426,10 @@ var ToolsClient = class {
|
|
|
8420
8426
|
if (this.settings.mode === "service") headers["x-divergent-user-id"] = this.settings.delegatedUserId;
|
|
8421
8427
|
return headers;
|
|
8422
8428
|
}
|
|
8423
|
-
scoped(
|
|
8429
|
+
scoped(path20) {
|
|
8424
8430
|
const query = new URLSearchParams({ organizationId: this.settings.organizationId });
|
|
8425
8431
|
if (this.settings.projectId) query.set("projectId", this.settings.projectId);
|
|
8426
|
-
return `${this.settings.apiUrl}${
|
|
8432
|
+
return `${this.settings.apiUrl}${path20}?${query.toString()}`;
|
|
8427
8433
|
}
|
|
8428
8434
|
async send(method, url, body) {
|
|
8429
8435
|
return this.request(method, url, body, void 0);
|
|
@@ -8495,7 +8501,12 @@ var ToolsClient = class {
|
|
|
8495
8501
|
* unwrapped here, once — a pending approval passes through as it is.
|
|
8496
8502
|
*/
|
|
8497
8503
|
async callTool(name, args) {
|
|
8498
|
-
const
|
|
8504
|
+
const continuation = devTaskContinuation(this.env);
|
|
8505
|
+
const body = await this.send("POST", this.scoped("/api/plugins/functions/tools/execute"), {
|
|
8506
|
+
toolName: name,
|
|
8507
|
+
arguments: args ?? {},
|
|
8508
|
+
...continuation ? { continuation } : {}
|
|
8509
|
+
});
|
|
8499
8510
|
const outcome = body.result;
|
|
8500
8511
|
if (outcome?.status !== "succeeded") return body;
|
|
8501
8512
|
return { ...body, result: outcome.result };
|
|
@@ -8507,9 +8518,11 @@ var ToolsClient = class {
|
|
|
8507
8518
|
* carries every call's outcome in `result.calls`.
|
|
8508
8519
|
*/
|
|
8509
8520
|
async callSet(calls, summary) {
|
|
8521
|
+
const continuation = devTaskContinuation(this.env);
|
|
8510
8522
|
const body = await this.send("POST", this.scoped("/api/plugins/functions/tools/sets"), {
|
|
8511
8523
|
...summary ? { summary } : {},
|
|
8512
|
-
calls: calls.map((call2) => ({ toolName: call2.tool, arguments: call2.args ?? {} }))
|
|
8524
|
+
calls: calls.map((call2) => ({ toolName: call2.tool, arguments: call2.args ?? {} })),
|
|
8525
|
+
...continuation ? { continuation } : {}
|
|
8513
8526
|
});
|
|
8514
8527
|
const outcome = body.result;
|
|
8515
8528
|
if (outcome?.status !== "succeeded") return body;
|
|
@@ -8520,36 +8533,16 @@ var ToolsClient = class {
|
|
|
8520
8533
|
return this.send("GET", this.scoped(`/api/plugins/functions/tools/invocations/${encodeURIComponent(invocationId)}`));
|
|
8521
8534
|
}
|
|
8522
8535
|
// ── Tenant plugins (RFC: Tenant Plugins) ──────────────────────────────
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
publishPlugin(manifest, options) {
|
|
8529
|
-
return this.request(
|
|
8530
|
-
"POST",
|
|
8531
|
-
this.descriptorsUrl(),
|
|
8532
|
-
{ targetOrganizationId: this.settings.organizationId, manifest, dryRun: options.dryRun },
|
|
8533
|
-
(status2, text2) => {
|
|
8534
|
-
if (status2 === 403) {
|
|
8535
|
-
const parsed2 = parseJson(text2);
|
|
8536
|
-
return parsed2?.code === AUTHORITY_CHANGE_CODE && Array.isArray(parsed2.details) ? { ok: false, details: parsed2.details.map(String), authorityChange: true } : void 0;
|
|
8537
|
-
}
|
|
8538
|
-
if (status2 !== 400) return void 0;
|
|
8539
|
-
const parsed = parseJson(text2);
|
|
8540
|
-
if (Array.isArray(parsed?.details)) return { ok: false, details: parsed.details.map(String) };
|
|
8541
|
-
return { ok: false, details: [parsed?.error ?? text2] };
|
|
8542
|
-
}
|
|
8543
|
-
).then((outcome) => "ok" in outcome ? outcome : { ok: true, result: outcome });
|
|
8544
|
-
}
|
|
8536
|
+
/**
|
|
8537
|
+
* The descriptors the organization published: the one read the CLI makes
|
|
8538
|
+
* outside Functions. Validating, publishing, reshaping and retiring are
|
|
8539
|
+
* tool invocations through `callTool` (ADR 0124).
|
|
8540
|
+
*/
|
|
8545
8541
|
async listPlugins() {
|
|
8546
|
-
const
|
|
8542
|
+
const query = new URLSearchParams({ targetOrganizationId: this.settings.organizationId });
|
|
8543
|
+
const body = await this.send("GET", `${this.settings.apiUrl}/api/plugins/forge/plugins/descriptors?${query.toString()}`);
|
|
8547
8544
|
return body.plugins ?? [];
|
|
8548
8545
|
}
|
|
8549
|
-
async retirePlugin(pluginId) {
|
|
8550
|
-
const body = await this.send("DELETE", this.descriptorsUrl(pluginId), { targetOrganizationId: this.settings.organizationId });
|
|
8551
|
-
return body.plugin;
|
|
8552
|
-
}
|
|
8553
8546
|
};
|
|
8554
8547
|
|
|
8555
8548
|
// src/agents/dsh.ts
|
|
@@ -8567,8 +8560,7 @@ function dshPatchYaml(options) {
|
|
|
8567
8560
|
const api = options.surface === "openai" ? "openai-completions" : "anthropic-messages";
|
|
8568
8561
|
const endpoint = options.surface === "openai" ? `${options.baseUrl}/v1` : options.baseUrl;
|
|
8569
8562
|
return [
|
|
8570
|
-
"# Divergent gateway overlay for dsh; written by `diverge
|
|
8571
|
-
`# Boot with: ${tokenEnv}="$(diverge token)" dsh --profile <profile> --patch <this file>`,
|
|
8563
|
+
"# Divergent gateway overlay for dsh; written by `diverge dsh` on every launch.",
|
|
8572
8564
|
"- id: llm-pi-ai",
|
|
8573
8565
|
" config:",
|
|
8574
8566
|
" providers:",
|
|
@@ -8589,6 +8581,7 @@ function dshPatchYaml(options) {
|
|
|
8589
8581
|
" config:",
|
|
8590
8582
|
` provider: ${route}`,
|
|
8591
8583
|
` model: ${JSON.stringify(options.defaultModel)}`,
|
|
8584
|
+
...options.reasoningEffort ? [` reasoningEffort: ${JSON.stringify(options.reasoningEffort)}`] : [],
|
|
8592
8585
|
// dsh's native DeepSeek adapter needs a vendor key nobody here holds and,
|
|
8593
8586
|
// where one exists, would send the organization's code straight to the
|
|
8594
8587
|
// vendor past the gateway, its ledger, budget and policy. Its session
|
|
@@ -8599,6 +8592,11 @@ function dshPatchYaml(options) {
|
|
|
8599
8592
|
" disabled: true",
|
|
8600
8593
|
"- id: session-telemetry-otel",
|
|
8601
8594
|
" disabled: true",
|
|
8595
|
+
// The headless profile turns dsh's credential store off, for a job given a
|
|
8596
|
+
// key in its environment. A launch keeps its token fresh in that file, so
|
|
8597
|
+
// it stays on in every profile.
|
|
8598
|
+
"- id: credentials",
|
|
8599
|
+
" disabled: false",
|
|
8602
8600
|
...dshSearchRows(),
|
|
8603
8601
|
...dshShellRows(),
|
|
8604
8602
|
...dshBrowserRows(),
|
|
@@ -8614,16 +8612,6 @@ function dshLaunchHeaderRows(launch) {
|
|
|
8614
8612
|
` ${DSH_LAUNCH_ID_HEADER}: ${JSON.stringify(launch.id)}`
|
|
8615
8613
|
];
|
|
8616
8614
|
}
|
|
8617
|
-
function dshVersionOf(binary) {
|
|
8618
|
-
const dir = dshPackageDir(binary);
|
|
8619
|
-
if (!dir) return null;
|
|
8620
|
-
try {
|
|
8621
|
-
const version = JSON.parse(fs5.readFileSync(path4.join(dir, "package.json"), "utf8")).version;
|
|
8622
|
-
return typeof version === "string" && version.length > 0 ? version : null;
|
|
8623
|
-
} catch {
|
|
8624
|
-
return null;
|
|
8625
|
-
}
|
|
8626
|
-
}
|
|
8627
8615
|
function dshShellRows() {
|
|
8628
8616
|
return ["bash-sandbox", "pwsh-sandbox"].flatMap((id) => [
|
|
8629
8617
|
`- id: ${id}`,
|
|
@@ -8657,11 +8645,6 @@ function dshSearchRows() {
|
|
|
8657
8645
|
" searchTimeoutMs: 60000"
|
|
8658
8646
|
];
|
|
8659
8647
|
}
|
|
8660
|
-
function servedIds(overlay) {
|
|
8661
|
-
const rows = (0, import_yaml.parse)(overlay) ?? [];
|
|
8662
|
-
const route = rows.find((row) => row.id === "llm-pi-ai");
|
|
8663
|
-
return Object.values(route?.config?.providers ?? {}).flatMap((provider) => (provider.models ?? []).map((entry) => entry.id));
|
|
8664
|
-
}
|
|
8665
8648
|
function mergeDshCredentials(existing, token, ref = "DIVERGENT_TOKEN") {
|
|
8666
8649
|
const parsed = existing?.trim() ? (0, import_yaml.parse)(existing) : null;
|
|
8667
8650
|
let document = {};
|
|
@@ -8676,95 +8659,41 @@ function mergeDshCredentials(existing, token, ref = "DIVERGENT_TOKEN") {
|
|
|
8676
8659
|
if (document.records && Object.keys(document.records).length > 0) ordered.records = document.records;
|
|
8677
8660
|
return (0, import_yaml.stringify)(ordered, { lineWidth: 0 });
|
|
8678
8661
|
}
|
|
8679
|
-
function
|
|
8680
|
-
if (!
|
|
8681
|
-
const
|
|
8682
|
-
if (!
|
|
8683
|
-
const
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
function
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
if (
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
} catch {
|
|
8703
|
-
return null;
|
|
8704
|
-
}
|
|
8705
|
-
for (; ; ) {
|
|
8706
|
-
try {
|
|
8707
|
-
if (JSON.parse(fs5.readFileSync(path4.join(dir, "package.json"), "utf8")).name === "@deepseek-ai/dsh") return dir;
|
|
8708
|
-
} catch {
|
|
8709
|
-
}
|
|
8710
|
-
const parent = path4.dirname(dir);
|
|
8711
|
-
if (parent === dir) return null;
|
|
8712
|
-
dir = parent;
|
|
8713
|
-
}
|
|
8714
|
-
}
|
|
8715
|
-
function shippedPresetDir(packageDir, id = "standard") {
|
|
8716
|
-
try {
|
|
8717
|
-
const require2 = createRequire(path4.join(packageDir, "package.json"));
|
|
8718
|
-
const dir = path4.join(path4.dirname(require2.resolve("@deepseek-ai/dsh-agent-presets/package.json")), "presets", id);
|
|
8719
|
-
return fs5.existsSync(path4.join(dir, "agent.cordis.yml")) ? dir : null;
|
|
8720
|
-
} catch {
|
|
8721
|
-
return null;
|
|
8722
|
-
}
|
|
8723
|
-
}
|
|
8724
|
-
function divergentPreset(shippedComposition) {
|
|
8725
|
-
const lines = shippedComposition.split("\n");
|
|
8726
|
-
const start = lines.findIndex((line) => /^- id: ['"]?tool-web['"]?\s*$/.test(line));
|
|
8727
|
-
if (start === -1) throw new Error("the shipped preset composes no tool-web row");
|
|
8728
|
-
let end = lines.findIndex((line, index) => index > start && /^- /.test(line));
|
|
8729
|
-
if (end === -1) end = lines.length;
|
|
8730
|
-
const row = lines.slice(start, end);
|
|
8731
|
-
const search = row.findIndex((line) => /^\s+search:/.test(line));
|
|
8662
|
+
function dshSavedModel(patchText, surface) {
|
|
8663
|
+
if (!patchText?.trim()) return null;
|
|
8664
|
+
const rows = (0, import_yaml.parse)(patchText, { logLevel: "silent" });
|
|
8665
|
+
if (!Array.isArray(rows)) return null;
|
|
8666
|
+
const row = rows.find((entry) => entry?.id === "agent-default-model");
|
|
8667
|
+
const config = row?.config;
|
|
8668
|
+
if (config?.provider !== `divergent-${surface}` || !config.model) return null;
|
|
8669
|
+
return { model: String(config.model), ...config.reasoningEffort ? { reasoningEffort: String(config.reasoningEffort) } : {} };
|
|
8670
|
+
}
|
|
8671
|
+
var dshPresetsDir = (prefix) => path4.join(prefix, "node_modules", "@deepseek-ai", "dsh-web-app", "presets");
|
|
8672
|
+
function dshPresetOverride(shipped) {
|
|
8673
|
+
const lines = shipped.split("\n");
|
|
8674
|
+
const insert = lines.indexOf("- insert:");
|
|
8675
|
+
if (insert === -1) throw new Error("a shipped preset is not one inserted row");
|
|
8676
|
+
const row = lines.slice(insert + 1).map((line) => line.replace(/^ {4}/, ""));
|
|
8677
|
+
const start = row.findIndex((line) => /^\s+- id: ['"]?tool-web['"]?\s*$/.test(line));
|
|
8678
|
+
if (start === -1) return null;
|
|
8679
|
+
const indent = row[start].indexOf("-");
|
|
8680
|
+
const body = " ".repeat(indent + 2);
|
|
8681
|
+
let end = row.findIndex((line, index) => index > start && line.trim() !== "" && line.search(/\S/) <= indent);
|
|
8682
|
+
if (end === -1) end = row.length;
|
|
8683
|
+
const block = row.slice(start, end);
|
|
8684
|
+
const search = block.findIndex((line) => line.startsWith(`${body} search:`));
|
|
8732
8685
|
if (search !== -1) {
|
|
8733
|
-
|
|
8686
|
+
block[search] = `${body} search: false`;
|
|
8734
8687
|
} else {
|
|
8735
|
-
const config =
|
|
8736
|
-
if (config !== -1)
|
|
8737
|
-
else
|
|
8688
|
+
const config = block.indexOf(`${body}config:`);
|
|
8689
|
+
if (config !== -1) block.splice(config + 1, 0, `${body} search: false`);
|
|
8690
|
+
else block.splice(1, 0, `${body}config:`, `${body} search: false`);
|
|
8738
8691
|
}
|
|
8739
|
-
return [
|
|
8740
|
-
`# The Divergent agent preset: dsh's shipped "standard" preset with the vendor web`,
|
|
8741
|
-
"# search left out of the model's tools; platform tools, search included, are reached",
|
|
8742
|
-
"# from the shell through `diverge tools`. Derived from the installed dsh by `diverge agents",
|
|
8743
|
-
"# setup dsh` and `diverge agents run dsh` on every run; edits do not survive.",
|
|
8744
|
-
...lines.slice(0, start),
|
|
8745
|
-
...row,
|
|
8746
|
-
...lines.slice(end)
|
|
8747
|
-
].join("\n");
|
|
8692
|
+
return [...row.slice(0, start), ...block, ...row.slice(end)].join("\n").trimEnd() + "\n";
|
|
8748
8693
|
}
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
"order: 0",
|
|
8753
|
-
""
|
|
8754
|
-
].join("\n");
|
|
8755
|
-
function writeDivergentPreset(home, shippedDir) {
|
|
8756
|
-
const dir = path4.join(home, DSH_USER_PRESETS_DIR, DIVERGENT_PRESET_ID);
|
|
8757
|
-
writeTextAtomic(path4.join(dir, "agent.cordis.yml"), divergentPreset(fs5.readFileSync(path4.join(shippedDir, "agent.cordis.yml"), "utf8")), 420);
|
|
8758
|
-
writeTextAtomic(path4.join(dir, "preset.yml"), DIVERGENT_PRESET_METADATA, 420);
|
|
8759
|
-
return dir;
|
|
8760
|
-
}
|
|
8761
|
-
function withDivergentPresetDefault(settingsText) {
|
|
8762
|
-
const parsed = settingsText?.trim() ? (0, import_yaml.parse)(settingsText) : null;
|
|
8763
|
-
const settings = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { ...parsed } : {};
|
|
8764
|
-
const presets = settings["agent-presets"];
|
|
8765
|
-
if (presets && typeof presets === "object" && presets.default) return null;
|
|
8766
|
-
settings["agent-presets"] = { ...presets ?? {}, default: DIVERGENT_PRESET_ID };
|
|
8767
|
-
return (0, import_yaml.stringify)(settings, { lineWidth: 0 });
|
|
8694
|
+
function dshPresetOverrides(prefix) {
|
|
8695
|
+
const dir = dshPresetsDir(prefix);
|
|
8696
|
+
return fs5.readdirSync(dir).filter((name) => name.endsWith(".patch.yml")).sort().flatMap((name) => dshPresetOverride(fs5.readFileSync(path4.join(dir, name), "utf8")) ?? []).join("");
|
|
8768
8697
|
}
|
|
8769
8698
|
|
|
8770
8699
|
// src/skills/install.ts
|
|
@@ -9121,7 +9050,9 @@ Signing in also installs the login's skills \u2014 every organization it can act
|
|
|
9121
9050
|
in, side by side \u2014 into the coding-agent harnesses on this machine.
|
|
9122
9051
|
--as <email> names the account: the platform signs that account in instead of
|
|
9123
9052
|
reusing whatever session the browser holds, and issues nothing to any other.
|
|
9124
|
-
Without --as, the account of the last login is used;
|
|
9053
|
+
Without --as, the account of the last login is used; a browser that signs in
|
|
9054
|
+
any account but the expected one (the --as one, else the last login's) is
|
|
9055
|
+
refused and changes nothing, so this host's shared login switches only by --as.
|
|
9125
9056
|
Without --scopes, the login asks for the scopes the platform registers for
|
|
9126
9057
|
this CLI \u2014 everything it and forge use \u2014 and holds what your roles grant; a
|
|
9127
9058
|
scope the platform adds later reaches this login on its next refresh.
|
|
@@ -9139,6 +9070,16 @@ function resolveLoginHint(flag, previous) {
|
|
|
9139
9070
|
const remembered = previous.account?.trim().toLowerCase();
|
|
9140
9071
|
return remembered || void 0;
|
|
9141
9072
|
}
|
|
9073
|
+
function refuseAnotherAccount(signedIn, expected, context) {
|
|
9074
|
+
const account = signedIn.trim().toLowerCase();
|
|
9075
|
+
if (account === expected) return;
|
|
9076
|
+
const asked = context.named ? `You asked to sign in as ${expected}` : `This host's login for ${context.apiUrl} is ${expected}`;
|
|
9077
|
+
throw new CliError(
|
|
9078
|
+
`${asked}, but the browser signed in ${account}; nothing was changed.
|
|
9079
|
+
To replace this host's login, for every session on it: diverge login --as ${account}
|
|
9080
|
+
To keep a second identity beside it: DIVERGE_HOME=<another directory> diverge login --as ${account}`
|
|
9081
|
+
);
|
|
9082
|
+
}
|
|
9142
9083
|
async function runLogin(args, io) {
|
|
9143
9084
|
refuseInSession(io.env, "login");
|
|
9144
9085
|
const { values } = parseArgs2({
|
|
@@ -9174,6 +9115,7 @@ async function runLogin(args, io) {
|
|
|
9174
9115
|
} catch (error) {
|
|
9175
9116
|
throw new CliError(`Signed in, but the platform could not say who or where (${error instanceof Error ? error.message : String(error)}); run: diverge login`);
|
|
9176
9117
|
}
|
|
9118
|
+
if (loginHint) refuseAnotherAccount(info.email, loginHint, { named: Boolean(values.as), apiUrl });
|
|
9177
9119
|
io.stdout(`Signed in as: ${info.email}`);
|
|
9178
9120
|
const organizationId = await chooseDefaultOrganization(io, values["organization-id"], previous?.organization, info);
|
|
9179
9121
|
const issued = await issueToken(source, { base_url: source.baseUrl, refresh_token: signIn.refreshToken, account: info.email }, organizationId);
|
|
@@ -9365,219 +9307,423 @@ function mergeOpencodeConfig(existing, options) {
|
|
|
9365
9307
|
return config;
|
|
9366
9308
|
}
|
|
9367
9309
|
|
|
9368
|
-
// src/agents/
|
|
9369
|
-
import
|
|
9370
|
-
import fs7 from "node:fs";
|
|
9310
|
+
// src/agents/rotate.ts
|
|
9311
|
+
import os7 from "node:os";
|
|
9371
9312
|
import path9 from "node:path";
|
|
9372
|
-
function
|
|
9373
|
-
|
|
9374
|
-
let merged;
|
|
9375
|
-
try {
|
|
9376
|
-
merged = mergeDshCredentials(existing, accessToken);
|
|
9377
|
-
} catch (error) {
|
|
9378
|
-
throw new CliError(`${credentialsPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9379
|
-
}
|
|
9380
|
-
writeTextAtomic(credentialsPath, merged, 384);
|
|
9381
|
-
}
|
|
9382
|
-
function writeDivergeShim(binDir, cli, sessionTokenPath, platform = process.platform) {
|
|
9383
|
-
if (platform === "win32") {
|
|
9384
|
-
const file2 = path9.join(binDir, "diverge.cmd");
|
|
9385
|
-
const run3 = [cli.command, ...cli.args].map((part) => `"${part}"`).join(" ");
|
|
9386
|
-
writeTextAtomic(file2, `@setlocal\r
|
|
9387
|
-
@set "${SESSION_TOKEN_VARIABLE}=${sessionTokenPath}"\r
|
|
9388
|
-
@${run3} %*\r
|
|
9389
|
-
`, 493);
|
|
9390
|
-
return file2;
|
|
9391
|
-
}
|
|
9392
|
-
const quote = (part) => `'${part.replace(/'/g, `'\\''`)}'`;
|
|
9393
|
-
const file = path9.join(binDir, "diverge");
|
|
9394
|
-
writeTextAtomic(file, `#!/bin/sh
|
|
9395
|
-
${SESSION_TOKEN_VARIABLE}=${quote(sessionTokenPath)}
|
|
9396
|
-
export ${SESSION_TOKEN_VARIABLE}
|
|
9397
|
-
exec ${[cli.command, ...cli.args].map(quote).join(" ")} "$@"
|
|
9398
|
-
`, 493);
|
|
9399
|
-
return file;
|
|
9400
|
-
}
|
|
9401
|
-
function dshEnv(env, binDir) {
|
|
9402
|
-
const out = {};
|
|
9403
|
-
for (const [key, value] of Object.entries(env)) {
|
|
9404
|
-
if (value !== void 0 && key !== "DIVERGENT_TOKEN" && key !== "DIVERGENT_ORG_ID") out[key] = value;
|
|
9405
|
-
}
|
|
9406
|
-
out.PATH = env.PATH ? `${binDir}${path9.delimiter}${env.PATH}` : binDir;
|
|
9407
|
-
return out;
|
|
9408
|
-
}
|
|
9409
|
-
function dshArgs(overlayPath, args) {
|
|
9410
|
-
const rest = args.includes("--profile") ? args : args.length === 0 ? ["--profile", "web"] : ["--profile", "headless", ...args];
|
|
9411
|
-
return ["--patch", overlayPath, ...rest];
|
|
9313
|
+
function defaultTokenFilePath() {
|
|
9314
|
+
return path9.join(os7.homedir(), ".divergent", "token");
|
|
9412
9315
|
}
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
const
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9316
|
+
var MIN_ROTATE_INTERVAL_SECONDS = 60;
|
|
9317
|
+
async function rotateTokenFile(loop) {
|
|
9318
|
+
const write = loop.write ?? ((file, token) => writeTextAtomic(file, token, 384));
|
|
9319
|
+
const interval = Math.max(loop.intervalSeconds, MIN_ROTATE_INTERVAL_SECONDS) * 1e3;
|
|
9320
|
+
for (; ; ) {
|
|
9321
|
+
write(loop.tokenFile, await loop.freshToken());
|
|
9322
|
+
loop.report(`${(loop.now ?? (() => /* @__PURE__ */ new Date()))().toISOString().slice(11, 19)} token written to ${loop.tokenFile}`);
|
|
9323
|
+
if (loop.once) return 0;
|
|
9324
|
+
await loop.sleep(interval);
|
|
9421
9325
|
}
|
|
9422
|
-
const script = typeof bin === "string" ? bin : typeof bin === "object" && bin !== null ? bin.dsh : void 0;
|
|
9423
|
-
if (typeof script !== "string") throw new CliError(`${packageDir}/package.json declares no dsh entry; reinstall: npm install -g @deepseek-ai/dsh`);
|
|
9424
|
-
return { command: execPath, args: [path9.join(packageDir, script), ...args] };
|
|
9425
9326
|
}
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9327
|
+
|
|
9328
|
+
// src/agents/self-command.ts
|
|
9329
|
+
import fs7 from "node:fs";
|
|
9330
|
+
import path10 from "node:path";
|
|
9331
|
+
function resolveSelfCommand(argv = process.argv, platform = process.platform, execPath = process.execPath) {
|
|
9332
|
+
const script = argv[1];
|
|
9333
|
+
if (!script) return { command: execPath, args: [] };
|
|
9334
|
+
if (platform !== "win32") {
|
|
9335
|
+
try {
|
|
9336
|
+
fs7.accessSync(script, fs7.constants.X_OK);
|
|
9337
|
+
return { command: path10.resolve(script), args: [] };
|
|
9338
|
+
} catch {
|
|
9437
9339
|
}
|
|
9438
9340
|
}
|
|
9341
|
+
return { command: execPath, args: [fs7.realpathSync(script)] };
|
|
9439
9342
|
}
|
|
9440
|
-
function
|
|
9441
|
-
|
|
9442
|
-
writeDshToken(options.credentialsPath, token);
|
|
9443
|
-
writeTextAtomic(options.sessionTokenPath, `${token}
|
|
9444
|
-
`, 384);
|
|
9445
|
-
};
|
|
9446
|
-
writeToken(options.initialToken);
|
|
9447
|
-
writeDivergeShim(options.binDir, options.cli, options.sessionTokenPath);
|
|
9448
|
-
const env = dshEnv(options.env, options.binDir);
|
|
9449
|
-
const launch = dshLaunch(options.binary, dshArgs(options.overlayPath, options.args));
|
|
9450
|
-
const child = spawn2(launch.command, launch.args, { env, stdio: "inherit" });
|
|
9451
|
-
let exited = false;
|
|
9452
|
-
let wake = null;
|
|
9453
|
-
const finish = () => {
|
|
9454
|
-
exited = true;
|
|
9455
|
-
wake?.();
|
|
9456
|
-
};
|
|
9457
|
-
const done = new Promise((resolve, reject) => {
|
|
9458
|
-
child.on("error", (error) => {
|
|
9459
|
-
finish();
|
|
9460
|
-
reject(error);
|
|
9461
|
-
});
|
|
9462
|
-
child.on("exit", (code2, signal) => {
|
|
9463
|
-
finish();
|
|
9464
|
-
resolve(code2 ?? (signal ? 128 + 15 : 1));
|
|
9465
|
-
});
|
|
9466
|
-
});
|
|
9467
|
-
const ignore = () => {
|
|
9468
|
-
};
|
|
9469
|
-
process.on("SIGINT", ignore);
|
|
9470
|
-
const terminate = () => child.kill("SIGTERM");
|
|
9471
|
-
process.on("SIGTERM", terminate);
|
|
9472
|
-
const loop = keepTokenFresh({
|
|
9473
|
-
freshToken: options.freshToken,
|
|
9474
|
-
write: writeToken,
|
|
9475
|
-
isRunning: () => !exited,
|
|
9476
|
-
sleep: (ms) => new Promise((resolve) => {
|
|
9477
|
-
const timer = setTimeout(() => {
|
|
9478
|
-
wake = null;
|
|
9479
|
-
resolve();
|
|
9480
|
-
}, ms);
|
|
9481
|
-
wake = () => {
|
|
9482
|
-
clearTimeout(timer);
|
|
9483
|
-
wake = null;
|
|
9484
|
-
resolve();
|
|
9485
|
-
};
|
|
9486
|
-
})
|
|
9487
|
-
});
|
|
9488
|
-
return done.finally(async () => {
|
|
9489
|
-
process.off("SIGINT", ignore);
|
|
9490
|
-
process.off("SIGTERM", terminate);
|
|
9491
|
-
await loop;
|
|
9492
|
-
});
|
|
9343
|
+
function shellCommand(self, ...trailing) {
|
|
9344
|
+
return [self.command, ...self.args, ...trailing].map((part) => /[\s"]/.test(part) ? `"${part.replace(/"/g, '\\"')}"` : part).join(" ");
|
|
9493
9345
|
}
|
|
9494
9346
|
|
|
9495
|
-
// src/agents
|
|
9496
|
-
|
|
9497
|
-
import path10 from "node:path";
|
|
9347
|
+
// src/commands/agents.ts
|
|
9348
|
+
var agentsUsage = `diverge agents <setup|rotate|status> ...
|
|
9498
9349
|
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
} else if (ch !== "~" && /^[A-Za-z0-9._-]$/.test(ch)) {
|
|
9514
|
-
readable += ch;
|
|
9515
|
-
separatorRun = false;
|
|
9516
|
-
} else {
|
|
9517
|
-
readable += `~${code2.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
9518
|
-
separatorRun = false;
|
|
9519
|
-
}
|
|
9520
|
-
}
|
|
9521
|
-
return `--${(readable.replace(/^-+/, "") || "root").slice(0, 251)}--`;
|
|
9350
|
+
setup claude [--scope user|project] [--project-id <id>] [--model-discovery] [--organization-id <id>]
|
|
9351
|
+
setup opencode [--surface anthropic|openai] [--config <path>] [--organization-id <id>]
|
|
9352
|
+
setup codex [--project-id <id>] [--config <path>] [--print] [--organization-id <id>]
|
|
9353
|
+
rotate [--into <path>] [--interval <seconds>] [--once] [--organization-id <id>]
|
|
9354
|
+
keep a token file fresh for a client that reads a file, not a helper (default ~/.divergent/token, every 600 s)
|
|
9355
|
+
status [--organization-id <id>]
|
|
9356
|
+
|
|
9357
|
+
Point a coding agent at the platform's model gateway. A sign-in is started
|
|
9358
|
+
when the cached login does not serve the gateway for the organization. dsh
|
|
9359
|
+
needs no setup: diverge dsh starts it on the gateway.`;
|
|
9360
|
+
function surfaceOf(value, fallback) {
|
|
9361
|
+
if (value === void 0) return fallback;
|
|
9362
|
+
if (SURFACES.includes(value)) return value;
|
|
9363
|
+
throw new CliError(`--surface must be one of: ${SURFACES.join(", ")}`);
|
|
9522
9364
|
}
|
|
9523
|
-
function
|
|
9524
|
-
|
|
9365
|
+
function readJson(file) {
|
|
9366
|
+
if (!fs8.existsSync(file)) return null;
|
|
9367
|
+
return JSON.parse(fs8.readFileSync(file, "utf8"));
|
|
9525
9368
|
}
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
var SKIPPABLE_MAGIC_HIGH = 407710303;
|
|
9529
|
-
function zstdFrameEnd(buffer, start) {
|
|
9530
|
-
if (buffer.length - start < 4) return null;
|
|
9531
|
-
const magic = buffer.readUInt32LE(start);
|
|
9532
|
-
if (magic >= SKIPPABLE_MAGIC_LOW && magic <= SKIPPABLE_MAGIC_HIGH) {
|
|
9533
|
-
if (buffer.length - start < 8) return null;
|
|
9534
|
-
const end = start + 8 + buffer.readUInt32LE(start + 4);
|
|
9535
|
-
return end <= buffer.length ? end : null;
|
|
9536
|
-
}
|
|
9537
|
-
if (magic !== ZSTD_MAGIC) return null;
|
|
9538
|
-
if (buffer.length - start < 5) return null;
|
|
9539
|
-
const descriptor = buffer[start + 4];
|
|
9540
|
-
const contentSizeFlag = descriptor >> 6;
|
|
9541
|
-
const singleSegment = descriptor >> 5 & 1;
|
|
9542
|
-
const checksum = descriptor >> 2 & 1;
|
|
9543
|
-
const dictionaryFlag = descriptor & 3;
|
|
9544
|
-
let pos = start + 5;
|
|
9545
|
-
if (!singleSegment) pos += 1;
|
|
9546
|
-
pos += [0, 1, 2, 4][dictionaryFlag];
|
|
9547
|
-
pos += contentSizeFlag === 0 ? singleSegment : [0, 2, 4, 8][contentSizeFlag];
|
|
9548
|
-
for (; ; ) {
|
|
9549
|
-
if (pos + 3 > buffer.length) return null;
|
|
9550
|
-
const header = buffer[pos] | buffer[pos + 1] << 8 | buffer[pos + 2] << 16;
|
|
9551
|
-
const last = header & 1;
|
|
9552
|
-
const type = header >> 1 & 3;
|
|
9553
|
-
const size = header >> 3;
|
|
9554
|
-
if (type === 3) return null;
|
|
9555
|
-
pos += 3 + (type === 1 ? 1 : size);
|
|
9556
|
-
if (pos > buffer.length) return null;
|
|
9557
|
-
if (last) break;
|
|
9558
|
-
}
|
|
9559
|
-
if (checksum) pos += 4;
|
|
9560
|
-
return pos <= buffer.length ? pos : null;
|
|
9369
|
+
async function servedModels(apiUrl, token, surface) {
|
|
9370
|
+
return (await listModels(apiUrl, token.access_token, surface)).map(servedModel);
|
|
9561
9371
|
}
|
|
9562
|
-
function
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
const
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
try {
|
|
9570
|
-
parts.push(zlib.zstdDecompressSync(buffer.subarray(offset, end)));
|
|
9571
|
-
} catch (error) {
|
|
9572
|
-
return { text: Buffer.concat(parts).toString("utf8"), consumed: offset, failed: { at: offset, error: error instanceof Error ? error.message : String(error) } };
|
|
9573
|
-
}
|
|
9574
|
-
}
|
|
9575
|
-
offset = end;
|
|
9372
|
+
async function skillsFor(io, apiUrl, harness, organizationId) {
|
|
9373
|
+
try {
|
|
9374
|
+
const skills = await loginSkillSet(apiUrl, io.env, organizationId?.trim() || void 0);
|
|
9375
|
+
const result = installForHarness(io, harness, skills, apiUrl);
|
|
9376
|
+
for (const note of [...skills.notes, ...skippedNote([result])]) io.stderr(`warning: ${note}`);
|
|
9377
|
+
} catch (error) {
|
|
9378
|
+
io.stderr(`warning: skills not installed (${error instanceof Error ? error.message : String(error)}); run: diverge skills install`);
|
|
9576
9379
|
}
|
|
9577
|
-
return { text: Buffer.concat(parts).toString("utf8"), consumed: offset };
|
|
9578
9380
|
}
|
|
9579
|
-
function
|
|
9580
|
-
|
|
9381
|
+
function organizationArgs(organizationId) {
|
|
9382
|
+
const trimmed = organizationId?.trim();
|
|
9383
|
+
return trimmed ? ["--organization-id", trimmed] : [];
|
|
9384
|
+
}
|
|
9385
|
+
async function setup(args, io) {
|
|
9386
|
+
const [agent, ...rest] = args;
|
|
9387
|
+
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
9388
|
+
switch (agent) {
|
|
9389
|
+
case "claude": {
|
|
9390
|
+
const { values } = parseArgs3({ args: rest, options: { scope: { type: "string", default: "user" }, "project-id": { type: "string" }, "organization-id": { type: "string" }, "model-discovery": { type: "boolean", default: false } }, strict: true });
|
|
9391
|
+
if (values.scope !== "user" && values.scope !== "project") throw new CliError("--scope must be user or project");
|
|
9392
|
+
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
9393
|
+
const file = claudeSettingsPath(values.scope);
|
|
9394
|
+
const { settings, warnings } = mergeClaudeSettings(readJson(file), {
|
|
9395
|
+
baseUrl: anthropicBaseUrl(apiUrl),
|
|
9396
|
+
tokenHelper: shellCommand(resolveSelfCommand(), "token", ...organizationArgs(values["organization-id"])),
|
|
9397
|
+
projectId: values["project-id"],
|
|
9398
|
+
modelDiscovery: values["model-discovery"],
|
|
9399
|
+
// Claude Code scans its skill folder when a session starts, so that is when the login's skills are brought current.
|
|
9400
|
+
skillsRefresh: values.scope === "user" ? shellCommand(resolveSelfCommand(), "skills", "refresh") : void 0
|
|
9401
|
+
});
|
|
9402
|
+
writeJsonAtomic(file, settings);
|
|
9403
|
+
io.stdout(`Claude Code \u2192 ${anthropicBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
|
|
9404
|
+
for (const warning of warnings) io.stderr(`warning: ${warning}`);
|
|
9405
|
+
await skillsFor(io, apiUrl, "claude", values["organization-id"]);
|
|
9406
|
+
return ExitCode.Ok;
|
|
9407
|
+
}
|
|
9408
|
+
case "opencode": {
|
|
9409
|
+
const { values } = parseArgs3({ args: rest, options: { surface: { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" } }, strict: true });
|
|
9410
|
+
const surface = surfaceOf(values.surface, "anthropic");
|
|
9411
|
+
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
9412
|
+
const baseUrl = surfaceBaseUrl(apiUrl, surface);
|
|
9413
|
+
let models = [];
|
|
9414
|
+
try {
|
|
9415
|
+
models = await servedModels(apiUrl, token, surface);
|
|
9416
|
+
} catch (error) {
|
|
9417
|
+
io.stderr(`warning: ${error instanceof Error ? error.message : String(error)}; writing the provider without a model list`);
|
|
9418
|
+
}
|
|
9419
|
+
const file = values.config ?? opencodeConfigPath();
|
|
9420
|
+
writeJsonAtomic(file, mergeOpencodeConfig(readJson(file), { baseUrl, surface, models }));
|
|
9421
|
+
io.stdout(`OpenCode provider divergent-${surface} \u2192 ${baseUrl}. Wrote ${file}.`);
|
|
9422
|
+
io.stdout("Keep DIVERGENT_TOKEN fresh for the session (user tokens expire; the login refreshes):");
|
|
9423
|
+
io.stdout(` export DIVERGENT_TOKEN="$(${["diverge", "token", ...organizationArgs(values["organization-id"])].join(" ")})"`);
|
|
9424
|
+
return ExitCode.Ok;
|
|
9425
|
+
}
|
|
9426
|
+
case "codex": {
|
|
9427
|
+
const { values } = parseArgs3({ args: rest, options: { "project-id": { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" }, print: { type: "boolean", default: false } }, strict: true });
|
|
9428
|
+
const block = codexProviderToml({ openaiBaseUrl: openaiBaseUrl(apiUrl), self: resolveSelfCommand(), projectId: values["project-id"], organizationId: values["organization-id"] });
|
|
9429
|
+
if (values.print) {
|
|
9430
|
+
io.stdout(block.trimEnd());
|
|
9431
|
+
return ExitCode.Ok;
|
|
9432
|
+
}
|
|
9433
|
+
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
9434
|
+
const file = values.config ?? codexConfigPath();
|
|
9435
|
+
const existing = fs8.existsSync(file) ? fs8.readFileSync(file, "utf8") : null;
|
|
9436
|
+
writeTextAtomic(file, mergeCodexConfig(existing, block));
|
|
9437
|
+
io.stdout(`Codex provider divergent \u2192 ${openaiBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
|
|
9438
|
+
io.stdout('Select it per run with: codex -c model_provider=divergent (or set model_provider = "divergent" in the config)');
|
|
9439
|
+
await skillsFor(io, apiUrl, "codex", values["organization-id"]);
|
|
9440
|
+
return ExitCode.Ok;
|
|
9441
|
+
}
|
|
9442
|
+
default:
|
|
9443
|
+
throw unknownVerb("agents", agentsUsage, ["setup"], agent, rest);
|
|
9444
|
+
}
|
|
9445
|
+
}
|
|
9446
|
+
async function status(args, io) {
|
|
9447
|
+
const { values } = parseArgs3({ args, options: { "organization-id": { type: "string" } }, strict: true });
|
|
9448
|
+
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
9449
|
+
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
9450
|
+
const claims = tokenClaims(token.access_token);
|
|
9451
|
+
io.stdout(`Gateway: ${apiUrl}/api/plugins/inference/{anthropic,openai}`);
|
|
9452
|
+
io.stdout(`Organization: ${claims.organizationId}`);
|
|
9453
|
+
io.stdout(`Scopes: ${(claims.scopes ?? []).join(" ")}`);
|
|
9454
|
+
for (const surface of SURFACES) {
|
|
9455
|
+
const models = (await servedModels(apiUrl, token, surface)).map((model) => model.id);
|
|
9456
|
+
io.stdout(`Models (${surface}): ${models.length ? models.join(", ") : "(none served on this surface)"}`);
|
|
9457
|
+
}
|
|
9458
|
+
return ExitCode.Ok;
|
|
9459
|
+
}
|
|
9460
|
+
async function rotate(args, io) {
|
|
9461
|
+
const { values } = parseArgs3({ args, options: { into: { type: "string" }, interval: { type: "string", default: "600" }, once: { type: "boolean", default: false }, "organization-id": { type: "string" } }, strict: true });
|
|
9462
|
+
const interval = Number(values.interval);
|
|
9463
|
+
if (!Number.isFinite(interval) || interval <= 0) throw new CliError("--interval must be a positive number of seconds");
|
|
9464
|
+
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
9465
|
+
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
9466
|
+
const source = tokenSource(io.env, apiUrl);
|
|
9467
|
+
const tokenFilePath = values.into ?? defaultTokenFilePath();
|
|
9468
|
+
io.stderr(`Keeping ${tokenFilePath} fresh as you (organization ${tokenClaims(token.access_token).organizationId})`);
|
|
9469
|
+
return rotateTokenFile({
|
|
9470
|
+
tokenFile: tokenFilePath,
|
|
9471
|
+
intervalSeconds: interval,
|
|
9472
|
+
once: values.once,
|
|
9473
|
+
freshToken: async () => (await getUserToken(source, { freshForSeconds: Math.max(interval, 60) + 60, organizationId: values["organization-id"] })).access_token,
|
|
9474
|
+
report: (line) => io.stderr(line),
|
|
9475
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
9476
|
+
});
|
|
9477
|
+
}
|
|
9478
|
+
async function runAgents(args, io) {
|
|
9479
|
+
const [verb, ...rest] = args;
|
|
9480
|
+
switch (verb) {
|
|
9481
|
+
case "setup":
|
|
9482
|
+
return setup(rest, io);
|
|
9483
|
+
case "rotate":
|
|
9484
|
+
return rotate(rest, io);
|
|
9485
|
+
case "status":
|
|
9486
|
+
return status(rest, io);
|
|
9487
|
+
default:
|
|
9488
|
+
throw unknownVerb("agents", agentsUsage, [], verb, rest);
|
|
9489
|
+
}
|
|
9490
|
+
}
|
|
9491
|
+
|
|
9492
|
+
// src/commands/dsh.ts
|
|
9493
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
9494
|
+
import { randomUUID } from "node:crypto";
|
|
9495
|
+
import fs15 from "node:fs";
|
|
9496
|
+
import os8 from "node:os";
|
|
9497
|
+
import path18 from "node:path";
|
|
9498
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
9499
|
+
|
|
9500
|
+
// src/agents/dsh-install.ts
|
|
9501
|
+
import fs10 from "node:fs";
|
|
9502
|
+
import path13 from "node:path";
|
|
9503
|
+
|
|
9504
|
+
// src/npm.ts
|
|
9505
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
9506
|
+
import path12 from "node:path";
|
|
9507
|
+
|
|
9508
|
+
// src/fs/path.ts
|
|
9509
|
+
import fs9 from "node:fs";
|
|
9510
|
+
import path11 from "node:path";
|
|
9511
|
+
function findOnPath(name, env = process.env, platform = process.platform) {
|
|
9512
|
+
const dirs = (env.PATH ?? "").split(path11.delimiter).filter(Boolean);
|
|
9513
|
+
const extensions = (env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
9514
|
+
const candidates = platform === "win32" ? extensions.map((extension) => `${name}${extension.toLowerCase()}`) : [name];
|
|
9515
|
+
for (const dir of dirs) {
|
|
9516
|
+
for (const candidate of candidates) {
|
|
9517
|
+
const full = path11.join(dir, candidate);
|
|
9518
|
+
try {
|
|
9519
|
+
fs9.accessSync(full, fs9.constants.X_OK);
|
|
9520
|
+
if (fs9.statSync(full).isFile()) return full;
|
|
9521
|
+
} catch {
|
|
9522
|
+
}
|
|
9523
|
+
}
|
|
9524
|
+
}
|
|
9525
|
+
return null;
|
|
9526
|
+
}
|
|
9527
|
+
|
|
9528
|
+
// src/npm.ts
|
|
9529
|
+
function runNpm(args, env = process.env, platform = process.platform, stdio = "inherit") {
|
|
9530
|
+
let command = "npm";
|
|
9531
|
+
const argv = [...args];
|
|
9532
|
+
if (platform === "win32") {
|
|
9533
|
+
const npm = findOnPath("npm", env, platform);
|
|
9534
|
+
if (!npm) throw new CliError("npm was not found on PATH; install Node.js with npm and try again.");
|
|
9535
|
+
command = npm;
|
|
9536
|
+
if (/\.(cmd|bat)$/i.test(npm)) {
|
|
9537
|
+
command = process.execPath;
|
|
9538
|
+
argv.unshift(path12.join(path12.dirname(npm), "node_modules", "npm", "bin", "npm-cli.js"));
|
|
9539
|
+
}
|
|
9540
|
+
}
|
|
9541
|
+
return new Promise((resolve, reject) => {
|
|
9542
|
+
const child = spawn2(command, argv, { env, stdio });
|
|
9543
|
+
child.on("error", reject);
|
|
9544
|
+
child.on("exit", (code2) => resolve(code2 ?? 1));
|
|
9545
|
+
});
|
|
9546
|
+
}
|
|
9547
|
+
|
|
9548
|
+
// src/agents/dsh-version.ts
|
|
9549
|
+
var TESTED_DSH_VERSION = "0.2.1-rc.5";
|
|
9550
|
+
|
|
9551
|
+
// src/agents/dsh-install.ts
|
|
9552
|
+
var DSH_PACKAGE = "@deepseek-ai/dsh";
|
|
9553
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
9554
|
+
var RETIRED_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
9555
|
+
function dshInstallRoot(env) {
|
|
9556
|
+
return path13.join(configDir(env), "harness", "dsh");
|
|
9557
|
+
}
|
|
9558
|
+
function dshPackageIn(prefix) {
|
|
9559
|
+
return path13.join(prefix, "node_modules", "@deepseek-ai", "dsh");
|
|
9560
|
+
}
|
|
9561
|
+
function readManifest(prefix) {
|
|
9562
|
+
try {
|
|
9563
|
+
return JSON.parse(fs10.readFileSync(path13.join(dshPackageIn(prefix), "package.json"), "utf8"));
|
|
9564
|
+
} catch {
|
|
9565
|
+
return null;
|
|
9566
|
+
}
|
|
9567
|
+
}
|
|
9568
|
+
function nodeSatisfies(range, nodeVersion) {
|
|
9569
|
+
const floor = /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(range?.trim() ?? "");
|
|
9570
|
+
if (!floor) return true;
|
|
9571
|
+
const have = nodeVersion.replace(/^v/, "").split(".").map(Number);
|
|
9572
|
+
for (let i = 0; i < 3; i += 1) {
|
|
9573
|
+
const want = Number(floor[i + 1] ?? 0);
|
|
9574
|
+
if ((have[i] ?? 0) !== want) return (have[i] ?? 0) > want;
|
|
9575
|
+
}
|
|
9576
|
+
return true;
|
|
9577
|
+
}
|
|
9578
|
+
function requireNode(version, range, nodeVersion) {
|
|
9579
|
+
if (!nodeSatisfies(range, nodeVersion)) {
|
|
9580
|
+
throw new CliError(`dsh ${version} needs Node ${range}; this is Node ${nodeVersion}. Install a newer Node, then run diverge dsh again.`);
|
|
9581
|
+
}
|
|
9582
|
+
}
|
|
9583
|
+
var realDeps = (env) => ({
|
|
9584
|
+
// npm 11 runs only the install scripts a project approves. dsh's own restores
|
|
9585
|
+
// the executable bit on node-pty's helper, without which a macOS shell cannot
|
|
9586
|
+
// start; the rest check prebuilt binaries or print a notice. An older npm
|
|
9587
|
+
// ignores the field and runs them all. stdout goes to stderr: a headless
|
|
9588
|
+
// run's stdout is the answer.
|
|
9589
|
+
npmInstall: (prefix, spec) => {
|
|
9590
|
+
fs10.writeFileSync(path13.join(prefix, "package.json"), JSON.stringify({ private: true, allowScripts: { "@deepseek-ai/dsh-subprocess-local": true } }));
|
|
9591
|
+
return runNpm(["install", "--prefix", prefix, "--no-audit", "--no-fund", "--loglevel=error", spec], env, process.platform, ["ignore", 2, 2]);
|
|
9592
|
+
},
|
|
9593
|
+
engines: async (version) => (await fetchJson(`${REGISTRY}/${DSH_PACKAGE}/${version}`, { timeoutMs: 15e3 })).engines?.node,
|
|
9594
|
+
nodeVersion: process.version,
|
|
9595
|
+
now: Date.now
|
|
9596
|
+
});
|
|
9597
|
+
async function ensureDsh(io, version = TESTED_DSH_VERSION, deps = realDeps(io.env)) {
|
|
9598
|
+
const root = dshInstallRoot(io.env);
|
|
9599
|
+
const prefix = path13.join(root, version);
|
|
9600
|
+
let manifest = readManifest(prefix);
|
|
9601
|
+
if (manifest) {
|
|
9602
|
+
requireNode(version, manifest.engines?.node, deps.nodeVersion);
|
|
9603
|
+
} else {
|
|
9604
|
+
let engines;
|
|
9605
|
+
try {
|
|
9606
|
+
engines = await deps.engines(version);
|
|
9607
|
+
} catch {
|
|
9608
|
+
}
|
|
9609
|
+
requireNode(version, engines, deps.nodeVersion);
|
|
9610
|
+
io.stderr(`Installing dsh ${version} (about a minute and nearly 1 GB, once per dsh release)\u2026`);
|
|
9611
|
+
const staging = `${prefix}.installing-${process.pid}`;
|
|
9612
|
+
fs10.mkdirSync(staging, { recursive: true });
|
|
9613
|
+
try {
|
|
9614
|
+
const code2 = await deps.npmInstall(staging, `${DSH_PACKAGE}@${version}`);
|
|
9615
|
+
if (code2 !== 0) throw new CliError(`Installing dsh ${version} failed: npm exited with ${code2}. Check the network and run diverge dsh again.`);
|
|
9616
|
+
try {
|
|
9617
|
+
fs10.renameSync(staging, prefix);
|
|
9618
|
+
} catch (error) {
|
|
9619
|
+
if (!readManifest(prefix)) throw error;
|
|
9620
|
+
}
|
|
9621
|
+
} finally {
|
|
9622
|
+
fs10.rmSync(staging, { recursive: true, force: true });
|
|
9623
|
+
}
|
|
9624
|
+
manifest = readManifest(prefix);
|
|
9625
|
+
if (!manifest) throw new CliError(`Installing dsh ${version} left no package at ${dshPackageIn(prefix)}`);
|
|
9626
|
+
}
|
|
9627
|
+
const bin = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.dsh;
|
|
9628
|
+
if (!bin) throw new CliError(`dsh ${version} declares no entry script`);
|
|
9629
|
+
retireOldVersions(root, version, deps.now());
|
|
9630
|
+
return { command: process.execPath, args: [path13.join(dshPackageIn(prefix), bin)] };
|
|
9631
|
+
}
|
|
9632
|
+
function retireOldVersions(root, current, now) {
|
|
9633
|
+
try {
|
|
9634
|
+
const moment = new Date(now);
|
|
9635
|
+
fs10.utimesSync(path13.join(root, current), moment, moment);
|
|
9636
|
+
for (const name of fs10.readdirSync(root)) {
|
|
9637
|
+
if (name === current) continue;
|
|
9638
|
+
const dir = path13.join(root, name);
|
|
9639
|
+
if (now - fs10.statSync(dir).mtimeMs > RETIRED_AFTER_MS) fs10.rmSync(dir, { recursive: true, force: true });
|
|
9640
|
+
}
|
|
9641
|
+
} catch {
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
|
|
9645
|
+
// src/agents/dsh-session-publisher.ts
|
|
9646
|
+
import fs12 from "node:fs";
|
|
9647
|
+
import path15 from "node:path";
|
|
9648
|
+
|
|
9649
|
+
// src/agents/dsh-session-log.ts
|
|
9650
|
+
import fs11 from "node:fs";
|
|
9651
|
+
import path14 from "node:path";
|
|
9652
|
+
import zlib from "node:zlib";
|
|
9653
|
+
var DSH_SESSION_LOG_FILE = /^session\.v(\d+)\.jsonl\.zstd$/;
|
|
9654
|
+
function dshSessionLogFile(dir) {
|
|
9655
|
+
let names;
|
|
9656
|
+
try {
|
|
9657
|
+
names = fs11.readdirSync(dir);
|
|
9658
|
+
} catch {
|
|
9659
|
+
return null;
|
|
9660
|
+
}
|
|
9661
|
+
let newest = null;
|
|
9662
|
+
for (const name of names) {
|
|
9663
|
+
const format = Number(DSH_SESSION_LOG_FILE.exec(name)?.[1]);
|
|
9664
|
+
if (!Number.isNaN(format) && (!newest || format > newest.format)) newest = { name, format };
|
|
9665
|
+
}
|
|
9666
|
+
return newest && path14.join(dir, newest.name);
|
|
9667
|
+
}
|
|
9668
|
+
var DSH_DELTA_RECORD_TYPES = /* @__PURE__ */ new Set(["assistant/chunk", "reasoning-chunks", "text-chunks", "tool-call-chunks"]);
|
|
9669
|
+
function zstdAvailable() {
|
|
9670
|
+
return typeof zlib.zstdDecompressSync === "function";
|
|
9671
|
+
}
|
|
9672
|
+
var ZSTD_MAGIC = 4247762216;
|
|
9673
|
+
var SKIPPABLE_MAGIC_LOW = 407710288;
|
|
9674
|
+
var SKIPPABLE_MAGIC_HIGH = 407710303;
|
|
9675
|
+
function zstdFrameEnd(buffer, start) {
|
|
9676
|
+
if (buffer.length - start < 4) return null;
|
|
9677
|
+
const magic = buffer.readUInt32LE(start);
|
|
9678
|
+
if (magic >= SKIPPABLE_MAGIC_LOW && magic <= SKIPPABLE_MAGIC_HIGH) {
|
|
9679
|
+
if (buffer.length - start < 8) return null;
|
|
9680
|
+
const end = start + 8 + buffer.readUInt32LE(start + 4);
|
|
9681
|
+
return end <= buffer.length ? end : null;
|
|
9682
|
+
}
|
|
9683
|
+
if (magic !== ZSTD_MAGIC) return null;
|
|
9684
|
+
if (buffer.length - start < 5) return null;
|
|
9685
|
+
const descriptor = buffer[start + 4];
|
|
9686
|
+
const contentSizeFlag = descriptor >> 6;
|
|
9687
|
+
const singleSegment = descriptor >> 5 & 1;
|
|
9688
|
+
const checksum = descriptor >> 2 & 1;
|
|
9689
|
+
const dictionaryFlag = descriptor & 3;
|
|
9690
|
+
let pos = start + 5;
|
|
9691
|
+
if (!singleSegment) pos += 1;
|
|
9692
|
+
pos += [0, 1, 2, 4][dictionaryFlag];
|
|
9693
|
+
pos += contentSizeFlag === 0 ? singleSegment : [0, 2, 4, 8][contentSizeFlag];
|
|
9694
|
+
for (; ; ) {
|
|
9695
|
+
if (pos + 3 > buffer.length) return null;
|
|
9696
|
+
const header = buffer[pos] | buffer[pos + 1] << 8 | buffer[pos + 2] << 16;
|
|
9697
|
+
const last = header & 1;
|
|
9698
|
+
const type = header >> 1 & 3;
|
|
9699
|
+
const size = header >> 3;
|
|
9700
|
+
if (type === 3) return null;
|
|
9701
|
+
pos += 3 + (type === 1 ? 1 : size);
|
|
9702
|
+
if (pos > buffer.length) return null;
|
|
9703
|
+
if (last) break;
|
|
9704
|
+
}
|
|
9705
|
+
if (checksum) pos += 4;
|
|
9706
|
+
return pos <= buffer.length ? pos : null;
|
|
9707
|
+
}
|
|
9708
|
+
function decodeZstdFrames(buffer) {
|
|
9709
|
+
const parts = [];
|
|
9710
|
+
let offset = 0;
|
|
9711
|
+
while (offset < buffer.length) {
|
|
9712
|
+
const end = zstdFrameEnd(buffer, offset);
|
|
9713
|
+
if (end === null) break;
|
|
9714
|
+
if (buffer.readUInt32LE(offset) === ZSTD_MAGIC) {
|
|
9715
|
+
try {
|
|
9716
|
+
parts.push(zlib.zstdDecompressSync(buffer.subarray(offset, end)));
|
|
9717
|
+
} catch (error) {
|
|
9718
|
+
return { text: Buffer.concat(parts).toString("utf8"), consumed: offset, failed: { at: offset, error: error instanceof Error ? error.message : String(error) } };
|
|
9719
|
+
}
|
|
9720
|
+
}
|
|
9721
|
+
offset = end;
|
|
9722
|
+
}
|
|
9723
|
+
return { text: Buffer.concat(parts).toString("utf8"), consumed: offset };
|
|
9724
|
+
}
|
|
9725
|
+
function text(value) {
|
|
9726
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
9581
9727
|
}
|
|
9582
9728
|
function instant(value, fallback) {
|
|
9583
9729
|
const ms = typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
@@ -9634,6 +9780,13 @@ var CAPTURE_NOTICES = {
|
|
|
9634
9780
|
metadata: "Recorded: metadata only \u2014 which tools ran and how many failed, tokens, models and timing; no prompt, reply, tool argument or result.",
|
|
9635
9781
|
conversation: "Recorded: the whole conversation \u2014 every prompt, reply, tool call and its result. Yours to read; anyone else in the organization needs the Agent Conversations role, and every such read is recorded on the session."
|
|
9636
9782
|
};
|
|
9783
|
+
function readDirectories(dir) {
|
|
9784
|
+
try {
|
|
9785
|
+
return fs12.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => entry.isDirectory() ? [entry.name] : []);
|
|
9786
|
+
} catch {
|
|
9787
|
+
return [];
|
|
9788
|
+
}
|
|
9789
|
+
}
|
|
9637
9790
|
function eventBytes(event) {
|
|
9638
9791
|
return Buffer.byteLength(JSON.stringify(event), "utf8");
|
|
9639
9792
|
}
|
|
@@ -9665,10 +9818,6 @@ var DshSessionPublisher = class {
|
|
|
9665
9818
|
/** The level the person was last told. */
|
|
9666
9819
|
announced = null;
|
|
9667
9820
|
ticking = null;
|
|
9668
|
-
/** The directory dsh writes this launch's sessions under. */
|
|
9669
|
-
get projectDir() {
|
|
9670
|
-
return path10.join(this.deps.sessionsRoot, dshProjectKey(this.deps.cwd));
|
|
9671
|
-
}
|
|
9672
9821
|
start() {
|
|
9673
9822
|
if (this.running) return;
|
|
9674
9823
|
if (!zstdAvailable()) {
|
|
@@ -9721,54 +9870,63 @@ var DshSessionPublisher = class {
|
|
|
9721
9870
|
await this.flush(session);
|
|
9722
9871
|
}
|
|
9723
9872
|
}
|
|
9873
|
+
/**
|
|
9874
|
+
* Every session this launch opened or continued, in any project: dsh files a
|
|
9875
|
+
* session under its workspace, and the web UI's workspace is the person's
|
|
9876
|
+
* choice (its default is a folder of dsh's own), not where the launch was
|
|
9877
|
+
* started; and it reopens the last conversation, which is older than the
|
|
9878
|
+
* launch and writes to its log again. dsh's home holds one launch's sessions
|
|
9879
|
+
* at a time in practice; a second launch on the same home would record the
|
|
9880
|
+
* first one's as well.
|
|
9881
|
+
*/
|
|
9724
9882
|
discover() {
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
continue;
|
|
9883
|
+
const cutoff = this.deps.launchedAt - LAUNCH_SLACK_MS;
|
|
9884
|
+
for (const project of readDirectories(this.deps.sessionsRoot)) {
|
|
9885
|
+
const projectDir = path15.join(this.deps.sessionsRoot, project);
|
|
9886
|
+
for (const name of readDirectories(projectDir)) {
|
|
9887
|
+
if (this.sessions.has(name)) continue;
|
|
9888
|
+
const dir = path15.join(projectDir, name);
|
|
9889
|
+
const log = dshSessionLogFile(dir);
|
|
9890
|
+
let touched;
|
|
9891
|
+
try {
|
|
9892
|
+
const stat = fs12.statSync(dir);
|
|
9893
|
+
touched = Math.max(Math.min(stat.birthtimeMs || Number.POSITIVE_INFINITY, stat.mtimeMs), log ? fs12.statSync(log).mtimeMs : 0);
|
|
9894
|
+
} catch {
|
|
9895
|
+
continue;
|
|
9896
|
+
}
|
|
9897
|
+
if (touched < cutoff) continue;
|
|
9898
|
+
this.sessions.set(name, {
|
|
9899
|
+
dir,
|
|
9900
|
+
file: null,
|
|
9901
|
+
offset: 0,
|
|
9902
|
+
header: null,
|
|
9903
|
+
pending: [],
|
|
9904
|
+
opened: false,
|
|
9905
|
+
lastSeq: -1,
|
|
9906
|
+
reportedFailure: null,
|
|
9907
|
+
unreadableReported: false,
|
|
9908
|
+
stalledAt: 0,
|
|
9909
|
+
stalledTicks: 0,
|
|
9910
|
+
dead: false
|
|
9911
|
+
});
|
|
9740
9912
|
}
|
|
9741
|
-
const born = Math.min(stat.birthtimeMs || Number.POSITIVE_INFINITY, stat.mtimeMs);
|
|
9742
|
-
if (born < this.deps.launchedAt - LAUNCH_SLACK_MS) continue;
|
|
9743
|
-
this.sessions.set(entry.name, {
|
|
9744
|
-
dir,
|
|
9745
|
-
file,
|
|
9746
|
-
offset: 0,
|
|
9747
|
-
header: null,
|
|
9748
|
-
pending: [],
|
|
9749
|
-
opened: false,
|
|
9750
|
-
lastSeq: -1,
|
|
9751
|
-
reportedFailure: null,
|
|
9752
|
-
unreadableReported: false,
|
|
9753
|
-
stalledAt: 0,
|
|
9754
|
-
stalledTicks: 0,
|
|
9755
|
-
dead: false
|
|
9756
|
-
});
|
|
9757
9913
|
}
|
|
9758
9914
|
}
|
|
9759
9915
|
read(session) {
|
|
9760
9916
|
if (session.dead) return;
|
|
9917
|
+
session.file ??= dshSessionLogFile(session.dir);
|
|
9918
|
+
if (!session.file) return;
|
|
9761
9919
|
let fd;
|
|
9762
9920
|
try {
|
|
9763
|
-
fd =
|
|
9921
|
+
fd = fs12.openSync(session.file, "r");
|
|
9764
9922
|
} catch {
|
|
9765
9923
|
return;
|
|
9766
9924
|
}
|
|
9767
9925
|
try {
|
|
9768
|
-
const size =
|
|
9926
|
+
const size = fs12.fstatSync(fd).size;
|
|
9769
9927
|
if (size <= session.offset) return;
|
|
9770
9928
|
const buffer = Buffer.alloc(size - session.offset);
|
|
9771
|
-
const read =
|
|
9929
|
+
const read = fs12.readSync(fd, buffer, 0, buffer.length, session.offset);
|
|
9772
9930
|
const decoded = decodeZstdFrames(buffer.subarray(0, read));
|
|
9773
9931
|
if (decoded.failed) {
|
|
9774
9932
|
this.giveUp(session, `the frame at byte ${session.offset + decoded.failed.at} does not decode: ${decoded.failed.error}`);
|
|
@@ -9796,7 +9954,7 @@ var DshSessionPublisher = class {
|
|
|
9796
9954
|
this.deps.report(`warning: the platform has not taken ${session.file} for a while; ${dropped} oldest unsent event(s) were dropped`);
|
|
9797
9955
|
}
|
|
9798
9956
|
} finally {
|
|
9799
|
-
|
|
9957
|
+
fs12.closeSync(fd);
|
|
9800
9958
|
}
|
|
9801
9959
|
}
|
|
9802
9960
|
/** The rest of this session's log cannot be read; what was sent stands, and the person is told once. */
|
|
@@ -9810,7 +9968,7 @@ var DshSessionPublisher = class {
|
|
|
9810
9968
|
harnessSessionId: header.id,
|
|
9811
9969
|
...this.deps.harnessVersion ? { harnessVersion: this.deps.harnessVersion } : {},
|
|
9812
9970
|
host: this.deps.host,
|
|
9813
|
-
workingDirectory: header.cwd ??
|
|
9971
|
+
workingDirectory: header.cwd ?? void 0,
|
|
9814
9972
|
...header.createdAt !== null ? { startedAt: new Date(header.createdAt).toISOString() } : {},
|
|
9815
9973
|
launchId: this.deps.launchId,
|
|
9816
9974
|
...header.parentSession ? { parentHarnessSessionId: header.parentSession } : {},
|
|
@@ -9846,256 +10004,338 @@ var DshSessionPublisher = class {
|
|
|
9846
10004
|
session.pending.splice(0, batch.length);
|
|
9847
10005
|
}
|
|
9848
10006
|
}
|
|
9849
|
-
};
|
|
9850
|
-
|
|
9851
|
-
// src/agents/dsh-session-tools.ts
|
|
9852
|
-
var AGENT_SESSION_APPEND_TOOL = "forge_append_agent_session_events";
|
|
9853
|
-
var AGENT_SESSION_LIST_TOOL = "forge_list_agent_sessions";
|
|
10007
|
+
};
|
|
10008
|
+
|
|
10009
|
+
// src/agents/dsh-session-tools.ts
|
|
10010
|
+
var AGENT_SESSION_APPEND_TOOL = "forge_append_agent_session_events";
|
|
10011
|
+
var AGENT_SESSION_LIST_TOOL = "forge_list_agent_sessions";
|
|
10012
|
+
|
|
10013
|
+
// src/agents/run-dsh.ts
|
|
10014
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
10015
|
+
import fs13 from "node:fs";
|
|
10016
|
+
import path16 from "node:path";
|
|
10017
|
+
function writeDshToken(credentialsPath, accessToken) {
|
|
10018
|
+
const existing = fs13.existsSync(credentialsPath) ? fs13.readFileSync(credentialsPath, "utf8") : null;
|
|
10019
|
+
let merged;
|
|
10020
|
+
try {
|
|
10021
|
+
merged = mergeDshCredentials(existing, accessToken);
|
|
10022
|
+
} catch (error) {
|
|
10023
|
+
throw new CliError(`${credentialsPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10024
|
+
}
|
|
10025
|
+
writeTextAtomic(credentialsPath, merged, 384);
|
|
10026
|
+
}
|
|
10027
|
+
function writeDivergeShim(binDir, cli, sessionTokenPath, platform = process.platform) {
|
|
10028
|
+
if (platform === "win32") {
|
|
10029
|
+
const file2 = path16.join(binDir, "diverge.cmd");
|
|
10030
|
+
const run2 = [cli.command, ...cli.args].map((part) => `"${part}"`).join(" ");
|
|
10031
|
+
writeTextAtomic(file2, `@setlocal\r
|
|
10032
|
+
@set "${SESSION_TOKEN_VARIABLE}=${sessionTokenPath}"\r
|
|
10033
|
+
@${run2} %*\r
|
|
10034
|
+
`, 493);
|
|
10035
|
+
return file2;
|
|
10036
|
+
}
|
|
10037
|
+
const quote = (part) => `'${part.replace(/'/g, `'\\''`)}'`;
|
|
10038
|
+
const file = path16.join(binDir, "diverge");
|
|
10039
|
+
writeTextAtomic(file, `#!/bin/sh
|
|
10040
|
+
${SESSION_TOKEN_VARIABLE}=${quote(sessionTokenPath)}
|
|
10041
|
+
export ${SESSION_TOKEN_VARIABLE}
|
|
10042
|
+
exec ${[cli.command, ...cli.args].map(quote).join(" ")} "$@"
|
|
10043
|
+
`, 493);
|
|
10044
|
+
return file;
|
|
10045
|
+
}
|
|
10046
|
+
function dshEnv(env, binDir) {
|
|
10047
|
+
const out = {};
|
|
10048
|
+
for (const [key, value] of Object.entries(env)) {
|
|
10049
|
+
if (value !== void 0 && key !== "DIVERGENT_TOKEN" && key !== "DIVERGENT_ORG_ID") out[key] = value;
|
|
10050
|
+
}
|
|
10051
|
+
out.PATH = env.PATH ? `${binDir}${path16.delimiter}${env.PATH}` : binDir;
|
|
10052
|
+
return out;
|
|
10053
|
+
}
|
|
10054
|
+
function dshArgs(overlayPath, args) {
|
|
10055
|
+
const rest = args.includes("--profile") ? args : args.length === 0 ? ["--profile", "web"] : ["--profile", "headless", ...args];
|
|
10056
|
+
return ["--patch", overlayPath, ...rest];
|
|
10057
|
+
}
|
|
10058
|
+
async function keepTokenFresh(loop) {
|
|
10059
|
+
let written = null;
|
|
10060
|
+
while (loop.isRunning()) {
|
|
10061
|
+
const token = await loop.freshToken();
|
|
10062
|
+
if (token && token !== written) {
|
|
10063
|
+
loop.write(token);
|
|
10064
|
+
written = token;
|
|
10065
|
+
}
|
|
10066
|
+
for (let i = 0; i < 30; i += 1) {
|
|
10067
|
+
if (!loop.isRunning()) return;
|
|
10068
|
+
await loop.sleep(1e3);
|
|
10069
|
+
}
|
|
10070
|
+
}
|
|
10071
|
+
}
|
|
10072
|
+
function runDsh(options) {
|
|
10073
|
+
const writeToken = (token) => {
|
|
10074
|
+
writeDshToken(options.credentialsPath, token);
|
|
10075
|
+
writeTextAtomic(options.sessionTokenPath, `${token}
|
|
10076
|
+
`, 384);
|
|
10077
|
+
};
|
|
10078
|
+
writeToken(options.initialToken);
|
|
10079
|
+
writeDivergeShim(options.binDir, options.cli, options.sessionTokenPath);
|
|
10080
|
+
const env = dshEnv(options.env, options.binDir);
|
|
10081
|
+
const child = spawn3(options.dsh.command, [...options.dsh.args, ...dshArgs(options.overlayPath, options.args)], { env, stdio: "inherit" });
|
|
10082
|
+
let exited = false;
|
|
10083
|
+
let wake = null;
|
|
10084
|
+
const finish = () => {
|
|
10085
|
+
exited = true;
|
|
10086
|
+
wake?.();
|
|
10087
|
+
};
|
|
10088
|
+
const done = new Promise((resolve, reject) => {
|
|
10089
|
+
child.on("error", (error) => {
|
|
10090
|
+
finish();
|
|
10091
|
+
reject(error);
|
|
10092
|
+
});
|
|
10093
|
+
child.on("exit", (code2, signal) => {
|
|
10094
|
+
finish();
|
|
10095
|
+
resolve(code2 ?? (signal ? 128 + 15 : 1));
|
|
10096
|
+
});
|
|
10097
|
+
});
|
|
10098
|
+
const ignore = () => {
|
|
10099
|
+
};
|
|
10100
|
+
process.on("SIGINT", ignore);
|
|
10101
|
+
const terminate = () => child.kill("SIGTERM");
|
|
10102
|
+
process.on("SIGTERM", terminate);
|
|
10103
|
+
const loop = keepTokenFresh({
|
|
10104
|
+
freshToken: options.freshToken,
|
|
10105
|
+
write: writeToken,
|
|
10106
|
+
isRunning: () => !exited,
|
|
10107
|
+
sleep: (ms) => new Promise((resolve) => {
|
|
10108
|
+
const timer = setTimeout(() => {
|
|
10109
|
+
wake = null;
|
|
10110
|
+
resolve();
|
|
10111
|
+
}, ms);
|
|
10112
|
+
wake = () => {
|
|
10113
|
+
clearTimeout(timer);
|
|
10114
|
+
wake = null;
|
|
10115
|
+
resolve();
|
|
10116
|
+
};
|
|
10117
|
+
})
|
|
10118
|
+
});
|
|
10119
|
+
return done.finally(async () => {
|
|
10120
|
+
process.off("SIGINT", ignore);
|
|
10121
|
+
process.off("SIGTERM", terminate);
|
|
10122
|
+
await loop;
|
|
10123
|
+
});
|
|
10124
|
+
}
|
|
9854
10125
|
|
|
9855
|
-
// src/
|
|
9856
|
-
import
|
|
9857
|
-
import
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
}
|
|
9870
|
-
}
|
|
10126
|
+
// src/update-notice.ts
|
|
10127
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
10128
|
+
import fs14 from "node:fs";
|
|
10129
|
+
import path17 from "node:path";
|
|
10130
|
+
var UPDATE_CHECK_FILE = "update-check.json";
|
|
10131
|
+
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
10132
|
+
function updateCheckFile(env) {
|
|
10133
|
+
return path17.join(configDir(env), UPDATE_CHECK_FILE);
|
|
10134
|
+
}
|
|
10135
|
+
function readUpdateCheck(env) {
|
|
10136
|
+
try {
|
|
10137
|
+
const parsed = JSON.parse(fs14.readFileSync(updateCheckFile(env), "utf8"));
|
|
10138
|
+
if (parsed && typeof parsed === "object" && typeof parsed.checkedAt === "number" && typeof parsed.latest === "string") return parsed;
|
|
10139
|
+
} catch {
|
|
9871
10140
|
}
|
|
9872
10141
|
return null;
|
|
9873
10142
|
}
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
import os7 from "node:os";
|
|
9877
|
-
import path12 from "node:path";
|
|
9878
|
-
function defaultTokenFilePath() {
|
|
9879
|
-
return path12.join(os7.homedir(), ".divergent", "token");
|
|
10143
|
+
function writeUpdateCheck(env, check) {
|
|
10144
|
+
writeJsonAtomic(updateCheckFile(env), check);
|
|
9880
10145
|
}
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
10146
|
+
function noticeFor(check, version = VERSION) {
|
|
10147
|
+
if (!check || !isNewer(check.latest, version)) return null;
|
|
10148
|
+
return `diverge ${check.latest} is available (this is ${version}). Update with: diverge update (or run npx @divergeai/cli@latest)`;
|
|
10149
|
+
}
|
|
10150
|
+
function noticeApplies(argv, io, version = VERSION) {
|
|
10151
|
+
if (version === "dev" || !io.interactive) return false;
|
|
10152
|
+
if (io.env.DIVERGE_NO_UPDATE_CHECK?.trim() || io.env.CI?.trim() || sessionTokenFile(io.env)) return false;
|
|
10153
|
+
if (argv[0] === "update") return false;
|
|
10154
|
+
if (argv[0] === "tools" && argv[1] === "bridge") return false;
|
|
10155
|
+
return true;
|
|
10156
|
+
}
|
|
10157
|
+
function scheduleUpdateCheck(spawnImpl = spawn4) {
|
|
10158
|
+
try {
|
|
10159
|
+
const child = spawnImpl(process.execPath, [process.argv[1], "update", "--record"], { detached: true, stdio: "ignore" });
|
|
10160
|
+
child.on("error", () => {
|
|
10161
|
+
});
|
|
10162
|
+
child.unref();
|
|
10163
|
+
} catch {
|
|
9890
10164
|
}
|
|
9891
10165
|
}
|
|
10166
|
+
function updateNotice(argv, io, deps = { now: Date.now, schedule: scheduleUpdateCheck, read: readUpdateCheck, version: VERSION }) {
|
|
10167
|
+
if (!noticeApplies(argv, io, deps.version)) return;
|
|
10168
|
+
const check = deps.read(io.env);
|
|
10169
|
+
const notice = noticeFor(check, deps.version);
|
|
10170
|
+
if (notice) io.stderr(notice);
|
|
10171
|
+
if (!check || deps.now() - check.checkedAt > UPDATE_CHECK_INTERVAL_MS) deps.schedule();
|
|
10172
|
+
}
|
|
9892
10173
|
|
|
9893
|
-
// src/
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
function
|
|
9897
|
-
const
|
|
9898
|
-
if (!
|
|
9899
|
-
|
|
10174
|
+
// src/commands/update.ts
|
|
10175
|
+
var PACKAGE_NAME = "@divergeai/cli";
|
|
10176
|
+
var updateUsage = "diverge update\n\nInstall the latest release with the npm that installed this one.";
|
|
10177
|
+
async function latestVersion(registry = "https://registry.npmjs.org", timeoutMs) {
|
|
10178
|
+
const body = await fetchJson(`${registry}/${PACKAGE_NAME}/latest`, { timeoutMs });
|
|
10179
|
+
if (!body.version) throw new CliError("The registry answered without a version");
|
|
10180
|
+
return body.version;
|
|
10181
|
+
}
|
|
10182
|
+
function isNewer(a, b) {
|
|
10183
|
+
const parse = (v) => {
|
|
10184
|
+
const [core, pre] = v.split("-", 2);
|
|
10185
|
+
return { nums: core.split(".").map((n) => Number(n) || 0), pre: pre ?? "" };
|
|
10186
|
+
};
|
|
10187
|
+
const x = parse(a);
|
|
10188
|
+
const y = parse(b);
|
|
10189
|
+
for (let i = 0; i < 3; i += 1) {
|
|
10190
|
+
const dx = x.nums[i] ?? 0;
|
|
10191
|
+
const dy = y.nums[i] ?? 0;
|
|
10192
|
+
if (dx !== dy) return dx > dy;
|
|
10193
|
+
}
|
|
10194
|
+
if (x.pre === y.pre) return false;
|
|
10195
|
+
if (!x.pre) return true;
|
|
10196
|
+
if (!y.pre) return false;
|
|
10197
|
+
return x.pre > y.pre;
|
|
10198
|
+
}
|
|
10199
|
+
async function runUpdate(args, io, deps = { latestVersion, install: installLatest, record: writeUpdateCheck, now: Date.now }) {
|
|
10200
|
+
refuseInSession(io.env, "update");
|
|
10201
|
+
if (args[0] === "--record") {
|
|
9900
10202
|
try {
|
|
9901
|
-
|
|
9902
|
-
return { command: path13.resolve(script), args: [] };
|
|
10203
|
+
deps.record(io.env, { checkedAt: deps.now(), latest: await deps.latestVersion() });
|
|
9903
10204
|
} catch {
|
|
9904
10205
|
}
|
|
10206
|
+
return ExitCode.Ok;
|
|
10207
|
+
}
|
|
10208
|
+
if (VERSION === "dev") throw new CliError("This is a source checkout, not an installed release; pull the repository instead.");
|
|
10209
|
+
const latest = await deps.latestVersion();
|
|
10210
|
+
if (!isNewer(latest, VERSION)) {
|
|
10211
|
+
io.stdout(`diverge ${VERSION} is up to date.`);
|
|
10212
|
+
return ExitCode.Ok;
|
|
9905
10213
|
}
|
|
9906
|
-
|
|
10214
|
+
io.stdout(`Updating diverge ${VERSION} \u2192 ${latest}\u2026`);
|
|
10215
|
+
const code2 = await deps.install(latest);
|
|
10216
|
+
if (code2 !== 0) throw new CliError(`npm install exited with ${code2}`);
|
|
10217
|
+
io.stdout(`diverge ${latest} installed.`);
|
|
10218
|
+
return ExitCode.Ok;
|
|
9907
10219
|
}
|
|
9908
|
-
function
|
|
9909
|
-
return
|
|
10220
|
+
function installLatest(version, env = process.env, platform = process.platform, stdio = "inherit") {
|
|
10221
|
+
return runNpm(["install", "-g", `${PACKAGE_NAME}@${version}`], env, platform, stdio);
|
|
9910
10222
|
}
|
|
9911
10223
|
|
|
9912
|
-
// src/commands/
|
|
9913
|
-
var
|
|
10224
|
+
// src/commands/dsh.ts
|
|
10225
|
+
var dshUsage = `diverge dsh [--surface anthropic|openai] [--model <id>] [--organization-id <id>] [-- <dsh args>]
|
|
9914
10226
|
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
setup dsh [--surface anthropic|openai] [--model <id>] [--into <path>] [--print] [--organization-id <id>]
|
|
9919
|
-
run dsh [--surface anthropic|openai] [--model <id>] [--organization-id <id>] [-- <dsh args>]
|
|
9920
|
-
nothing after --: the interactive web UI; a task after --: one headless run
|
|
9921
|
-
rotate [--into <path>] [--interval <seconds>] [--once] [--organization-id <id>]
|
|
9922
|
-
keep a token file fresh for a client that reads a file, not a helper (default ~/.divergent/token, every 600 s)
|
|
9923
|
-
status [--organization-id <id>]
|
|
10227
|
+
Start DeepSeek Harness on the organization's model gateway. Nothing after --
|
|
10228
|
+
opens the web UI; a task after -- answers it headless and exits; any other dsh
|
|
10229
|
+
arguments go after -- as given.
|
|
9924
10230
|
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
}
|
|
9936
|
-
|
|
9937
|
-
return (await listModels(apiUrl, token.access_token, surface)).map(servedModel);
|
|
9938
|
-
}
|
|
9939
|
-
function syncDivergentPreset(io, binary, home) {
|
|
9940
|
-
if (!binary) {
|
|
9941
|
-
io.stderr("dsh is not on PATH; the Divergent agent preset is derived when `diverge agents run dsh` first runs.");
|
|
9942
|
-
return;
|
|
10231
|
+
Every launch brings the CLI, the dsh release it is tested with and your skills
|
|
10232
|
+
up to date first. DIVERGE_NO_UPDATE_CHECK skips the CLI's update.`;
|
|
10233
|
+
var RELAUNCHED = "DIVERGE_DSH_RELAUNCHED";
|
|
10234
|
+
var LATEST_TIMEOUT_MS = 5e3;
|
|
10235
|
+
async function updateThenRelaunch(io, deps) {
|
|
10236
|
+
const env = io.env;
|
|
10237
|
+
if (deps.version === "dev" || env[RELAUNCHED] || env.DIVERGE_NO_UPDATE_CHECK?.trim() || env.CI?.trim() || sessionTokenFile(env)) return null;
|
|
10238
|
+
let latest;
|
|
10239
|
+
try {
|
|
10240
|
+
latest = await deps.latest();
|
|
10241
|
+
} catch {
|
|
10242
|
+
return null;
|
|
9943
10243
|
}
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
10244
|
+
if (!isNewer(latest, deps.version)) return null;
|
|
10245
|
+
io.stderr(`Updating diverge ${deps.version} \u2192 ${latest}\u2026`);
|
|
10246
|
+
let code2;
|
|
10247
|
+
try {
|
|
10248
|
+
code2 = await deps.install(latest);
|
|
10249
|
+
} catch (error) {
|
|
10250
|
+
io.stderr(`warning: could not update diverge (${error instanceof Error ? error.message : String(error)}); going on with ${deps.version}. To retry: diverge update`);
|
|
10251
|
+
return null;
|
|
9949
10252
|
}
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
10253
|
+
if (code2 !== 0) {
|
|
10254
|
+
io.stderr(`warning: could not update diverge (npm exited with ${code2}); going on with ${deps.version}. To retry: diverge update`);
|
|
10255
|
+
return null;
|
|
10256
|
+
}
|
|
10257
|
+
return deps.relaunch();
|
|
10258
|
+
}
|
|
10259
|
+
function relaunch(args, env) {
|
|
10260
|
+
return new Promise((resolve, reject) => {
|
|
10261
|
+
const child = spawn5(process.execPath, [process.argv[1], "dsh", ...args], { env: { ...env, [RELAUNCHED]: "1" }, stdio: "inherit" });
|
|
10262
|
+
const ignore = () => {
|
|
10263
|
+
};
|
|
10264
|
+
const terminate = () => child.kill("SIGTERM");
|
|
10265
|
+
process.on("SIGINT", ignore);
|
|
10266
|
+
process.on("SIGTERM", terminate);
|
|
10267
|
+
const settle = () => {
|
|
10268
|
+
process.off("SIGINT", ignore);
|
|
10269
|
+
process.off("SIGTERM", terminate);
|
|
10270
|
+
};
|
|
10271
|
+
child.on("error", (error) => {
|
|
10272
|
+
settle();
|
|
10273
|
+
reject(error);
|
|
10274
|
+
});
|
|
10275
|
+
child.on("exit", (code2, signal) => {
|
|
10276
|
+
settle();
|
|
10277
|
+
resolve(code2 ?? (signal ? 128 + 15 : 1));
|
|
10278
|
+
});
|
|
10279
|
+
});
|
|
10280
|
+
}
|
|
10281
|
+
function surfaceOf2(value) {
|
|
10282
|
+
if (value === void 0) return "openai";
|
|
10283
|
+
if (SURFACES.includes(value)) return value;
|
|
10284
|
+
throw new CliError(`--surface must be one of: ${SURFACES.join(", ")}`);
|
|
9955
10285
|
}
|
|
9956
|
-
|
|
10286
|
+
function savedModel(home, surface) {
|
|
10287
|
+
const file = path18.join(home, "profiles", "web", "cordis.patch.yml");
|
|
9957
10288
|
try {
|
|
9958
|
-
|
|
9959
|
-
const result = installForHarness(io, harness, skills, apiUrl);
|
|
9960
|
-
for (const note of [...skills.notes, ...skippedNote([result])]) io.stderr(`warning: ${note}`);
|
|
10289
|
+
return dshSavedModel(fs15.existsSync(file) ? fs15.readFileSync(file, "utf8") : null, surface);
|
|
9961
10290
|
} catch (error) {
|
|
9962
|
-
|
|
10291
|
+
throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9963
10292
|
}
|
|
9964
10293
|
}
|
|
9965
|
-
async function overlayAndDefault(apiUrl, token, surface,
|
|
9966
|
-
const served = await
|
|
10294
|
+
async function overlayAndDefault(io, apiUrl, token, surface, requested, saved, launch) {
|
|
10295
|
+
const served = (await listModels(apiUrl, token.access_token, surface)).map(servedModel);
|
|
9967
10296
|
const models = served.map((model) => model.id);
|
|
9968
10297
|
if (models.length === 0) throw new CliError(`the gateway serves no models on the ${surface} surface for this organization`);
|
|
9969
10298
|
if (requested && !models.includes(requested)) throw new CliError(`${requested} is not served on the ${surface} surface; choose one of: ${models.join(", ")}`);
|
|
9970
|
-
const
|
|
9971
|
-
|
|
9972
|
-
}
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
const [
|
|
9979
|
-
const
|
|
9980
|
-
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
tokenHelper: shellCommand(resolveSelfCommand(), "token", ...organizationArgs(values["organization-id"])),
|
|
9989
|
-
projectId: values["project-id"],
|
|
9990
|
-
modelDiscovery: values["model-discovery"],
|
|
9991
|
-
// Claude Code scans its skill folder when a session starts, so that is when the login's skills are brought current.
|
|
9992
|
-
skillsRefresh: values.scope === "user" ? shellCommand(resolveSelfCommand(), "skills", "refresh") : void 0
|
|
9993
|
-
});
|
|
9994
|
-
writeJsonAtomic(file, settings);
|
|
9995
|
-
io.stdout(`Claude Code \u2192 ${anthropicBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
|
|
9996
|
-
for (const warning of warnings) io.stderr(`warning: ${warning}`);
|
|
9997
|
-
await skillsFor(io, apiUrl, "claude", values["organization-id"]);
|
|
9998
|
-
return ExitCode.Ok;
|
|
9999
|
-
}
|
|
10000
|
-
case "opencode": {
|
|
10001
|
-
const { values } = parseArgs3({ args: rest, options: { surface: { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" } }, strict: true });
|
|
10002
|
-
const surface = surfaceOf(values.surface, "anthropic");
|
|
10003
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10004
|
-
const baseUrl = surfaceBaseUrl(apiUrl, surface);
|
|
10005
|
-
let models = [];
|
|
10006
|
-
try {
|
|
10007
|
-
models = await servedModels(apiUrl, token, surface);
|
|
10008
|
-
} catch (error) {
|
|
10009
|
-
io.stderr(`warning: ${error instanceof Error ? error.message : String(error)}; writing the provider without a model list`);
|
|
10010
|
-
}
|
|
10011
|
-
const file = values.config ?? opencodeConfigPath();
|
|
10012
|
-
writeJsonAtomic(file, mergeOpencodeConfig(readJson(file), { baseUrl, surface, models }));
|
|
10013
|
-
io.stdout(`OpenCode provider divergent-${surface} \u2192 ${baseUrl}. Wrote ${file}.`);
|
|
10014
|
-
io.stdout("Keep DIVERGENT_TOKEN fresh for the session (user tokens expire; the login refreshes):");
|
|
10015
|
-
io.stdout(` export DIVERGENT_TOKEN="$(${["diverge", "token", ...organizationArgs(values["organization-id"])].join(" ")})"`);
|
|
10016
|
-
return ExitCode.Ok;
|
|
10017
|
-
}
|
|
10018
|
-
case "codex": {
|
|
10019
|
-
const { values } = parseArgs3({ args: rest, options: { "project-id": { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" }, print: { type: "boolean", default: false } }, strict: true });
|
|
10020
|
-
const block = codexProviderToml({ openaiBaseUrl: openaiBaseUrl(apiUrl), self: resolveSelfCommand(), projectId: values["project-id"], organizationId: values["organization-id"] });
|
|
10021
|
-
if (values.print) {
|
|
10022
|
-
io.stdout(block.trimEnd());
|
|
10023
|
-
return ExitCode.Ok;
|
|
10024
|
-
}
|
|
10025
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10026
|
-
const file = values.config ?? codexConfigPath();
|
|
10027
|
-
const existing = fs11.existsSync(file) ? fs11.readFileSync(file, "utf8") : null;
|
|
10028
|
-
writeTextAtomic(file, mergeCodexConfig(existing, block));
|
|
10029
|
-
io.stdout(`Codex provider divergent \u2192 ${openaiBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
|
|
10030
|
-
io.stdout('Select it per run with: codex -c model_provider=divergent (or set model_provider = "divergent" in the config)');
|
|
10031
|
-
await skillsFor(io, apiUrl, "codex", values["organization-id"]);
|
|
10032
|
-
return ExitCode.Ok;
|
|
10033
|
-
}
|
|
10034
|
-
case "dsh": {
|
|
10035
|
-
const { values } = parseArgs3({ args: rest, options: { surface: { type: "string" }, model: { type: "string" }, "organization-id": { type: "string" }, into: { type: "string" }, print: { type: "boolean", default: false } }, strict: true });
|
|
10036
|
-
const surface = surfaceOf(values.surface, "openai");
|
|
10037
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10038
|
-
if (!values.print) await skillsFor(io, apiUrl, "dsh", values["organization-id"]);
|
|
10039
|
-
const { overlay, defaultModel } = await overlayAndDefault(apiUrl, token, surface, io, values.model);
|
|
10040
|
-
if (values.print) {
|
|
10041
|
-
io.stdout(overlay.trimEnd());
|
|
10042
|
-
return ExitCode.Ok;
|
|
10043
|
-
}
|
|
10044
|
-
const file = values.into ?? path14.join(dshHome(io.env), `divergent-${surface}.yml`);
|
|
10045
|
-
writeTextAtomic(file, overlay);
|
|
10046
|
-
io.stdout(`dsh route divergent-${surface} \u2192 ${surfaceBaseUrl(apiUrl, surface)} as you, organization ${tokenClaims(token.access_token).organizationId}, default model ${defaultModel}. Wrote ${file}.`);
|
|
10047
|
-
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.");
|
|
10048
|
-
syncDivergentPreset(io, findOnPath("dsh", io.env), dshHome(io.env));
|
|
10049
|
-
io.stdout("Boot a profile with the overlay and the token in its environment (user tokens expire; the login refreshes):");
|
|
10050
|
-
io.stdout(` DIVERGENT_TOKEN="$(${["diverge", "token", ...organizationArgs(values["organization-id"])].join(" ")})" dsh --profile headless --patch ${file} "<task>"`);
|
|
10051
|
-
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`);
|
|
10052
|
-
return ExitCode.Ok;
|
|
10053
|
-
}
|
|
10054
|
-
default:
|
|
10055
|
-
throw unknownVerb("agents", agentsUsage, ["setup"], agent, rest);
|
|
10056
|
-
}
|
|
10057
|
-
}
|
|
10058
|
-
async function run(args, io) {
|
|
10059
|
-
const [agent, ...rest] = args;
|
|
10060
|
-
if (agent !== "dsh") throw unknownVerb("agents", agentsUsage, ["run"], agent, rest);
|
|
10061
|
-
const terminator = rest.indexOf("--");
|
|
10062
|
-
const flags = terminator === -1 ? rest : rest.slice(0, terminator);
|
|
10063
|
-
const passthrough = terminator === -1 ? [] : rest.slice(terminator + 1);
|
|
10064
|
-
const { values } = parseArgs3({ args: flags, options: { surface: { type: "string" }, model: { type: "string" }, "organization-id": { type: "string" } }, strict: true });
|
|
10065
|
-
const surface = surfaceOf(values.surface, "openai");
|
|
10299
|
+
const usable = !requested && saved && models.includes(saved.model) ? saved : null;
|
|
10300
|
+
const defaultModel = dshDefaultModel(models, requested ?? usable?.model);
|
|
10301
|
+
if (!requested && saved && !usable) io.stderr(`The model you last chose, ${saved.model}, is no longer served here; using ${defaultModel}.`);
|
|
10302
|
+
return { overlay: dshPatchYaml({ baseUrl: surfaceBaseUrl(apiUrl, surface), surface, models: served, defaultModel, reasoningEffort: usable?.reasoningEffort, launch }), defaultModel };
|
|
10303
|
+
}
|
|
10304
|
+
async function runDshCommand(args, io) {
|
|
10305
|
+
const terminator = args.indexOf("--");
|
|
10306
|
+
const flags = terminator === -1 ? args : args.slice(0, terminator);
|
|
10307
|
+
const passthrough = terminator === -1 ? [] : args.slice(terminator + 1);
|
|
10308
|
+
const { values } = parseArgs4({ args: flags, options: { surface: { type: "string" }, model: { type: "string" }, "organization-id": { type: "string" } }, strict: true });
|
|
10309
|
+
const surface = surfaceOf2(values.surface);
|
|
10310
|
+
const relaunched = await updateThenRelaunch(io, {
|
|
10311
|
+
version: VERSION,
|
|
10312
|
+
latest: () => latestVersion(void 0, LATEST_TIMEOUT_MS),
|
|
10313
|
+
install: (version) => installLatest(version, io.env, process.platform, ["ignore", 2, 2]),
|
|
10314
|
+
relaunch: () => relaunch(args, io.env)
|
|
10315
|
+
});
|
|
10316
|
+
if (relaunched !== null) return relaunched;
|
|
10066
10317
|
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
10067
10318
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10068
|
-
const
|
|
10069
|
-
|
|
10319
|
+
const dsh = await ensureDsh(io);
|
|
10320
|
+
const prefix = path18.join(dshInstallRoot(io.env), TESTED_DSH_VERSION);
|
|
10070
10321
|
await skillsFor(io, apiUrl, "dsh", values["organization-id"]);
|
|
10071
|
-
const launch = { id: randomUUID(), client: `dsh/${
|
|
10072
|
-
const { overlay, defaultModel } = await overlayAndDefault(apiUrl, token, surface, io, values.model, launch);
|
|
10322
|
+
const launch = { id: randomUUID(), client: `dsh/${TESTED_DSH_VERSION}` };
|
|
10073
10323
|
const home = dshHome(io.env);
|
|
10074
|
-
const
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
throw new CliError(`${settingsPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10080
|
-
}
|
|
10081
|
-
const served = servedIds(overlay);
|
|
10082
|
-
if (pinned && !served.includes(pinned)) throw new CliError(stalePinMessage(settingsPath, pinned, surface, served));
|
|
10083
|
-
const overlayPath = path14.join(home, `divergent-${surface}.yml`);
|
|
10084
|
-
writeTextAtomic(overlayPath, overlay);
|
|
10085
|
-
syncDivergentPreset(io, binary, home);
|
|
10086
|
-
io.stderr(`dsh on divergent-${surface} as you (organization ${tokenClaims(token.access_token).organizationId}), default model ${defaultModel}`);
|
|
10087
|
-
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>"');
|
|
10324
|
+
const { overlay, defaultModel } = await overlayAndDefault(io, apiUrl, token, surface, values.model, savedModel(home, surface), launch);
|
|
10325
|
+
const overlayPath = path18.join(home, `divergent-${surface}.yml`);
|
|
10326
|
+
writeTextAtomic(overlayPath, `${overlay}${dshPresetOverrides(prefix)}`);
|
|
10327
|
+
io.stderr(`dsh ${TESTED_DSH_VERSION} on divergent-${surface} as you (organization ${tokenClaims(token.access_token).organizationId}), default model ${defaultModel}`);
|
|
10328
|
+
if (passthrough.length === 0) io.stderr('Starting the web UI; open the URL dsh prints. For one task without the UI: diverge dsh -- "<task>"');
|
|
10088
10329
|
const source = tokenSource(io.env, apiUrl);
|
|
10089
10330
|
const organizationId = tokenClaims(token.access_token).organizationId ?? values["organization-id"];
|
|
10090
10331
|
if (!organizationId) throw new CliError("the gateway token names no organization; pass --organization-id");
|
|
10091
10332
|
io.stderr(`This session is organization ${organizationId}'s own record at the level its administrators set \u2014 off, metadata (tools, tokens, models; no content) or the whole conversation \u2014 named once its record opens. Yours to read: diverge tools call ${AGENT_SESSION_LIST_TOOL} (ADR 0121).`);
|
|
10092
10333
|
const tools = new ToolsClient({ apiUrl, organizationId, mode: "operator", clientId: clientId(io.env) }, io.env);
|
|
10093
10334
|
const publisher = new DshSessionPublisher({
|
|
10094
|
-
sessionsRoot:
|
|
10095
|
-
cwd: process.cwd(),
|
|
10335
|
+
sessionsRoot: path18.join(home, "sessions"),
|
|
10096
10336
|
launchedAt: Date.now(),
|
|
10097
10337
|
launchId: launch.id,
|
|
10098
|
-
harnessVersion:
|
|
10338
|
+
harnessVersion: TESTED_DSH_VERSION,
|
|
10099
10339
|
host: os8.hostname(),
|
|
10100
10340
|
append: (input) => appendSession(tools, input),
|
|
10101
10341
|
report: (line) => io.stderr(line),
|
|
@@ -10104,12 +10344,12 @@ async function run(args, io) {
|
|
|
10104
10344
|
publisher.start();
|
|
10105
10345
|
try {
|
|
10106
10346
|
return await runDsh({
|
|
10107
|
-
|
|
10347
|
+
dsh,
|
|
10108
10348
|
overlayPath,
|
|
10109
|
-
credentialsPath:
|
|
10110
|
-
sessionTokenPath:
|
|
10111
|
-
binDir:
|
|
10112
|
-
cli: { command: process.execPath, args: [
|
|
10349
|
+
credentialsPath: path18.join(home, ".credentials.yaml"),
|
|
10350
|
+
sessionTokenPath: path18.join(home, "session-token"),
|
|
10351
|
+
binDir: path18.join(home, "bin"),
|
|
10352
|
+
cli: { command: process.execPath, args: [path18.resolve(process.argv[1])] },
|
|
10113
10353
|
args: passthrough,
|
|
10114
10354
|
env: io.env,
|
|
10115
10355
|
initialToken: token.access_token,
|
|
@@ -10136,56 +10376,9 @@ async function appendSession(tools, input) {
|
|
|
10136
10376
|
}
|
|
10137
10377
|
return { capture: result.capture, sessionId: result.sessionId ?? null, stored: result.stored ?? 0, skipped: result.skipped ?? 0, lastSeq: typeof result.lastSeq === "number" ? result.lastSeq : -1 };
|
|
10138
10378
|
}
|
|
10139
|
-
async function status(args, io) {
|
|
10140
|
-
const { values } = parseArgs3({ args, options: { "organization-id": { type: "string" } }, strict: true });
|
|
10141
|
-
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
10142
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10143
|
-
const claims = tokenClaims(token.access_token);
|
|
10144
|
-
io.stdout(`Gateway: ${apiUrl}/api/plugins/inference/{anthropic,openai}`);
|
|
10145
|
-
io.stdout(`Organization: ${claims.organizationId}`);
|
|
10146
|
-
io.stdout(`Scopes: ${(claims.scopes ?? []).join(" ")}`);
|
|
10147
|
-
for (const surface of SURFACES) {
|
|
10148
|
-
const models = (await servedModels(apiUrl, token, surface)).map((model) => model.id);
|
|
10149
|
-
io.stdout(`Models (${surface}): ${models.length ? models.join(", ") : "(none served on this surface)"}`);
|
|
10150
|
-
}
|
|
10151
|
-
return ExitCode.Ok;
|
|
10152
|
-
}
|
|
10153
|
-
async function rotate(args, io) {
|
|
10154
|
-
const { values } = parseArgs3({ args, options: { into: { type: "string" }, interval: { type: "string", default: "600" }, once: { type: "boolean", default: false }, "organization-id": { type: "string" } }, strict: true });
|
|
10155
|
-
const interval = Number(values.interval);
|
|
10156
|
-
if (!Number.isFinite(interval) || interval <= 0) throw new CliError("--interval must be a positive number of seconds");
|
|
10157
|
-
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
10158
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10159
|
-
const source = tokenSource(io.env, apiUrl);
|
|
10160
|
-
const tokenFilePath = values.into ?? defaultTokenFilePath();
|
|
10161
|
-
io.stderr(`Keeping ${tokenFilePath} fresh as you (organization ${tokenClaims(token.access_token).organizationId})`);
|
|
10162
|
-
return rotateTokenFile({
|
|
10163
|
-
tokenFile: tokenFilePath,
|
|
10164
|
-
intervalSeconds: interval,
|
|
10165
|
-
once: values.once,
|
|
10166
|
-
freshToken: async () => (await getUserToken(source, { freshForSeconds: Math.max(interval, 60) + 60, organizationId: values["organization-id"] })).access_token,
|
|
10167
|
-
report: (line) => io.stderr(line),
|
|
10168
|
-
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
10169
|
-
});
|
|
10170
|
-
}
|
|
10171
|
-
async function runAgents(args, io) {
|
|
10172
|
-
const [verb, ...rest] = args;
|
|
10173
|
-
switch (verb) {
|
|
10174
|
-
case "setup":
|
|
10175
|
-
return setup(rest, io);
|
|
10176
|
-
case "run":
|
|
10177
|
-
return run(rest, io);
|
|
10178
|
-
case "rotate":
|
|
10179
|
-
return rotate(rest, io);
|
|
10180
|
-
case "status":
|
|
10181
|
-
return status(rest, io);
|
|
10182
|
-
default:
|
|
10183
|
-
throw unknownVerb("agents", agentsUsage, [], verb, rest);
|
|
10184
|
-
}
|
|
10185
|
-
}
|
|
10186
10379
|
|
|
10187
10380
|
// src/commands/org.ts
|
|
10188
|
-
import { parseArgs as
|
|
10381
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
10189
10382
|
var orgUsage = `diverge org [use <id|name>]
|
|
10190
10383
|
|
|
10191
10384
|
(no verb) list the organizations you may act in, the default marked
|
|
@@ -10198,7 +10391,7 @@ there, which proves you may act in it. For one command, pass
|
|
|
10198
10391
|
act in, and so sees, every organization.`;
|
|
10199
10392
|
async function runOrg(args, io) {
|
|
10200
10393
|
refuseInSession(io.env, "org");
|
|
10201
|
-
const { positionals } =
|
|
10394
|
+
const { positionals } = parseArgs5({ args, options: {}, strict: true, allowPositionals: true });
|
|
10202
10395
|
const verb = positionals[0];
|
|
10203
10396
|
if (verb && verb !== "use") throw unknownVerb("org", orgUsage, [], verb, positionals.slice(1));
|
|
10204
10397
|
const source = tokenSource(io.env, resolveApiUrl(void 0, io.env));
|
|
@@ -10245,7 +10438,7 @@ async function runLogout(_args, io) {
|
|
|
10245
10438
|
}
|
|
10246
10439
|
|
|
10247
10440
|
// src/commands/status.ts
|
|
10248
|
-
import
|
|
10441
|
+
import path19 from "node:path";
|
|
10249
10442
|
var statusUsage = "diverge status\n\nShow the deployment, the cached login (or the session token), whose account it is, what it carries, and who a tool call would run as. One request: the account behind the login.";
|
|
10250
10443
|
async function runStatus(_args, io) {
|
|
10251
10444
|
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
@@ -10261,7 +10454,7 @@ async function runStatus(_args, io) {
|
|
|
10261
10454
|
io.stdout(describeToolExecution(io.env));
|
|
10262
10455
|
return 0;
|
|
10263
10456
|
}
|
|
10264
|
-
io.stdout(`State: ${
|
|
10457
|
+
io.stdout(`State: ${path19.dirname(source.loginFile)}`);
|
|
10265
10458
|
const record = loadLogin(source);
|
|
10266
10459
|
if (!record) {
|
|
10267
10460
|
io.stdout(`Not logged in. Run: diverge login${apiUrl === DEFAULT_API_URL ? "" : ` --api-url ${apiUrl}`}`);
|
|
@@ -10309,7 +10502,7 @@ function describeSignIn(record, nowMs = Date.now()) {
|
|
|
10309
10502
|
}
|
|
10310
10503
|
|
|
10311
10504
|
// src/commands/token.ts
|
|
10312
|
-
import { parseArgs as
|
|
10505
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
10313
10506
|
var tokenUsage = `diverge token [--organization-id <id>]
|
|
10314
10507
|
|
|
10315
10508
|
Print a fresh access token and nothing else: the credential helper for Claude
|
|
@@ -10318,7 +10511,7 @@ Code, Codex and forge. The token is for the organization named by
|
|
|
10318
10511
|
DIVERGENT_TOKEN_FILE set, it prints the session token in that file, and an
|
|
10319
10512
|
organization other than the token's is refused.`;
|
|
10320
10513
|
async function runToken(args, io) {
|
|
10321
|
-
const { values } =
|
|
10514
|
+
const { values } = parseArgs6({ args, options: { "organization-id": { type: "string" } }, strict: true, allowPositionals: false });
|
|
10322
10515
|
const organizationId = values["organization-id"]?.trim() || io.env.DIVERGENT_ORG_ID?.trim() || void 0;
|
|
10323
10516
|
const token = await getUserToken(tokenSource(io.env, resolveApiUrl(void 0, io.env)), { organizationId });
|
|
10324
10517
|
io.stdout(token.access_token);
|
|
@@ -10326,7 +10519,7 @@ async function runToken(args, io) {
|
|
|
10326
10519
|
}
|
|
10327
10520
|
|
|
10328
10521
|
// src/commands/tools.ts
|
|
10329
|
-
import { parseArgs as
|
|
10522
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
10330
10523
|
|
|
10331
10524
|
// src/mcp/bridge.ts
|
|
10332
10525
|
import readline2 from "node:readline";
|
|
@@ -10553,7 +10746,7 @@ async function waitForInvocation(id, options, deps) {
|
|
|
10553
10746
|
}
|
|
10554
10747
|
|
|
10555
10748
|
// src/tools/call-args.ts
|
|
10556
|
-
import
|
|
10749
|
+
import fs16 from "node:fs";
|
|
10557
10750
|
async function resolveCallArgs(options, stdin = readStdin) {
|
|
10558
10751
|
if (options.argsJson !== void 0 && options.argsJson !== "{}" && options.argsFile !== void 0) {
|
|
10559
10752
|
throw new CliError("give the arguments once: --args-json or --args-file");
|
|
@@ -10563,7 +10756,7 @@ async function resolveCallArgs(options, stdin = readStdin) {
|
|
|
10563
10756
|
if (options.argsFile !== void 0) {
|
|
10564
10757
|
source = `--args-file ${options.argsFile}`;
|
|
10565
10758
|
try {
|
|
10566
|
-
text2 =
|
|
10759
|
+
text2 = fs16.readFileSync(options.argsFile, "utf8");
|
|
10567
10760
|
} catch (error) {
|
|
10568
10761
|
throw new CliError(`${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10569
10762
|
}
|
|
@@ -10655,7 +10848,7 @@ function worstExitCode(outcomes) {
|
|
|
10655
10848
|
}
|
|
10656
10849
|
|
|
10657
10850
|
// src/commands/tools.ts
|
|
10658
|
-
import
|
|
10851
|
+
import fs17 from "node:fs";
|
|
10659
10852
|
|
|
10660
10853
|
// src/tools/print.ts
|
|
10661
10854
|
function matchesCapability(selector, capability) {
|
|
@@ -10780,7 +10973,7 @@ async function call(rest, io) {
|
|
|
10780
10973
|
let waited = "";
|
|
10781
10974
|
let outcome;
|
|
10782
10975
|
try {
|
|
10783
|
-
const { values, positionals } =
|
|
10976
|
+
const { values, positionals } = parseArgs7({ args: rest, options: CALL_OPTIONS, strict: true, allowPositionals: true });
|
|
10784
10977
|
tool = positionals[0] ?? "";
|
|
10785
10978
|
if (!tool) throw new CliError("call needs a tool name");
|
|
10786
10979
|
const args = await resolveCallArgs({ argsJson: values["args-json"], argsFile: values["args-file"] });
|
|
@@ -10807,12 +11000,12 @@ async function set(rest, io) {
|
|
|
10807
11000
|
let waited = "";
|
|
10808
11001
|
let outcome;
|
|
10809
11002
|
try {
|
|
10810
|
-
const { values, positionals } =
|
|
11003
|
+
const { values, positionals } = parseArgs7({ args: rest, options: SET_OPTIONS, strict: true, allowPositionals: true });
|
|
10811
11004
|
const file = positionals[0];
|
|
10812
11005
|
if (!file) throw new CliError("set needs a file of calls, or - for stdin");
|
|
10813
11006
|
let entries;
|
|
10814
11007
|
try {
|
|
10815
|
-
entries = parseBatch(file === "-" ? await readStdin() :
|
|
11008
|
+
entries = parseBatch(file === "-" ? await readStdin() : fs17.readFileSync(file, "utf8"));
|
|
10816
11009
|
} catch (error) {
|
|
10817
11010
|
throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10818
11011
|
}
|
|
@@ -10856,7 +11049,7 @@ async function runTools(args, io) {
|
|
|
10856
11049
|
const [verb, ...rest] = args;
|
|
10857
11050
|
switch (verb) {
|
|
10858
11051
|
case "list": {
|
|
10859
|
-
const { values } =
|
|
11052
|
+
const { values } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, filter: { type: "string" }, capability: { type: "string" }, json: { type: "boolean", default: false } }, strict: true });
|
|
10860
11053
|
const { client: tools } = await client(values, io);
|
|
10861
11054
|
let list = await tools.listTools();
|
|
10862
11055
|
if (values.filter) list = list.filter((tool) => matchesFilter(values.filter, tool));
|
|
@@ -10866,7 +11059,7 @@ async function runTools(args, io) {
|
|
|
10866
11059
|
return ExitCode.Ok;
|
|
10867
11060
|
}
|
|
10868
11061
|
case "capabilities": {
|
|
10869
|
-
const { values } =
|
|
11062
|
+
const { values } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true });
|
|
10870
11063
|
const { client: tools } = await client(values, io);
|
|
10871
11064
|
const list = await tools.listTools();
|
|
10872
11065
|
if (values.json) io.stdout(JSON.stringify(capabilityClusters(list), null, 2));
|
|
@@ -10874,7 +11067,7 @@ async function runTools(args, io) {
|
|
|
10874
11067
|
return ExitCode.Ok;
|
|
10875
11068
|
}
|
|
10876
11069
|
case "describe": {
|
|
10877
|
-
const { values, positionals } =
|
|
11070
|
+
const { values, positionals } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true, allowPositionals: true });
|
|
10878
11071
|
const name = positionals[0];
|
|
10879
11072
|
if (!name) throw new CliError("describe needs a tool name");
|
|
10880
11073
|
const { settings, client: tools } = await client(values, io);
|
|
@@ -10894,12 +11087,12 @@ async function runTools(args, io) {
|
|
|
10894
11087
|
case "set":
|
|
10895
11088
|
return set(rest, io);
|
|
10896
11089
|
case "run": {
|
|
10897
|
-
const { values, positionals } =
|
|
11090
|
+
const { values, positionals } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, wait: { type: "boolean", default: false }, timeout: { type: "string", default: "600" } }, strict: true, allowPositionals: true });
|
|
10898
11091
|
const file = positionals[0];
|
|
10899
11092
|
if (!file) throw new CliError("run needs a file of calls, or - for stdin");
|
|
10900
11093
|
let entries;
|
|
10901
11094
|
try {
|
|
10902
|
-
entries = parseBatch(file === "-" ? await readStdin() :
|
|
11095
|
+
entries = parseBatch(file === "-" ? await readStdin() : fs17.readFileSync(file, "utf8"));
|
|
10903
11096
|
} catch (error) {
|
|
10904
11097
|
throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10905
11098
|
}
|
|
@@ -10914,7 +11107,7 @@ async function runTools(args, io) {
|
|
|
10914
11107
|
return exitCode;
|
|
10915
11108
|
}
|
|
10916
11109
|
case "invocation": {
|
|
10917
|
-
const { values, positionals } =
|
|
11110
|
+
const { values, positionals } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, wait: { type: "boolean", default: false }, timeout: { type: "string", default: "600" }, json: { type: "boolean", default: false } }, strict: true, allowPositionals: true });
|
|
10918
11111
|
const id = positionals[0];
|
|
10919
11112
|
if (!id) throw new CliError("invocation needs an id");
|
|
10920
11113
|
const { client: tools } = await client(values, io);
|
|
@@ -10932,7 +11125,7 @@ async function runTools(args, io) {
|
|
|
10932
11125
|
return worstExitCode([outcome]);
|
|
10933
11126
|
}
|
|
10934
11127
|
case "bridge": {
|
|
10935
|
-
const { values } =
|
|
11128
|
+
const { values } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, tools: { type: "string" } }, strict: true });
|
|
10936
11129
|
const { client: tools } = await client(values, io);
|
|
10937
11130
|
const allow = (values.tools ?? "").split(",").map((name) => name.trim()).filter(Boolean);
|
|
10938
11131
|
return serveBridge(new BridgeServer(tools, { tools: allow }), process.stdin, process.stdout);
|
|
@@ -10943,26 +11136,30 @@ async function runTools(args, io) {
|
|
|
10943
11136
|
}
|
|
10944
11137
|
|
|
10945
11138
|
// src/commands/plugins.ts
|
|
10946
|
-
import
|
|
10947
|
-
import { parseArgs as
|
|
11139
|
+
import fs18 from "node:fs";
|
|
11140
|
+
import { parseArgs as parseArgs8 } from "node:util";
|
|
10948
11141
|
var pluginsUsage = `diverge plugins <validate|publish|list|retire> [--organization-id <id>] ...
|
|
10949
11142
|
|
|
10950
|
-
validate <manifest.json>
|
|
10951
|
-
|
|
10952
|
-
|
|
11143
|
+
validate <manifest.json> [--json] (forge_validate_plugin: the platform's full check, nothing stored;
|
|
11144
|
+
says whether publishing needs a person's decision)
|
|
11145
|
+
publish <manifest.json> [--json] (forge_publish_plugin: raised as you, decided in Compass;
|
|
11146
|
+
exits 3 while it waits)
|
|
10953
11147
|
list [--json]
|
|
10954
|
-
retire <pluginId>
|
|
11148
|
+
retire <pluginId> [--json] (forge_retire_plugin: raised as you, decided in Compass;
|
|
11149
|
+
exits 3 while it waits)
|
|
10955
11150
|
|
|
10956
11151
|
Register a tenant plugin: a plugin whose code runs in your organization's own
|
|
10957
11152
|
runtime and never inside the platform. The manifest lists the tools the
|
|
10958
11153
|
platform registers, pinned; a change is a new version published again.
|
|
10959
11154
|
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
11155
|
+
Every write is a tool invocation decided by your organization's policy: publish
|
|
11156
|
+
and retire print the invocation and its Compass link and exit 3 while they
|
|
11157
|
+
wait; a person decides there, and diverge tools invocation <id> reads the
|
|
11158
|
+
outcome. A version that only reshapes tools already served is what the deploy
|
|
11159
|
+
pipeline stores unattended with forge_reshape_plugin; validate says which a
|
|
11160
|
+
version needs. Enable the plugin with forge_set_plugin_state and bind the
|
|
11161
|
+
runtime with a Forge bridge binding whose runtimeType matches the manifest's
|
|
11162
|
+
runtime.type.`;
|
|
10966
11163
|
var SCOPE_OPTIONS2 = { "organization-id": { type: "string" } };
|
|
10967
11164
|
async function client2(values, io) {
|
|
10968
11165
|
const settings = loadToolsSettings({ organizationId: values["organization-id"] }, io.env);
|
|
@@ -10975,29 +11172,24 @@ function printProblems(details, io) {
|
|
|
10975
11172
|
for (const detail of details) io.stderr(` - ${detail}`);
|
|
10976
11173
|
return ExitCode.Failure;
|
|
10977
11174
|
}
|
|
10978
|
-
async function
|
|
11175
|
+
async function raise(entry, context, io) {
|
|
10979
11176
|
const { settings, tools, json } = context;
|
|
10980
|
-
|
|
10981
|
-
io.stdout("This version changes authority, so it is stored only once it is decided:");
|
|
10982
|
-
for (const change of authorityChanges) io.stdout(` - ${change}`);
|
|
10983
|
-
}
|
|
10984
|
-
const outcome = await runOne({ tool: "forge_publish_plugin", args: { manifest } }, {
|
|
11177
|
+
const outcome = await runOne(entry, {
|
|
10985
11178
|
callTool: (name, args) => tools.callTool(name, args),
|
|
10986
11179
|
decideAt: (pending) => `${settings.apiUrl}${pending.approvalUrl ?? ""}`,
|
|
10987
11180
|
...json ? {} : { onPending: (pending) => formatPending(pending, settings.apiUrl, settings.organizationId).forEach((line) => io.stdout(line)) }
|
|
10988
11181
|
});
|
|
10989
|
-
if (json) io.stdout(JSON.stringify(
|
|
10990
|
-
else if (outcome.status === "
|
|
10991
|
-
|
|
10992
|
-
return worstExitCode([outcome]);
|
|
11182
|
+
if (json) io.stdout(JSON.stringify(outcome, null, 2));
|
|
11183
|
+
else if (outcome.status === "failed") throw new CliError(`${entry.tool} failed: ${outcome.error}`);
|
|
11184
|
+
return outcome;
|
|
10993
11185
|
}
|
|
10994
|
-
function
|
|
11186
|
+
function readManifest2(file) {
|
|
10995
11187
|
if (!file) throw new CliError(`A manifest file is required.
|
|
10996
11188
|
|
|
10997
11189
|
${pluginsUsage}`);
|
|
10998
11190
|
let text2;
|
|
10999
11191
|
try {
|
|
11000
|
-
text2 =
|
|
11192
|
+
text2 = fs18.readFileSync(file, "utf8");
|
|
11001
11193
|
} catch (error) {
|
|
11002
11194
|
throw new CliError(`Cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
11003
11195
|
}
|
|
@@ -11024,42 +11216,44 @@ async function runPlugins(args, io) {
|
|
|
11024
11216
|
const [verb, ...rest] = args;
|
|
11025
11217
|
switch (verb) {
|
|
11026
11218
|
case "validate": {
|
|
11027
|
-
const { values, positionals } =
|
|
11028
|
-
const manifest =
|
|
11219
|
+
const { values, positionals } = parseArgs8({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
11220
|
+
const manifest = readManifest2(positionals[0]);
|
|
11029
11221
|
const { tools } = await client2(values, io);
|
|
11030
|
-
const
|
|
11031
|
-
if (
|
|
11032
|
-
|
|
11222
|
+
const body = await tools.callTool("forge_validate_plugin", { manifest });
|
|
11223
|
+
if (body.success === false) throw new CliError(`forge_validate_plugin failed: ${body.error ?? "unknown error"}`);
|
|
11224
|
+
const result = body.result;
|
|
11225
|
+
if (!result.valid) {
|
|
11226
|
+
if (values.json) io.stdout(JSON.stringify(result, null, 2));
|
|
11227
|
+
return printProblems(result.problems, io);
|
|
11228
|
+
}
|
|
11229
|
+
if (values.json) io.stdout(JSON.stringify(result, null, 2));
|
|
11033
11230
|
else {
|
|
11034
|
-
io.stdout(`Valid. ${
|
|
11035
|
-
for (const line of formatPlugin(
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
11231
|
+
io.stdout(`Valid. ${result.changed ? "Publishing would register" : "Already published at this digest:"}`);
|
|
11232
|
+
for (const line of formatPlugin(result.plugin)) io.stdout(line);
|
|
11233
|
+
if (result.authorityChanges.length) {
|
|
11234
|
+
io.stdout("Publishing it changes authority, so forge_publish_plugin stores it once a person decides in Compass:");
|
|
11235
|
+
for (const change of result.authorityChanges) io.stdout(` - ${change}`);
|
|
11236
|
+
} else if (result.changed) {
|
|
11237
|
+
io.stdout("It keeps the serving version's authority: forge_reshape_plugin stores it without a decision.");
|
|
11040
11238
|
}
|
|
11041
11239
|
}
|
|
11042
11240
|
return ExitCode.Ok;
|
|
11043
11241
|
}
|
|
11044
11242
|
case "publish": {
|
|
11045
|
-
const { values, positionals } =
|
|
11046
|
-
const manifest =
|
|
11243
|
+
const { values, positionals } = parseArgs8({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
11244
|
+
const manifest = readManifest2(positionals[0]);
|
|
11047
11245
|
const { settings, tools } = await client2(values, io);
|
|
11048
|
-
const outcome = await
|
|
11049
|
-
if (!
|
|
11050
|
-
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
else {
|
|
11055
|
-
io.stdout(outcome.result.changed ? "Published." : "Unchanged: this digest was already published.");
|
|
11056
|
-
for (const line of formatPlugin(outcome.result.plugin)) io.stdout(line);
|
|
11057
|
-
io.stdout("Enable it for the organization with forge plugins (or forge_set_plugin_state); the tools run through the bridge binding of the manifest's runtime.type.");
|
|
11246
|
+
const outcome = await raise({ tool: "forge_publish_plugin", args: { manifest } }, { settings, tools, json: values.json }, io);
|
|
11247
|
+
if (!values.json && outcome.status === "executed") {
|
|
11248
|
+
const result = outcome.result;
|
|
11249
|
+
io.stdout(result.changed ? "Published." : "Unchanged: this digest was already published.");
|
|
11250
|
+
for (const line of formatPlugin(result.plugin)) io.stdout(line);
|
|
11251
|
+
io.stdout("Enable it for the organization with forge_set_plugin_state; the tools run through the bridge binding of the manifest's runtime.type.");
|
|
11058
11252
|
}
|
|
11059
|
-
return
|
|
11253
|
+
return worstExitCode([outcome]);
|
|
11060
11254
|
}
|
|
11061
11255
|
case "list": {
|
|
11062
|
-
const { values } =
|
|
11256
|
+
const { values } = parseArgs8({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, strict: true });
|
|
11063
11257
|
const { tools } = await client2(values, io);
|
|
11064
11258
|
const plugins = await tools.listPlugins();
|
|
11065
11259
|
if (values.json) io.stdout(JSON.stringify(plugins, null, 2));
|
|
@@ -11068,138 +11262,24 @@ async function runPlugins(args, io) {
|
|
|
11068
11262
|
return ExitCode.Ok;
|
|
11069
11263
|
}
|
|
11070
11264
|
case "retire": {
|
|
11071
|
-
const { values, positionals } =
|
|
11265
|
+
const { values, positionals } = parseArgs8({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
11072
11266
|
const pluginId = positionals[0];
|
|
11073
11267
|
if (!pluginId) throw new CliError(`A plugin id is required.
|
|
11074
11268
|
|
|
11075
11269
|
${pluginsUsage}`);
|
|
11076
|
-
const { tools } = await client2(values, io);
|
|
11077
|
-
const
|
|
11078
|
-
|
|
11079
|
-
|
|
11270
|
+
const { settings, tools } = await client2(values, io);
|
|
11271
|
+
const outcome = await raise({ tool: "forge_retire_plugin", args: { pluginId } }, { settings, tools, json: values.json }, io);
|
|
11272
|
+
if (!values.json && outcome.status === "executed") {
|
|
11273
|
+
const { plugin } = outcome.result;
|
|
11274
|
+
io.stdout(`Retired ${plugin.pluginId}@${plugin.version}; its ${plugin.toolNames.length} tool(s) are gone. Installation records stay for a re-publish.`);
|
|
11275
|
+
}
|
|
11276
|
+
return worstExitCode([outcome]);
|
|
11080
11277
|
}
|
|
11081
11278
|
default:
|
|
11082
11279
|
throw unknownVerb("plugins", pluginsUsage, [], verb, rest);
|
|
11083
11280
|
}
|
|
11084
11281
|
}
|
|
11085
11282
|
|
|
11086
|
-
// src/commands/update.ts
|
|
11087
|
-
import { spawn as spawn4 } from "node:child_process";
|
|
11088
|
-
import path17 from "node:path";
|
|
11089
|
-
|
|
11090
|
-
// src/update-notice.ts
|
|
11091
|
-
import { spawn as spawn3 } from "node:child_process";
|
|
11092
|
-
import fs15 from "node:fs";
|
|
11093
|
-
import path16 from "node:path";
|
|
11094
|
-
var UPDATE_CHECK_FILE = "update-check.json";
|
|
11095
|
-
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
11096
|
-
function updateCheckFile(env) {
|
|
11097
|
-
return path16.join(configDir(env), UPDATE_CHECK_FILE);
|
|
11098
|
-
}
|
|
11099
|
-
function readUpdateCheck(env) {
|
|
11100
|
-
try {
|
|
11101
|
-
const parsed = JSON.parse(fs15.readFileSync(updateCheckFile(env), "utf8"));
|
|
11102
|
-
if (parsed && typeof parsed === "object" && typeof parsed.checkedAt === "number" && typeof parsed.latest === "string") return parsed;
|
|
11103
|
-
} catch {
|
|
11104
|
-
}
|
|
11105
|
-
return null;
|
|
11106
|
-
}
|
|
11107
|
-
function writeUpdateCheck(env, check) {
|
|
11108
|
-
writeJsonAtomic(updateCheckFile(env), check);
|
|
11109
|
-
}
|
|
11110
|
-
function noticeFor(check, version = VERSION) {
|
|
11111
|
-
if (!check || !isNewer(check.latest, version)) return null;
|
|
11112
|
-
return `diverge ${check.latest} is available (this is ${version}). Update with: diverge update (or run npx @divergeai/cli@latest)`;
|
|
11113
|
-
}
|
|
11114
|
-
function noticeApplies(argv, io, version = VERSION) {
|
|
11115
|
-
if (version === "dev" || !io.interactive) return false;
|
|
11116
|
-
if (io.env.DIVERGE_NO_UPDATE_CHECK?.trim() || io.env.CI?.trim() || sessionTokenFile(io.env)) return false;
|
|
11117
|
-
if (argv[0] === "update") return false;
|
|
11118
|
-
if (argv[0] === "tools" && argv[1] === "bridge") return false;
|
|
11119
|
-
return true;
|
|
11120
|
-
}
|
|
11121
|
-
function scheduleUpdateCheck(spawnImpl = spawn3) {
|
|
11122
|
-
try {
|
|
11123
|
-
const child = spawnImpl(process.execPath, [process.argv[1], "update", "--record"], { detached: true, stdio: "ignore" });
|
|
11124
|
-
child.on("error", () => {
|
|
11125
|
-
});
|
|
11126
|
-
child.unref();
|
|
11127
|
-
} catch {
|
|
11128
|
-
}
|
|
11129
|
-
}
|
|
11130
|
-
function updateNotice(argv, io, deps = { now: Date.now, schedule: scheduleUpdateCheck, read: readUpdateCheck, version: VERSION }) {
|
|
11131
|
-
if (!noticeApplies(argv, io, deps.version)) return;
|
|
11132
|
-
const check = deps.read(io.env);
|
|
11133
|
-
const notice = noticeFor(check, deps.version);
|
|
11134
|
-
if (notice) io.stderr(notice);
|
|
11135
|
-
if (!check || deps.now() - check.checkedAt > UPDATE_CHECK_INTERVAL_MS) deps.schedule();
|
|
11136
|
-
}
|
|
11137
|
-
|
|
11138
|
-
// src/commands/update.ts
|
|
11139
|
-
var PACKAGE_NAME = "@divergeai/cli";
|
|
11140
|
-
var updateUsage = "diverge update\n\nInstall the latest release with the npm that installed this one.";
|
|
11141
|
-
async function latestVersion(registry = "https://registry.npmjs.org") {
|
|
11142
|
-
const body = await fetchJson(`${registry}/${PACKAGE_NAME}/latest`);
|
|
11143
|
-
if (!body.version) throw new CliError("The registry answered without a version");
|
|
11144
|
-
return body.version;
|
|
11145
|
-
}
|
|
11146
|
-
function isNewer(a, b) {
|
|
11147
|
-
const parse = (v) => {
|
|
11148
|
-
const [core, pre] = v.split("-", 2);
|
|
11149
|
-
return { nums: core.split(".").map((n) => Number(n) || 0), pre: pre ?? "" };
|
|
11150
|
-
};
|
|
11151
|
-
const x = parse(a);
|
|
11152
|
-
const y = parse(b);
|
|
11153
|
-
for (let i = 0; i < 3; i += 1) {
|
|
11154
|
-
const dx = x.nums[i] ?? 0;
|
|
11155
|
-
const dy = y.nums[i] ?? 0;
|
|
11156
|
-
if (dx !== dy) return dx > dy;
|
|
11157
|
-
}
|
|
11158
|
-
if (x.pre === y.pre) return false;
|
|
11159
|
-
if (!x.pre) return true;
|
|
11160
|
-
if (!y.pre) return false;
|
|
11161
|
-
return x.pre > y.pre;
|
|
11162
|
-
}
|
|
11163
|
-
async function runUpdate(args, io, deps = { latestVersion, install: installLatest, record: writeUpdateCheck, now: Date.now }) {
|
|
11164
|
-
refuseInSession(io.env, "update");
|
|
11165
|
-
if (args[0] === "--record") {
|
|
11166
|
-
try {
|
|
11167
|
-
deps.record(io.env, { checkedAt: deps.now(), latest: await deps.latestVersion() });
|
|
11168
|
-
} catch {
|
|
11169
|
-
}
|
|
11170
|
-
return ExitCode.Ok;
|
|
11171
|
-
}
|
|
11172
|
-
if (VERSION === "dev") throw new CliError("This is a source checkout, not an installed release; pull the repository instead.");
|
|
11173
|
-
const latest = await deps.latestVersion();
|
|
11174
|
-
if (!isNewer(latest, VERSION)) {
|
|
11175
|
-
io.stdout(`diverge ${VERSION} is up to date.`);
|
|
11176
|
-
return ExitCode.Ok;
|
|
11177
|
-
}
|
|
11178
|
-
io.stdout(`Updating diverge ${VERSION} \u2192 ${latest}\u2026`);
|
|
11179
|
-
const code2 = await deps.install(latest);
|
|
11180
|
-
if (code2 !== 0) throw new CliError(`npm install exited with ${code2}`);
|
|
11181
|
-
io.stdout(`diverge ${latest} installed.`);
|
|
11182
|
-
return ExitCode.Ok;
|
|
11183
|
-
}
|
|
11184
|
-
function installLatest(version, env = process.env, platform = process.platform) {
|
|
11185
|
-
let command = "npm";
|
|
11186
|
-
const args = ["install", "-g", `${PACKAGE_NAME}@${version}`];
|
|
11187
|
-
if (platform === "win32") {
|
|
11188
|
-
const npm = findOnPath("npm", env, platform);
|
|
11189
|
-
if (!npm) throw new CliError("npm was not found on PATH; install Node.js with npm and try again.");
|
|
11190
|
-
command = npm;
|
|
11191
|
-
if (/\.(cmd|bat)$/i.test(npm)) {
|
|
11192
|
-
command = process.execPath;
|
|
11193
|
-
args.unshift(path17.join(path17.dirname(npm), "node_modules", "npm", "bin", "npm-cli.js"));
|
|
11194
|
-
}
|
|
11195
|
-
}
|
|
11196
|
-
return new Promise((resolve, reject) => {
|
|
11197
|
-
const child = spawn4(command, args, { env, stdio: "inherit" });
|
|
11198
|
-
child.on("error", reject);
|
|
11199
|
-
child.on("exit", (code2) => resolve(code2 ?? 1));
|
|
11200
|
-
});
|
|
11201
|
-
}
|
|
11202
|
-
|
|
11203
11283
|
// src/cli.ts
|
|
11204
11284
|
var COMMANDS = [
|
|
11205
11285
|
{ name: "login", summary: "Sign in (authenticate) through the browser", usage: loginUsage, run: runLogin },
|
|
@@ -11207,7 +11287,8 @@ var COMMANDS = [
|
|
|
11207
11287
|
{ name: "status", summary: "Show who you are signed in as: the deployment, the account and what the login carries", usage: statusUsage, run: runStatus },
|
|
11208
11288
|
{ name: "org", summary: "Show or switch the organization the login acts in", usage: orgUsage, run: runOrg },
|
|
11209
11289
|
{ name: "token", summary: "Print a fresh access token", usage: tokenUsage, run: runToken },
|
|
11210
|
-
{ name: "
|
|
11290
|
+
{ name: "dsh", summary: "Start DeepSeek Harness on the model gateway, up to date, with your skills", usage: dshUsage, run: (args, io) => runDshCommand(args, io) },
|
|
11291
|
+
{ name: "agents", summary: "Point a coding agent at the model gateway (setup, status)", usage: agentsUsage, run: runAgents },
|
|
11211
11292
|
{ name: "tools", summary: "Discover and execute platform tools; raise and read approvals", usage: toolsUsage, run: runTools },
|
|
11212
11293
|
{ name: "plugins", summary: "Validate, publish, list and retire tenant plugins for your organization", usage: pluginsUsage, run: runPlugins },
|
|
11213
11294
|
{ name: "skills", summary: "Install the skills of every organization you can act in into Claude Code, Codex and dsh", usage: skillsUsage, run: runSkills },
|
|
@@ -11228,7 +11309,7 @@ function usage() {
|
|
|
11228
11309
|
"Environment: DIVERGENT_API_URL (deployment; production when unset), DIVERGE_HOME (state directory, default ~/.diverge), DIVERGENT_ORG_ID and DIVERGENT_PROJECT_ID (the organization and project a command acts in), DIVERGENT_TOKEN_FILE (a file holding the token this session runs on, in place of any login)."
|
|
11229
11310
|
].join("\n");
|
|
11230
11311
|
}
|
|
11231
|
-
async function
|
|
11312
|
+
async function run(argv, io) {
|
|
11232
11313
|
const [name, ...rest] = argv;
|
|
11233
11314
|
if (!name || name === "--help" || name === "-h" || name === "help") {
|
|
11234
11315
|
io.stdout(usage());
|
|
@@ -11248,9 +11329,9 @@ ${usage()}`);
|
|
|
11248
11329
|
}
|
|
11249
11330
|
const own = rest.includes("--") ? rest.slice(0, rest.indexOf("--")) : rest;
|
|
11250
11331
|
if (own.includes("--help") || own.includes("-h")) {
|
|
11251
|
-
const
|
|
11252
|
-
if (
|
|
11253
|
-
io.stdout(verbUsage(command.name, command.usage,
|
|
11332
|
+
const path20 = verbPath(command.usage, own);
|
|
11333
|
+
if (path20.length > 0 || own[0].startsWith("-")) {
|
|
11334
|
+
io.stdout(verbUsage(command.name, command.usage, path20));
|
|
11254
11335
|
return ExitCode.Ok;
|
|
11255
11336
|
}
|
|
11256
11337
|
}
|
|
@@ -11278,7 +11359,7 @@ ${verbUsage(command.name, command.usage, verbPath(command.usage, rest))}`);
|
|
|
11278
11359
|
}
|
|
11279
11360
|
|
|
11280
11361
|
// src/index.ts
|
|
11281
|
-
var code = await
|
|
11362
|
+
var code = await run(process.argv.slice(2), {
|
|
11282
11363
|
stdout: (line) => process.stdout.write(`${line}
|
|
11283
11364
|
`),
|
|
11284
11365
|
stderr: (line) => process.stderr.write(`${line}
|