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