@divergeai/cli 0.1.11 → 0.1.14
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 +505 -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.14" : "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,242 @@ 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 few(names, limit = 6) {
|
|
8320
|
+
return names.length <= limit ? names.join(", ") : `${names.slice(0, limit).join(", ")} and ${names.length - limit} more`;
|
|
8321
|
+
}
|
|
8322
|
+
function installSkills(root, skills, source, now = () => /* @__PURE__ */ new Date()) {
|
|
8323
|
+
const previous = readSkillsManifest(root);
|
|
8324
|
+
const held = previous?.skills ?? {};
|
|
8325
|
+
const written = [];
|
|
8326
|
+
const added = [];
|
|
8327
|
+
const updated = [];
|
|
8328
|
+
for (const skill of skills) {
|
|
8329
|
+
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`);
|
|
8330
|
+
writeTextAtomic(path8.join(root, skill.name, "SKILL.md"), skill.content, 420);
|
|
8331
|
+
written.push(skill.name);
|
|
8332
|
+
if (!(skill.name in held)) added.push(skill.name);
|
|
8333
|
+
else if (held[skill.name] !== skill.updatedAt) updated.push(skill.name);
|
|
8334
|
+
}
|
|
8335
|
+
const removed = [];
|
|
8336
|
+
for (const name of Object.keys(previous?.skills ?? {})) {
|
|
8337
|
+
if (skills.some((skill) => skill.name === name)) continue;
|
|
8338
|
+
fs6.rmSync(path8.join(root, name), { recursive: true, force: true });
|
|
8339
|
+
removed.push(name);
|
|
8340
|
+
}
|
|
8341
|
+
const manifest = {
|
|
8342
|
+
installedAt: now().toISOString(),
|
|
8343
|
+
source,
|
|
8344
|
+
skills: Object.fromEntries(skills.map((skill) => [skill.name, skill.updatedAt]))
|
|
8345
|
+
};
|
|
8346
|
+
writeJsonAtomic(path8.join(root, SKILLS_MANIFEST), manifest, 420);
|
|
8347
|
+
return { root, written, added, updated, removed };
|
|
8348
|
+
}
|
|
8349
|
+
function skillsStatus(root, skills) {
|
|
8350
|
+
const manifest = readSkillsManifest(root);
|
|
8351
|
+
const held = manifest?.skills ?? {};
|
|
8352
|
+
const current = [];
|
|
8353
|
+
const behind = [];
|
|
8354
|
+
const missing = [];
|
|
8355
|
+
for (const skill of skills) {
|
|
8356
|
+
if (!(skill.name in held) || !fs6.existsSync(path8.join(root, skill.name, "SKILL.md"))) missing.push(skill.name);
|
|
8357
|
+
else if (held[skill.name] !== skill.updatedAt) behind.push(skill.name);
|
|
8358
|
+
else current.push(skill.name);
|
|
8359
|
+
}
|
|
8360
|
+
const retired = Object.keys(held).filter((name) => !skills.some((skill) => skill.name === name));
|
|
8361
|
+
return { root, installedAt: manifest?.installedAt ?? null, current, behind, missing, retired };
|
|
8362
|
+
}
|
|
8363
|
+
|
|
8364
|
+
// src/skills/installable.ts
|
|
8365
|
+
async function fetchInstallableSkills(baseUrl, accessToken) {
|
|
8366
|
+
const body = await fetchJson(
|
|
8367
|
+
`${baseUrl.replace(/\/+$/, "")}/api/plugins/symphony/skills/installable`,
|
|
8368
|
+
{ headers: { Authorization: `Bearer ${accessToken}` } }
|
|
8369
|
+
);
|
|
8370
|
+
if (!body.success || !body.data) throw new CliError(body.error ?? "The platform answered without skills");
|
|
8371
|
+
return body.data;
|
|
8372
|
+
}
|
|
8373
|
+
|
|
8374
|
+
// src/commands/skills.ts
|
|
8375
|
+
var skillsUsage = `diverge skills <list|install|status> [--harness claude,codex,dsh] [--into <dir>]
|
|
8376
|
+
|
|
8377
|
+
list the skills the platform serves you: your organization's skills whose tools you can run
|
|
8378
|
+
install write them, and the CLI's own "diverge" skill, into each harness's user skill folder
|
|
8379
|
+
(~/.claude/skills, ~/.codex/skills, $DSH_HOME/skills); retires the ones the organization retired;
|
|
8380
|
+
reports what changed \u2014 the full list is \`skills list\`
|
|
8381
|
+
status what each folder holds against what the platform serves now
|
|
8382
|
+
|
|
8383
|
+
A skill is a procedure over the tools it names; you get the ones whose every
|
|
8384
|
+
tool you may run. Re-run install after the organization changes its skills.`;
|
|
8385
|
+
async function installableSkillSet(apiUrl, env) {
|
|
8386
|
+
const source = { tokenFile: tokenFile(env), baseUrl: apiUrl, clientId: clientId(env) };
|
|
8387
|
+
const token = await getUserToken(source);
|
|
8388
|
+
const organizationId = tokenClaims(token.access_token).organizationId;
|
|
8389
|
+
if (!organizationId) throw new CliError("The login is not bound to an organization; run: diverge org use <id|name>");
|
|
8390
|
+
const served = await fetchInstallableSkills(apiUrl, token.access_token);
|
|
8391
|
+
return { skills: [DIVERGE_SKILL, ...served.skills], organizationId };
|
|
8392
|
+
}
|
|
8393
|
+
function harnessesOf(flag) {
|
|
8394
|
+
if (!flag) return [...HARNESSES];
|
|
8395
|
+
const chosen = flag.split(",").map((name) => name.trim()).filter(Boolean);
|
|
8396
|
+
for (const name of chosen) if (!HARNESSES.includes(name)) throw new CliError(`--harness must name some of: ${HARNESSES.join(", ")}`);
|
|
8397
|
+
return chosen;
|
|
8398
|
+
}
|
|
8399
|
+
function installForHarness(io, harness, skills, apiUrl, into) {
|
|
8400
|
+
const root = into ?? skillsRoot(harness, io.env);
|
|
8401
|
+
const result = installSkills(root, skills, apiUrl);
|
|
8402
|
+
const changes = [
|
|
8403
|
+
...result.added.length ? [`${result.added.length} new: ${few(result.added)}`] : [],
|
|
8404
|
+
...result.updated.length ? [`${result.updated.length} updated: ${few(result.updated)}`] : [],
|
|
8405
|
+
...result.removed.length ? [`${result.removed.length} retired: ${few(result.removed)}`] : []
|
|
8406
|
+
];
|
|
8407
|
+
io.stdout(`${harness}: ${result.written.length} skills in ${root}${changes.length ? ` (${changes.join("; ")})` : " (unchanged)"}`);
|
|
8408
|
+
}
|
|
8409
|
+
async function runSkills(args, io) {
|
|
8410
|
+
const [verb, ...rest] = args;
|
|
8411
|
+
const { values } = parseArgs({ args: rest, options: { harness: { type: "string" }, into: { type: "string" } }, strict: true });
|
|
8412
|
+
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
8413
|
+
switch (verb) {
|
|
8414
|
+
case "list": {
|
|
8415
|
+
const { skills, organizationId } = await installableSkillSet(apiUrl, io.env);
|
|
8416
|
+
io.stdout(`Skills the platform serves you at organization ${organizationId}:`);
|
|
8417
|
+
for (const skill of skills) io.stdout(` ${skill.name.padEnd(36)} ${skill.tools.length ? skill.tools.join(", ") : "(the CLI itself)"}`);
|
|
8418
|
+
return ExitCode.Ok;
|
|
8419
|
+
}
|
|
8420
|
+
case "install": {
|
|
8421
|
+
const { skills } = await installableSkillSet(apiUrl, io.env);
|
|
8422
|
+
const harnesses = values.into ? [harnessesOf(values.harness)[0]] : harnessesOf(values.harness);
|
|
8423
|
+
for (const harness of harnesses) installForHarness(io, harness, skills, apiUrl, values.into);
|
|
8424
|
+
return ExitCode.Ok;
|
|
8425
|
+
}
|
|
8426
|
+
case "status": {
|
|
8427
|
+
const { skills } = await installableSkillSet(apiUrl, io.env);
|
|
8428
|
+
let behind = false;
|
|
8429
|
+
for (const harness of harnessesOf(values.harness)) {
|
|
8430
|
+
const status2 = skillsStatus(values.into ?? skillsRoot(harness, io.env), skills);
|
|
8431
|
+
const parts = [`${status2.current.length} current`];
|
|
8432
|
+
if (status2.behind.length) parts.push(`${status2.behind.length} behind (${few(status2.behind)})`);
|
|
8433
|
+
if (status2.missing.length) parts.push(`${status2.missing.length} missing${status2.current.length || status2.behind.length ? ` (${few(status2.missing)})` : ""}`);
|
|
8434
|
+
if (status2.retired.length) parts.push(`${status2.retired.length} retired (${few(status2.retired)})`);
|
|
8435
|
+
behind ||= status2.behind.length + status2.missing.length + status2.retired.length > 0;
|
|
8436
|
+
io.stdout(`${harness}: ${parts.join(", ")} in ${status2.root}${status2.installedAt ? ` (installed ${status2.installedAt})` : " (never installed)"}`);
|
|
8437
|
+
}
|
|
8438
|
+
if (behind) io.stdout("Run: diverge skills install");
|
|
8439
|
+
return ExitCode.Ok;
|
|
8440
|
+
}
|
|
8441
|
+
default:
|
|
8442
|
+
throw new CliError(`skills needs one of: list, install, status
|
|
8443
|
+
|
|
8444
|
+
${skillsUsage}`);
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
|
|
8448
|
+
// src/agents/rotate.ts
|
|
8449
|
+
import os7 from "node:os";
|
|
8450
|
+
import path9 from "node:path";
|
|
8243
8451
|
function defaultTokenFilePath() {
|
|
8244
|
-
return
|
|
8452
|
+
return path9.join(os7.homedir(), ".divergent", "token");
|
|
8245
8453
|
}
|
|
8246
8454
|
var MIN_ROTATE_INTERVAL_SECONDS = 60;
|
|
8247
8455
|
async function rotateTokenFile(loop) {
|
|
@@ -8256,19 +8464,19 @@ async function rotateTokenFile(loop) {
|
|
|
8256
8464
|
}
|
|
8257
8465
|
|
|
8258
8466
|
// src/agents/self-command.ts
|
|
8259
|
-
import
|
|
8260
|
-
import
|
|
8467
|
+
import fs7 from "node:fs";
|
|
8468
|
+
import path10 from "node:path";
|
|
8261
8469
|
function resolveSelfCommand(argv = process.argv, platform = process.platform, execPath = process.execPath) {
|
|
8262
8470
|
const script = argv[1];
|
|
8263
8471
|
if (!script) return { command: execPath, args: [] };
|
|
8264
8472
|
if (platform !== "win32") {
|
|
8265
8473
|
try {
|
|
8266
|
-
|
|
8267
|
-
return { command:
|
|
8474
|
+
fs7.accessSync(script, fs7.constants.X_OK);
|
|
8475
|
+
return { command: path10.resolve(script), args: [] };
|
|
8268
8476
|
} catch {
|
|
8269
8477
|
}
|
|
8270
8478
|
}
|
|
8271
|
-
return { command: execPath, args: [
|
|
8479
|
+
return { command: execPath, args: [fs7.realpathSync(script)] };
|
|
8272
8480
|
}
|
|
8273
8481
|
function shellCommand(self, ...trailing) {
|
|
8274
8482
|
return [self.command, ...self.args, ...trailing].map((part) => /[\s"]/.test(part) ? `"${part.replace(/"/g, '\\"')}"` : part).join(" ");
|
|
@@ -8295,8 +8503,8 @@ function surfaceOf(value, fallback) {
|
|
|
8295
8503
|
throw new CliError(`--surface must be one of: ${SURFACES.join(", ")}`);
|
|
8296
8504
|
}
|
|
8297
8505
|
function readJson(file) {
|
|
8298
|
-
if (!
|
|
8299
|
-
return JSON.parse(
|
|
8506
|
+
if (!fs8.existsSync(file)) return null;
|
|
8507
|
+
return JSON.parse(fs8.readFileSync(file, "utf8"));
|
|
8300
8508
|
}
|
|
8301
8509
|
async function servedModels(apiUrl, token, surface) {
|
|
8302
8510
|
return (await listModels(apiUrl, token.access_token, surface)).map((entry) => entry.id);
|
|
@@ -8313,25 +8521,32 @@ function syncDivergentPreset(io, binary, home) {
|
|
|
8313
8521
|
return;
|
|
8314
8522
|
}
|
|
8315
8523
|
const dir = writeDivergentPreset(home, shipped);
|
|
8316
|
-
const settingsPath =
|
|
8317
|
-
const settings = withDivergentPresetDefault(
|
|
8524
|
+
const settingsPath = path11.join(home, "settings.yaml");
|
|
8525
|
+
const settings = withDivergentPresetDefault(fs8.existsSync(settingsPath) ? fs8.readFileSync(settingsPath, "utf8") : null);
|
|
8318
8526
|
if (settings !== null) writeTextAtomic(settingsPath, settings);
|
|
8319
8527
|
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
8528
|
}
|
|
8529
|
+
async function skillsFor(io, apiUrl, harness) {
|
|
8530
|
+
try {
|
|
8531
|
+
const { skills } = await installableSkillSet(apiUrl, io.env);
|
|
8532
|
+
installForHarness(io, harness, skills, apiUrl);
|
|
8533
|
+
} catch (error) {
|
|
8534
|
+
io.stderr(`warning: skills not installed (${error instanceof Error ? error.message : String(error)}); run: diverge skills install`);
|
|
8535
|
+
}
|
|
8536
|
+
}
|
|
8321
8537
|
async function overlayAndDefault(apiUrl, token, surface, io, requested) {
|
|
8322
8538
|
const models = await servedModels(apiUrl, token, surface);
|
|
8323
8539
|
if (models.length === 0) throw new CliError(`the gateway serves no models on the ${surface} surface for this organization`);
|
|
8324
8540
|
if (requested && !models.includes(requested)) throw new CliError(`${requested} is not served on the ${surface} surface; choose one of: ${models.join(", ")}`);
|
|
8325
8541
|
const defaultModel = dshDefaultModel(models, requested);
|
|
8326
|
-
|
|
8327
|
-
return { overlay: dshPatchYaml({ baseUrl: surfaceBaseUrl(apiUrl, surface), surface, models, defaultModel, bridge }), defaultModel, models };
|
|
8542
|
+
return { overlay: dshPatchYaml({ baseUrl: surfaceBaseUrl(apiUrl, surface), surface, models, defaultModel }), defaultModel, models };
|
|
8328
8543
|
}
|
|
8329
8544
|
async function setup(args, io) {
|
|
8330
8545
|
const [agent, ...rest] = args;
|
|
8331
8546
|
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
8332
8547
|
switch (agent) {
|
|
8333
8548
|
case "claude": {
|
|
8334
|
-
const { values } =
|
|
8549
|
+
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
8550
|
if (values.scope !== "user" && values.scope !== "project") throw new CliError("--scope must be user or project");
|
|
8336
8551
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
8337
8552
|
const file = claudeSettingsPath(values.scope);
|
|
@@ -8344,10 +8559,11 @@ async function setup(args, io) {
|
|
|
8344
8559
|
writeJsonAtomic(file, settings);
|
|
8345
8560
|
io.stdout(`Claude Code \u2192 ${anthropicBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
|
|
8346
8561
|
for (const warning of warnings) io.stderr(`warning: ${warning}`);
|
|
8562
|
+
await skillsFor(io, apiUrl, "claude");
|
|
8347
8563
|
return ExitCode.Ok;
|
|
8348
8564
|
}
|
|
8349
8565
|
case "opencode": {
|
|
8350
|
-
const { values } =
|
|
8566
|
+
const { values } = parseArgs2({ args: rest, options: { surface: { type: "string" }, "organization-id": { type: "string" }, config: { type: "string" } }, strict: true });
|
|
8351
8567
|
const surface = surfaceOf(values.surface, "anthropic");
|
|
8352
8568
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
8353
8569
|
const baseUrl = surfaceBaseUrl(apiUrl, surface);
|
|
@@ -8365,7 +8581,7 @@ async function setup(args, io) {
|
|
|
8365
8581
|
return ExitCode.Ok;
|
|
8366
8582
|
}
|
|
8367
8583
|
case "codex": {
|
|
8368
|
-
const { values } =
|
|
8584
|
+
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
8585
|
const block = codexProviderToml({ openaiBaseUrl: openaiBaseUrl(apiUrl), self: resolveSelfCommand(), projectId: values["project-id"] });
|
|
8370
8586
|
if (values.print) {
|
|
8371
8587
|
io.stdout(block.trimEnd());
|
|
@@ -8373,25 +8589,27 @@ async function setup(args, io) {
|
|
|
8373
8589
|
}
|
|
8374
8590
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
8375
8591
|
const file = values.config ?? codexConfigPath();
|
|
8376
|
-
const existing =
|
|
8592
|
+
const existing = fs8.existsSync(file) ? fs8.readFileSync(file, "utf8") : null;
|
|
8377
8593
|
writeTextAtomic(file, mergeCodexConfig(existing, block));
|
|
8378
8594
|
io.stdout(`Codex provider divergent \u2192 ${openaiBaseUrl(apiUrl)} as you, organization ${tokenClaims(token.access_token).organizationId}. Wrote ${file}.`);
|
|
8379
8595
|
io.stdout('Select it per run with: codex -c model_provider=divergent (or set model_provider = "divergent" in the config)');
|
|
8596
|
+
await skillsFor(io, apiUrl, "codex");
|
|
8380
8597
|
return ExitCode.Ok;
|
|
8381
8598
|
}
|
|
8382
8599
|
case "dsh": {
|
|
8383
|
-
const { values } =
|
|
8600
|
+
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
8601
|
const surface = surfaceOf(values.surface, "openai");
|
|
8385
8602
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
8603
|
+
if (!values.print) await skillsFor(io, apiUrl, "dsh");
|
|
8386
8604
|
const { overlay, defaultModel } = await overlayAndDefault(apiUrl, token, surface, io, values.model);
|
|
8387
8605
|
if (values.print) {
|
|
8388
8606
|
io.stdout(overlay.trimEnd());
|
|
8389
8607
|
return ExitCode.Ok;
|
|
8390
8608
|
}
|
|
8391
|
-
const file = values.into ??
|
|
8609
|
+
const file = values.into ?? path11.join(dshHome(io.env), `divergent-${surface}.yml`);
|
|
8392
8610
|
writeTextAtomic(file, overlay);
|
|
8393
8611
|
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("
|
|
8612
|
+
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
8613
|
syncDivergentPreset(io, findOnPath("dsh", io.env), dshHome(io.env));
|
|
8396
8614
|
io.stdout("Boot a profile with the overlay and the token in its environment (user tokens expire; the login refreshes):");
|
|
8397
8615
|
io.stdout(` DIVERGENT_TOKEN="$(diverge token)" dsh --profile headless --patch ${file} "<task>"`);
|
|
@@ -8412,24 +8630,25 @@ ${agentsUsage}`);
|
|
|
8412
8630
|
const terminator = rest.indexOf("--");
|
|
8413
8631
|
const flags = terminator === -1 ? rest : rest.slice(0, terminator);
|
|
8414
8632
|
const passthrough = terminator === -1 ? [] : rest.slice(terminator + 1);
|
|
8415
|
-
const { values } =
|
|
8633
|
+
const { values } = parseArgs2({ args: flags, options: { surface: { type: "string" }, model: { type: "string" }, "organization-id": { type: "string" } }, strict: true });
|
|
8416
8634
|
const surface = surfaceOf(values.surface, "openai");
|
|
8417
8635
|
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
8418
8636
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
8419
8637
|
const binary = findOnPath("dsh", io.env);
|
|
8420
8638
|
if (!binary) throw new CliError("dsh is not on PATH (install: npm install -g @deepseek-ai/dsh)");
|
|
8639
|
+
await skillsFor(io, apiUrl, "dsh");
|
|
8421
8640
|
const { overlay, defaultModel } = await overlayAndDefault(apiUrl, token, surface, io, values.model);
|
|
8422
8641
|
const home = dshHome(io.env);
|
|
8423
|
-
const settingsPath =
|
|
8642
|
+
const settingsPath = path11.join(home, "settings.yaml");
|
|
8424
8643
|
let pinned;
|
|
8425
8644
|
try {
|
|
8426
|
-
pinned = dshSettingsDefaultModel(
|
|
8645
|
+
pinned = dshSettingsDefaultModel(fs8.existsSync(settingsPath) ? fs8.readFileSync(settingsPath, "utf8") : null, surface);
|
|
8427
8646
|
} catch (error) {
|
|
8428
8647
|
throw new CliError(`${settingsPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
8429
8648
|
}
|
|
8430
8649
|
const served = servedIds(overlay);
|
|
8431
8650
|
if (pinned && !served.includes(pinned)) throw new CliError(stalePinMessage(settingsPath, pinned, surface, served));
|
|
8432
|
-
const overlayPath =
|
|
8651
|
+
const overlayPath = path11.join(home, `divergent-${surface}.yml`);
|
|
8433
8652
|
writeTextAtomic(overlayPath, overlay);
|
|
8434
8653
|
syncDivergentPreset(io, binary, home);
|
|
8435
8654
|
io.stderr(`dsh on divergent-${surface} as you (organization ${tokenClaims(token.access_token).organizationId}), default model ${defaultModel}`);
|
|
@@ -8438,7 +8657,7 @@ ${agentsUsage}`);
|
|
|
8438
8657
|
return runDsh({
|
|
8439
8658
|
binary,
|
|
8440
8659
|
overlayPath,
|
|
8441
|
-
credentialsPath:
|
|
8660
|
+
credentialsPath: path11.join(home, ".credentials.yaml"),
|
|
8442
8661
|
args: passthrough,
|
|
8443
8662
|
env: io.env,
|
|
8444
8663
|
initialToken: token.access_token,
|
|
@@ -8453,7 +8672,7 @@ ${agentsUsage}`);
|
|
|
8453
8672
|
});
|
|
8454
8673
|
}
|
|
8455
8674
|
async function status(args, io) {
|
|
8456
|
-
const { values } =
|
|
8675
|
+
const { values } = parseArgs2({ args, options: { "organization-id": { type: "string" } }, strict: true });
|
|
8457
8676
|
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
8458
8677
|
const token = await ensureInferenceLogin(io, apiUrl, values["organization-id"]);
|
|
8459
8678
|
const claims = tokenClaims(token.access_token);
|
|
@@ -8467,7 +8686,7 @@ async function status(args, io) {
|
|
|
8467
8686
|
return ExitCode.Ok;
|
|
8468
8687
|
}
|
|
8469
8688
|
async function rotate(args, io) {
|
|
8470
|
-
const { values } =
|
|
8689
|
+
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
8690
|
const interval = Number(values.interval);
|
|
8472
8691
|
if (!Number.isFinite(interval) || interval <= 0) throw new CliError("--interval must be a positive number of seconds");
|
|
8473
8692
|
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
@@ -8513,7 +8732,7 @@ ${agentsUsage}`);
|
|
|
8513
8732
|
}
|
|
8514
8733
|
|
|
8515
8734
|
// src/commands/login.ts
|
|
8516
|
-
import { parseArgs as
|
|
8735
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
8517
8736
|
|
|
8518
8737
|
// src/auth/organizations.ts
|
|
8519
8738
|
function chooseOrganization(input) {
|
|
@@ -8597,7 +8816,7 @@ function resolveLoginHint(flag, config) {
|
|
|
8597
8816
|
return remembered || void 0;
|
|
8598
8817
|
}
|
|
8599
8818
|
async function runLogin(args, io) {
|
|
8600
|
-
const { values } =
|
|
8819
|
+
const { values } = parseArgs3({
|
|
8601
8820
|
args,
|
|
8602
8821
|
options: {
|
|
8603
8822
|
"organization-id": { type: "string" },
|
|
@@ -8667,7 +8886,7 @@ ${organizationMenu(choice.options).join("\n")}`);
|
|
|
8667
8886
|
}
|
|
8668
8887
|
|
|
8669
8888
|
// src/commands/org.ts
|
|
8670
|
-
import { parseArgs as
|
|
8889
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
8671
8890
|
var orgUsage = `diverge org [use <id|name>]
|
|
8672
8891
|
|
|
8673
8892
|
Without a verb: the organizations you may act in, the bound one marked.
|
|
@@ -8675,7 +8894,7 @@ use <id|name>: bind the login to that organization on the platform, without a
|
|
|
8675
8894
|
browser; every later command acts there. A platform actor may name an
|
|
8676
8895
|
organization they are not a member of.`;
|
|
8677
8896
|
async function runOrg(args, io) {
|
|
8678
|
-
const { positionals } =
|
|
8897
|
+
const { positionals } = parseArgs4({ args, options: {}, strict: true, allowPositionals: true });
|
|
8679
8898
|
const apiUrl = resolveApiUrl(void 0, io.env);
|
|
8680
8899
|
const source = { tokenFile: tokenFile(io.env), baseUrl: apiUrl, clientId: clientId(io.env) };
|
|
8681
8900
|
const token = await getUserToken(source);
|
|
@@ -8835,7 +9054,7 @@ async function runToken(_args, io) {
|
|
|
8835
9054
|
}
|
|
8836
9055
|
|
|
8837
9056
|
// src/commands/tools.ts
|
|
8838
|
-
import { parseArgs as
|
|
9057
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
8839
9058
|
|
|
8840
9059
|
// src/mcp/bridge.ts
|
|
8841
9060
|
import readline2 from "node:readline";
|
|
@@ -8891,10 +9110,10 @@ var ToolsClient = class {
|
|
|
8891
9110
|
if (this.settings.mode === "service") headers["x-divergent-user-id"] = this.settings.delegatedUserId;
|
|
8892
9111
|
return headers;
|
|
8893
9112
|
}
|
|
8894
|
-
scoped(
|
|
9113
|
+
scoped(path13) {
|
|
8895
9114
|
const query = new URLSearchParams({ organizationId: this.settings.organizationId });
|
|
8896
9115
|
if (this.settings.projectId) query.set("projectId", this.settings.projectId);
|
|
8897
|
-
return `${this.settings.apiUrl}${
|
|
9116
|
+
return `${this.settings.apiUrl}${path13}?${query.toString()}`;
|
|
8898
9117
|
}
|
|
8899
9118
|
async send(method, url, body) {
|
|
8900
9119
|
return this.request(method, url, body, void 0);
|
|
@@ -9161,6 +9380,98 @@ async function waitForInvocation(id, options, deps) {
|
|
|
9161
9380
|
}
|
|
9162
9381
|
}
|
|
9163
9382
|
|
|
9383
|
+
// src/tools/call-args.ts
|
|
9384
|
+
import fs9 from "node:fs";
|
|
9385
|
+
function resolveCallArgs(options, readStdin = () => fs9.readFileSync(0, "utf8")) {
|
|
9386
|
+
if (options.argsJson !== void 0 && options.argsJson !== "{}" && options.argsFile !== void 0) {
|
|
9387
|
+
throw new CliError("give the arguments once: --args-json or --args-file");
|
|
9388
|
+
}
|
|
9389
|
+
let text;
|
|
9390
|
+
let source;
|
|
9391
|
+
if (options.argsFile !== void 0) {
|
|
9392
|
+
source = `--args-file ${options.argsFile}`;
|
|
9393
|
+
try {
|
|
9394
|
+
text = fs9.readFileSync(options.argsFile, "utf8");
|
|
9395
|
+
} catch (error) {
|
|
9396
|
+
throw new CliError(`${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9397
|
+
}
|
|
9398
|
+
} else if (options.argsJson === "-") {
|
|
9399
|
+
source = "--args-json - (stdin)";
|
|
9400
|
+
text = readStdin();
|
|
9401
|
+
} else {
|
|
9402
|
+
source = "--args-json";
|
|
9403
|
+
text = options.argsJson ?? "{}";
|
|
9404
|
+
}
|
|
9405
|
+
let parsed;
|
|
9406
|
+
try {
|
|
9407
|
+
parsed = JSON.parse(text.trim() === "" ? "{}" : text);
|
|
9408
|
+
} catch (error) {
|
|
9409
|
+
throw new CliError(`${source} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
9410
|
+
}
|
|
9411
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError(`${source} must be a JSON object`);
|
|
9412
|
+
return parsed;
|
|
9413
|
+
}
|
|
9414
|
+
|
|
9415
|
+
// src/tools/batch.ts
|
|
9416
|
+
function parseBatch(text) {
|
|
9417
|
+
const trimmed = text.trim();
|
|
9418
|
+
if (!trimmed) throw new Error("the batch file is empty");
|
|
9419
|
+
const raw = trimmed.startsWith("[") ? JSON.parse(trimmed) : trimmed.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
9420
|
+
if (!Array.isArray(raw)) throw new Error("the batch file must hold a JSON array or one JSON object per line");
|
|
9421
|
+
return raw.map((entry, index) => {
|
|
9422
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`entry ${index + 1} is not an object`);
|
|
9423
|
+
const { tool, args } = entry;
|
|
9424
|
+
if (typeof tool !== "string" || !tool) throw new Error(`entry ${index + 1} names no tool`);
|
|
9425
|
+
if (args !== void 0 && (!args || typeof args !== "object" || Array.isArray(args))) throw new Error(`entry ${index + 1}: args must be an object`);
|
|
9426
|
+
return { tool, args: args ?? {} };
|
|
9427
|
+
});
|
|
9428
|
+
}
|
|
9429
|
+
async function runBatch(entries, deps) {
|
|
9430
|
+
const outcomes = [];
|
|
9431
|
+
for (const entry of entries) {
|
|
9432
|
+
const outcome = await runOne(entry, deps);
|
|
9433
|
+
outcomes.push(outcome);
|
|
9434
|
+
deps.onOutcome?.(outcome);
|
|
9435
|
+
}
|
|
9436
|
+
return { outcomes, exitCode: worstExitCode(outcomes) };
|
|
9437
|
+
}
|
|
9438
|
+
async function runOne(entry, deps) {
|
|
9439
|
+
let body;
|
|
9440
|
+
try {
|
|
9441
|
+
body = await deps.callTool(entry.tool, entry.args);
|
|
9442
|
+
} catch (error) {
|
|
9443
|
+
return { tool: entry.tool, status: "failed", error: error instanceof Error ? error.message : String(error) };
|
|
9444
|
+
}
|
|
9445
|
+
if (body.success === false) return { tool: entry.tool, status: "failed", error: body.error || "Tool execution failed" };
|
|
9446
|
+
if (!isPending(body.result)) return { tool: entry.tool, status: "executed", result: body.result ?? null };
|
|
9447
|
+
const pending = { tool: entry.tool, status: "pending", invocationId: body.result.invocationId, decideAt: deps.decideAt(body.result) };
|
|
9448
|
+
if (!deps.waitFor) return pending;
|
|
9449
|
+
const settled = await deps.waitFor(body.result);
|
|
9450
|
+
switch (settled?.status) {
|
|
9451
|
+
case "succeeded":
|
|
9452
|
+
return { tool: entry.tool, status: "executed", result: settled.result ?? null, invocationId: pending.invocationId };
|
|
9453
|
+
case "rejected":
|
|
9454
|
+
return { tool: entry.tool, status: "rejected", invocationId: pending.invocationId, error: settled.decisionReason ?? void 0 };
|
|
9455
|
+
case "expired":
|
|
9456
|
+
return { tool: entry.tool, status: "expired", invocationId: pending.invocationId };
|
|
9457
|
+
case "failed":
|
|
9458
|
+
return { tool: entry.tool, status: "failed", invocationId: pending.invocationId, error: settled.errorMessage || "execution failed" };
|
|
9459
|
+
default:
|
|
9460
|
+
return pending;
|
|
9461
|
+
}
|
|
9462
|
+
}
|
|
9463
|
+
function worstExitCode(outcomes) {
|
|
9464
|
+
const has = (status2) => outcomes.some((outcome) => outcome.status === status2);
|
|
9465
|
+
if (has("failed")) return ExitCode.Failure;
|
|
9466
|
+
if (has("rejected")) return ExitCode.Rejected;
|
|
9467
|
+
if (has("expired")) return ExitCode.Expired;
|
|
9468
|
+
if (has("pending")) return ExitCode.Pending;
|
|
9469
|
+
return ExitCode.Ok;
|
|
9470
|
+
}
|
|
9471
|
+
|
|
9472
|
+
// src/commands/tools.ts
|
|
9473
|
+
import fs10 from "node:fs";
|
|
9474
|
+
|
|
9164
9475
|
// src/tools/print.ts
|
|
9165
9476
|
function matchesCapability(selector, capability) {
|
|
9166
9477
|
if (!capability) return false;
|
|
@@ -9241,12 +9552,16 @@ function settledOutcome(invocation) {
|
|
|
9241
9552
|
}
|
|
9242
9553
|
|
|
9243
9554
|
// src/commands/tools.ts
|
|
9244
|
-
var toolsUsage = `diverge tools <list|capabilities|describe|call|invocation|bridge> [--organization-id <id>] [--project-id <id>] ...
|
|
9555
|
+
var toolsUsage = `diverge tools <list|capabilities|describe|call|run|invocation|bridge> [--organization-id <id>] [--project-id <id>] ...
|
|
9245
9556
|
|
|
9246
9557
|
list [--filter <substring>] [--capability <selector>] [--json]
|
|
9247
9558
|
capabilities [--json]
|
|
9248
9559
|
describe <name> [--json]
|
|
9249
|
-
call <name> [--args-json '{...}'] [--wait] [--timeout <seconds>]
|
|
9560
|
+
call <name> [--args-json '{...}' | --args-json - | --args-file <path>] [--wait] [--timeout <seconds>]
|
|
9561
|
+
arguments inline, from stdin (-), or from a file: PowerShell strips quotes inside inline JSON
|
|
9562
|
+
run <file> [--wait] [--timeout <seconds>]
|
|
9563
|
+
several calls from one file (a JSON array of {tool, args}, or one per line), in order,
|
|
9564
|
+
one JSON line of outcome each; exit code is the worst: 1 failed, 4 rejected, 5 expired, 3 pending
|
|
9250
9565
|
invocation <id> [--wait] [--timeout <seconds>] [--json]
|
|
9251
9566
|
bridge [--tools <name,name>] (stdio MCP server; the allowlist keeps a harness to the tools it was given)
|
|
9252
9567
|
|
|
@@ -9282,7 +9597,7 @@ async function runTools(args, io) {
|
|
|
9282
9597
|
}
|
|
9283
9598
|
switch (verb) {
|
|
9284
9599
|
case "list": {
|
|
9285
|
-
const { values } =
|
|
9600
|
+
const { values } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, filter: { type: "string" }, capability: { type: "string" }, json: { type: "boolean", default: false } }, strict: true });
|
|
9286
9601
|
const { client: tools } = await client(values, io);
|
|
9287
9602
|
let list = await tools.listTools();
|
|
9288
9603
|
if (values.filter) list = list.filter((tool) => tool.name.includes(values.filter));
|
|
@@ -9292,7 +9607,7 @@ async function runTools(args, io) {
|
|
|
9292
9607
|
return ExitCode.Ok;
|
|
9293
9608
|
}
|
|
9294
9609
|
case "capabilities": {
|
|
9295
|
-
const { values } =
|
|
9610
|
+
const { values } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true });
|
|
9296
9611
|
const { client: tools } = await client(values, io);
|
|
9297
9612
|
const list = await tools.listTools();
|
|
9298
9613
|
if (values.json) io.stdout(JSON.stringify(capabilityClusters(list), null, 2));
|
|
@@ -9300,7 +9615,7 @@ async function runTools(args, io) {
|
|
|
9300
9615
|
return ExitCode.Ok;
|
|
9301
9616
|
}
|
|
9302
9617
|
case "describe": {
|
|
9303
|
-
const { values, positionals } =
|
|
9618
|
+
const { values, positionals } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, json: { type: "boolean", default: false } }, strict: true, allowPositionals: true });
|
|
9304
9619
|
const name = positionals[0];
|
|
9305
9620
|
if (!name) throw new CliError("describe needs a tool name");
|
|
9306
9621
|
const { settings, client: tools } = await client(values, io);
|
|
@@ -9315,16 +9630,10 @@ async function runTools(args, io) {
|
|
|
9315
9630
|
return ExitCode.Ok;
|
|
9316
9631
|
}
|
|
9317
9632
|
case "call": {
|
|
9318
|
-
const { values, positionals } =
|
|
9633
|
+
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
9634
|
const name = positionals[0];
|
|
9320
9635
|
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");
|
|
9636
|
+
const parsedArgs = resolveCallArgs({ argsJson: values["args-json"], argsFile: values["args-file"] });
|
|
9328
9637
|
const timeoutMs = Number(values.timeout) * 1e3;
|
|
9329
9638
|
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new CliError("--timeout must be a number of seconds");
|
|
9330
9639
|
const { settings, client: tools } = await client(values, io);
|
|
@@ -9343,8 +9652,34 @@ async function runTools(args, io) {
|
|
|
9343
9652
|
}
|
|
9344
9653
|
return emit(io, settledOutcome(invocation));
|
|
9345
9654
|
}
|
|
9655
|
+
case "run": {
|
|
9656
|
+
const { values, positionals } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, wait: { type: "boolean", default: false }, timeout: { type: "string", default: "600" } }, strict: true, allowPositionals: true });
|
|
9657
|
+
const file = positionals[0];
|
|
9658
|
+
if (!file) throw new CliError("run needs a file of calls");
|
|
9659
|
+
let entries;
|
|
9660
|
+
try {
|
|
9661
|
+
entries = parseBatch(fs10.readFileSync(file, "utf8"));
|
|
9662
|
+
} catch (error) {
|
|
9663
|
+
throw new CliError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9664
|
+
}
|
|
9665
|
+
const timeoutMs = Number(values.timeout) * 1e3;
|
|
9666
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new CliError("--timeout must be a number of seconds");
|
|
9667
|
+
const { settings, client: tools } = await client(values, io);
|
|
9668
|
+
const { exitCode } = await runBatch(entries, {
|
|
9669
|
+
callTool: (tool, args2) => tools.callTool(tool, args2),
|
|
9670
|
+
decideAt: (pending) => `${settings.apiUrl}${pending.approvalUrl ?? ""}`,
|
|
9671
|
+
onOutcome: (outcome) => io.stdout(JSON.stringify(outcome)),
|
|
9672
|
+
...values.wait ? {
|
|
9673
|
+
waitFor: async (pending) => {
|
|
9674
|
+
const invocation = await waitForInvocation(pending.invocationId ?? "", { pollAfterMs: pending.pollAfterMs, timeoutMs }, { getInvocation: (id) => tools.getInvocation(id), sleep: realSleep, now: Date.now });
|
|
9675
|
+
return PENDING_INVOCATION_STATUSES.includes(invocation.status ?? "") ? null : invocation;
|
|
9676
|
+
}
|
|
9677
|
+
} : {}
|
|
9678
|
+
});
|
|
9679
|
+
return exitCode;
|
|
9680
|
+
}
|
|
9346
9681
|
case "invocation": {
|
|
9347
|
-
const { values, positionals } =
|
|
9682
|
+
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
9683
|
const id = positionals[0];
|
|
9349
9684
|
if (!id) throw new CliError("invocation needs an id");
|
|
9350
9685
|
const { client: tools } = await client(values, io);
|
|
@@ -9362,21 +9697,21 @@ async function runTools(args, io) {
|
|
|
9362
9697
|
return emit(io, settledOutcome(invocation));
|
|
9363
9698
|
}
|
|
9364
9699
|
case "bridge": {
|
|
9365
|
-
const { values } =
|
|
9700
|
+
const { values } = parseArgs5({ args: rest, options: { ...SCOPE_OPTIONS, tools: { type: "string" } }, strict: true });
|
|
9366
9701
|
const { client: tools } = await client(values, io);
|
|
9367
9702
|
const allow = (values.tools ?? "").split(",").map((name) => name.trim()).filter(Boolean);
|
|
9368
9703
|
return serveBridge(new BridgeServer(tools, { tools: allow }), process.stdin, process.stdout);
|
|
9369
9704
|
}
|
|
9370
9705
|
default:
|
|
9371
|
-
throw new CliError(`tools needs one of: list, capabilities, describe, call, invocation, bridge
|
|
9706
|
+
throw new CliError(`tools needs one of: list, capabilities, describe, call, run, invocation, bridge
|
|
9372
9707
|
|
|
9373
9708
|
${toolsUsage}`);
|
|
9374
9709
|
}
|
|
9375
9710
|
}
|
|
9376
9711
|
|
|
9377
9712
|
// src/commands/plugins.ts
|
|
9378
|
-
import
|
|
9379
|
-
import { parseArgs as
|
|
9713
|
+
import fs11 from "node:fs";
|
|
9714
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
9380
9715
|
var pluginsUsage = `diverge plugins <validate|publish|list|retire> [--organization-id <id>] ...
|
|
9381
9716
|
|
|
9382
9717
|
validate <manifest.json> (dry run: the platform's full validation, nothing stored)
|
|
@@ -9402,7 +9737,7 @@ function readManifest(file) {
|
|
|
9402
9737
|
${pluginsUsage}`);
|
|
9403
9738
|
let text;
|
|
9404
9739
|
try {
|
|
9405
|
-
text =
|
|
9740
|
+
text = fs11.readFileSync(file, "utf8");
|
|
9406
9741
|
} catch (error) {
|
|
9407
9742
|
throw new CliError(`Cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9408
9743
|
}
|
|
@@ -9439,7 +9774,7 @@ async function runPlugins(args, io) {
|
|
|
9439
9774
|
}
|
|
9440
9775
|
switch (verb) {
|
|
9441
9776
|
case "validate": {
|
|
9442
|
-
const { values, positionals } =
|
|
9777
|
+
const { values, positionals } = parseArgs6({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
9443
9778
|
const manifest = readManifest(positionals[0]);
|
|
9444
9779
|
const tools = await client2(values, io);
|
|
9445
9780
|
const outcome = await tools.publishPlugin(manifest, { dryRun: true });
|
|
@@ -9456,7 +9791,7 @@ async function runPlugins(args, io) {
|
|
|
9456
9791
|
return ExitCode.Ok;
|
|
9457
9792
|
}
|
|
9458
9793
|
case "publish": {
|
|
9459
|
-
const { values, positionals } =
|
|
9794
|
+
const { values, positionals } = parseArgs6({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
9460
9795
|
const manifest = readManifest(positionals[0]);
|
|
9461
9796
|
const tools = await client2(values, io);
|
|
9462
9797
|
const outcome = await tools.publishPlugin(manifest, { dryRun: false });
|
|
@@ -9474,7 +9809,7 @@ async function runPlugins(args, io) {
|
|
|
9474
9809
|
return ExitCode.Ok;
|
|
9475
9810
|
}
|
|
9476
9811
|
case "list": {
|
|
9477
|
-
const { values } =
|
|
9812
|
+
const { values } = parseArgs6({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, strict: true });
|
|
9478
9813
|
const tools = await client2(values, io);
|
|
9479
9814
|
const plugins = await tools.listPlugins();
|
|
9480
9815
|
if (values.json) io.stdout(JSON.stringify(plugins, null, 2));
|
|
@@ -9483,7 +9818,7 @@ async function runPlugins(args, io) {
|
|
|
9483
9818
|
return ExitCode.Ok;
|
|
9484
9819
|
}
|
|
9485
9820
|
case "retire": {
|
|
9486
|
-
const { values, positionals } =
|
|
9821
|
+
const { values, positionals } = parseArgs6({ args: rest, options: SCOPE_OPTIONS2, allowPositionals: true, strict: true });
|
|
9487
9822
|
const pluginId = positionals[0];
|
|
9488
9823
|
if (!pluginId) throw new CliError(`A plugin id is required.
|
|
9489
9824
|
|
|
@@ -9505,16 +9840,16 @@ import { spawn as spawn4 } from "node:child_process";
|
|
|
9505
9840
|
|
|
9506
9841
|
// src/update-notice.ts
|
|
9507
9842
|
import { spawn as spawn3 } from "node:child_process";
|
|
9508
|
-
import
|
|
9509
|
-
import
|
|
9843
|
+
import fs12 from "node:fs";
|
|
9844
|
+
import path12 from "node:path";
|
|
9510
9845
|
var UPDATE_CHECK_FILE = "update-check.json";
|
|
9511
9846
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
9512
9847
|
function updateCheckFile(env) {
|
|
9513
|
-
return
|
|
9848
|
+
return path12.join(configDir(env), UPDATE_CHECK_FILE);
|
|
9514
9849
|
}
|
|
9515
9850
|
function readUpdateCheck(env) {
|
|
9516
9851
|
try {
|
|
9517
|
-
const parsed = JSON.parse(
|
|
9852
|
+
const parsed = JSON.parse(fs12.readFileSync(updateCheckFile(env), "utf8"));
|
|
9518
9853
|
if (parsed && typeof parsed === "object" && typeof parsed.checkedAt === "number" && typeof parsed.latest === "string") return parsed;
|
|
9519
9854
|
} catch {
|
|
9520
9855
|
}
|
|
@@ -9615,6 +9950,7 @@ var COMMANDS = [
|
|
|
9615
9950
|
{ name: "agents", summary: "Point a coding agent at the model gateway (setup, run, status)", usage: agentsUsage, run: runAgents },
|
|
9616
9951
|
{ name: "tools", summary: "Discover and execute platform tools; raise and read approvals", usage: toolsUsage, run: runTools },
|
|
9617
9952
|
{ name: "plugins", summary: "Validate, publish, list and retire tenant plugins for your organization", usage: pluginsUsage, run: runPlugins },
|
|
9953
|
+
{ name: "skills", summary: "Install your organization's skills into Claude Code, Codex and dsh", usage: skillsUsage, run: runSkills },
|
|
9618
9954
|
{ name: "update", summary: "Install the latest release", usage: updateUsage, run: (args, io) => runUpdate(args, io) }
|
|
9619
9955
|
];
|
|
9620
9956
|
function usage() {
|
package/package.json
CHANGED