@topolo/cli 0.7.0 → 0.7.3

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 CHANGED
@@ -153,6 +153,7 @@ and the CLI calls them through the shared SDK.
153
153
 
154
154
  ```bash
155
155
  topolo actions --service mail --json
156
+ topolo actions --service mail --detail --json
156
157
  topolo actions get app_topolo_mail.messages.send --json
157
158
  topolo actions call app_topolo_mail.messages.send \
158
159
  --data '{"mailboxId":"primary","subject":"Hello","text":"..."}' \
@@ -163,6 +164,11 @@ Use `topolo actions` before `topolo api` when an app has published a machine
163
164
  contract for the operation. `topolo api` remains the escape hatch for endpoints
164
165
  that are not yet in the action catalog.
165
166
 
167
+ Action search, capability discovery, validation, and planning return compact
168
+ agent guidance by default. Use `--detail` only when the full schemas and agent
169
+ contract are required. `topolo resources --service <app>` is accepted alongside
170
+ the positional application selector.
171
+
166
172
  Workspace-scoped actions carry resource context independently from the active
167
173
  personal/organization credential and independently from the browser session:
168
174
 
@@ -215,8 +221,9 @@ Agents should call `topolo apps requirements <app> --json` before creating or
215
221
  expanding a Topolo app. The output is the versioned platform contract for app
216
222
  metadata, canonical docs, shared auth, shared shell, service registration,
217
223
  deployment metadata, agent surfaces, observability, and verification.
218
- `topolo apps audit --json` turns the same contract into per-app scores and a
219
- catalog-backed migration queue. Add `--local --repo-root <topolo-checkout>` to
224
+ `topolo apps audit --json` returns compact per-app scores and a migration queue
225
+ count. Add `--detail` for every finding and migration item, or
226
+ `--local --repo-root <topolo-checkout>` to
220
227
  include filesystem evidence from the app repo, CloudControl metadata, and
221
228
  TopoloDocs. Add `--fail-on needs_review` for the default new-work gate, or
222
229
  `--fail-on partial` for strict release readiness.
package/dist/index.js CHANGED
@@ -3914,6 +3914,52 @@ function mergeSignals(a, b) {
3914
3914
  b.addEventListener("abort", onAbort, { once: true });
3915
3915
  return controller.signal;
3916
3916
  }
3917
+ function compactActionCatalogEntry(action) {
3918
+ const required = Array.isArray(action.inputSchema["required"]) ? action.inputSchema["required"].filter((value) => typeof value === "string") : [];
3919
+ const examples = action.agentContract?.examples ?? [];
3920
+ return {
3921
+ actionId: action.actionId,
3922
+ title: action.title,
3923
+ description: action.description,
3924
+ method: action.method,
3925
+ readOnly: action.readOnly,
3926
+ requiresConfirmation: action.requiresConfirmation,
3927
+ agentAccess: action.agentAccess ?? (action.requiresConfirmation ? "confirm" : action.readOnly ? "auto" : "confirm"),
3928
+ requiredPermission: action.requiredPermission,
3929
+ requiredInput: required,
3930
+ resourceType: action.agentContract?.resource?.kind ?? null,
3931
+ exampleInput: examples[0]?.input ?? null,
3932
+ docsUrl: action.agentContract?.docsUrl ?? null
3933
+ };
3934
+ }
3935
+ function compactActionValidation(actionId, validation) {
3936
+ return { actionId, valid: validation.valid, errors: validation.errors };
3937
+ }
3938
+ function compactActionPlan(plan) {
3939
+ return {
3940
+ actionId: plan.action.actionId,
3941
+ valid: plan.validation.valid,
3942
+ errors: plan.validation.errors,
3943
+ executable: plan.executable,
3944
+ confirmationRequired: plan.confirmationRequired,
3945
+ confirmationProvided: plan.confirmationProvided,
3946
+ request: plan.request,
3947
+ verification: plan.verification,
3948
+ rollbackActionId: plan.rollbackActionId,
3949
+ nextActionIds: plan.nextActionIds
3950
+ };
3951
+ }
3952
+ function compactApplicationAudit(report) {
3953
+ return {
3954
+ version: report.version,
3955
+ applications: report.applications.map((audit) => ({
3956
+ applicationId: audit.application.id,
3957
+ applicationName: audit.application.name,
3958
+ score: audit.score
3959
+ })),
3960
+ migrationQueueCount: report.migrationQueue.length
3961
+ };
3962
+ }
3917
3963
  var CrmModule = class {
3918
3964
  constructor(client) {
3919
3965
  this.client = client;
@@ -4598,6 +4644,18 @@ function emit(data, mode, pretty) {
4598
4644
  process.stdout.write(pretty() + "\n");
4599
4645
  }
4600
4646
  }
4647
+ function handleOutputStreamError(err, exit = process.exit) {
4648
+ if (err?.code === "EPIPE") {
4649
+ exit(0);
4650
+ return;
4651
+ }
4652
+ throw err;
4653
+ }
4654
+ function installOutputErrorHandlers(stdout = process.stdout, stderr = process.stderr, exit = process.exit) {
4655
+ const handler = (err) => handleOutputStreamError(err, exit);
4656
+ stdout.on("error", handler);
4657
+ stderr.on("error", handler);
4658
+ }
4601
4659
  function fail(err, mode) {
4602
4660
  const payload = normalizeError(err);
4603
4661
  if (mode === "json") {
@@ -5107,7 +5165,7 @@ function parseQueryPairs(pairs) {
5107
5165
 
5108
5166
  // src/commands/actions.ts
5109
5167
  function registerActionsCommand(program2) {
5110
- const actions = program2.command("actions").description("Search permission-scoped Topolo actions for one application").option("-s, --service <service>", "App id, slug, or name (required for search)").option("-q, --query <query>", "Filter by action id, name, or title").option("--cursor <cursor>", "Continuation cursor from the previous page").option("--limit <limit>", "Page size (1-100)", "25").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts) => {
5168
+ const actions = program2.command("actions").description("Search permission-scoped Topolo actions for one application").option("-s, --service <service>", "App id, slug, or name (required for search)").option("-q, --query <query>", "Filter by action id, name, or title").option("--cursor <cursor>", "Continuation cursor from the previous page").option("--limit <limit>", "Page size (1-100)", "25").option("--detail", "Include full schemas and agent contracts").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts) => {
5111
5169
  const mode = resolveOutputMode(opts.json);
5112
5170
  try {
5113
5171
  const topolo = await buildClient();
@@ -5121,7 +5179,8 @@ function registerActionsCommand(program2) {
5121
5179
  cursor: opts.cursor,
5122
5180
  limit: parsePageSize(opts.limit)
5123
5181
  });
5124
- emit(result, mode, () => formatActionRows(result.actions, result.page.nextCursor));
5182
+ const output2 = opts.detail ? result : { actions: result.actions.map(compactActionCatalogEntry), page: result.page };
5183
+ emit(output2, mode, () => formatActionRows(result.actions, result.page.nextCursor));
5125
5184
  } catch (err) {
5126
5185
  fail(err, mode);
5127
5186
  }
@@ -5181,17 +5240,18 @@ function registerActionsCommand(program2) {
5181
5240
  fail(err, mode);
5182
5241
  }
5183
5242
  });
5184
- actions.command("validate <action>").description("Validate action input locally against the published JSON Schema").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
5243
+ actions.command("validate <action>").description("Validate action input locally against the published JSON Schema").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--detail", "Include the full action contract").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
5185
5244
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5186
5245
  try {
5187
5246
  const topolo = await requiredClient();
5188
5247
  const result = await topolo.client.validateActionInput(action, parseJsonObject(opts.data || "{}"));
5189
- emit(result, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
5248
+ const output2 = detailRequested(opts.detail, command) ? result : compactActionValidation(result.action.actionId, result.validation);
5249
+ emit(output2, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
5190
5250
  } catch (err) {
5191
5251
  fail(err, mode);
5192
5252
  }
5193
5253
  });
5194
- actions.command("plan <action>").description("Validate and preview the exact request, confirmation, verification, and rollback plan").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Include confirmation in the plan without executing it").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
5254
+ actions.command("plan <action>").description("Validate and preview the exact request, confirmation, verification, and rollback plan").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Include confirmation in the plan without executing it").option("--detail", "Include the full action contract").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
5195
5255
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5196
5256
  try {
5197
5257
  const topolo = await requiredClient();
@@ -5200,7 +5260,7 @@ function registerActionsCommand(program2) {
5200
5260
  confirm: opts.confirm === true,
5201
5261
  ...resource ? { resource } : {}
5202
5262
  });
5203
- emit(plan, mode, () => formatActionPlan(plan));
5263
+ emit(detailRequested(opts.detail, command) ? plan : compactActionPlan(plan), mode, () => formatActionPlan(plan));
5204
5264
  } catch (err) {
5205
5265
  fail(err, mode);
5206
5266
  }
@@ -5220,19 +5280,65 @@ function registerActionsCommand(program2) {
5220
5280
  fail(err, mode);
5221
5281
  }
5222
5282
  });
5223
- actions.command("capabilities").description("List every published action for one application (its capability contract)").option("-s, --service <service>", "App id, slug, or name").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts, command) => {
5283
+ actions.command("capabilities").description("List every published action for one application (its capability contract)").option("-s, --service <service>", "App id, slug, or name").option("--summary", "Return compact capability counts without action rows").option("--detail", "Include full schemas and agent contracts").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts, command) => {
5224
5284
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5225
5285
  try {
5226
5286
  const topolo = await requiredClient();
5227
5287
  const service = opts.service || command.parent?.opts().service;
5228
5288
  if (!service?.trim()) throw new Error("--service is required for capability discovery");
5229
- const result = await topolo.client.searchActions({ service: service.trim(), limit: 100 });
5230
- emit(result, mode, () => formatActionRows(result.actions, result.page.nextCursor));
5289
+ if (opts.summary && detailRequested(opts.detail, command)) {
5290
+ throw new Error("--summary cannot be combined with --detail");
5291
+ }
5292
+ const resolvedService = service.trim();
5293
+ const discovered = [];
5294
+ const seenCursors = /* @__PURE__ */ new Set();
5295
+ let cursor;
5296
+ for (; ; ) {
5297
+ const page = await topolo.client.searchActions({
5298
+ service: resolvedService,
5299
+ limit: 100,
5300
+ ...cursor ? { cursor } : {}
5301
+ });
5302
+ discovered.push(...page.actions);
5303
+ if (!page.page.hasMore) break;
5304
+ const nextCursor = page.page.nextCursor?.trim();
5305
+ if (!nextCursor || seenCursors.has(nextCursor)) {
5306
+ throw new Error("Capability discovery returned an invalid continuation cursor.");
5307
+ }
5308
+ seenCursors.add(nextCursor);
5309
+ cursor = nextCursor;
5310
+ }
5311
+ const result = { actions: discovered, page: { nextCursor: null, hasMore: false } };
5312
+ const output2 = opts.summary ? summarizeActionCapabilities(resolvedService, discovered) : detailRequested(opts.detail, command) ? result : { actions: discovered.map(compactActionCatalogEntry), page: result.page };
5313
+ emit(output2, mode, () => opts.summary ? formatActionCapabilitySummary(summarizeActionCapabilities(resolvedService, discovered)) : formatActionRows(discovered));
5231
5314
  } catch (err) {
5232
5315
  fail(err, mode);
5233
5316
  }
5234
5317
  });
5235
5318
  }
5319
+ function summarizeActionCapabilities(service, actions) {
5320
+ return {
5321
+ service,
5322
+ total: actions.length,
5323
+ readOnly: actions.filter((action) => action.readOnly).length,
5324
+ mutating: actions.filter((action) => !action.readOnly).length,
5325
+ destructive: actions.filter((action) => action.destructive).length,
5326
+ confirmationRequired: actions.filter((action) => action.requiresConfirmation).length,
5327
+ resourceTypes: [...new Set(actions.map((action) => action.agentContract?.resource?.kind).filter(Boolean))].sort()
5328
+ };
5329
+ }
5330
+ function formatActionCapabilitySummary(summary) {
5331
+ return [
5332
+ `Service: ${summary.service}`,
5333
+ `Actions: ${summary.total} (${summary.readOnly} read-only, ${summary.mutating} mutating)`,
5334
+ `Destructive: ${summary.destructive}`,
5335
+ `Confirmation required: ${summary.confirmationRequired}`,
5336
+ `Resource types: ${summary.resourceTypes.join(", ") || "-"}`
5337
+ ].join("\n");
5338
+ }
5339
+ function detailRequested(local, command) {
5340
+ return local === true || command.parent?.opts().detail === true;
5341
+ }
5236
5342
  async function requiredClient() {
5237
5343
  const topolo = await buildClient();
5238
5344
  if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
@@ -5336,7 +5442,7 @@ function formatActionPlan(plan) {
5336
5442
 
5337
5443
  // src/commands/apps.ts
5338
5444
  import { execSync } from "child_process";
5339
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
5445
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
5340
5446
  import path6 from "path";
5341
5447
 
5342
5448
  // src/commands/app-audit-local.ts
@@ -6639,7 +6745,7 @@ function firstPartyAppPath(appDir, _audience) {
6639
6745
  }
6640
6746
 
6641
6747
  // src/commands/app-scaffold-platform-versions.ts
6642
- import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
6748
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
6643
6749
  import path2 from "path";
6644
6750
  var versionCache = /* @__PURE__ */ new Map();
6645
6751
  function platformPackagePin(packageName, options = {}) {
@@ -6647,7 +6753,7 @@ function platformPackagePin(packageName, options = {}) {
6647
6753
  options.repoRoot ?? process.env.TOPOLO_WORKSPACE_ROOT ?? void 0,
6648
6754
  options.cwd ?? process.cwd()
6649
6755
  );
6650
- if (!workspaceRoot) {
6756
+ if (!workspaceRoot || !existsSync3(path2.join(workspaceRoot, "topolo-platform", "packages"))) {
6651
6757
  throw new Error(
6652
6758
  `Cannot resolve a concrete version for ${packageName}: platform-target scaffolds must run inside the Topolo workspace checkout (a directory containing apps/ and system-apps/), or receive --repo-root. The fleet pinning policy requires exact ^<latest> pins, never "latest".`
6653
6759
  );
@@ -7194,6 +7300,7 @@ function readme(application, target, options) {
7194
7300
  "- `@topolo-io/app-launcher` provides shared app switching and handoff helpers.",
7195
7301
  "- `@topolo-io/ui-kit` provides every control, table, panel, workbench, inspector, icon, transition, and bug reporter primitive used by the generated product surface.",
7196
7302
  "- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
7303
+ '- Build the app data client with `authClient.createJsonClient()` (or `createAuthenticatedFetch()`) from `@topolo-io/auth-client`. It already attaches the bearer token, refreshes proactively near expiry, and refreshes-and-retries once on a 401. NEVER hand-roll an `Authorization: Bearer` header in browser code: every app that has done so forgot the 401 recovery and showed raw auth errors ("Token expired") to the user on an ordinary 15-minute expiry. The `browser-auth-refresh` fleet static guard fails any browser file that builds its own bearer header.',
7197
7304
  "- `@topolo-io/api-key-management`, `@topolo-io/chat-widget`, `@topolo-io/notifications`, and `@topolo-io/resource-context` complete the AppShell capability baseline.",
7198
7305
  "- Every Topolo-owned browser dependency is emitted at `latest`; product-domain packages are added only when the app actually adopts that domain.",
7199
7306
  "- `.npmrc` routes private `@topolo-io` packages to GitHub Packages without storing credentials; public `@topolo` packages remain on npm. Local and CI environments must provide a GitHub Packages token.",
@@ -8760,7 +8867,7 @@ function developerWorkerTs(application) {
8760
8867
  }
8761
8868
 
8762
8869
  // src/commands/app-scaffold-generate.ts
8763
- import { existsSync as existsSync3, mkdirSync, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync } from "fs";
8870
+ import { existsSync as existsSync4, mkdirSync, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync } from "fs";
8764
8871
  import { spawnSync } from "child_process";
8765
8872
  import { tmpdir } from "os";
8766
8873
  import path3 from "path";
@@ -8774,7 +8881,7 @@ function resolveGeneratorPath() {
8774
8881
  ];
8775
8882
  for (const candidate of candidates) {
8776
8883
  const p = decodeURIComponent(candidate.pathname);
8777
- if (existsSync3(p)) return p;
8884
+ if (existsSync4(p)) return p;
8778
8885
  }
8779
8886
  throw new Error(
8780
8887
  `topolo: could not locate generate-wrangler.mjs. A published CLI vendors it into dist/ at build time; in-repo it is read from topolo-platform/tooling/. Looked in:
@@ -11668,7 +11775,7 @@ function registerAppsCommand(program2) {
11668
11775
  });
11669
11776
  apps.command("audit [application]").description(
11670
11777
  "Audit Topolo applications against the versioned application-build requirements."
11671
- ).option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").option("--local", "Inspect the local Topolo checkout for filesystem-backed evidence").option("--repo-root <path>", "Topolo checkout root to inspect when --local is set").option(
11778
+ ).option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").option("--detail", "Include all findings and migration queue items").option("--local", "Inspect the local Topolo checkout for filesystem-backed evidence").option("--repo-root <path>", "Topolo checkout root to inspect when --local is set").option(
11672
11779
  "--fail-on <status>",
11673
11780
  "Exit non-zero when audit has findings at this severity or worse: missing, needs_review, partial"
11674
11781
  ).action(async (application, opts) => {
@@ -11680,8 +11787,9 @@ function registerAppsCommand(program2) {
11680
11787
  client: await directoryClient()
11681
11788
  });
11682
11789
  const gate = opts.failOn ? evaluateApplicationAuditGate(payload, opts.failOn) : null;
11683
- const output2 = gate ? { ...payload, conformanceGate: gate } : payload;
11684
- emit(output2, mode, () => formatAudit(output2));
11790
+ const compact = opts.detail ? payload : compactApplicationAudit(payload);
11791
+ const output2 = gate ? { ...compact, conformanceGate: gate } : compact;
11792
+ emit(output2, mode, () => formatAudit(gate ? { ...payload, conformanceGate: gate } : payload));
11685
11793
  if (gate && !gate.passed) {
11686
11794
  if (mode === "json") {
11687
11795
  process.stderr.write(JSON.stringify({ error: gate.message, conformanceGate: gate }, null, 2) + "\n");
@@ -11824,7 +11932,7 @@ async function applicationAuditPayload(application, options = {}) {
11824
11932
  async function recordApplicationEvidence(application, options = {}) {
11825
11933
  const appId = (await resolveApplication(application, options.client ?? null)).id;
11826
11934
  const root = path6.resolve(options.appRoot ?? process.cwd());
11827
- if (!existsSync4(root)) {
11935
+ if (!existsSync5(root)) {
11828
11936
  throw new Error(`Application root does not exist: ${root}`);
11829
11937
  }
11830
11938
  let commit = options.commit;
@@ -11838,7 +11946,7 @@ async function recordApplicationEvidence(application, options = {}) {
11838
11946
  const evidenceDir = path6.join(root, ".topolo");
11839
11947
  const evidencePath = path6.join(evidenceDir, "evidence.json");
11840
11948
  let before = null;
11841
- if (existsSync4(evidencePath)) {
11949
+ if (existsSync5(evidencePath)) {
11842
11950
  try {
11843
11951
  const raw = JSON.parse(readFileSync6(evidencePath, "utf8"));
11844
11952
  if (raw && typeof raw === "object" && raw.version === 1) {
@@ -13110,7 +13218,7 @@ function formatCheckResult(result) {
13110
13218
 
13111
13219
  // src/commands/skills.ts
13112
13220
  import { spawnSync as spawnSync2 } from "child_process";
13113
- import { existsSync as existsSync5 } from "fs";
13221
+ import { existsSync as existsSync6 } from "fs";
13114
13222
  import { mkdir as mkdir4, copyFile, readFile as readFile7, writeFile as writeFile7, rm } from "fs/promises";
13115
13223
  import { homedir as homedir2 } from "os";
13116
13224
  import { dirname as dirname4, join as join5, resolve as resolve3 } from "path";
@@ -13122,7 +13230,7 @@ function skillsSourceDir() {
13122
13230
  const distCandidate = resolve3(here, "../skills");
13123
13231
  const srcCandidate = resolve3(here, "../../skills");
13124
13232
  for (const candidate of [distCandidate, srcCandidate]) {
13125
- if (existsSync5(join5(candidate, "topolo", "SKILL.md"))) return candidate;
13233
+ if (existsSync6(join5(candidate, "topolo", "SKILL.md"))) return candidate;
13126
13234
  }
13127
13235
  return distCandidate;
13128
13236
  }
@@ -13167,7 +13275,7 @@ function registerViaOfficialCli(cli, mcp, agentName) {
13167
13275
  async function registerClaudeViaConfig(mcp) {
13168
13276
  const path9 = join5(homedir2(), ".claude.json");
13169
13277
  let config = {};
13170
- if (existsSync5(path9)) {
13278
+ if (existsSync6(path9)) {
13171
13279
  try {
13172
13280
  config = JSON.parse(await readFile7(path9, "utf8"));
13173
13281
  } catch {
@@ -13188,7 +13296,7 @@ async function registerClaudeViaConfig(mcp) {
13188
13296
  async function registerCodexViaConfig(mcp) {
13189
13297
  const path9 = join5(homedir2(), ".codex", "config.toml");
13190
13298
  let text = "";
13191
- if (existsSync5(path9)) text = await readFile7(path9, "utf8");
13299
+ if (existsSync6(path9)) text = await readFile7(path9, "utf8");
13192
13300
  text = text.replace(
13193
13301
  /(^|\n)\[mcp_servers\.topolo(?:\.[a-z]+)?\][^\[]*/g,
13194
13302
  (match) => match.startsWith("\n") ? "\n" : ""
@@ -13243,7 +13351,7 @@ function registerSkillsCommand(program2) {
13243
13351
  const mode = resolveOutputMode(opts.json);
13244
13352
  try {
13245
13353
  const sourceDir = skillsSourceDir();
13246
- if (!existsSync5(join5(sourceDir, "topolo", "SKILL.md"))) {
13354
+ if (!existsSync6(join5(sourceDir, "topolo", "SKILL.md"))) {
13247
13355
  throw new Error(
13248
13356
  "Bundled skill file not found. Reinstall the CLI: npm install -g @topolo/cli"
13249
13357
  );
@@ -13272,7 +13380,7 @@ function registerSkillsCommand(program2) {
13272
13380
  const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
13273
13381
  return {
13274
13382
  agent,
13275
- skillInstalled: existsSync5(join5(skillDir, "SKILL.md")),
13383
+ skillInstalled: existsSync6(join5(skillDir, "SKILL.md")),
13276
13384
  skillPath: join5(skillDir, "SKILL.md"),
13277
13385
  cliAvailable: agentBinaryExists(agent)
13278
13386
  };
@@ -13289,7 +13397,7 @@ function registerSkillsCommand(program2) {
13289
13397
  for (const agent of resolveTargets(opts)) {
13290
13398
  const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
13291
13399
  const skillFile = join5(skillDir, "SKILL.md");
13292
- const skillRemoved = existsSync5(skillFile);
13400
+ const skillRemoved = existsSync6(skillFile);
13293
13401
  if (skillRemoved) await rm(skillFile, { force: true });
13294
13402
  if (agentBinaryExists(agent)) {
13295
13403
  spawnSync2(agent, ["mcp", "remove", "topolo"], { stdio: "ignore" });
@@ -13541,12 +13649,14 @@ function registerContextCommand(program2) {
13541
13649
 
13542
13650
  // src/commands/resources.ts
13543
13651
  function registerResourcesCommand(program2) {
13544
- program2.command("resources <application>").description("List bindable resource ids for one credential-scoped Topolo application").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, opts) => {
13652
+ program2.command("resources [application]").description("List bindable resource ids for one credential-scoped Topolo application").option("-s, --service <service>", "App id, slug, or name").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, opts) => {
13545
13653
  const mode = resolveOutputMode(opts.json);
13546
13654
  try {
13547
13655
  const topolo = await buildClient();
13548
13656
  if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
13549
- const result = await topolo.client.listBindableResources(application);
13657
+ const selector = application?.trim() || opts.service?.trim();
13658
+ if (!selector) throw new Error("application or --service is required");
13659
+ const result = await topolo.client.listBindableResources(selector);
13550
13660
  emit(result, mode, () => formatResources(result));
13551
13661
  } catch (err) {
13552
13662
  fail(err, mode);
@@ -13641,6 +13751,7 @@ Workspace: ${workspace.name} (${workspace.id})${workspace.isDefault ? "\nDefault
13641
13751
  }
13642
13752
 
13643
13753
  // src/index.ts
13754
+ installOutputErrorHandlers();
13644
13755
  var program = new Command();
13645
13756
  var argv;
13646
13757
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
- "version": "0.7.0",
4
- "topoloPublishedVersion": "0.7.0",
3
+ "version": "0.7.3",
4
+ "topoloPublishedVersion": "0.7.3",
5
5
  "description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -20,6 +20,22 @@ topolo skills install
20
20
 
21
21
  `topolo skills install` installs this skill and registers the Topolo MCP server for Claude Code and Codex. Both use the same credential written by `topolo auth login`.
22
22
 
23
+ Before testing platform behavior, verify every installed agent surface against
24
+ the package registry. Do not diagnose behavior with stale tooling:
25
+
26
+ ```bash
27
+ topolo --version
28
+ npm list -g --depth=0 @topolo/cli @topolo/mcp @topolo/sdk
29
+ npm view @topolo/cli version
30
+ npm view @topolo/mcp version
31
+ npm view @topolo/sdk version
32
+ ```
33
+
34
+ If any version differs, install the three latest released versions together,
35
+ run `topolo skills install`, and restart the MCP client before testing. Record
36
+ the versions used with the results. Source builds may test a candidate fix, but
37
+ must be labelled separately from released-tooling proof.
38
+
23
39
  Production is the default. Use `--env staging` on every staging command, or set `TOPOLO_ENV=staging`. Never mix staging discovery with production execution.
24
40
 
25
41
  ## Authentication gate
@@ -153,7 +169,7 @@ For large inputs, write one schema-valid JSON object to a file and pass it witho
153
169
 
154
170
  ```bash
155
171
  topolo actions validate <action-id> --data @payload.json --json
156
- topolo actions plan <action-id> --data @payload.json --json
172
+ topolo actions plan <action-id> --data @payload.json --confirm --json
157
173
  topolo actions call <action-id> --data @payload.json --confirm --json
158
174
  ```
159
175
 
@@ -167,7 +183,7 @@ topolo actions plan <action-id> --data '<json>' --json
167
183
 
168
184
  MCP: `topolo_plan_action`.
169
185
 
170
- The plan must be valid and must expose the resolved request, confirmation requirement, verification steps, and rollback when one is published. If `executable` is false, do not call the action.
186
+ The plan must be valid and must expose the resolved request, confirmation requirement, verification steps, and rollback when one is published. For a mutation that the user has already authorized, include `--confirm` in the plan so `executable` reflects the exact confirmed call; planning never executes the action. Before approval, omit `--confirm`, present the plan, and treat `executable: false` with `confirmationRequired: true` and `confirmationProvided: false` as awaiting approval rather than an invalid contract. Once approval is obtained, call the unchanged request with `--confirm` without planning it a second time. Any other `executable: false` result blocks the call.
171
187
 
172
188
  ### 6. Confirm mutations
173
189