@divergeai/cli 0.1.26 → 0.1.27
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 +13 -3
- package/dist/diverge.mjs +116 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -157,8 +157,13 @@ The set is derived from the login, never chosen on the machine: who is
|
|
|
157
157
|
signed in, where they may act, and what they can run there. A skill's
|
|
158
158
|
identity is its organization and its name, so every organization's skills
|
|
159
159
|
sit side by side in one folder, each under the organization's prefix — its
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
handle, the same lowercase slug that prefixes its case numbers, or a slug of
|
|
161
|
+
its name when it chose none: `acme-consultar-inventario`,
|
|
162
|
+
`red-team-triage-cloud-run`. A folder longer than the 64 characters a
|
|
163
|
+
harness accepts as a skill name is cut and ended with a short stable hash,
|
|
164
|
+
and its SKILL.md names the skill; a served folder that is not a skill name is
|
|
165
|
+
skipped with a warning naming it, and the folder that skill was installed
|
|
166
|
+
under before stays. Each SKILL.md is rendered from the skill's
|
|
162
167
|
record on every install, named after its folder, and opens with the
|
|
163
168
|
organization its tools run in, so an agent passes `--organization-id` on
|
|
164
169
|
every call instead of depending on the machine's default organization.
|
|
@@ -249,12 +254,17 @@ reaches it through your bridge binding. See
|
|
|
249
254
|
|
|
250
255
|
```bash
|
|
251
256
|
diverge plugins validate manifest.json # dry run: every problem the platform would refuse, at once
|
|
252
|
-
diverge plugins publish manifest.json # forge_plugins:create in the login's organization
|
|
257
|
+
diverge plugins publish manifest.json # forge_plugins:create in the login's organization; exits 3 when it waits on a decision
|
|
253
258
|
diverge plugins list
|
|
254
259
|
diverge plugins retire <pluginId> # the descriptor and its tools go; installations stay
|
|
255
260
|
```
|
|
256
261
|
|
|
257
262
|
Tools are pinned to the manifest: a change is a new version published again.
|
|
263
|
+
A version that only reshapes or removes the tools already served is stored at
|
|
264
|
+
once. Anything else changes authority and is stored only once a person decides
|
|
265
|
+
it: `publish` raises it as `forge_publish_plugin`, prints the invocation and
|
|
266
|
+
its Compass link, and exits 3; read the outcome with
|
|
267
|
+
`diverge tools invocation <id>`.
|
|
258
268
|
After publishing, enable the plugin with `forge_set_plugin_state` (or the
|
|
259
269
|
Forge Routing page) and bind the runtime with a bridge binding whose
|
|
260
270
|
`runtimeType` equals the manifest's `runtime.type`; declare `networkPolicy`
|
package/dist/diverge.mjs
CHANGED
|
@@ -7556,7 +7556,7 @@ function tokenSource(env, baseUrl) {
|
|
|
7556
7556
|
}
|
|
7557
7557
|
|
|
7558
7558
|
// src/version.ts
|
|
7559
|
-
var VERSION = true ? "0.1.
|
|
7559
|
+
var VERSION = true ? "0.1.27" : "dev";
|
|
7560
7560
|
var USER_AGENT = `diverge/${VERSION}`;
|
|
7561
7561
|
|
|
7562
7562
|
// src/http.ts
|
|
@@ -8361,8 +8361,16 @@ function describeToolExecution(env = process.env) {
|
|
|
8361
8361
|
}
|
|
8362
8362
|
|
|
8363
8363
|
// src/tools/client.ts
|
|
8364
|
+
var AUTHORITY_CHANGE_CODE = "REMOTE_PLUGIN_AUTHORITY_CHANGE";
|
|
8364
8365
|
var INVOCATION_SET_TOOL = "functions_invocation_set";
|
|
8365
8366
|
var PENDING_INVOCATION_STATUSES = ["pending", "approved", "executing"];
|
|
8367
|
+
function parseJson(text2) {
|
|
8368
|
+
try {
|
|
8369
|
+
return JSON.parse(text2);
|
|
8370
|
+
} catch {
|
|
8371
|
+
return void 0;
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8366
8374
|
function isPending(result) {
|
|
8367
8375
|
return !!result && typeof result === "object" && result.status === "pending_approval";
|
|
8368
8376
|
}
|
|
@@ -8451,7 +8459,7 @@ var ToolsClient = class {
|
|
|
8451
8459
|
if (typeof parsed.error === "string" && parsed.error) message = parsed.error;
|
|
8452
8460
|
} catch {
|
|
8453
8461
|
}
|
|
8454
|
-
if (response.status === 403 &&
|
|
8462
|
+
if (response.status === 403 && /^insufficient scope$/i.test(message.trim())) {
|
|
8455
8463
|
throw new CliError(`${response.status}: ${message}
|
|
8456
8464
|
The signed-in token is missing a required scope. Re-run: ${LOGIN_HINT}`);
|
|
8457
8465
|
}
|
|
@@ -8523,14 +8531,14 @@ var ToolsClient = class {
|
|
|
8523
8531
|
this.descriptorsUrl(),
|
|
8524
8532
|
{ targetOrganizationId: this.settings.organizationId, manifest, dryRun: options.dryRun },
|
|
8525
8533
|
(status2, text2) => {
|
|
8526
|
-
if (status2
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
if (Array.isArray(parsed.details)) return { ok: false, details: parsed.details.map(String) };
|
|
8530
|
-
return { ok: false, details: [parsed.error ?? text2] };
|
|
8531
|
-
} catch {
|
|
8532
|
-
return { ok: false, details: [text2] };
|
|
8534
|
+
if (status2 === 403) {
|
|
8535
|
+
const parsed2 = parseJson(text2);
|
|
8536
|
+
return parsed2?.code === AUTHORITY_CHANGE_CODE && Array.isArray(parsed2.details) ? { ok: false, details: parsed2.details.map(String), authorityChange: true } : void 0;
|
|
8533
8537
|
}
|
|
8538
|
+
if (status2 !== 400) return void 0;
|
|
8539
|
+
const parsed = parseJson(text2);
|
|
8540
|
+
if (Array.isArray(parsed?.details)) return { ok: false, details: parsed.details.map(String) };
|
|
8541
|
+
return { ok: false, details: [parsed?.error ?? text2] };
|
|
8534
8542
|
}
|
|
8535
8543
|
).then((outcome) => "ok" in outcome ? outcome : { ok: true, result: outcome });
|
|
8536
8544
|
}
|
|
@@ -8802,6 +8810,19 @@ function readSkillsManifest(root) {
|
|
|
8802
8810
|
function few(names, limit = 6) {
|
|
8803
8811
|
return names.length <= limit ? names.join(", ") : `${names.slice(0, limit).join(", ")} and ${names.length - limit} more`;
|
|
8804
8812
|
}
|
|
8813
|
+
function isSkillFolder(folder) {
|
|
8814
|
+
return /^[a-z0-9][a-z0-9-]{0,63}$/.test(folder);
|
|
8815
|
+
}
|
|
8816
|
+
function retainedFolders(manifest, set2) {
|
|
8817
|
+
const retained = /* @__PURE__ */ new Map();
|
|
8818
|
+
for (const organization of set2.organizations) {
|
|
8819
|
+
const skipped = organization.skills.filter((skill) => !isSkillFolder(skill.folder));
|
|
8820
|
+
const held = Object.keys(manifest?.organizations[organization.id]?.skills ?? {});
|
|
8821
|
+
const kept = held.filter((folder) => skipped.some((skill) => folder.endsWith(`-${skill.name}`)));
|
|
8822
|
+
if (kept.length) retained.set(organization.id, kept);
|
|
8823
|
+
}
|
|
8824
|
+
return retained;
|
|
8825
|
+
}
|
|
8805
8826
|
function heldFolders(manifest) {
|
|
8806
8827
|
const held = /* @__PURE__ */ new Map();
|
|
8807
8828
|
for (const [folder, version] of Object.entries(manifest?.flat ?? {})) held.set(folder, version);
|
|
@@ -8811,9 +8832,10 @@ function heldFolders(manifest) {
|
|
|
8811
8832
|
}
|
|
8812
8833
|
function retirable(manifest, set2) {
|
|
8813
8834
|
const refreshed = new Set(set2.organizations.map((organization) => organization.id));
|
|
8835
|
+
const retained = new Set([...retainedFolders(manifest, set2).values()].flat());
|
|
8814
8836
|
const folders = [...Object.keys(manifest?.flat ?? {}), ...Object.keys(manifest?.cli ?? {})];
|
|
8815
8837
|
for (const [id, section] of Object.entries(manifest?.organizations ?? {})) {
|
|
8816
|
-
if (set2.complete || refreshed.has(id)) folders.push(...Object.keys(section.skills));
|
|
8838
|
+
if (set2.complete || refreshed.has(id)) folders.push(...Object.keys(section.skills).filter((folder) => !retained.has(folder)));
|
|
8817
8839
|
}
|
|
8818
8840
|
return folders;
|
|
8819
8841
|
}
|
|
@@ -8823,21 +8845,28 @@ function installSkills(root, set2, source, now = () => /* @__PURE__ */ new Date(
|
|
|
8823
8845
|
const written = [];
|
|
8824
8846
|
const added = [];
|
|
8825
8847
|
const updated = [];
|
|
8848
|
+
const skipped = [];
|
|
8826
8849
|
const counts = [];
|
|
8827
8850
|
const write = (skill) => {
|
|
8828
|
-
if (
|
|
8851
|
+
if (!isSkillFolder(skill.folder)) {
|
|
8852
|
+
skipped.push(skill.folder);
|
|
8853
|
+
return;
|
|
8854
|
+
}
|
|
8829
8855
|
writeTextAtomic(path5.join(root, skill.folder, "SKILL.md"), skill.content, 420);
|
|
8830
8856
|
written.push(skill.folder);
|
|
8831
|
-
const
|
|
8832
|
-
if (
|
|
8833
|
-
else if (
|
|
8857
|
+
const before2 = held.get(skill.folder);
|
|
8858
|
+
if (before2 === void 0) added.push(skill.folder);
|
|
8859
|
+
else if (before2 !== skill.updatedAt) updated.push(skill.folder);
|
|
8834
8860
|
};
|
|
8835
8861
|
for (const organization of set2.organizations) {
|
|
8862
|
+
const before2 = written.length;
|
|
8836
8863
|
organization.skills.forEach(write);
|
|
8837
|
-
counts.push({ label: organization.name, count:
|
|
8864
|
+
counts.push({ label: organization.name, count: written.length - before2 });
|
|
8838
8865
|
}
|
|
8866
|
+
const before = written.length;
|
|
8839
8867
|
set2.cli.forEach(write);
|
|
8840
|
-
if (set2.cli.length) counts.push({ label: "CLI", count:
|
|
8868
|
+
if (set2.cli.length) counts.push({ label: "CLI", count: written.length - before });
|
|
8869
|
+
const retained = retainedFolders(previous, set2);
|
|
8841
8870
|
const removed = [];
|
|
8842
8871
|
const kept = new Set(written);
|
|
8843
8872
|
for (const folder of retirable(previous, set2)) {
|
|
@@ -8855,7 +8884,10 @@ function installSkills(root, set2, source, now = () => /* @__PURE__ */ new Date(
|
|
|
8855
8884
|
organizations[organization.id] = {
|
|
8856
8885
|
name: organization.name,
|
|
8857
8886
|
installedAt: stamp,
|
|
8858
|
-
skills: Object.fromEntries(
|
|
8887
|
+
skills: Object.fromEntries([
|
|
8888
|
+
...(retained.get(organization.id) ?? []).map((folder) => [folder, previous.organizations[organization.id].skills[folder]]),
|
|
8889
|
+
...organization.skills.filter((skill) => isSkillFolder(skill.folder)).map((skill) => [skill.folder, skill.updatedAt])
|
|
8890
|
+
])
|
|
8859
8891
|
};
|
|
8860
8892
|
}
|
|
8861
8893
|
const manifest = {
|
|
@@ -8865,7 +8897,7 @@ function installSkills(root, set2, source, now = () => /* @__PURE__ */ new Date(
|
|
|
8865
8897
|
organizations
|
|
8866
8898
|
};
|
|
8867
8899
|
writeJsonAtomic(path5.join(root, SKILLS_MANIFEST), manifest, 420);
|
|
8868
|
-
return { root, written, added, updated, removed, counts };
|
|
8900
|
+
return { root, written, added, updated, removed, skipped, counts };
|
|
8869
8901
|
}
|
|
8870
8902
|
function skillsStatus(root, set2) {
|
|
8871
8903
|
const manifest = readSkillsManifest(root);
|
|
@@ -8873,7 +8905,9 @@ function skillsStatus(root, set2) {
|
|
|
8873
8905
|
const current = [];
|
|
8874
8906
|
const behind = [];
|
|
8875
8907
|
const missing = [];
|
|
8876
|
-
const
|
|
8908
|
+
const all = [...set2.organizations.flatMap((organization) => organization.skills), ...set2.cli];
|
|
8909
|
+
const served = all.filter((skill) => isSkillFolder(skill.folder));
|
|
8910
|
+
const skipped = all.filter((skill) => !isSkillFolder(skill.folder)).map((skill) => skill.folder);
|
|
8877
8911
|
for (const skill of served) {
|
|
8878
8912
|
const before = held.get(skill.folder);
|
|
8879
8913
|
if (before === void 0 || !fs6.existsSync(path5.join(root, skill.folder, "SKILL.md"))) missing.push(skill.folder);
|
|
@@ -8882,7 +8916,7 @@ function skillsStatus(root, set2) {
|
|
|
8882
8916
|
}
|
|
8883
8917
|
const servedFolders = new Set(served.map((skill) => skill.folder));
|
|
8884
8918
|
const retired = [...new Set(retirable(manifest, set2))].filter((folder) => !servedFolders.has(folder));
|
|
8885
|
-
return { root, installedAt: manifest?.installedAt || null, current, behind, missing, retired };
|
|
8919
|
+
return { root, installedAt: manifest?.installedAt || null, current, behind, missing, retired, skipped };
|
|
8886
8920
|
}
|
|
8887
8921
|
|
|
8888
8922
|
// src/skills/installable.ts
|
|
@@ -8940,8 +8974,11 @@ var skillsUsage = `diverge skills <list|install|refresh|status> [--organization-
|
|
|
8940
8974
|
|
|
8941
8975
|
A skill is a procedure over the tools it names; you get the ones whose every
|
|
8942
8976
|
tool you may run, in every organization the login can act in. Each lands as
|
|
8943
|
-
<organization prefix>-<name> \u2014 the organization's
|
|
8944
|
-
its name
|
|
8977
|
+
<organization prefix>-<name> \u2014 the organization's handle, or a slug of
|
|
8978
|
+
its name; the platform shortens a longer one to the 64 characters a harness
|
|
8979
|
+
accepts \u2014 and its SKILL.md opens with the organization its tools run in.
|
|
8980
|
+
A folder that is not a skill name is skipped with a warning naming it, and
|
|
8981
|
+
the folder that skill was installed under before stays.
|
|
8945
8982
|
--organization-id refreshes one organization and leaves the others as they
|
|
8946
8983
|
are; without it the whole login is refreshed, and an organization the login
|
|
8947
8984
|
no longer lists is retired. Without --harness, the harnesses present on this
|
|
@@ -8980,6 +9017,11 @@ function harnessesOf(flag, env) {
|
|
|
8980
9017
|
function installSet(harness, skills) {
|
|
8981
9018
|
return { cli: bundledSkills(harness), organizations: skills.organizations, complete: skills.complete };
|
|
8982
9019
|
}
|
|
9020
|
+
var SKIPPED = "not installed, the folder is not a skill name (at most 64 lowercase letters, digits and hyphens)";
|
|
9021
|
+
function skippedNote(results) {
|
|
9022
|
+
const skipped = [...new Set(results.flatMap((result) => result.skipped))];
|
|
9023
|
+
return skipped.length ? [`${skipped.length} ${SKIPPED}: ${few(skipped)}`] : [];
|
|
9024
|
+
}
|
|
8983
9025
|
function installReport(harness, result) {
|
|
8984
9026
|
const holdings = result.counts.map((entry) => `${entry.label} ${entry.count}`).join(", ");
|
|
8985
9027
|
const changes = [
|
|
@@ -9007,11 +9049,13 @@ async function refreshSkills(io, apiUrl, options = {}) {
|
|
|
9007
9049
|
io.stderr(`warning: skills not refreshed (${error instanceof Error ? error.message : String(error)}); run: diverge skills install`);
|
|
9008
9050
|
return;
|
|
9009
9051
|
}
|
|
9052
|
+
const results = [];
|
|
9010
9053
|
for (const harness of harnesses) {
|
|
9011
9054
|
const result = installSkills(skillsRoot(harness, io.env), installSet(harness, skills), apiUrl);
|
|
9012
9055
|
if (!options.quiet || result.added.length || result.updated.length || result.removed.length) io.stdout(installReport(harness, result));
|
|
9056
|
+
results.push(result);
|
|
9013
9057
|
}
|
|
9014
|
-
for (const note of skills.notes) io.stderr(`warning: ${note}`);
|
|
9058
|
+
for (const note of [...skills.notes, ...skippedNote(results)]) io.stderr(`warning: ${note}`);
|
|
9015
9059
|
}
|
|
9016
9060
|
async function runSkills(args, io) {
|
|
9017
9061
|
const [verb, ...rest] = args;
|
|
@@ -9033,8 +9077,8 @@ async function runSkills(args, io) {
|
|
|
9033
9077
|
const skills = await loginSkillSet(apiUrl, io.env, organizationId);
|
|
9034
9078
|
const harnesses = values.into ? [harnessesOf(values.harness ?? HARNESSES[0], io.env)[0]] : harnessesOf(values.harness, io.env);
|
|
9035
9079
|
if (harnesses.length === 0) throw new CliError("No coding-agent harness on this machine yet (Claude Code, Codex or dsh); name one with --harness to write its folder anyway.");
|
|
9036
|
-
|
|
9037
|
-
for (const note of skills.notes) io.stderr(`warning: ${note}`);
|
|
9080
|
+
const results = harnesses.map((harness) => installForHarness(io, harness, skills, apiUrl, values.into));
|
|
9081
|
+
for (const note of [...skills.notes, ...skippedNote(results)]) io.stderr(`warning: ${note}`);
|
|
9038
9082
|
return ExitCode.Ok;
|
|
9039
9083
|
}
|
|
9040
9084
|
case "refresh": {
|
|
@@ -9050,6 +9094,7 @@ async function runSkills(args, io) {
|
|
|
9050
9094
|
if (status2.behind.length) parts.push(`${status2.behind.length} behind (${few(status2.behind)})`);
|
|
9051
9095
|
if (status2.missing.length) parts.push(`${status2.missing.length} missing${status2.current.length || status2.behind.length ? ` (${few(status2.missing)})` : ""}`);
|
|
9052
9096
|
if (status2.retired.length) parts.push(`${status2.retired.length} retired (${few(status2.retired)})`);
|
|
9097
|
+
if (status2.skipped.length) parts.push(`${status2.skipped.length} ${SKIPPED} (${few(status2.skipped)})`);
|
|
9053
9098
|
behind ||= status2.behind.length + status2.missing.length + status2.retired.length > 0;
|
|
9054
9099
|
io.stdout(`${harness}: ${parts.join(", ")} in ${status2.root}${status2.installedAt ? ` (installed ${status2.installedAt})` : " (never installed)"}`);
|
|
9055
9100
|
}
|
|
@@ -9587,7 +9632,7 @@ var STALLED_TICKS = 30;
|
|
|
9587
9632
|
var CAPTURE_NOTICES = {
|
|
9588
9633
|
off: "This organization does not record agent sessions (Forge installation config agentSessions.capture is off); nothing is sent.",
|
|
9589
9634
|
metadata: "Recorded: metadata only \u2014 which tools ran and how many failed, tokens, models and timing; no prompt, reply, tool argument or result.",
|
|
9590
|
-
conversation: "Recorded: the whole conversation \u2014 every prompt, reply, tool call and its result. Yours to read; anyone else in the organization needs the Agent
|
|
9635
|
+
conversation: "Recorded: the whole conversation \u2014 every prompt, reply, tool call and its result. Yours to read; anyone else in the organization needs the Agent Conversations role, and every such read is recorded on the session."
|
|
9591
9636
|
};
|
|
9592
9637
|
function eventBytes(event) {
|
|
9593
9638
|
return Buffer.byteLength(JSON.stringify(event), "utf8");
|
|
@@ -9911,8 +9956,8 @@ function syncDivergentPreset(io, binary, home) {
|
|
|
9911
9956
|
async function skillsFor(io, apiUrl, harness, organizationId) {
|
|
9912
9957
|
try {
|
|
9913
9958
|
const skills = await loginSkillSet(apiUrl, io.env, organizationId?.trim() || void 0);
|
|
9914
|
-
installForHarness(io, harness, skills, apiUrl);
|
|
9915
|
-
for (const note of skills.notes) io.stderr(`warning: ${note}`);
|
|
9959
|
+
const result = installForHarness(io, harness, skills, apiUrl);
|
|
9960
|
+
for (const note of [...skills.notes, ...skippedNote([result])]) io.stderr(`warning: ${note}`);
|
|
9916
9961
|
} catch (error) {
|
|
9917
9962
|
io.stderr(`warning: skills not installed (${error instanceof Error ? error.message : String(error)}); run: diverge skills install`);
|
|
9918
9963
|
}
|
|
@@ -10903,13 +10948,19 @@ import { parseArgs as parseArgs7 } from "node:util";
|
|
|
10903
10948
|
var pluginsUsage = `diverge plugins <validate|publish|list|retire> [--organization-id <id>] ...
|
|
10904
10949
|
|
|
10905
10950
|
validate <manifest.json> (dry run: the platform's full validation, nothing stored)
|
|
10906
|
-
publish <manifest.json> [--json] (forge_plugins:create in the signed-in organization
|
|
10951
|
+
publish <manifest.json> [--json] (forge_plugins:create in the signed-in organization;
|
|
10952
|
+
a version that changes authority waits on a decision)
|
|
10907
10953
|
list [--json]
|
|
10908
10954
|
retire <pluginId>
|
|
10909
10955
|
|
|
10910
10956
|
Register a tenant plugin: a plugin whose code runs in your organization's own
|
|
10911
10957
|
runtime and never inside the platform. The manifest lists the tools the
|
|
10912
|
-
platform registers, pinned; a change is a new version published again.
|
|
10958
|
+
platform registers, pinned; a change is a new version published again.
|
|
10959
|
+
|
|
10960
|
+
A version that only reshapes or removes the tools already served is stored at
|
|
10961
|
+
once. Anything else changes authority: publish raises it as
|
|
10962
|
+
forge_publish_plugin, prints the invocation, and exits 3; a person decides it
|
|
10963
|
+
in Compass, and diverge tools invocation <id> reads the outcome. Enable
|
|
10913
10964
|
it with forge plugins (or forge_set_plugin_state) and bind the runtime with a
|
|
10914
10965
|
Forge bridge binding whose runtimeType matches the manifest's runtime.type.`;
|
|
10915
10966
|
var SCOPE_OPTIONS2 = { "organization-id": { type: "string" } };
|
|
@@ -10917,7 +10968,28 @@ async function client2(values, io) {
|
|
|
10917
10968
|
const settings = loadToolsSettings({ organizationId: values["organization-id"] }, io.env);
|
|
10918
10969
|
io.stderr(scopeLine(settings));
|
|
10919
10970
|
io.stderr(await actingAsLine(settings, io.env));
|
|
10920
|
-
return new ToolsClient(settings, io.env);
|
|
10971
|
+
return { settings, tools: new ToolsClient(settings, io.env) };
|
|
10972
|
+
}
|
|
10973
|
+
function printProblems(details, io) {
|
|
10974
|
+
io.stderr(`Invalid manifest (${details.length} problem${details.length === 1 ? "" : "s"}):`);
|
|
10975
|
+
for (const detail of details) io.stderr(` - ${detail}`);
|
|
10976
|
+
return ExitCode.Failure;
|
|
10977
|
+
}
|
|
10978
|
+
async function raisePublish(manifest, authorityChanges, context, io) {
|
|
10979
|
+
const { settings, tools, json } = context;
|
|
10980
|
+
if (!json) {
|
|
10981
|
+
io.stdout("This version changes authority, so it is stored only once it is decided:");
|
|
10982
|
+
for (const change of authorityChanges) io.stdout(` - ${change}`);
|
|
10983
|
+
}
|
|
10984
|
+
const outcome = await runOne({ tool: "forge_publish_plugin", args: { manifest } }, {
|
|
10985
|
+
callTool: (name, args) => tools.callTool(name, args),
|
|
10986
|
+
decideAt: (pending) => `${settings.apiUrl}${pending.approvalUrl ?? ""}`,
|
|
10987
|
+
...json ? {} : { onPending: (pending) => formatPending(pending, settings.apiUrl, settings.organizationId).forEach((line) => io.stdout(line)) }
|
|
10988
|
+
});
|
|
10989
|
+
if (json) io.stdout(JSON.stringify({ authorityChanges, ...outcome }, null, 2));
|
|
10990
|
+
else if (outcome.status === "executed") io.stdout("Published.");
|
|
10991
|
+
else if (outcome.status === "failed") throw new CliError(`forge_publish_plugin failed: ${outcome.error}`);
|
|
10992
|
+
return worstExitCode([outcome]);
|
|
10921
10993
|
}
|
|
10922
10994
|
function readManifest(file) {
|
|
10923
10995
|
if (!file) throw new CliError(`A manifest file is required.
|
|
@@ -10954,30 +11026,30 @@ async function runPlugins(args, io) {
|
|
|
10954
11026
|
case "validate": {
|
|
10955
11027
|
const { values, positionals } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
10956
11028
|
const manifest = readManifest(positionals[0]);
|
|
10957
|
-
const tools = await client2(values, io);
|
|
11029
|
+
const { tools } = await client2(values, io);
|
|
10958
11030
|
const outcome = await tools.publishPlugin(manifest, { dryRun: true });
|
|
10959
|
-
if (!outcome.ok)
|
|
10960
|
-
io.stderr(`Invalid manifest (${outcome.details.length} problem${outcome.details.length === 1 ? "" : "s"}):`);
|
|
10961
|
-
for (const detail of outcome.details) io.stderr(` - ${detail}`);
|
|
10962
|
-
return ExitCode.Failure;
|
|
10963
|
-
}
|
|
11031
|
+
if (!outcome.ok) return printProblems(outcome.details, io);
|
|
10964
11032
|
if (values.json) io.stdout(JSON.stringify(outcome.result, null, 2));
|
|
10965
11033
|
else {
|
|
10966
11034
|
io.stdout(`Valid. ${outcome.result.changed ? "Publishing would register" : "Already published at this digest:"}`);
|
|
10967
11035
|
for (const line of formatPlugin(outcome.result.plugin)) io.stdout(line);
|
|
11036
|
+
const authority = outcome.result.authorityChanges ?? [];
|
|
11037
|
+
if (authority.length) {
|
|
11038
|
+
io.stdout("Publishing it changes authority, so it waits on a person's decision in Compass:");
|
|
11039
|
+
for (const change of authority) io.stdout(` - ${change}`);
|
|
11040
|
+
}
|
|
10968
11041
|
}
|
|
10969
11042
|
return ExitCode.Ok;
|
|
10970
11043
|
}
|
|
10971
11044
|
case "publish": {
|
|
10972
11045
|
const { values, positionals } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
10973
11046
|
const manifest = readManifest(positionals[0]);
|
|
10974
|
-
const tools = await client2(values, io);
|
|
11047
|
+
const { settings, tools } = await client2(values, io);
|
|
10975
11048
|
const outcome = await tools.publishPlugin(manifest, { dryRun: false });
|
|
10976
|
-
if (!outcome.ok) {
|
|
10977
|
-
|
|
10978
|
-
for (const detail of outcome.details) io.stderr(` - ${detail}`);
|
|
10979
|
-
return ExitCode.Failure;
|
|
11049
|
+
if (!outcome.ok && outcome.authorityChange) {
|
|
11050
|
+
return raisePublish(manifest, outcome.details, { settings, tools, json: values.json }, io);
|
|
10980
11051
|
}
|
|
11052
|
+
if (!outcome.ok) return printProblems(outcome.details, io);
|
|
10981
11053
|
if (values.json) io.stdout(JSON.stringify(outcome.result, null, 2));
|
|
10982
11054
|
else {
|
|
10983
11055
|
io.stdout(outcome.result.changed ? "Published." : "Unchanged: this digest was already published.");
|
|
@@ -10988,7 +11060,7 @@ async function runPlugins(args, io) {
|
|
|
10988
11060
|
}
|
|
10989
11061
|
case "list": {
|
|
10990
11062
|
const { values } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, strict: true });
|
|
10991
|
-
const tools = await client2(values, io);
|
|
11063
|
+
const { tools } = await client2(values, io);
|
|
10992
11064
|
const plugins = await tools.listPlugins();
|
|
10993
11065
|
if (values.json) io.stdout(JSON.stringify(plugins, null, 2));
|
|
10994
11066
|
else if (!plugins.length) io.stdout("No tenant plugins published for this organization.");
|
|
@@ -11001,7 +11073,7 @@ async function runPlugins(args, io) {
|
|
|
11001
11073
|
if (!pluginId) throw new CliError(`A plugin id is required.
|
|
11002
11074
|
|
|
11003
11075
|
${pluginsUsage}`);
|
|
11004
|
-
const tools = await client2(values, io);
|
|
11076
|
+
const { tools } = await client2(values, io);
|
|
11005
11077
|
const plugin = await tools.retirePlugin(pluginId);
|
|
11006
11078
|
io.stdout(`Retired ${plugin.pluginId}@${plugin.version}; its ${plugin.toolNames.length} tool(s) are gone. Installation records stay for a re-publish.`);
|
|
11007
11079
|
return ExitCode.Ok;
|
package/package.json
CHANGED