@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.
Files changed (3) hide show
  1. package/README.md +13 -3
  2. package/dist/diverge.mjs +116 -44
  3. 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
- short code, lowercased, or a slug of its name: `dvx-consultar-inventario`,
161
- `red-team-triage-cloud-run`. Each SKILL.md is rendered from the skill's
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.26" : "dev";
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 && /scope/i.test(message)) {
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 !== 400) return void 0;
8527
- try {
8528
- const parsed = JSON.parse(text2);
8529
- if (Array.isArray(parsed.details)) return { ok: false, details: parsed.details.map(String) };
8530
- return { ok: false, details: [parsed.error ?? text2] };
8531
- } catch {
8532
- return { ok: false, details: [text2] };
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 (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(skill.folder)) throw new Error(`skill folder ${JSON.stringify(skill.folder)} is not a folder name`);
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 before = held.get(skill.folder);
8832
- if (before === void 0) added.push(skill.folder);
8833
- else if (before !== skill.updatedAt) updated.push(skill.folder);
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: organization.skills.length });
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: set2.cli.length });
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(organization.skills.map((skill) => [skill.folder, skill.updatedAt]))
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 served = [...set2.organizations.flatMap((organization) => organization.skills), ...set2.cli];
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 short code, or a slug of
8944
- its name \u2014 and its SKILL.md opens with the organization its tools run in.
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
- for (const harness of harnesses) installForHarness(io, harness, skills, apiUrl, values.into);
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 conversations role, and every such read is recorded on the session."
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. Enable
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
- io.stderr(`Invalid manifest (${outcome.details.length} problem${outcome.details.length === 1 ? "" : "s"}):`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divergeai/cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "Divergent platform CLI: sign in, wire coding agents to the inference gateway, call platform tools",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",