@divergeai/cli 0.1.26 → 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 +60 -18
- package/dist/diverge.mjs +1089 -936
- 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
|
|
|
@@ -8366,6 +8366,20 @@ var PENDING_INVOCATION_STATUSES = ["pending", "approved", "executing"];
|
|
|
8366
8366
|
function isPending(result) {
|
|
8367
8367
|
return !!result && typeof result === "object" && result.status === "pending_approval";
|
|
8368
8368
|
}
|
|
8369
|
+
function devTaskContinuation(env) {
|
|
8370
|
+
const raw = env.DIVERGENT_DEV_TASK?.trim();
|
|
8371
|
+
if (!raw) return void 0;
|
|
8372
|
+
let task;
|
|
8373
|
+
try {
|
|
8374
|
+
task = JSON.parse(raw);
|
|
8375
|
+
} catch {
|
|
8376
|
+
throw new CliError('DIVERGENT_DEV_TASK is not JSON; the launcher writes {"organizationId","host","worktreePath"}');
|
|
8377
|
+
}
|
|
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 };
|
|
8382
|
+
}
|
|
8369
8383
|
var REQUEST_TIMEOUT_MS = 63e4;
|
|
8370
8384
|
var TOKEN_REFRESH_MARGIN_SECONDS = 60;
|
|
8371
8385
|
var ToolsClient = class {
|
|
@@ -8412,10 +8426,10 @@ var ToolsClient = class {
|
|
|
8412
8426
|
if (this.settings.mode === "service") headers["x-divergent-user-id"] = this.settings.delegatedUserId;
|
|
8413
8427
|
return headers;
|
|
8414
8428
|
}
|
|
8415
|
-
scoped(
|
|
8429
|
+
scoped(path20) {
|
|
8416
8430
|
const query = new URLSearchParams({ organizationId: this.settings.organizationId });
|
|
8417
8431
|
if (this.settings.projectId) query.set("projectId", this.settings.projectId);
|
|
8418
|
-
return `${this.settings.apiUrl}${
|
|
8432
|
+
return `${this.settings.apiUrl}${path20}?${query.toString()}`;
|
|
8419
8433
|
}
|
|
8420
8434
|
async send(method, url, body) {
|
|
8421
8435
|
return this.request(method, url, body, void 0);
|
|
@@ -8451,7 +8465,7 @@ var ToolsClient = class {
|
|
|
8451
8465
|
if (typeof parsed.error === "string" && parsed.error) message = parsed.error;
|
|
8452
8466
|
} catch {
|
|
8453
8467
|
}
|
|
8454
|
-
if (response.status === 403 &&
|
|
8468
|
+
if (response.status === 403 && /^insufficient scope$/i.test(message.trim())) {
|
|
8455
8469
|
throw new CliError(`${response.status}: ${message}
|
|
8456
8470
|
The signed-in token is missing a required scope. Re-run: ${LOGIN_HINT}`);
|
|
8457
8471
|
}
|
|
@@ -8487,7 +8501,12 @@ var ToolsClient = class {
|
|
|
8487
8501
|
* unwrapped here, once — a pending approval passes through as it is.
|
|
8488
8502
|
*/
|
|
8489
8503
|
async callTool(name, args) {
|
|
8490
|
-
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
|
+
});
|
|
8491
8510
|
const outcome = body.result;
|
|
8492
8511
|
if (outcome?.status !== "succeeded") return body;
|
|
8493
8512
|
return { ...body, result: outcome.result };
|
|
@@ -8499,9 +8518,11 @@ var ToolsClient = class {
|
|
|
8499
8518
|
* carries every call's outcome in `result.calls`.
|
|
8500
8519
|
*/
|
|
8501
8520
|
async callSet(calls, summary) {
|
|
8521
|
+
const continuation = devTaskContinuation(this.env);
|
|
8502
8522
|
const body = await this.send("POST", this.scoped("/api/plugins/functions/tools/sets"), {
|
|
8503
8523
|
...summary ? { summary } : {},
|
|
8504
|
-
calls: calls.map((call2) => ({ toolName: call2.tool, arguments: call2.args ?? {} }))
|
|
8524
|
+
calls: calls.map((call2) => ({ toolName: call2.tool, arguments: call2.args ?? {} })),
|
|
8525
|
+
...continuation ? { continuation } : {}
|
|
8505
8526
|
});
|
|
8506
8527
|
const outcome = body.result;
|
|
8507
8528
|
if (outcome?.status !== "succeeded") return body;
|
|
@@ -8512,36 +8533,16 @@ var ToolsClient = class {
|
|
|
8512
8533
|
return this.send("GET", this.scoped(`/api/plugins/functions/tools/invocations/${encodeURIComponent(invocationId)}`));
|
|
8513
8534
|
}
|
|
8514
8535
|
// ── Tenant plugins (RFC: Tenant Plugins) ──────────────────────────────
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
publishPlugin(manifest, options) {
|
|
8521
|
-
return this.request(
|
|
8522
|
-
"POST",
|
|
8523
|
-
this.descriptorsUrl(),
|
|
8524
|
-
{ targetOrganizationId: this.settings.organizationId, manifest, dryRun: options.dryRun },
|
|
8525
|
-
(status2, text2) => {
|
|
8526
|
-
if (status2 !== 400) return void 0;
|
|
8527
|
-
try {
|
|
8528
|
-
const parsed = JSON.parse(text2);
|
|
8529
|
-
if (Array.isArray(parsed.details)) return { ok: false, details: parsed.details.map(String) };
|
|
8530
|
-
return { ok: false, details: [parsed.error ?? text2] };
|
|
8531
|
-
} catch {
|
|
8532
|
-
return { ok: false, details: [text2] };
|
|
8533
|
-
}
|
|
8534
|
-
}
|
|
8535
|
-
).then((outcome) => "ok" in outcome ? outcome : { ok: true, result: outcome });
|
|
8536
|
-
}
|
|
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
|
+
*/
|
|
8537
8541
|
async listPlugins() {
|
|
8538
|
-
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()}`);
|
|
8539
8544
|
return body.plugins ?? [];
|
|
8540
8545
|
}
|
|
8541
|
-
async retirePlugin(pluginId) {
|
|
8542
|
-
const body = await this.send("DELETE", this.descriptorsUrl(pluginId), { targetOrganizationId: this.settings.organizationId });
|
|
8543
|
-
return body.plugin;
|
|
8544
|
-
}
|
|
8545
8546
|
};
|
|
8546
8547
|
|
|
8547
8548
|
// src/agents/dsh.ts
|
|
@@ -8559,8 +8560,7 @@ function dshPatchYaml(options) {
|
|
|
8559
8560
|
const api = options.surface === "openai" ? "openai-completions" : "anthropic-messages";
|
|
8560
8561
|
const endpoint = options.surface === "openai" ? `${options.baseUrl}/v1` : options.baseUrl;
|
|
8561
8562
|
return [
|
|
8562
|
-
"# Divergent gateway overlay for dsh; written by `diverge
|
|
8563
|
-
`# Boot with: ${tokenEnv}="$(diverge token)" dsh --profile <profile> --patch <this file>`,
|
|
8563
|
+
"# Divergent gateway overlay for dsh; written by `diverge dsh` on every launch.",
|
|
8564
8564
|
"- id: llm-pi-ai",
|
|
8565
8565
|
" config:",
|
|
8566
8566
|
" providers:",
|
|
@@ -8581,6 +8581,7 @@ function dshPatchYaml(options) {
|
|
|
8581
8581
|
" config:",
|
|
8582
8582
|
` provider: ${route}`,
|
|
8583
8583
|
` model: ${JSON.stringify(options.defaultModel)}`,
|
|
8584
|
+
...options.reasoningEffort ? [` reasoningEffort: ${JSON.stringify(options.reasoningEffort)}`] : [],
|
|
8584
8585
|
// dsh's native DeepSeek adapter needs a vendor key nobody here holds and,
|
|
8585
8586
|
// where one exists, would send the organization's code straight to the
|
|
8586
8587
|
// vendor past the gateway, its ledger, budget and policy. Its session
|
|
@@ -8591,6 +8592,11 @@ function dshPatchYaml(options) {
|
|
|
8591
8592
|
" disabled: true",
|
|
8592
8593
|
"- id: session-telemetry-otel",
|
|
8593
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",
|
|
8594
8600
|
...dshSearchRows(),
|
|
8595
8601
|
...dshShellRows(),
|
|
8596
8602
|
...dshBrowserRows(),
|
|
@@ -8606,16 +8612,6 @@ function dshLaunchHeaderRows(launch) {
|
|
|
8606
8612
|
` ${DSH_LAUNCH_ID_HEADER}: ${JSON.stringify(launch.id)}`
|
|
8607
8613
|
];
|
|
8608
8614
|
}
|
|
8609
|
-
function dshVersionOf(binary) {
|
|
8610
|
-
const dir = dshPackageDir(binary);
|
|
8611
|
-
if (!dir) return null;
|
|
8612
|
-
try {
|
|
8613
|
-
const version = JSON.parse(fs5.readFileSync(path4.join(dir, "package.json"), "utf8")).version;
|
|
8614
|
-
return typeof version === "string" && version.length > 0 ? version : null;
|
|
8615
|
-
} catch {
|
|
8616
|
-
return null;
|
|
8617
|
-
}
|
|
8618
|
-
}
|
|
8619
8615
|
function dshShellRows() {
|
|
8620
8616
|
return ["bash-sandbox", "pwsh-sandbox"].flatMap((id) => [
|
|
8621
8617
|
`- id: ${id}`,
|
|
@@ -8649,11 +8645,6 @@ function dshSearchRows() {
|
|
|
8649
8645
|
" searchTimeoutMs: 60000"
|
|
8650
8646
|
];
|
|
8651
8647
|
}
|
|
8652
|
-
function servedIds(overlay) {
|
|
8653
|
-
const rows = (0, import_yaml.parse)(overlay) ?? [];
|
|
8654
|
-
const route = rows.find((row) => row.id === "llm-pi-ai");
|
|
8655
|
-
return Object.values(route?.config?.providers ?? {}).flatMap((provider) => (provider.models ?? []).map((entry) => entry.id));
|
|
8656
|
-
}
|
|
8657
8648
|
function mergeDshCredentials(existing, token, ref = "DIVERGENT_TOKEN") {
|
|
8658
8649
|
const parsed = existing?.trim() ? (0, import_yaml.parse)(existing) : null;
|
|
8659
8650
|
let document = {};
|
|
@@ -8668,95 +8659,41 @@ function mergeDshCredentials(existing, token, ref = "DIVERGENT_TOKEN") {
|
|
|
8668
8659
|
if (document.records && Object.keys(document.records).length > 0) ordered.records = document.records;
|
|
8669
8660
|
return (0, import_yaml.stringify)(ordered, { lineWidth: 0 });
|
|
8670
8661
|
}
|
|
8671
|
-
function
|
|
8672
|
-
if (!
|
|
8673
|
-
const
|
|
8674
|
-
if (!
|
|
8675
|
-
const
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
function
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
if (
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
} catch {
|
|
8695
|
-
return null;
|
|
8696
|
-
}
|
|
8697
|
-
for (; ; ) {
|
|
8698
|
-
try {
|
|
8699
|
-
if (JSON.parse(fs5.readFileSync(path4.join(dir, "package.json"), "utf8")).name === "@deepseek-ai/dsh") return dir;
|
|
8700
|
-
} catch {
|
|
8701
|
-
}
|
|
8702
|
-
const parent = path4.dirname(dir);
|
|
8703
|
-
if (parent === dir) return null;
|
|
8704
|
-
dir = parent;
|
|
8705
|
-
}
|
|
8706
|
-
}
|
|
8707
|
-
function shippedPresetDir(packageDir, id = "standard") {
|
|
8708
|
-
try {
|
|
8709
|
-
const require2 = createRequire(path4.join(packageDir, "package.json"));
|
|
8710
|
-
const dir = path4.join(path4.dirname(require2.resolve("@deepseek-ai/dsh-agent-presets/package.json")), "presets", id);
|
|
8711
|
-
return fs5.existsSync(path4.join(dir, "agent.cordis.yml")) ? dir : null;
|
|
8712
|
-
} catch {
|
|
8713
|
-
return null;
|
|
8714
|
-
}
|
|
8715
|
-
}
|
|
8716
|
-
function divergentPreset(shippedComposition) {
|
|
8717
|
-
const lines = shippedComposition.split("\n");
|
|
8718
|
-
const start = lines.findIndex((line) => /^- id: ['"]?tool-web['"]?\s*$/.test(line));
|
|
8719
|
-
if (start === -1) throw new Error("the shipped preset composes no tool-web row");
|
|
8720
|
-
let end = lines.findIndex((line, index) => index > start && /^- /.test(line));
|
|
8721
|
-
if (end === -1) end = lines.length;
|
|
8722
|
-
const row = lines.slice(start, end);
|
|
8723
|
-
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:`));
|
|
8724
8685
|
if (search !== -1) {
|
|
8725
|
-
|
|
8686
|
+
block[search] = `${body} search: false`;
|
|
8726
8687
|
} else {
|
|
8727
|
-
const config =
|
|
8728
|
-
if (config !== -1)
|
|
8729
|
-
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`);
|
|
8730
8691
|
}
|
|
8731
|
-
return [
|
|
8732
|
-
`# The Divergent agent preset: dsh's shipped "standard" preset with the vendor web`,
|
|
8733
|
-
"# search left out of the model's tools; platform tools, search included, are reached",
|
|
8734
|
-
"# from the shell through `diverge tools`. Derived from the installed dsh by `diverge agents",
|
|
8735
|
-
"# setup dsh` and `diverge agents run dsh` on every run; edits do not survive.",
|
|
8736
|
-
...lines.slice(0, start),
|
|
8737
|
-
...row,
|
|
8738
|
-
...lines.slice(end)
|
|
8739
|
-
].join("\n");
|
|
8692
|
+
return [...row.slice(0, start), ...block, ...row.slice(end)].join("\n").trimEnd() + "\n";
|
|
8740
8693
|
}
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
"order: 0",
|
|
8745
|
-
""
|
|
8746
|
-
].join("\n");
|
|
8747
|
-
function writeDivergentPreset(home, shippedDir) {
|
|
8748
|
-
const dir = path4.join(home, DSH_USER_PRESETS_DIR, DIVERGENT_PRESET_ID);
|
|
8749
|
-
writeTextAtomic(path4.join(dir, "agent.cordis.yml"), divergentPreset(fs5.readFileSync(path4.join(shippedDir, "agent.cordis.yml"), "utf8")), 420);
|
|
8750
|
-
writeTextAtomic(path4.join(dir, "preset.yml"), DIVERGENT_PRESET_METADATA, 420);
|
|
8751
|
-
return dir;
|
|
8752
|
-
}
|
|
8753
|
-
function withDivergentPresetDefault(settingsText) {
|
|
8754
|
-
const parsed = settingsText?.trim() ? (0, import_yaml.parse)(settingsText) : null;
|
|
8755
|
-
const settings = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { ...parsed } : {};
|
|
8756
|
-
const presets = settings["agent-presets"];
|
|
8757
|
-
if (presets && typeof presets === "object" && presets.default) return null;
|
|
8758
|
-
settings["agent-presets"] = { ...presets ?? {}, default: DIVERGENT_PRESET_ID };
|
|
8759
|
-
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("");
|
|
8760
8697
|
}
|
|
8761
8698
|
|
|
8762
8699
|
// src/skills/install.ts
|
|
@@ -8802,6 +8739,19 @@ function readSkillsManifest(root) {
|
|
|
8802
8739
|
function few(names, limit = 6) {
|
|
8803
8740
|
return names.length <= limit ? names.join(", ") : `${names.slice(0, limit).join(", ")} and ${names.length - limit} more`;
|
|
8804
8741
|
}
|
|
8742
|
+
function isSkillFolder(folder) {
|
|
8743
|
+
return /^[a-z0-9][a-z0-9-]{0,63}$/.test(folder);
|
|
8744
|
+
}
|
|
8745
|
+
function retainedFolders(manifest, set2) {
|
|
8746
|
+
const retained = /* @__PURE__ */ new Map();
|
|
8747
|
+
for (const organization of set2.organizations) {
|
|
8748
|
+
const skipped = organization.skills.filter((skill) => !isSkillFolder(skill.folder));
|
|
8749
|
+
const held = Object.keys(manifest?.organizations[organization.id]?.skills ?? {});
|
|
8750
|
+
const kept = held.filter((folder) => skipped.some((skill) => folder.endsWith(`-${skill.name}`)));
|
|
8751
|
+
if (kept.length) retained.set(organization.id, kept);
|
|
8752
|
+
}
|
|
8753
|
+
return retained;
|
|
8754
|
+
}
|
|
8805
8755
|
function heldFolders(manifest) {
|
|
8806
8756
|
const held = /* @__PURE__ */ new Map();
|
|
8807
8757
|
for (const [folder, version] of Object.entries(manifest?.flat ?? {})) held.set(folder, version);
|
|
@@ -8811,9 +8761,10 @@ function heldFolders(manifest) {
|
|
|
8811
8761
|
}
|
|
8812
8762
|
function retirable(manifest, set2) {
|
|
8813
8763
|
const refreshed = new Set(set2.organizations.map((organization) => organization.id));
|
|
8764
|
+
const retained = new Set([...retainedFolders(manifest, set2).values()].flat());
|
|
8814
8765
|
const folders = [...Object.keys(manifest?.flat ?? {}), ...Object.keys(manifest?.cli ?? {})];
|
|
8815
8766
|
for (const [id, section] of Object.entries(manifest?.organizations ?? {})) {
|
|
8816
|
-
if (set2.complete || refreshed.has(id)) folders.push(...Object.keys(section.skills));
|
|
8767
|
+
if (set2.complete || refreshed.has(id)) folders.push(...Object.keys(section.skills).filter((folder) => !retained.has(folder)));
|
|
8817
8768
|
}
|
|
8818
8769
|
return folders;
|
|
8819
8770
|
}
|
|
@@ -8823,21 +8774,28 @@ function installSkills(root, set2, source, now = () => /* @__PURE__ */ new Date(
|
|
|
8823
8774
|
const written = [];
|
|
8824
8775
|
const added = [];
|
|
8825
8776
|
const updated = [];
|
|
8777
|
+
const skipped = [];
|
|
8826
8778
|
const counts = [];
|
|
8827
8779
|
const write = (skill) => {
|
|
8828
|
-
if (
|
|
8780
|
+
if (!isSkillFolder(skill.folder)) {
|
|
8781
|
+
skipped.push(skill.folder);
|
|
8782
|
+
return;
|
|
8783
|
+
}
|
|
8829
8784
|
writeTextAtomic(path5.join(root, skill.folder, "SKILL.md"), skill.content, 420);
|
|
8830
8785
|
written.push(skill.folder);
|
|
8831
|
-
const
|
|
8832
|
-
if (
|
|
8833
|
-
else if (
|
|
8786
|
+
const before2 = held.get(skill.folder);
|
|
8787
|
+
if (before2 === void 0) added.push(skill.folder);
|
|
8788
|
+
else if (before2 !== skill.updatedAt) updated.push(skill.folder);
|
|
8834
8789
|
};
|
|
8835
8790
|
for (const organization of set2.organizations) {
|
|
8791
|
+
const before2 = written.length;
|
|
8836
8792
|
organization.skills.forEach(write);
|
|
8837
|
-
counts.push({ label: organization.name, count:
|
|
8793
|
+
counts.push({ label: organization.name, count: written.length - before2 });
|
|
8838
8794
|
}
|
|
8795
|
+
const before = written.length;
|
|
8839
8796
|
set2.cli.forEach(write);
|
|
8840
|
-
if (set2.cli.length) counts.push({ label: "CLI", count:
|
|
8797
|
+
if (set2.cli.length) counts.push({ label: "CLI", count: written.length - before });
|
|
8798
|
+
const retained = retainedFolders(previous, set2);
|
|
8841
8799
|
const removed = [];
|
|
8842
8800
|
const kept = new Set(written);
|
|
8843
8801
|
for (const folder of retirable(previous, set2)) {
|
|
@@ -8855,7 +8813,10 @@ function installSkills(root, set2, source, now = () => /* @__PURE__ */ new Date(
|
|
|
8855
8813
|
organizations[organization.id] = {
|
|
8856
8814
|
name: organization.name,
|
|
8857
8815
|
installedAt: stamp,
|
|
8858
|
-
skills: Object.fromEntries(
|
|
8816
|
+
skills: Object.fromEntries([
|
|
8817
|
+
...(retained.get(organization.id) ?? []).map((folder) => [folder, previous.organizations[organization.id].skills[folder]]),
|
|
8818
|
+
...organization.skills.filter((skill) => isSkillFolder(skill.folder)).map((skill) => [skill.folder, skill.updatedAt])
|
|
8819
|
+
])
|
|
8859
8820
|
};
|
|
8860
8821
|
}
|
|
8861
8822
|
const manifest = {
|
|
@@ -8865,7 +8826,7 @@ function installSkills(root, set2, source, now = () => /* @__PURE__ */ new Date(
|
|
|
8865
8826
|
organizations
|
|
8866
8827
|
};
|
|
8867
8828
|
writeJsonAtomic(path5.join(root, SKILLS_MANIFEST), manifest, 420);
|
|
8868
|
-
return { root, written, added, updated, removed, counts };
|
|
8829
|
+
return { root, written, added, updated, removed, skipped, counts };
|
|
8869
8830
|
}
|
|
8870
8831
|
function skillsStatus(root, set2) {
|
|
8871
8832
|
const manifest = readSkillsManifest(root);
|
|
@@ -8873,7 +8834,9 @@ function skillsStatus(root, set2) {
|
|
|
8873
8834
|
const current = [];
|
|
8874
8835
|
const behind = [];
|
|
8875
8836
|
const missing = [];
|
|
8876
|
-
const
|
|
8837
|
+
const all = [...set2.organizations.flatMap((organization) => organization.skills), ...set2.cli];
|
|
8838
|
+
const served = all.filter((skill) => isSkillFolder(skill.folder));
|
|
8839
|
+
const skipped = all.filter((skill) => !isSkillFolder(skill.folder)).map((skill) => skill.folder);
|
|
8877
8840
|
for (const skill of served) {
|
|
8878
8841
|
const before = held.get(skill.folder);
|
|
8879
8842
|
if (before === void 0 || !fs6.existsSync(path5.join(root, skill.folder, "SKILL.md"))) missing.push(skill.folder);
|
|
@@ -8882,7 +8845,7 @@ function skillsStatus(root, set2) {
|
|
|
8882
8845
|
}
|
|
8883
8846
|
const servedFolders = new Set(served.map((skill) => skill.folder));
|
|
8884
8847
|
const retired = [...new Set(retirable(manifest, set2))].filter((folder) => !servedFolders.has(folder));
|
|
8885
|
-
return { root, installedAt: manifest?.installedAt || null, current, behind, missing, retired };
|
|
8848
|
+
return { root, installedAt: manifest?.installedAt || null, current, behind, missing, retired, skipped };
|
|
8886
8849
|
}
|
|
8887
8850
|
|
|
8888
8851
|
// src/skills/installable.ts
|
|
@@ -8940,8 +8903,11 @@ var skillsUsage = `diverge skills <list|install|refresh|status> [--organization-
|
|
|
8940
8903
|
|
|
8941
8904
|
A skill is a procedure over the tools it names; you get the ones whose every
|
|
8942
8905
|
tool you may run, in every organization the login can act in. Each lands as
|
|
8943
|
-
<organization prefix>-<name> \u2014 the organization's
|
|
8944
|
-
its name
|
|
8906
|
+
<organization prefix>-<name> \u2014 the organization's handle, or a slug of
|
|
8907
|
+
its name; the platform shortens a longer one to the 64 characters a harness
|
|
8908
|
+
accepts \u2014 and its SKILL.md opens with the organization its tools run in.
|
|
8909
|
+
A folder that is not a skill name is skipped with a warning naming it, and
|
|
8910
|
+
the folder that skill was installed under before stays.
|
|
8945
8911
|
--organization-id refreshes one organization and leaves the others as they
|
|
8946
8912
|
are; without it the whole login is refreshed, and an organization the login
|
|
8947
8913
|
no longer lists is retired. Without --harness, the harnesses present on this
|
|
@@ -8980,6 +8946,11 @@ function harnessesOf(flag, env) {
|
|
|
8980
8946
|
function installSet(harness, skills) {
|
|
8981
8947
|
return { cli: bundledSkills(harness), organizations: skills.organizations, complete: skills.complete };
|
|
8982
8948
|
}
|
|
8949
|
+
var SKIPPED = "not installed, the folder is not a skill name (at most 64 lowercase letters, digits and hyphens)";
|
|
8950
|
+
function skippedNote(results) {
|
|
8951
|
+
const skipped = [...new Set(results.flatMap((result) => result.skipped))];
|
|
8952
|
+
return skipped.length ? [`${skipped.length} ${SKIPPED}: ${few(skipped)}`] : [];
|
|
8953
|
+
}
|
|
8983
8954
|
function installReport(harness, result) {
|
|
8984
8955
|
const holdings = result.counts.map((entry) => `${entry.label} ${entry.count}`).join(", ");
|
|
8985
8956
|
const changes = [
|
|
@@ -9007,11 +8978,13 @@ async function refreshSkills(io, apiUrl, options = {}) {
|
|
|
9007
8978
|
io.stderr(`warning: skills not refreshed (${error instanceof Error ? error.message : String(error)}); run: diverge skills install`);
|
|
9008
8979
|
return;
|
|
9009
8980
|
}
|
|
8981
|
+
const results = [];
|
|
9010
8982
|
for (const harness of harnesses) {
|
|
9011
8983
|
const result = installSkills(skillsRoot(harness, io.env), installSet(harness, skills), apiUrl);
|
|
9012
8984
|
if (!options.quiet || result.added.length || result.updated.length || result.removed.length) io.stdout(installReport(harness, result));
|
|
8985
|
+
results.push(result);
|
|
9013
8986
|
}
|
|
9014
|
-
for (const note of skills.notes) io.stderr(`warning: ${note}`);
|
|
8987
|
+
for (const note of [...skills.notes, ...skippedNote(results)]) io.stderr(`warning: ${note}`);
|
|
9015
8988
|
}
|
|
9016
8989
|
async function runSkills(args, io) {
|
|
9017
8990
|
const [verb, ...rest] = args;
|
|
@@ -9033,8 +9006,8 @@ async function runSkills(args, io) {
|
|
|
9033
9006
|
const skills = await loginSkillSet(apiUrl, io.env, organizationId);
|
|
9034
9007
|
const harnesses = values.into ? [harnessesOf(values.harness ?? HARNESSES[0], io.env)[0]] : harnessesOf(values.harness, io.env);
|
|
9035
9008
|
if (harnesses.length === 0) throw new CliError("No coding-agent harness on this machine yet (Claude Code, Codex or dsh); name one with --harness to write its folder anyway.");
|
|
9036
|
-
|
|
9037
|
-
for (const note of skills.notes) io.stderr(`warning: ${note}`);
|
|
9009
|
+
const results = harnesses.map((harness) => installForHarness(io, harness, skills, apiUrl, values.into));
|
|
9010
|
+
for (const note of [...skills.notes, ...skippedNote(results)]) io.stderr(`warning: ${note}`);
|
|
9038
9011
|
return ExitCode.Ok;
|
|
9039
9012
|
}
|
|
9040
9013
|
case "refresh": {
|
|
@@ -9050,6 +9023,7 @@ async function runSkills(args, io) {
|
|
|
9050
9023
|
if (status2.behind.length) parts.push(`${status2.behind.length} behind (${few(status2.behind)})`);
|
|
9051
9024
|
if (status2.missing.length) parts.push(`${status2.missing.length} missing${status2.current.length || status2.behind.length ? ` (${few(status2.missing)})` : ""}`);
|
|
9052
9025
|
if (status2.retired.length) parts.push(`${status2.retired.length} retired (${few(status2.retired)})`);
|
|
9026
|
+
if (status2.skipped.length) parts.push(`${status2.skipped.length} ${SKIPPED} (${few(status2.skipped)})`);
|
|
9053
9027
|
behind ||= status2.behind.length + status2.missing.length + status2.retired.length > 0;
|
|
9054
9028
|
io.stdout(`${harness}: ${parts.join(", ")} in ${status2.root}${status2.installedAt ? ` (installed ${status2.installedAt})` : " (never installed)"}`);
|
|
9055
9029
|
}
|
|
@@ -9076,7 +9050,9 @@ Signing in also installs the login's skills \u2014 every organization it can act
|
|
|
9076
9050
|
in, side by side \u2014 into the coding-agent harnesses on this machine.
|
|
9077
9051
|
--as <email> names the account: the platform signs that account in instead of
|
|
9078
9052
|
reusing whatever session the browser holds, and issues nothing to any other.
|
|
9079
|
-
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.
|
|
9080
9056
|
Without --scopes, the login asks for the scopes the platform registers for
|
|
9081
9057
|
this CLI \u2014 everything it and forge use \u2014 and holds what your roles grant; a
|
|
9082
9058
|
scope the platform adds later reaches this login on its next refresh.
|
|
@@ -9094,6 +9070,16 @@ function resolveLoginHint(flag, previous) {
|
|
|
9094
9070
|
const remembered = previous.account?.trim().toLowerCase();
|
|
9095
9071
|
return remembered || void 0;
|
|
9096
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
|
+
}
|
|
9097
9083
|
async function runLogin(args, io) {
|
|
9098
9084
|
refuseInSession(io.env, "login");
|
|
9099
9085
|
const { values } = parseArgs2({
|
|
@@ -9129,6 +9115,7 @@ async function runLogin(args, io) {
|
|
|
9129
9115
|
} catch (error) {
|
|
9130
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`);
|
|
9131
9117
|
}
|
|
9118
|
+
if (loginHint) refuseAnotherAccount(info.email, loginHint, { named: Boolean(values.as), apiUrl });
|
|
9132
9119
|
io.stdout(`Signed in as: ${info.email}`);
|
|
9133
9120
|
const organizationId = await chooseDefaultOrganization(io, values["organization-id"], previous?.organization, info);
|
|
9134
9121
|
const issued = await issueToken(source, { base_url: source.baseUrl, refresh_token: signIn.refreshToken, account: info.email }, organizationId);
|
|
@@ -9320,211 +9307,415 @@ function mergeOpencodeConfig(existing, options) {
|
|
|
9320
9307
|
return config;
|
|
9321
9308
|
}
|
|
9322
9309
|
|
|
9323
|
-
// src/agents/
|
|
9324
|
-
import
|
|
9325
|
-
import fs7 from "node:fs";
|
|
9310
|
+
// src/agents/rotate.ts
|
|
9311
|
+
import os7 from "node:os";
|
|
9326
9312
|
import path9 from "node:path";
|
|
9327
|
-
function
|
|
9328
|
-
|
|
9329
|
-
let merged;
|
|
9330
|
-
try {
|
|
9331
|
-
merged = mergeDshCredentials(existing, accessToken);
|
|
9332
|
-
} catch (error) {
|
|
9333
|
-
throw new CliError(`${credentialsPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9334
|
-
}
|
|
9335
|
-
writeTextAtomic(credentialsPath, merged, 384);
|
|
9336
|
-
}
|
|
9337
|
-
function writeDivergeShim(binDir, cli, sessionTokenPath, platform = process.platform) {
|
|
9338
|
-
if (platform === "win32") {
|
|
9339
|
-
const file2 = path9.join(binDir, "diverge.cmd");
|
|
9340
|
-
const run3 = [cli.command, ...cli.args].map((part) => `"${part}"`).join(" ");
|
|
9341
|
-
writeTextAtomic(file2, `@setlocal\r
|
|
9342
|
-
@set "${SESSION_TOKEN_VARIABLE}=${sessionTokenPath}"\r
|
|
9343
|
-
@${run3} %*\r
|
|
9344
|
-
`, 493);
|
|
9345
|
-
return file2;
|
|
9346
|
-
}
|
|
9347
|
-
const quote = (part) => `'${part.replace(/'/g, `'\\''`)}'`;
|
|
9348
|
-
const file = path9.join(binDir, "diverge");
|
|
9349
|
-
writeTextAtomic(file, `#!/bin/sh
|
|
9350
|
-
${SESSION_TOKEN_VARIABLE}=${quote(sessionTokenPath)}
|
|
9351
|
-
export ${SESSION_TOKEN_VARIABLE}
|
|
9352
|
-
exec ${[cli.command, ...cli.args].map(quote).join(" ")} "$@"
|
|
9353
|
-
`, 493);
|
|
9354
|
-
return file;
|
|
9355
|
-
}
|
|
9356
|
-
function dshEnv(env, binDir) {
|
|
9357
|
-
const out = {};
|
|
9358
|
-
for (const [key, value] of Object.entries(env)) {
|
|
9359
|
-
if (value !== void 0 && key !== "DIVERGENT_TOKEN" && key !== "DIVERGENT_ORG_ID") out[key] = value;
|
|
9360
|
-
}
|
|
9361
|
-
out.PATH = env.PATH ? `${binDir}${path9.delimiter}${env.PATH}` : binDir;
|
|
9362
|
-
return out;
|
|
9363
|
-
}
|
|
9364
|
-
function dshArgs(overlayPath, args) {
|
|
9365
|
-
const rest = args.includes("--profile") ? args : args.length === 0 ? ["--profile", "web"] : ["--profile", "headless", ...args];
|
|
9366
|
-
return ["--patch", overlayPath, ...rest];
|
|
9313
|
+
function defaultTokenFilePath() {
|
|
9314
|
+
return path9.join(os7.homedir(), ".divergent", "token");
|
|
9367
9315
|
}
|
|
9368
|
-
|
|
9369
|
-
|
|
9370
|
-
const
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
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);
|
|
9376
9325
|
}
|
|
9377
|
-
const script = typeof bin === "string" ? bin : typeof bin === "object" && bin !== null ? bin.dsh : void 0;
|
|
9378
|
-
if (typeof script !== "string") throw new CliError(`${packageDir}/package.json declares no dsh entry; reinstall: npm install -g @deepseek-ai/dsh`);
|
|
9379
|
-
return { command: execPath, args: [path9.join(packageDir, script), ...args] };
|
|
9380
9326
|
}
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
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 {
|
|
9392
9339
|
}
|
|
9393
9340
|
}
|
|
9341
|
+
return { command: execPath, args: [fs7.realpathSync(script)] };
|
|
9394
9342
|
}
|
|
9395
|
-
function
|
|
9396
|
-
|
|
9397
|
-
writeDshToken(options.credentialsPath, token);
|
|
9398
|
-
writeTextAtomic(options.sessionTokenPath, `${token}
|
|
9399
|
-
`, 384);
|
|
9400
|
-
};
|
|
9401
|
-
writeToken(options.initialToken);
|
|
9402
|
-
writeDivergeShim(options.binDir, options.cli, options.sessionTokenPath);
|
|
9403
|
-
const env = dshEnv(options.env, options.binDir);
|
|
9404
|
-
const launch = dshLaunch(options.binary, dshArgs(options.overlayPath, options.args));
|
|
9405
|
-
const child = spawn2(launch.command, launch.args, { env, stdio: "inherit" });
|
|
9406
|
-
let exited = false;
|
|
9407
|
-
let wake = null;
|
|
9408
|
-
const finish = () => {
|
|
9409
|
-
exited = true;
|
|
9410
|
-
wake?.();
|
|
9411
|
-
};
|
|
9412
|
-
const done = new Promise((resolve, reject) => {
|
|
9413
|
-
child.on("error", (error) => {
|
|
9414
|
-
finish();
|
|
9415
|
-
reject(error);
|
|
9416
|
-
});
|
|
9417
|
-
child.on("exit", (code2, signal) => {
|
|
9418
|
-
finish();
|
|
9419
|
-
resolve(code2 ?? (signal ? 128 + 15 : 1));
|
|
9420
|
-
});
|
|
9421
|
-
});
|
|
9422
|
-
const ignore = () => {
|
|
9423
|
-
};
|
|
9424
|
-
process.on("SIGINT", ignore);
|
|
9425
|
-
const terminate = () => child.kill("SIGTERM");
|
|
9426
|
-
process.on("SIGTERM", terminate);
|
|
9427
|
-
const loop = keepTokenFresh({
|
|
9428
|
-
freshToken: options.freshToken,
|
|
9429
|
-
write: writeToken,
|
|
9430
|
-
isRunning: () => !exited,
|
|
9431
|
-
sleep: (ms) => new Promise((resolve) => {
|
|
9432
|
-
const timer = setTimeout(() => {
|
|
9433
|
-
wake = null;
|
|
9434
|
-
resolve();
|
|
9435
|
-
}, ms);
|
|
9436
|
-
wake = () => {
|
|
9437
|
-
clearTimeout(timer);
|
|
9438
|
-
wake = null;
|
|
9439
|
-
resolve();
|
|
9440
|
-
};
|
|
9441
|
-
})
|
|
9442
|
-
});
|
|
9443
|
-
return done.finally(async () => {
|
|
9444
|
-
process.off("SIGINT", ignore);
|
|
9445
|
-
process.off("SIGTERM", terminate);
|
|
9446
|
-
await loop;
|
|
9447
|
-
});
|
|
9343
|
+
function shellCommand(self, ...trailing) {
|
|
9344
|
+
return [self.command, ...self.args, ...trailing].map((part) => /[\s"]/.test(part) ? `"${part.replace(/"/g, '\\"')}"` : part).join(" ");
|
|
9448
9345
|
}
|
|
9449
9346
|
|
|
9450
|
-
// src/agents
|
|
9451
|
-
|
|
9452
|
-
import path10 from "node:path";
|
|
9347
|
+
// src/commands/agents.ts
|
|
9348
|
+
var agentsUsage = `diverge agents <setup|rotate|status> ...
|
|
9453
9349
|
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
} else if (ch !== "~" && /^[A-Za-z0-9._-]$/.test(ch)) {
|
|
9469
|
-
readable += ch;
|
|
9470
|
-
separatorRun = false;
|
|
9471
|
-
} else {
|
|
9472
|
-
readable += `~${code2.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
9473
|
-
separatorRun = false;
|
|
9474
|
-
}
|
|
9475
|
-
}
|
|
9476
|
-
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(", ")}`);
|
|
9477
9364
|
}
|
|
9478
|
-
function
|
|
9479
|
-
|
|
9365
|
+
function readJson(file) {
|
|
9366
|
+
if (!fs8.existsSync(file)) return null;
|
|
9367
|
+
return JSON.parse(fs8.readFileSync(file, "utf8"));
|
|
9480
9368
|
}
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
function
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
}
|
|
9492
|
-
if (magic !== ZSTD_MAGIC) return null;
|
|
9493
|
-
if (buffer.length - start < 5) return null;
|
|
9494
|
-
const descriptor = buffer[start + 4];
|
|
9495
|
-
const contentSizeFlag = descriptor >> 6;
|
|
9496
|
-
const singleSegment = descriptor >> 5 & 1;
|
|
9497
|
-
const checksum = descriptor >> 2 & 1;
|
|
9498
|
-
const dictionaryFlag = descriptor & 3;
|
|
9499
|
-
let pos = start + 5;
|
|
9500
|
-
if (!singleSegment) pos += 1;
|
|
9501
|
-
pos += [0, 1, 2, 4][dictionaryFlag];
|
|
9502
|
-
pos += contentSizeFlag === 0 ? singleSegment : [0, 2, 4, 8][contentSizeFlag];
|
|
9503
|
-
for (; ; ) {
|
|
9504
|
-
if (pos + 3 > buffer.length) return null;
|
|
9505
|
-
const header = buffer[pos] | buffer[pos + 1] << 8 | buffer[pos + 2] << 16;
|
|
9506
|
-
const last = header & 1;
|
|
9507
|
-
const type = header >> 1 & 3;
|
|
9508
|
-
const size = header >> 3;
|
|
9509
|
-
if (type === 3) return null;
|
|
9510
|
-
pos += 3 + (type === 1 ? 1 : size);
|
|
9511
|
-
if (pos > buffer.length) return null;
|
|
9512
|
-
if (last) break;
|
|
9369
|
+
async function servedModels(apiUrl, token, surface) {
|
|
9370
|
+
return (await listModels(apiUrl, token.access_token, surface)).map(servedModel);
|
|
9371
|
+
}
|
|
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`);
|
|
9513
9379
|
}
|
|
9514
|
-
if (checksum) pos += 4;
|
|
9515
|
-
return pos <= buffer.length ? pos : null;
|
|
9516
9380
|
}
|
|
9517
|
-
function
|
|
9518
|
-
const
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
}
|
|
9527
|
-
|
|
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) } };
|
|
9528
9719
|
}
|
|
9529
9720
|
}
|
|
9530
9721
|
offset = end;
|
|
@@ -9587,11 +9778,18 @@ var STALLED_TICKS = 30;
|
|
|
9587
9778
|
var CAPTURE_NOTICES = {
|
|
9588
9779
|
off: "This organization does not record agent sessions (Forge installation config agentSessions.capture is off); nothing is sent.",
|
|
9589
9780
|
metadata: "Recorded: metadata only \u2014 which tools ran and how many failed, tokens, models and timing; no prompt, reply, tool argument or result.",
|
|
9590
|
-
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
|
|
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."
|
|
9591
9782
|
};
|
|
9592
|
-
function
|
|
9593
|
-
|
|
9594
|
-
}
|
|
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
|
+
}
|
|
9790
|
+
function eventBytes(event) {
|
|
9791
|
+
return Buffer.byteLength(JSON.stringify(event), "utf8");
|
|
9792
|
+
}
|
|
9595
9793
|
function nextBatch(pending) {
|
|
9596
9794
|
const batch = [];
|
|
9597
9795
|
let bytes = 0;
|
|
@@ -9620,10 +9818,6 @@ var DshSessionPublisher = class {
|
|
|
9620
9818
|
/** The level the person was last told. */
|
|
9621
9819
|
announced = null;
|
|
9622
9820
|
ticking = null;
|
|
9623
|
-
/** The directory dsh writes this launch's sessions under. */
|
|
9624
|
-
get projectDir() {
|
|
9625
|
-
return path10.join(this.deps.sessionsRoot, dshProjectKey(this.deps.cwd));
|
|
9626
|
-
}
|
|
9627
9821
|
start() {
|
|
9628
9822
|
if (this.running) return;
|
|
9629
9823
|
if (!zstdAvailable()) {
|
|
@@ -9676,54 +9870,63 @@ var DshSessionPublisher = class {
|
|
|
9676
9870
|
await this.flush(session);
|
|
9677
9871
|
}
|
|
9678
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
|
+
*/
|
|
9679
9882
|
discover() {
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
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
|
+
});
|
|
9695
9912
|
}
|
|
9696
|
-
const born = Math.min(stat.birthtimeMs || Number.POSITIVE_INFINITY, stat.mtimeMs);
|
|
9697
|
-
if (born < this.deps.launchedAt - LAUNCH_SLACK_MS) continue;
|
|
9698
|
-
this.sessions.set(entry.name, {
|
|
9699
|
-
dir,
|
|
9700
|
-
file,
|
|
9701
|
-
offset: 0,
|
|
9702
|
-
header: null,
|
|
9703
|
-
pending: [],
|
|
9704
|
-
opened: false,
|
|
9705
|
-
lastSeq: -1,
|
|
9706
|
-
reportedFailure: null,
|
|
9707
|
-
unreadableReported: false,
|
|
9708
|
-
stalledAt: 0,
|
|
9709
|
-
stalledTicks: 0,
|
|
9710
|
-
dead: false
|
|
9711
|
-
});
|
|
9712
9913
|
}
|
|
9713
9914
|
}
|
|
9714
9915
|
read(session) {
|
|
9715
9916
|
if (session.dead) return;
|
|
9917
|
+
session.file ??= dshSessionLogFile(session.dir);
|
|
9918
|
+
if (!session.file) return;
|
|
9716
9919
|
let fd;
|
|
9717
9920
|
try {
|
|
9718
|
-
fd =
|
|
9921
|
+
fd = fs12.openSync(session.file, "r");
|
|
9719
9922
|
} catch {
|
|
9720
9923
|
return;
|
|
9721
9924
|
}
|
|
9722
9925
|
try {
|
|
9723
|
-
const size =
|
|
9926
|
+
const size = fs12.fstatSync(fd).size;
|
|
9724
9927
|
if (size <= session.offset) return;
|
|
9725
9928
|
const buffer = Buffer.alloc(size - session.offset);
|
|
9726
|
-
const read =
|
|
9929
|
+
const read = fs12.readSync(fd, buffer, 0, buffer.length, session.offset);
|
|
9727
9930
|
const decoded = decodeZstdFrames(buffer.subarray(0, read));
|
|
9728
9931
|
if (decoded.failed) {
|
|
9729
9932
|
this.giveUp(session, `the frame at byte ${session.offset + decoded.failed.at} does not decode: ${decoded.failed.error}`);
|
|
@@ -9751,7 +9954,7 @@ var DshSessionPublisher = class {
|
|
|
9751
9954
|
this.deps.report(`warning: the platform has not taken ${session.file} for a while; ${dropped} oldest unsent event(s) were dropped`);
|
|
9752
9955
|
}
|
|
9753
9956
|
} finally {
|
|
9754
|
-
|
|
9957
|
+
fs12.closeSync(fd);
|
|
9755
9958
|
}
|
|
9756
9959
|
}
|
|
9757
9960
|
/** The rest of this session's log cannot be read; what was sent stands, and the person is told once. */
|
|
@@ -9765,7 +9968,7 @@ var DshSessionPublisher = class {
|
|
|
9765
9968
|
harnessSessionId: header.id,
|
|
9766
9969
|
...this.deps.harnessVersion ? { harnessVersion: this.deps.harnessVersion } : {},
|
|
9767
9970
|
host: this.deps.host,
|
|
9768
|
-
workingDirectory: header.cwd ??
|
|
9971
|
+
workingDirectory: header.cwd ?? void 0,
|
|
9769
9972
|
...header.createdAt !== null ? { startedAt: new Date(header.createdAt).toISOString() } : {},
|
|
9770
9973
|
launchId: this.deps.launchId,
|
|
9771
9974
|
...header.parentSession ? { parentHarnessSessionId: header.parentSession } : {},
|
|
@@ -9807,250 +10010,332 @@ var DshSessionPublisher = class {
|
|
|
9807
10010
|
var AGENT_SESSION_APPEND_TOOL = "forge_append_agent_session_events";
|
|
9808
10011
|
var AGENT_SESSION_LIST_TOOL = "forge_list_agent_sessions";
|
|
9809
10012
|
|
|
9810
|
-
// src/
|
|
9811
|
-
import
|
|
9812
|
-
import
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
const
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
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);
|
|
9825
10069
|
}
|
|
9826
10070
|
}
|
|
9827
|
-
|
|
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
|
+
});
|
|
9828
10124
|
}
|
|
9829
10125
|
|
|
9830
|
-
// src/
|
|
9831
|
-
import
|
|
9832
|
-
import
|
|
9833
|
-
|
|
9834
|
-
|
|
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);
|
|
9835
10134
|
}
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
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 {
|
|
10140
|
+
}
|
|
10141
|
+
return null;
|
|
10142
|
+
}
|
|
10143
|
+
function writeUpdateCheck(env, check) {
|
|
10144
|
+
writeJsonAtomic(updateCheckFile(env), check);
|
|
10145
|
+
}
|
|
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 {
|
|
9845
10164
|
}
|
|
9846
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
|
+
}
|
|
9847
10173
|
|
|
9848
|
-
// src/
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
function
|
|
9852
|
-
const
|
|
9853
|
-
if (!
|
|
9854
|
-
|
|
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") {
|
|
9855
10202
|
try {
|
|
9856
|
-
|
|
9857
|
-
return { command: path13.resolve(script), args: [] };
|
|
10203
|
+
deps.record(io.env, { checkedAt: deps.now(), latest: await deps.latestVersion() });
|
|
9858
10204
|
} catch {
|
|
9859
10205
|
}
|
|
10206
|
+
return ExitCode.Ok;
|
|
9860
10207
|
}
|
|
9861
|
-
|
|
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;
|
|
10213
|
+
}
|
|
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;
|
|
9862
10219
|
}
|
|
9863
|
-
function
|
|
9864
|
-
return
|
|
10220
|
+
function installLatest(version, env = process.env, platform = process.platform, stdio = "inherit") {
|
|
10221
|
+
return runNpm(["install", "-g", `${PACKAGE_NAME}@${version}`], env, platform, stdio);
|
|
9865
10222
|
}
|
|
9866
10223
|
|
|
9867
|
-
// src/commands/
|
|
9868
|
-
var
|
|
10224
|
+
// src/commands/dsh.ts
|
|
10225
|
+
var dshUsage = `diverge dsh [--surface anthropic|openai] [--model <id>] [--organization-id <id>] [-- <dsh args>]
|
|
9869
10226
|
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
setup dsh [--surface anthropic|openai] [--model <id>] [--into <path>] [--print] [--organization-id <id>]
|
|
9874
|
-
run dsh [--surface anthropic|openai] [--model <id>] [--organization-id <id>] [-- <dsh args>]
|
|
9875
|
-
nothing after --: the interactive web UI; a task after --: one headless run
|
|
9876
|
-
rotate [--into <path>] [--interval <seconds>] [--once] [--organization-id <id>]
|
|
9877
|
-
keep a token file fresh for a client that reads a file, not a helper (default ~/.divergent/token, every 600 s)
|
|
9878
|
-
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.
|
|
9879
10230
|
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
}
|
|
9891
|
-
|
|
9892
|
-
return (await listModels(apiUrl, token.access_token, surface)).map(servedModel);
|
|
9893
|
-
}
|
|
9894
|
-
function syncDivergentPreset(io, binary, home) {
|
|
9895
|
-
if (!binary) {
|
|
9896
|
-
io.stderr("dsh is not on PATH; the Divergent agent preset is derived when `diverge agents run dsh` first runs.");
|
|
9897
|
-
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;
|
|
9898
10243
|
}
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
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;
|
|
10252
|
+
}
|
|
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;
|
|
9904
10256
|
}
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
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
|
+
});
|
|
9910
10280
|
}
|
|
9911
|
-
|
|
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(", ")}`);
|
|
10285
|
+
}
|
|
10286
|
+
function savedModel(home, surface) {
|
|
10287
|
+
const file = path18.join(home, "profiles", "web", "cordis.patch.yml");
|
|
9912
10288
|
try {
|
|
9913
|
-
|
|
9914
|
-
installForHarness(io, harness, skills, apiUrl);
|
|
9915
|
-
for (const note of skills.notes) io.stderr(`warning: ${note}`);
|
|
10289
|
+
return dshSavedModel(fs15.existsSync(file) ? fs15.readFileSync(file, "utf8") : null, surface);
|
|
9916
10290
|
} catch (error) {
|
|
9917
|
-
|
|
10291
|
+
throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9918
10292
|
}
|
|
9919
10293
|
}
|
|
9920
|
-
async function overlayAndDefault(apiUrl, token, surface,
|
|
9921
|
-
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);
|
|
9922
10296
|
const models = served.map((model) => model.id);
|
|
9923
10297
|
if (models.length === 0) throw new CliError(`the gateway serves no models on the ${surface} surface for this organization`);
|
|
9924
10298
|
if (requested && !models.includes(requested)) throw new CliError(`${requested} is not served on the ${surface} surface; choose one of: ${models.join(", ")}`);
|
|
9925
|
-
const
|
|
9926
|
-
|
|
9927
|
-
}
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
const [
|
|
9934
|
-
const
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
tokenHelper: shellCommand(resolveSelfCommand(), "token", ...organizationArgs(values["organization-id"])),
|
|
9944
|
-
projectId: values["project-id"],
|
|
9945
|
-
modelDiscovery: values["model-discovery"],
|
|
9946
|
-
// Claude Code scans its skill folder when a session starts, so that is when the login's skills are brought current.
|
|
9947
|
-
skillsRefresh: values.scope === "user" ? shellCommand(resolveSelfCommand(), "skills", "refresh") : void 0
|
|
9948
|
-
});
|
|
9949
|
-
writeJsonAtomic(file, settings);
|
|
9950
|
-
io.stdout(`Claude Code \u2192 ${anthropicBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
|
|
9951
|
-
for (const warning of warnings) io.stderr(`warning: ${warning}`);
|
|
9952
|
-
await skillsFor(io, apiUrl, "claude", values["organization-id"]);
|
|
9953
|
-
return ExitCode.Ok;
|
|
9954
|
-
}
|
|
9955
|
-
case "opencode": {
|
|
9956
|
-
const { values } = parseArgs3({ args: rest, options: { surface: { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" } }, strict: true });
|
|
9957
|
-
const surface = surfaceOf(values.surface, "anthropic");
|
|
9958
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
9959
|
-
const baseUrl = surfaceBaseUrl(apiUrl, surface);
|
|
9960
|
-
let models = [];
|
|
9961
|
-
try {
|
|
9962
|
-
models = await servedModels(apiUrl, token, surface);
|
|
9963
|
-
} catch (error) {
|
|
9964
|
-
io.stderr(`warning: ${error instanceof Error ? error.message : String(error)}; writing the provider without a model list`);
|
|
9965
|
-
}
|
|
9966
|
-
const file = values.config ?? opencodeConfigPath();
|
|
9967
|
-
writeJsonAtomic(file, mergeOpencodeConfig(readJson(file), { baseUrl, surface, models }));
|
|
9968
|
-
io.stdout(`OpenCode provider divergent-${surface} \u2192 ${baseUrl}. Wrote ${file}.`);
|
|
9969
|
-
io.stdout("Keep DIVERGENT_TOKEN fresh for the session (user tokens expire; the login refreshes):");
|
|
9970
|
-
io.stdout(` export DIVERGENT_TOKEN="$(${["diverge", "token", ...organizationArgs(values["organization-id"])].join(" ")})"`);
|
|
9971
|
-
return ExitCode.Ok;
|
|
9972
|
-
}
|
|
9973
|
-
case "codex": {
|
|
9974
|
-
const { values } = parseArgs3({ args: rest, options: { "project-id": { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" }, print: { type: "boolean", default: false } }, strict: true });
|
|
9975
|
-
const block = codexProviderToml({ openaiBaseUrl: openaiBaseUrl(apiUrl), self: resolveSelfCommand(), projectId: values["project-id"], organizationId: values["organization-id"] });
|
|
9976
|
-
if (values.print) {
|
|
9977
|
-
io.stdout(block.trimEnd());
|
|
9978
|
-
return ExitCode.Ok;
|
|
9979
|
-
}
|
|
9980
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
9981
|
-
const file = values.config ?? codexConfigPath();
|
|
9982
|
-
const existing = fs11.existsSync(file) ? fs11.readFileSync(file, "utf8") : null;
|
|
9983
|
-
writeTextAtomic(file, mergeCodexConfig(existing, block));
|
|
9984
|
-
io.stdout(`Codex provider divergent \u2192 ${openaiBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
|
|
9985
|
-
io.stdout('Select it per run with: codex -c model_provider=divergent (or set model_provider = "divergent" in the config)');
|
|
9986
|
-
await skillsFor(io, apiUrl, "codex", values["organization-id"]);
|
|
9987
|
-
return ExitCode.Ok;
|
|
9988
|
-
}
|
|
9989
|
-
case "dsh": {
|
|
9990
|
-
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 });
|
|
9991
|
-
const surface = surfaceOf(values.surface, "openai");
|
|
9992
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
9993
|
-
if (!values.print) await skillsFor(io, apiUrl, "dsh", values["organization-id"]);
|
|
9994
|
-
const { overlay, defaultModel } = await overlayAndDefault(apiUrl, token, surface, io, values.model);
|
|
9995
|
-
if (values.print) {
|
|
9996
|
-
io.stdout(overlay.trimEnd());
|
|
9997
|
-
return ExitCode.Ok;
|
|
9998
|
-
}
|
|
9999
|
-
const file = values.into ?? path14.join(dshHome(io.env), `divergent-${surface}.yml`);
|
|
10000
|
-
writeTextAtomic(file, overlay);
|
|
10001
|
-
io.stdout(`dsh route divergent-${surface} \u2192 ${surfaceBaseUrl(apiUrl, surface)} as you, organization ${tokenClaims(token.access_token).organizationId}, default model ${defaultModel}. Wrote ${file}.`);
|
|
10002
|
-
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.");
|
|
10003
|
-
syncDivergentPreset(io, findOnPath("dsh", io.env), dshHome(io.env));
|
|
10004
|
-
io.stdout("Boot a profile with the overlay and the token in its environment (user tokens expire; the login refreshes):");
|
|
10005
|
-
io.stdout(` DIVERGENT_TOKEN="$(${["diverge", "token", ...organizationArgs(values["organization-id"])].join(" ")})" dsh --profile headless --patch ${file} "<task>"`);
|
|
10006
|
-
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`);
|
|
10007
|
-
return ExitCode.Ok;
|
|
10008
|
-
}
|
|
10009
|
-
default:
|
|
10010
|
-
throw unknownVerb("agents", agentsUsage, ["setup"], agent, rest);
|
|
10011
|
-
}
|
|
10012
|
-
}
|
|
10013
|
-
async function run(args, io) {
|
|
10014
|
-
const [agent, ...rest] = args;
|
|
10015
|
-
if (agent !== "dsh") throw unknownVerb("agents", agentsUsage, ["run"], agent, rest);
|
|
10016
|
-
const terminator = rest.indexOf("--");
|
|
10017
|
-
const flags = terminator === -1 ? rest : rest.slice(0, terminator);
|
|
10018
|
-
const passthrough = terminator === -1 ? [] : rest.slice(terminator + 1);
|
|
10019
|
-
const { values } = parseArgs3({ args: flags, options: { surface: { type: "string" }, model: { type: "string" }, "organization-id": { type: "string" } }, strict: true });
|
|
10020
|
-
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;
|
|
10021
10317
|
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
10022
10318
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10023
|
-
const
|
|
10024
|
-
|
|
10319
|
+
const dsh = await ensureDsh(io);
|
|
10320
|
+
const prefix = path18.join(dshInstallRoot(io.env), TESTED_DSH_VERSION);
|
|
10025
10321
|
await skillsFor(io, apiUrl, "dsh", values["organization-id"]);
|
|
10026
|
-
const launch = { id: randomUUID(), client: `dsh/${
|
|
10027
|
-
const { overlay, defaultModel } = await overlayAndDefault(apiUrl, token, surface, io, values.model, launch);
|
|
10322
|
+
const launch = { id: randomUUID(), client: `dsh/${TESTED_DSH_VERSION}` };
|
|
10028
10323
|
const home = dshHome(io.env);
|
|
10029
|
-
const
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
throw new CliError(`${settingsPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10035
|
-
}
|
|
10036
|
-
const served = servedIds(overlay);
|
|
10037
|
-
if (pinned && !served.includes(pinned)) throw new CliError(stalePinMessage(settingsPath, pinned, surface, served));
|
|
10038
|
-
const overlayPath = path14.join(home, `divergent-${surface}.yml`);
|
|
10039
|
-
writeTextAtomic(overlayPath, overlay);
|
|
10040
|
-
syncDivergentPreset(io, binary, home);
|
|
10041
|
-
io.stderr(`dsh on divergent-${surface} as you (organization ${tokenClaims(token.access_token).organizationId}), default model ${defaultModel}`);
|
|
10042
|
-
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>"');
|
|
10043
10329
|
const source = tokenSource(io.env, apiUrl);
|
|
10044
10330
|
const organizationId = tokenClaims(token.access_token).organizationId ?? values["organization-id"];
|
|
10045
10331
|
if (!organizationId) throw new CliError("the gateway token names no organization; pass --organization-id");
|
|
10046
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).`);
|
|
10047
10333
|
const tools = new ToolsClient({ apiUrl, organizationId, mode: "operator", clientId: clientId(io.env) }, io.env);
|
|
10048
10334
|
const publisher = new DshSessionPublisher({
|
|
10049
|
-
sessionsRoot:
|
|
10050
|
-
cwd: process.cwd(),
|
|
10335
|
+
sessionsRoot: path18.join(home, "sessions"),
|
|
10051
10336
|
launchedAt: Date.now(),
|
|
10052
10337
|
launchId: launch.id,
|
|
10053
|
-
harnessVersion:
|
|
10338
|
+
harnessVersion: TESTED_DSH_VERSION,
|
|
10054
10339
|
host: os8.hostname(),
|
|
10055
10340
|
append: (input) => appendSession(tools, input),
|
|
10056
10341
|
report: (line) => io.stderr(line),
|
|
@@ -10059,12 +10344,12 @@ async function run(args, io) {
|
|
|
10059
10344
|
publisher.start();
|
|
10060
10345
|
try {
|
|
10061
10346
|
return await runDsh({
|
|
10062
|
-
|
|
10347
|
+
dsh,
|
|
10063
10348
|
overlayPath,
|
|
10064
|
-
credentialsPath:
|
|
10065
|
-
sessionTokenPath:
|
|
10066
|
-
binDir:
|
|
10067
|
-
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])] },
|
|
10068
10353
|
args: passthrough,
|
|
10069
10354
|
env: io.env,
|
|
10070
10355
|
initialToken: token.access_token,
|
|
@@ -10091,56 +10376,9 @@ async function appendSession(tools, input) {
|
|
|
10091
10376
|
}
|
|
10092
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 };
|
|
10093
10378
|
}
|
|
10094
|
-
async function status(args, io) {
|
|
10095
|
-
const { values } = parseArgs3({ args, options: { "organization-id": { type: "string" } }, strict: true });
|
|
10096
|
-
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
10097
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10098
|
-
const claims = tokenClaims(token.access_token);
|
|
10099
|
-
io.stdout(`Gateway: ${apiUrl}/api/plugins/inference/{anthropic,openai}`);
|
|
10100
|
-
io.stdout(`Organization: ${claims.organizationId}`);
|
|
10101
|
-
io.stdout(`Scopes: ${(claims.scopes ?? []).join(" ")}`);
|
|
10102
|
-
for (const surface of SURFACES) {
|
|
10103
|
-
const models = (await servedModels(apiUrl, token, surface)).map((model) => model.id);
|
|
10104
|
-
io.stdout(`Models (${surface}): ${models.length ? models.join(", ") : "(none served on this surface)"}`);
|
|
10105
|
-
}
|
|
10106
|
-
return ExitCode.Ok;
|
|
10107
|
-
}
|
|
10108
|
-
async function rotate(args, io) {
|
|
10109
|
-
const { values } = parseArgs3({ args, options: { into: { type: "string" }, interval: { type: "string", default: "600" }, once: { type: "boolean", default: false }, "organization-id": { type: "string" } }, strict: true });
|
|
10110
|
-
const interval = Number(values.interval);
|
|
10111
|
-
if (!Number.isFinite(interval) || interval <= 0) throw new CliError("--interval must be a positive number of seconds");
|
|
10112
|
-
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
10113
|
-
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
10114
|
-
const source = tokenSource(io.env, apiUrl);
|
|
10115
|
-
const tokenFilePath = values.into ?? defaultTokenFilePath();
|
|
10116
|
-
io.stderr(`Keeping ${tokenFilePath} fresh as you (organization ${tokenClaims(token.access_token).organizationId})`);
|
|
10117
|
-
return rotateTokenFile({
|
|
10118
|
-
tokenFile: tokenFilePath,
|
|
10119
|
-
intervalSeconds: interval,
|
|
10120
|
-
once: values.once,
|
|
10121
|
-
freshToken: async () => (await getUserToken(source, { freshForSeconds: Math.max(interval, 60) + 60, organizationId: values["organization-id"] })).access_token,
|
|
10122
|
-
report: (line) => io.stderr(line),
|
|
10123
|
-
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
10124
|
-
});
|
|
10125
|
-
}
|
|
10126
|
-
async function runAgents(args, io) {
|
|
10127
|
-
const [verb, ...rest] = args;
|
|
10128
|
-
switch (verb) {
|
|
10129
|
-
case "setup":
|
|
10130
|
-
return setup(rest, io);
|
|
10131
|
-
case "run":
|
|
10132
|
-
return run(rest, io);
|
|
10133
|
-
case "rotate":
|
|
10134
|
-
return rotate(rest, io);
|
|
10135
|
-
case "status":
|
|
10136
|
-
return status(rest, io);
|
|
10137
|
-
default:
|
|
10138
|
-
throw unknownVerb("agents", agentsUsage, [], verb, rest);
|
|
10139
|
-
}
|
|
10140
|
-
}
|
|
10141
10379
|
|
|
10142
10380
|
// src/commands/org.ts
|
|
10143
|
-
import { parseArgs as
|
|
10381
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
10144
10382
|
var orgUsage = `diverge org [use <id|name>]
|
|
10145
10383
|
|
|
10146
10384
|
(no verb) list the organizations you may act in, the default marked
|
|
@@ -10153,7 +10391,7 @@ there, which proves you may act in it. For one command, pass
|
|
|
10153
10391
|
act in, and so sees, every organization.`;
|
|
10154
10392
|
async function runOrg(args, io) {
|
|
10155
10393
|
refuseInSession(io.env, "org");
|
|
10156
|
-
const { positionals } =
|
|
10394
|
+
const { positionals } = parseArgs5({ args, options: {}, strict: true, allowPositionals: true });
|
|
10157
10395
|
const verb = positionals[0];
|
|
10158
10396
|
if (verb && verb !== "use") throw unknownVerb("org", orgUsage, [], verb, positionals.slice(1));
|
|
10159
10397
|
const source = tokenSource(io.env, resolveApiUrl(void 0, io.env));
|
|
@@ -10200,7 +10438,7 @@ async function runLogout(_args, io) {
|
|
|
10200
10438
|
}
|
|
10201
10439
|
|
|
10202
10440
|
// src/commands/status.ts
|
|
10203
|
-
import
|
|
10441
|
+
import path19 from "node:path";
|
|
10204
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.";
|
|
10205
10443
|
async function runStatus(_args, io) {
|
|
10206
10444
|
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
@@ -10216,7 +10454,7 @@ async function runStatus(_args, io) {
|
|
|
10216
10454
|
io.stdout(describeToolExecution(io.env));
|
|
10217
10455
|
return 0;
|
|
10218
10456
|
}
|
|
10219
|
-
io.stdout(`State: ${
|
|
10457
|
+
io.stdout(`State: ${path19.dirname(source.loginFile)}`);
|
|
10220
10458
|
const record = loadLogin(source);
|
|
10221
10459
|
if (!record) {
|
|
10222
10460
|
io.stdout(`Not logged in. Run: diverge login${apiUrl === DEFAULT_API_URL ? "" : ` --api-url ${apiUrl}`}`);
|
|
@@ -10264,7 +10502,7 @@ function describeSignIn(record, nowMs = Date.now()) {
|
|
|
10264
10502
|
}
|
|
10265
10503
|
|
|
10266
10504
|
// src/commands/token.ts
|
|
10267
|
-
import { parseArgs as
|
|
10505
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
10268
10506
|
var tokenUsage = `diverge token [--organization-id <id>]
|
|
10269
10507
|
|
|
10270
10508
|
Print a fresh access token and nothing else: the credential helper for Claude
|
|
@@ -10273,7 +10511,7 @@ Code, Codex and forge. The token is for the organization named by
|
|
|
10273
10511
|
DIVERGENT_TOKEN_FILE set, it prints the session token in that file, and an
|
|
10274
10512
|
organization other than the token's is refused.`;
|
|
10275
10513
|
async function runToken(args, io) {
|
|
10276
|
-
const { values } =
|
|
10514
|
+
const { values } = parseArgs6({ args, options: { "organization-id": { type: "string" } }, strict: true, allowPositionals: false });
|
|
10277
10515
|
const organizationId = values["organization-id"]?.trim() || io.env.DIVERGENT_ORG_ID?.trim() || void 0;
|
|
10278
10516
|
const token = await getUserToken(tokenSource(io.env, resolveApiUrl(void 0, io.env)), { organizationId });
|
|
10279
10517
|
io.stdout(token.access_token);
|
|
@@ -10281,7 +10519,7 @@ async function runToken(args, io) {
|
|
|
10281
10519
|
}
|
|
10282
10520
|
|
|
10283
10521
|
// src/commands/tools.ts
|
|
10284
|
-
import { parseArgs as
|
|
10522
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
10285
10523
|
|
|
10286
10524
|
// src/mcp/bridge.ts
|
|
10287
10525
|
import readline2 from "node:readline";
|
|
@@ -10508,7 +10746,7 @@ async function waitForInvocation(id, options, deps) {
|
|
|
10508
10746
|
}
|
|
10509
10747
|
|
|
10510
10748
|
// src/tools/call-args.ts
|
|
10511
|
-
import
|
|
10749
|
+
import fs16 from "node:fs";
|
|
10512
10750
|
async function resolveCallArgs(options, stdin = readStdin) {
|
|
10513
10751
|
if (options.argsJson !== void 0 && options.argsJson !== "{}" && options.argsFile !== void 0) {
|
|
10514
10752
|
throw new CliError("give the arguments once: --args-json or --args-file");
|
|
@@ -10518,7 +10756,7 @@ async function resolveCallArgs(options, stdin = readStdin) {
|
|
|
10518
10756
|
if (options.argsFile !== void 0) {
|
|
10519
10757
|
source = `--args-file ${options.argsFile}`;
|
|
10520
10758
|
try {
|
|
10521
|
-
text2 =
|
|
10759
|
+
text2 = fs16.readFileSync(options.argsFile, "utf8");
|
|
10522
10760
|
} catch (error) {
|
|
10523
10761
|
throw new CliError(`${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10524
10762
|
}
|
|
@@ -10610,7 +10848,7 @@ function worstExitCode(outcomes) {
|
|
|
10610
10848
|
}
|
|
10611
10849
|
|
|
10612
10850
|
// src/commands/tools.ts
|
|
10613
|
-
import
|
|
10851
|
+
import fs17 from "node:fs";
|
|
10614
10852
|
|
|
10615
10853
|
// src/tools/print.ts
|
|
10616
10854
|
function matchesCapability(selector, capability) {
|
|
@@ -10735,7 +10973,7 @@ async function call(rest, io) {
|
|
|
10735
10973
|
let waited = "";
|
|
10736
10974
|
let outcome;
|
|
10737
10975
|
try {
|
|
10738
|
-
const { values, positionals } =
|
|
10976
|
+
const { values, positionals } = parseArgs7({ args: rest, options: CALL_OPTIONS, strict: true, allowPositionals: true });
|
|
10739
10977
|
tool = positionals[0] ?? "";
|
|
10740
10978
|
if (!tool) throw new CliError("call needs a tool name");
|
|
10741
10979
|
const args = await resolveCallArgs({ argsJson: values["args-json"], argsFile: values["args-file"] });
|
|
@@ -10762,12 +11000,12 @@ async function set(rest, io) {
|
|
|
10762
11000
|
let waited = "";
|
|
10763
11001
|
let outcome;
|
|
10764
11002
|
try {
|
|
10765
|
-
const { values, positionals } =
|
|
11003
|
+
const { values, positionals } = parseArgs7({ args: rest, options: SET_OPTIONS, strict: true, allowPositionals: true });
|
|
10766
11004
|
const file = positionals[0];
|
|
10767
11005
|
if (!file) throw new CliError("set needs a file of calls, or - for stdin");
|
|
10768
11006
|
let entries;
|
|
10769
11007
|
try {
|
|
10770
|
-
entries = parseBatch(file === "-" ? await readStdin() :
|
|
11008
|
+
entries = parseBatch(file === "-" ? await readStdin() : fs17.readFileSync(file, "utf8"));
|
|
10771
11009
|
} catch (error) {
|
|
10772
11010
|
throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10773
11011
|
}
|
|
@@ -10811,7 +11049,7 @@ async function runTools(args, io) {
|
|
|
10811
11049
|
const [verb, ...rest] = args;
|
|
10812
11050
|
switch (verb) {
|
|
10813
11051
|
case "list": {
|
|
10814
|
-
const { values } =
|
|
11052
|
+
const { values } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, filter: { type: "string" }, capability: { type: "string" }, json: { type: "boolean", default: false } }, strict: true });
|
|
10815
11053
|
const { client: tools } = await client(values, io);
|
|
10816
11054
|
let list = await tools.listTools();
|
|
10817
11055
|
if (values.filter) list = list.filter((tool) => matchesFilter(values.filter, tool));
|
|
@@ -10821,7 +11059,7 @@ async function runTools(args, io) {
|
|
|
10821
11059
|
return ExitCode.Ok;
|
|
10822
11060
|
}
|
|
10823
11061
|
case "capabilities": {
|
|
10824
|
-
const { values } =
|
|
11062
|
+
const { values } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true });
|
|
10825
11063
|
const { client: tools } = await client(values, io);
|
|
10826
11064
|
const list = await tools.listTools();
|
|
10827
11065
|
if (values.json) io.stdout(JSON.stringify(capabilityClusters(list), null, 2));
|
|
@@ -10829,7 +11067,7 @@ async function runTools(args, io) {
|
|
|
10829
11067
|
return ExitCode.Ok;
|
|
10830
11068
|
}
|
|
10831
11069
|
case "describe": {
|
|
10832
|
-
const { values, positionals } =
|
|
11070
|
+
const { values, positionals } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true, allowPositionals: true });
|
|
10833
11071
|
const name = positionals[0];
|
|
10834
11072
|
if (!name) throw new CliError("describe needs a tool name");
|
|
10835
11073
|
const { settings, client: tools } = await client(values, io);
|
|
@@ -10849,12 +11087,12 @@ async function runTools(args, io) {
|
|
|
10849
11087
|
case "set":
|
|
10850
11088
|
return set(rest, io);
|
|
10851
11089
|
case "run": {
|
|
10852
|
-
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 });
|
|
10853
11091
|
const file = positionals[0];
|
|
10854
11092
|
if (!file) throw new CliError("run needs a file of calls, or - for stdin");
|
|
10855
11093
|
let entries;
|
|
10856
11094
|
try {
|
|
10857
|
-
entries = parseBatch(file === "-" ? await readStdin() :
|
|
11095
|
+
entries = parseBatch(file === "-" ? await readStdin() : fs17.readFileSync(file, "utf8"));
|
|
10858
11096
|
} catch (error) {
|
|
10859
11097
|
throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10860
11098
|
}
|
|
@@ -10869,7 +11107,7 @@ async function runTools(args, io) {
|
|
|
10869
11107
|
return exitCode;
|
|
10870
11108
|
}
|
|
10871
11109
|
case "invocation": {
|
|
10872
|
-
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 });
|
|
10873
11111
|
const id = positionals[0];
|
|
10874
11112
|
if (!id) throw new CliError("invocation needs an id");
|
|
10875
11113
|
const { client: tools } = await client(values, io);
|
|
@@ -10887,7 +11125,7 @@ async function runTools(args, io) {
|
|
|
10887
11125
|
return worstExitCode([outcome]);
|
|
10888
11126
|
}
|
|
10889
11127
|
case "bridge": {
|
|
10890
|
-
const { values } =
|
|
11128
|
+
const { values } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS, tools: { type: "string" } }, strict: true });
|
|
10891
11129
|
const { client: tools } = await client(values, io);
|
|
10892
11130
|
const allow = (values.tools ?? "").split(",").map((name) => name.trim()).filter(Boolean);
|
|
10893
11131
|
return serveBridge(new BridgeServer(tools, { tools: allow }), process.stdin, process.stdout);
|
|
@@ -10898,34 +11136,60 @@ async function runTools(args, io) {
|
|
|
10898
11136
|
}
|
|
10899
11137
|
|
|
10900
11138
|
// src/commands/plugins.ts
|
|
10901
|
-
import
|
|
10902
|
-
import { parseArgs as
|
|
11139
|
+
import fs18 from "node:fs";
|
|
11140
|
+
import { parseArgs as parseArgs8 } from "node:util";
|
|
10903
11141
|
var pluginsUsage = `diverge plugins <validate|publish|list|retire> [--organization-id <id>] ...
|
|
10904
11142
|
|
|
10905
|
-
validate <manifest.json>
|
|
10906
|
-
|
|
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)
|
|
10907
11147
|
list [--json]
|
|
10908
|
-
retire <pluginId>
|
|
11148
|
+
retire <pluginId> [--json] (forge_retire_plugin: raised as you, decided in Compass;
|
|
11149
|
+
exits 3 while it waits)
|
|
10909
11150
|
|
|
10910
11151
|
Register a tenant plugin: a plugin whose code runs in your organization's own
|
|
10911
11152
|
runtime and never inside the platform. The manifest lists the tools the
|
|
10912
|
-
platform registers, pinned; a change is a new version published again.
|
|
10913
|
-
|
|
10914
|
-
|
|
11153
|
+
platform registers, pinned; a change is a new version published again.
|
|
11154
|
+
|
|
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.`;
|
|
10915
11163
|
var SCOPE_OPTIONS2 = { "organization-id": { type: "string" } };
|
|
10916
11164
|
async function client2(values, io) {
|
|
10917
11165
|
const settings = loadToolsSettings({ organizationId: values["organization-id"] }, io.env);
|
|
10918
11166
|
io.stderr(scopeLine(settings));
|
|
10919
11167
|
io.stderr(await actingAsLine(settings, io.env));
|
|
10920
|
-
return new ToolsClient(settings, io.env);
|
|
11168
|
+
return { settings, tools: new ToolsClient(settings, io.env) };
|
|
11169
|
+
}
|
|
11170
|
+
function printProblems(details, io) {
|
|
11171
|
+
io.stderr(`Invalid manifest (${details.length} problem${details.length === 1 ? "" : "s"}):`);
|
|
11172
|
+
for (const detail of details) io.stderr(` - ${detail}`);
|
|
11173
|
+
return ExitCode.Failure;
|
|
11174
|
+
}
|
|
11175
|
+
async function raise(entry, context, io) {
|
|
11176
|
+
const { settings, tools, json } = context;
|
|
11177
|
+
const outcome = await runOne(entry, {
|
|
11178
|
+
callTool: (name, args) => tools.callTool(name, args),
|
|
11179
|
+
decideAt: (pending) => `${settings.apiUrl}${pending.approvalUrl ?? ""}`,
|
|
11180
|
+
...json ? {} : { onPending: (pending) => formatPending(pending, settings.apiUrl, settings.organizationId).forEach((line) => io.stdout(line)) }
|
|
11181
|
+
});
|
|
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;
|
|
10921
11185
|
}
|
|
10922
|
-
function
|
|
11186
|
+
function readManifest2(file) {
|
|
10923
11187
|
if (!file) throw new CliError(`A manifest file is required.
|
|
10924
11188
|
|
|
10925
11189
|
${pluginsUsage}`);
|
|
10926
11190
|
let text2;
|
|
10927
11191
|
try {
|
|
10928
|
-
text2 =
|
|
11192
|
+
text2 = fs18.readFileSync(file, "utf8");
|
|
10929
11193
|
} catch (error) {
|
|
10930
11194
|
throw new CliError(`Cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10931
11195
|
}
|
|
@@ -10952,43 +11216,45 @@ async function runPlugins(args, io) {
|
|
|
10952
11216
|
const [verb, ...rest] = args;
|
|
10953
11217
|
switch (verb) {
|
|
10954
11218
|
case "validate": {
|
|
10955
|
-
const { values, positionals } =
|
|
10956
|
-
const manifest =
|
|
10957
|
-
const tools = await client2(values, io);
|
|
10958
|
-
const
|
|
10959
|
-
if (
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
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]);
|
|
11221
|
+
const { tools } = await client2(values, io);
|
|
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));
|
|
10965
11230
|
else {
|
|
10966
|
-
io.stdout(`Valid. ${
|
|
10967
|
-
for (const line of formatPlugin(
|
|
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.");
|
|
11238
|
+
}
|
|
10968
11239
|
}
|
|
10969
11240
|
return ExitCode.Ok;
|
|
10970
11241
|
}
|
|
10971
11242
|
case "publish": {
|
|
10972
|
-
const { values, positionals } =
|
|
10973
|
-
const manifest =
|
|
10974
|
-
const tools = await client2(values, io);
|
|
10975
|
-
const outcome = await
|
|
10976
|
-
if (!outcome.
|
|
10977
|
-
|
|
10978
|
-
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
if (values.json) io.stdout(JSON.stringify(outcome.result, null, 2));
|
|
10982
|
-
else {
|
|
10983
|
-
io.stdout(outcome.result.changed ? "Published." : "Unchanged: this digest was already published.");
|
|
10984
|
-
for (const line of formatPlugin(outcome.result.plugin)) io.stdout(line);
|
|
10985
|
-
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.");
|
|
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]);
|
|
11245
|
+
const { settings, tools } = await client2(values, io);
|
|
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.");
|
|
10986
11252
|
}
|
|
10987
|
-
return
|
|
11253
|
+
return worstExitCode([outcome]);
|
|
10988
11254
|
}
|
|
10989
11255
|
case "list": {
|
|
10990
|
-
const { values } =
|
|
10991
|
-
const tools = await client2(values, io);
|
|
11256
|
+
const { values } = parseArgs8({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, strict: true });
|
|
11257
|
+
const { tools } = await client2(values, io);
|
|
10992
11258
|
const plugins = await tools.listPlugins();
|
|
10993
11259
|
if (values.json) io.stdout(JSON.stringify(plugins, null, 2));
|
|
10994
11260
|
else if (!plugins.length) io.stdout("No tenant plugins published for this organization.");
|
|
@@ -10996,138 +11262,24 @@ async function runPlugins(args, io) {
|
|
|
10996
11262
|
return ExitCode.Ok;
|
|
10997
11263
|
}
|
|
10998
11264
|
case "retire": {
|
|
10999
|
-
const { values, positionals } =
|
|
11265
|
+
const { values, positionals } = parseArgs8({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
11000
11266
|
const pluginId = positionals[0];
|
|
11001
11267
|
if (!pluginId) throw new CliError(`A plugin id is required.
|
|
11002
11268
|
|
|
11003
11269
|
${pluginsUsage}`);
|
|
11004
|
-
const tools = await client2(values, io);
|
|
11005
|
-
const
|
|
11006
|
-
|
|
11007
|
-
|
|
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]);
|
|
11008
11277
|
}
|
|
11009
11278
|
default:
|
|
11010
11279
|
throw unknownVerb("plugins", pluginsUsage, [], verb, rest);
|
|
11011
11280
|
}
|
|
11012
11281
|
}
|
|
11013
11282
|
|
|
11014
|
-
// src/commands/update.ts
|
|
11015
|
-
import { spawn as spawn4 } from "node:child_process";
|
|
11016
|
-
import path17 from "node:path";
|
|
11017
|
-
|
|
11018
|
-
// src/update-notice.ts
|
|
11019
|
-
import { spawn as spawn3 } from "node:child_process";
|
|
11020
|
-
import fs15 from "node:fs";
|
|
11021
|
-
import path16 from "node:path";
|
|
11022
|
-
var UPDATE_CHECK_FILE = "update-check.json";
|
|
11023
|
-
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
11024
|
-
function updateCheckFile(env) {
|
|
11025
|
-
return path16.join(configDir(env), UPDATE_CHECK_FILE);
|
|
11026
|
-
}
|
|
11027
|
-
function readUpdateCheck(env) {
|
|
11028
|
-
try {
|
|
11029
|
-
const parsed = JSON.parse(fs15.readFileSync(updateCheckFile(env), "utf8"));
|
|
11030
|
-
if (parsed && typeof parsed === "object" && typeof parsed.checkedAt === "number" && typeof parsed.latest === "string") return parsed;
|
|
11031
|
-
} catch {
|
|
11032
|
-
}
|
|
11033
|
-
return null;
|
|
11034
|
-
}
|
|
11035
|
-
function writeUpdateCheck(env, check) {
|
|
11036
|
-
writeJsonAtomic(updateCheckFile(env), check);
|
|
11037
|
-
}
|
|
11038
|
-
function noticeFor(check, version = VERSION) {
|
|
11039
|
-
if (!check || !isNewer(check.latest, version)) return null;
|
|
11040
|
-
return `diverge ${check.latest} is available (this is ${version}). Update with: diverge update (or run npx @divergeai/cli@latest)`;
|
|
11041
|
-
}
|
|
11042
|
-
function noticeApplies(argv, io, version = VERSION) {
|
|
11043
|
-
if (version === "dev" || !io.interactive) return false;
|
|
11044
|
-
if (io.env.DIVERGE_NO_UPDATE_CHECK?.trim() || io.env.CI?.trim() || sessionTokenFile(io.env)) return false;
|
|
11045
|
-
if (argv[0] === "update") return false;
|
|
11046
|
-
if (argv[0] === "tools" && argv[1] === "bridge") return false;
|
|
11047
|
-
return true;
|
|
11048
|
-
}
|
|
11049
|
-
function scheduleUpdateCheck(spawnImpl = spawn3) {
|
|
11050
|
-
try {
|
|
11051
|
-
const child = spawnImpl(process.execPath, [process.argv[1], "update", "--record"], { detached: true, stdio: "ignore" });
|
|
11052
|
-
child.on("error", () => {
|
|
11053
|
-
});
|
|
11054
|
-
child.unref();
|
|
11055
|
-
} catch {
|
|
11056
|
-
}
|
|
11057
|
-
}
|
|
11058
|
-
function updateNotice(argv, io, deps = { now: Date.now, schedule: scheduleUpdateCheck, read: readUpdateCheck, version: VERSION }) {
|
|
11059
|
-
if (!noticeApplies(argv, io, deps.version)) return;
|
|
11060
|
-
const check = deps.read(io.env);
|
|
11061
|
-
const notice = noticeFor(check, deps.version);
|
|
11062
|
-
if (notice) io.stderr(notice);
|
|
11063
|
-
if (!check || deps.now() - check.checkedAt > UPDATE_CHECK_INTERVAL_MS) deps.schedule();
|
|
11064
|
-
}
|
|
11065
|
-
|
|
11066
|
-
// src/commands/update.ts
|
|
11067
|
-
var PACKAGE_NAME = "@divergeai/cli";
|
|
11068
|
-
var updateUsage = "diverge update\n\nInstall the latest release with the npm that installed this one.";
|
|
11069
|
-
async function latestVersion(registry = "https://registry.npmjs.org") {
|
|
11070
|
-
const body = await fetchJson(`${registry}/${PACKAGE_NAME}/latest`);
|
|
11071
|
-
if (!body.version) throw new CliError("The registry answered without a version");
|
|
11072
|
-
return body.version;
|
|
11073
|
-
}
|
|
11074
|
-
function isNewer(a, b) {
|
|
11075
|
-
const parse = (v) => {
|
|
11076
|
-
const [core, pre] = v.split("-", 2);
|
|
11077
|
-
return { nums: core.split(".").map((n) => Number(n) || 0), pre: pre ?? "" };
|
|
11078
|
-
};
|
|
11079
|
-
const x = parse(a);
|
|
11080
|
-
const y = parse(b);
|
|
11081
|
-
for (let i = 0; i < 3; i += 1) {
|
|
11082
|
-
const dx = x.nums[i] ?? 0;
|
|
11083
|
-
const dy = y.nums[i] ?? 0;
|
|
11084
|
-
if (dx !== dy) return dx > dy;
|
|
11085
|
-
}
|
|
11086
|
-
if (x.pre === y.pre) return false;
|
|
11087
|
-
if (!x.pre) return true;
|
|
11088
|
-
if (!y.pre) return false;
|
|
11089
|
-
return x.pre > y.pre;
|
|
11090
|
-
}
|
|
11091
|
-
async function runUpdate(args, io, deps = { latestVersion, install: installLatest, record: writeUpdateCheck, now: Date.now }) {
|
|
11092
|
-
refuseInSession(io.env, "update");
|
|
11093
|
-
if (args[0] === "--record") {
|
|
11094
|
-
try {
|
|
11095
|
-
deps.record(io.env, { checkedAt: deps.now(), latest: await deps.latestVersion() });
|
|
11096
|
-
} catch {
|
|
11097
|
-
}
|
|
11098
|
-
return ExitCode.Ok;
|
|
11099
|
-
}
|
|
11100
|
-
if (VERSION === "dev") throw new CliError("This is a source checkout, not an installed release; pull the repository instead.");
|
|
11101
|
-
const latest = await deps.latestVersion();
|
|
11102
|
-
if (!isNewer(latest, VERSION)) {
|
|
11103
|
-
io.stdout(`diverge ${VERSION} is up to date.`);
|
|
11104
|
-
return ExitCode.Ok;
|
|
11105
|
-
}
|
|
11106
|
-
io.stdout(`Updating diverge ${VERSION} \u2192 ${latest}\u2026`);
|
|
11107
|
-
const code2 = await deps.install(latest);
|
|
11108
|
-
if (code2 !== 0) throw new CliError(`npm install exited with ${code2}`);
|
|
11109
|
-
io.stdout(`diverge ${latest} installed.`);
|
|
11110
|
-
return ExitCode.Ok;
|
|
11111
|
-
}
|
|
11112
|
-
function installLatest(version, env = process.env, platform = process.platform) {
|
|
11113
|
-
let command = "npm";
|
|
11114
|
-
const args = ["install", "-g", `${PACKAGE_NAME}@${version}`];
|
|
11115
|
-
if (platform === "win32") {
|
|
11116
|
-
const npm = findOnPath("npm", env, platform);
|
|
11117
|
-
if (!npm) throw new CliError("npm was not found on PATH; install Node.js with npm and try again.");
|
|
11118
|
-
command = npm;
|
|
11119
|
-
if (/\.(cmd|bat)$/i.test(npm)) {
|
|
11120
|
-
command = process.execPath;
|
|
11121
|
-
args.unshift(path17.join(path17.dirname(npm), "node_modules", "npm", "bin", "npm-cli.js"));
|
|
11122
|
-
}
|
|
11123
|
-
}
|
|
11124
|
-
return new Promise((resolve, reject) => {
|
|
11125
|
-
const child = spawn4(command, args, { env, stdio: "inherit" });
|
|
11126
|
-
child.on("error", reject);
|
|
11127
|
-
child.on("exit", (code2) => resolve(code2 ?? 1));
|
|
11128
|
-
});
|
|
11129
|
-
}
|
|
11130
|
-
|
|
11131
11283
|
// src/cli.ts
|
|
11132
11284
|
var COMMANDS = [
|
|
11133
11285
|
{ name: "login", summary: "Sign in (authenticate) through the browser", usage: loginUsage, run: runLogin },
|
|
@@ -11135,7 +11287,8 @@ var COMMANDS = [
|
|
|
11135
11287
|
{ name: "status", summary: "Show who you are signed in as: the deployment, the account and what the login carries", usage: statusUsage, run: runStatus },
|
|
11136
11288
|
{ name: "org", summary: "Show or switch the organization the login acts in", usage: orgUsage, run: runOrg },
|
|
11137
11289
|
{ name: "token", summary: "Print a fresh access token", usage: tokenUsage, run: runToken },
|
|
11138
|
-
{ 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 },
|
|
11139
11292
|
{ name: "tools", summary: "Discover and execute platform tools; raise and read approvals", usage: toolsUsage, run: runTools },
|
|
11140
11293
|
{ name: "plugins", summary: "Validate, publish, list and retire tenant plugins for your organization", usage: pluginsUsage, run: runPlugins },
|
|
11141
11294
|
{ name: "skills", summary: "Install the skills of every organization you can act in into Claude Code, Codex and dsh", usage: skillsUsage, run: runSkills },
|
|
@@ -11156,7 +11309,7 @@ function usage() {
|
|
|
11156
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)."
|
|
11157
11310
|
].join("\n");
|
|
11158
11311
|
}
|
|
11159
|
-
async function
|
|
11312
|
+
async function run(argv, io) {
|
|
11160
11313
|
const [name, ...rest] = argv;
|
|
11161
11314
|
if (!name || name === "--help" || name === "-h" || name === "help") {
|
|
11162
11315
|
io.stdout(usage());
|
|
@@ -11176,9 +11329,9 @@ ${usage()}`);
|
|
|
11176
11329
|
}
|
|
11177
11330
|
const own = rest.includes("--") ? rest.slice(0, rest.indexOf("--")) : rest;
|
|
11178
11331
|
if (own.includes("--help") || own.includes("-h")) {
|
|
11179
|
-
const
|
|
11180
|
-
if (
|
|
11181
|
-
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));
|
|
11182
11335
|
return ExitCode.Ok;
|
|
11183
11336
|
}
|
|
11184
11337
|
}
|
|
@@ -11206,7 +11359,7 @@ ${verbUsage(command.name, command.usage, verbPath(command.usage, rest))}`);
|
|
|
11206
11359
|
}
|
|
11207
11360
|
|
|
11208
11361
|
// src/index.ts
|
|
11209
|
-
var code = await
|
|
11362
|
+
var code = await run(process.argv.slice(2), {
|
|
11210
11363
|
stdout: (line) => process.stdout.write(`${line}
|
|
11211
11364
|
`),
|
|
11212
11365
|
stderr: (line) => process.stderr.write(`${line}
|