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