@topolo/cli 0.6.10 → 0.7.1

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
@@ -110,57 +110,8 @@ var TopoloHttpError = class extends TopoloSdkError {
110
110
  }
111
111
  };
112
112
  var APP_API_URLS = {
113
- agent: "https://agent.topolo.app",
114
113
  auth: "https://auth.topolo.app",
115
- backup: "https://backup.topolo.app",
116
- blog: "https://blog.topolo.app",
117
- books: "https://books.topolo.app",
118
- brand: "https://brand.topolo.app",
119
- bugfix: "https://bugfix.topolo.app",
120
- bytes: "https://bytes.topolo.app",
121
- calendar: "https://calendar.topolo.app",
122
- campaigns: "https://campaigns.topolo.app",
123
- capacity: "https://capacity.topolo.app",
124
- chat: "https://chat.topolo.app",
125
- commerce: "https://commerce.topolo.app",
126
- compose: "https://compose.topolo.app",
127
- consent: "https://consent.topolo.app",
128
- crm: "https://crm.topolo.app",
129
- design: "https://design.topolo.app",
130
- developers: "https://developers.topolo.app",
131
- director: "https://director.topolo.app",
132
- feed: "https://feed.topolo.app",
133
- flow: "https://flow.topolo.app",
134
- forecast: "https://forecast.topolo.app",
135
- forms: "https://forms.topolo.app",
136
- home: "https://home.topolo.app",
137
- insights: "https://insights.topolo.app",
138
- inventory: "https://inventory.topolo.app",
139
- learn: "https://learn.topolo.app",
140
- localize: "https://localize.topolo.app",
141
- mail: "https://mail.topolo.app",
142
- mdm: "https://mdm.topolo.app",
143
- messages: "https://messages.topolo.app",
144
- nexus: "https://nexus.topolo.app",
145
- notify: "https://notify.topolo.app",
146
- observability_watch: "https://observability-watch.topolo.app",
147
- one: "https://www.topolo.app",
148
- p2p: "https://p2p.topolo.app",
149
- pay: "https://pay.topolo.app",
150
- people: "https://people.topolo.app",
151
- "quro.api": "https://api.ol0.me",
152
- "quro.redirect": "https://ol0.me",
153
- roadmapper: "https://roadmapper.topolo.app",
154
- sign: "https://sign.topolo.app",
155
- social_studio: "https://studio.topolo.app",
156
- socialize: "https://socialize.topolo.app",
157
- spaces: "https://spaces.topolo.app",
158
- status: "https://status.topolo.app",
159
- success: "https://success.topolo.app",
160
- support: "https://support.topolo.app",
161
- survey: "https://survey.topolo.app",
162
- voice: "https://voice.topolo.app",
163
- web: "https://web.topolo.app"
114
+ developers: "https://developers.topolo.app"
164
115
  };
165
116
  var APPLICATIONS = {
166
117
  "admin": {
@@ -1886,7 +1837,145 @@ var APPLICATIONS = {
1886
1837
  ]
1887
1838
  }
1888
1839
  };
1889
- var APPLICATION_REQUIREMENTS_VERSION = "2026-06-07.1";
1840
+ function friendlyAppId(entry) {
1841
+ for (const value of [entry.slug, entry.name, entry.appId]) {
1842
+ if (typeof value !== "string" || !value.trim()) continue;
1843
+ const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
1844
+ const withoutPrefix = normalized.replace(/^(?:app[_-])?topolo[-_]+/, "");
1845
+ if (withoutPrefix) return withoutPrefix;
1846
+ }
1847
+ return entry.appId;
1848
+ }
1849
+ function idVariants(value) {
1850
+ const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
1851
+ return [.../* @__PURE__ */ new Set([
1852
+ normalized,
1853
+ normalized.replace(/-/g, "_"),
1854
+ normalized.replace(/_/g, "-")
1855
+ ])].filter(Boolean);
1856
+ }
1857
+ var STATIC_APPLICATIONS = APPLICATIONS;
1858
+ function staticApplicationForId(id) {
1859
+ for (const variant of idVariants(id)) {
1860
+ const entry = STATIC_APPLICATIONS[variant];
1861
+ if (entry) return entry;
1862
+ }
1863
+ return null;
1864
+ }
1865
+ function applicationEntryFromCatalog(live) {
1866
+ const id = friendlyAppId(live);
1867
+ return {
1868
+ id,
1869
+ appDir: "-",
1870
+ name: live.name ?? live.slug ?? live.appId,
1871
+ packageName: null,
1872
+ productionUrl: live.launchUrl ?? live.apiBaseUrl ?? null,
1873
+ services: live.apiBaseUrl ? [id] : [],
1874
+ deployTargets: []
1875
+ };
1876
+ }
1877
+ var DIRECTORY_PAGE_LIMIT = 100;
1878
+ var DIRECTORY_MAX_PAGES = 100;
1879
+ function staticApplicationDirectory() {
1880
+ const applications = Object.entries(STATIC_APPLICATIONS).map(([id, application]) => ({
1881
+ id,
1882
+ application,
1883
+ source: "static",
1884
+ live: null
1885
+ })).sort((left, right) => left.id.localeCompare(right.id));
1886
+ return { source: "static_fallback", applications };
1887
+ }
1888
+ function directoryApplicationFromLive(live) {
1889
+ const id = friendlyAppId(live);
1890
+ return {
1891
+ id,
1892
+ // Prefer the static snapshot's richer repo/deploy metadata when the live
1893
+ // registry confirms the app; synthesize from the live entry otherwise.
1894
+ application: staticApplicationForId(id) ?? applicationEntryFromCatalog(live),
1895
+ source: "live",
1896
+ live
1897
+ };
1898
+ }
1899
+ async function listApplicationDirectory(client) {
1900
+ if (!client) return staticApplicationDirectory();
1901
+ let services;
1902
+ try {
1903
+ services = [];
1904
+ let cursor;
1905
+ for (let page = 0; page < DIRECTORY_MAX_PAGES; page += 1) {
1906
+ const result = await client.searchServices({
1907
+ ...cursor ? { cursor } : {},
1908
+ limit: DIRECTORY_PAGE_LIMIT
1909
+ });
1910
+ services.push(...result.services);
1911
+ if (!result.page.nextCursor) break;
1912
+ cursor = result.page.nextCursor;
1913
+ }
1914
+ } catch {
1915
+ return staticApplicationDirectory();
1916
+ }
1917
+ const seen = /* @__PURE__ */ new Map();
1918
+ for (const live of services) {
1919
+ const entry = directoryApplicationFromLive(live);
1920
+ if (!seen.has(entry.id)) seen.set(entry.id, entry);
1921
+ }
1922
+ return {
1923
+ source: "live",
1924
+ applications: [...seen.values()].sort((left, right) => left.id.localeCompare(right.id))
1925
+ };
1926
+ }
1927
+ function matchesDirectoryApplication(app, needle) {
1928
+ const candidates = new Set(idVariants(needle));
1929
+ const keys = [
1930
+ app.id,
1931
+ app.application.id,
1932
+ app.application.name,
1933
+ app.live?.appId,
1934
+ app.live?.slug,
1935
+ app.live?.name
1936
+ ];
1937
+ for (const key of keys) {
1938
+ if (typeof key !== "string" || !key.trim()) continue;
1939
+ for (const variant of idVariants(key)) {
1940
+ if (candidates.has(variant)) return true;
1941
+ }
1942
+ for (const variant of idVariants(friendlyAppId({ appId: key }))) {
1943
+ if (candidates.has(variant)) return true;
1944
+ }
1945
+ }
1946
+ return false;
1947
+ }
1948
+ function formatDirectoryApplicationIds(directory) {
1949
+ const ids = directory.applications.map((app) => app.id).sort();
1950
+ const preview = ids.slice(0, 50).join(", ");
1951
+ const suffix = ids.length > 50 ? `, ... +${ids.length - 50} more` : "";
1952
+ const label = directory.source === "live" ? "Applications available to this credential" : "Applications in the bundled static snapshot (live registry unreachable)";
1953
+ return `${label}: ${preview}${suffix}`;
1954
+ }
1955
+ async function resolveApplicationDirectoryEntry(client, application) {
1956
+ const needle = typeof application === "string" ? application.trim() : "";
1957
+ if (!needle) {
1958
+ throw new TopoloSdkError("unknown_application", "application is required");
1959
+ }
1960
+ if (client) {
1961
+ let live = null;
1962
+ try {
1963
+ live = await client.getService(needle);
1964
+ } catch {
1965
+ live = null;
1966
+ }
1967
+ if (live) return directoryApplicationFromLive(live);
1968
+ }
1969
+ const directory = await listApplicationDirectory(client);
1970
+ const match = directory.applications.find((app) => matchesDirectoryApplication(app, needle));
1971
+ if (match) return match;
1972
+ throw new TopoloSdkError(
1973
+ "unknown_application",
1974
+ `Unknown application "${application}". ${formatDirectoryApplicationIds(directory)}`
1975
+ );
1976
+ }
1977
+ var STATIC_APPLICATIONS2 = APPLICATIONS;
1978
+ var APPLICATION_REQUIREMENTS_VERSION = "2026-08-11.1";
1890
1979
  var APPLICATION_REQUIREMENTS = [
1891
1980
  {
1892
1981
  id: "platform-metadata",
@@ -1896,10 +1985,10 @@ var APPLICATION_REQUIREMENTS = [
1896
1985
  evidence: [
1897
1986
  "topolo.cloudcontrol.json exists for every deployed app surface",
1898
1987
  "package.json names the package or app when the repo ships code",
1899
- "The SDK application catalog exposes the app through topolo apps and MCP discovery"
1988
+ "The live platform registry exposes the app through topolo apps and MCP discovery"
1900
1989
  ],
1901
1990
  implementation: [
1902
- "Add the app to the SDK application catalog generator before exposing it to agents",
1991
+ "Register the app in the live platform registry (Auth service catalog) before exposing it to agents",
1903
1992
  "Keep production URLs, Worker names, Pages projects, routes, domains, and bindings current in CloudControl metadata",
1904
1993
  "Do not leave a new Topolo* app directory without an explicit application ID"
1905
1994
  ]
@@ -1974,7 +2063,7 @@ var APPLICATION_REQUIREMENTS = [
1974
2063
  implementation: [
1975
2064
  "Add production Worker api_url metadata and sdk_app_id for every agent-callable API target",
1976
2065
  "Mark browser, documentation, and runtime-only Worker targets with agent_callable:false",
1977
- "Expose stable app IDs through generated DEFAULT_APP_URLS and live catalog discovery",
2066
+ "Expose stable app IDs through live catalog discovery",
1978
2067
  "Keep typed SDK/CLI/MCP surfaces in sync when an API contract stabilizes"
1979
2068
  ]
1980
2069
  },
@@ -2094,15 +2183,24 @@ function applicationRequirementScopes(application) {
2094
2183
  }
2095
2184
  return Array.from(scopes);
2096
2185
  }
2186
+ function fallbackApplication(id) {
2187
+ const entry = STATIC_APPLICATIONS2[id];
2188
+ if (!entry) {
2189
+ throw new Error(
2190
+ `Unknown application "${id}" in the static fallback snapshot. Resolve applications from the live registry (resolveApplicationDirectoryEntry) and pass the entry directly.`
2191
+ );
2192
+ }
2193
+ return entry;
2194
+ }
2097
2195
  function requirementsForApplication(applicationOrId) {
2098
- const application = typeof applicationOrId === "string" ? APPLICATIONS[applicationOrId] : applicationOrId;
2196
+ const application = typeof applicationOrId === "string" ? fallbackApplication(applicationOrId) : applicationOrId;
2099
2197
  const scopes = new Set(applicationRequirementScopes(application));
2100
2198
  return APPLICATION_REQUIREMENTS.filter(
2101
2199
  (requirement) => requirement.appliesTo.some((scope) => scopes.has(scope))
2102
2200
  );
2103
2201
  }
2104
2202
  function auditApplicationRequirements(applicationOrId) {
2105
- const application = typeof applicationOrId === "string" ? APPLICATIONS[applicationOrId] : applicationOrId;
2203
+ const application = typeof applicationOrId === "string" ? fallbackApplication(applicationOrId) : applicationOrId;
2106
2204
  const scopes = applicationRequirementScopes(application);
2107
2205
  const findings = requirementsForApplication(application).map(
2108
2206
  (requirement) => evaluateRequirement(application, requirement)
@@ -2115,10 +2213,12 @@ function auditApplicationRequirements(applicationOrId) {
2115
2213
  findings
2116
2214
  };
2117
2215
  }
2118
- function auditAllApplicationRequirements(applicationIds = Object.keys(APPLICATIONS).sort()) {
2119
- const applications = applicationIds.map(
2120
- (applicationId) => auditApplicationRequirements(applicationId)
2216
+ function auditApplicationEntries(applications) {
2217
+ return buildAuditReport(
2218
+ applications.map((application) => auditApplicationRequirements(application))
2121
2219
  );
2220
+ }
2221
+ function buildAuditReport(applications) {
2122
2222
  const migrationQueue = applications.flatMap(
2123
2223
  (audit) => audit.findings.filter((finding2) => finding2.status !== "met").map((finding2) => ({
2124
2224
  applicationId: audit.application.id,
@@ -3421,8 +3521,9 @@ var TopoloClient = class _TopoloClient {
3421
3521
  });
3422
3522
  }
3423
3523
  /**
3424
- * Bundled generated registry for tooling that audits platform packaging, not
3425
- * for deciding what the current credential may use.
3524
+ * @deprecated Static fallback snapshot only. Use `searchServices()` (the
3525
+ * live credential-scoped registry) for anything that decides behavior; this
3526
+ * exists for offline packaging audits when the registry is unreachable.
3426
3527
  */
3427
3528
  listBundledServices() {
3428
3529
  const apps = Object.values(APPLICATIONS);
@@ -3813,6 +3914,52 @@ function mergeSignals(a, b) {
3813
3914
  b.addEventListener("abort", onAbort, { once: true });
3814
3915
  return controller.signal;
3815
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
+ }
3816
3963
  var CrmModule = class {
3817
3964
  constructor(client) {
3818
3965
  this.client = client;
@@ -4079,57 +4226,8 @@ var TopoloHttpError2 = class extends TopoloSdkError2 {
4079
4226
  }
4080
4227
  };
4081
4228
  var APP_API_URLS2 = {
4082
- agent: "https://agent.topolo.app",
4083
4229
  auth: "https://auth.topolo.app",
4084
- backup: "https://backup.topolo.app",
4085
- blog: "https://blog.topolo.app",
4086
- books: "https://books.topolo.app",
4087
- brand: "https://brand.topolo.app",
4088
- bugfix: "https://bugfix.topolo.app",
4089
- bytes: "https://bytes.topolo.app",
4090
- calendar: "https://calendar.topolo.app",
4091
- campaigns: "https://campaigns.topolo.app",
4092
- capacity: "https://capacity.topolo.app",
4093
- chat: "https://chat.topolo.app",
4094
- commerce: "https://commerce.topolo.app",
4095
- compose: "https://compose.topolo.app",
4096
- consent: "https://consent.topolo.app",
4097
- crm: "https://crm.topolo.app",
4098
- design: "https://design.topolo.app",
4099
- developers: "https://developers.topolo.app",
4100
- director: "https://director.topolo.app",
4101
- feed: "https://feed.topolo.app",
4102
- flow: "https://flow.topolo.app",
4103
- forecast: "https://forecast.topolo.app",
4104
- forms: "https://forms.topolo.app",
4105
- home: "https://home.topolo.app",
4106
- insights: "https://insights.topolo.app",
4107
- inventory: "https://inventory.topolo.app",
4108
- learn: "https://learn.topolo.app",
4109
- localize: "https://localize.topolo.app",
4110
- mail: "https://mail.topolo.app",
4111
- mdm: "https://mdm.topolo.app",
4112
- messages: "https://messages.topolo.app",
4113
- nexus: "https://nexus.topolo.app",
4114
- notify: "https://notify.topolo.app",
4115
- observability_watch: "https://observability-watch.topolo.app",
4116
- one: "https://www.topolo.app",
4117
- p2p: "https://p2p.topolo.app",
4118
- pay: "https://pay.topolo.app",
4119
- people: "https://people.topolo.app",
4120
- "quro.api": "https://api.ol0.me",
4121
- "quro.redirect": "https://ol0.me",
4122
- roadmapper: "https://roadmapper.topolo.app",
4123
- sign: "https://sign.topolo.app",
4124
- social_studio: "https://studio.topolo.app",
4125
- socialize: "https://socialize.topolo.app",
4126
- spaces: "https://spaces.topolo.app",
4127
- status: "https://status.topolo.app",
4128
- success: "https://success.topolo.app",
4129
- support: "https://support.topolo.app",
4130
- survey: "https://survey.topolo.app",
4131
- voice: "https://voice.topolo.app",
4132
- web: "https://web.topolo.app"
4230
+ developers: "https://developers.topolo.app"
4133
4231
  };
4134
4232
  var DEFAULT_APP_URLS2 = APP_API_URLS2;
4135
4233
  function resolveServiceUrl2(service, overrides) {
@@ -5055,7 +5153,7 @@ function parseQueryPairs(pairs) {
5055
5153
 
5056
5154
  // src/commands/actions.ts
5057
5155
  function registerActionsCommand(program2) {
5058
- 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) => {
5156
+ 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) => {
5059
5157
  const mode = resolveOutputMode(opts.json);
5060
5158
  try {
5061
5159
  const topolo = await buildClient();
@@ -5069,7 +5167,8 @@ function registerActionsCommand(program2) {
5069
5167
  cursor: opts.cursor,
5070
5168
  limit: parsePageSize(opts.limit)
5071
5169
  });
5072
- emit(result, mode, () => formatActionRows(result.actions, result.page.nextCursor));
5170
+ const output2 = opts.detail ? result : { actions: result.actions.map(compactActionCatalogEntry), page: result.page };
5171
+ emit(output2, mode, () => formatActionRows(result.actions, result.page.nextCursor));
5073
5172
  } catch (err) {
5074
5173
  fail(err, mode);
5075
5174
  }
@@ -5129,17 +5228,18 @@ function registerActionsCommand(program2) {
5129
5228
  fail(err, mode);
5130
5229
  }
5131
5230
  });
5132
- 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) => {
5231
+ 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) => {
5133
5232
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5134
5233
  try {
5135
5234
  const topolo = await requiredClient();
5136
5235
  const result = await topolo.client.validateActionInput(action, parseJsonObject(opts.data || "{}"));
5137
- emit(result, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
5236
+ const output2 = detailRequested(opts.detail, command) ? result : compactActionValidation(result.action.actionId, result.validation);
5237
+ emit(output2, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
5138
5238
  } catch (err) {
5139
5239
  fail(err, mode);
5140
5240
  }
5141
5241
  });
5142
- 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) => {
5242
+ 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) => {
5143
5243
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5144
5244
  try {
5145
5245
  const topolo = await requiredClient();
@@ -5148,7 +5248,7 @@ function registerActionsCommand(program2) {
5148
5248
  confirm: opts.confirm === true,
5149
5249
  ...resource ? { resource } : {}
5150
5250
  });
5151
- emit(plan, mode, () => formatActionPlan(plan));
5251
+ emit(detailRequested(opts.detail, command) ? plan : compactActionPlan(plan), mode, () => formatActionPlan(plan));
5152
5252
  } catch (err) {
5153
5253
  fail(err, mode);
5154
5254
  }
@@ -5168,19 +5268,23 @@ function registerActionsCommand(program2) {
5168
5268
  fail(err, mode);
5169
5269
  }
5170
5270
  });
5171
- 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) => {
5271
+ actions.command("capabilities").description("List every published action for one application (its capability contract)").option("-s, --service <service>", "App id, slug, or name").option("--detail", "Include full schemas and agent contracts").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts, command) => {
5172
5272
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5173
5273
  try {
5174
5274
  const topolo = await requiredClient();
5175
5275
  const service = opts.service || command.parent?.opts().service;
5176
5276
  if (!service?.trim()) throw new Error("--service is required for capability discovery");
5177
5277
  const result = await topolo.client.searchActions({ service: service.trim(), limit: 100 });
5178
- emit(result, mode, () => formatActionRows(result.actions, result.page.nextCursor));
5278
+ const output2 = detailRequested(opts.detail, command) ? result : { actions: result.actions.map(compactActionCatalogEntry), page: result.page };
5279
+ emit(output2, mode, () => formatActionRows(result.actions, result.page.nextCursor));
5179
5280
  } catch (err) {
5180
5281
  fail(err, mode);
5181
5282
  }
5182
5283
  });
5183
5284
  }
5285
+ function detailRequested(local, command) {
5286
+ return local === true || command.parent?.opts().detail === true;
5287
+ }
5184
5288
  async function requiredClient() {
5185
5289
  const topolo = await buildClient();
5186
5290
  if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
@@ -5284,7 +5388,7 @@ function formatActionPlan(plan) {
5284
5388
 
5285
5389
  // src/commands/apps.ts
5286
5390
  import { execSync } from "child_process";
5287
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
5391
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
5288
5392
  import path6 from "path";
5289
5393
 
5290
5394
  // src/commands/app-audit-local.ts
@@ -6523,12 +6627,6 @@ function normalizeContainerAppId(value, surfaceModel) {
6523
6627
  function normalizeApplicationId(application) {
6524
6628
  const raw = application.trim().toLowerCase();
6525
6629
  const normalized = raw.replace(/-/g, "_");
6526
- if (Object.prototype.hasOwnProperty.call(APPLICATIONS, raw)) {
6527
- return raw;
6528
- }
6529
- if (Object.prototype.hasOwnProperty.call(APPLICATIONS, normalized)) {
6530
- return normalized;
6531
- }
6532
6630
  if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(raw) || /^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/.test(raw)) {
6533
6631
  return normalized;
6534
6632
  }
@@ -6593,7 +6691,7 @@ function firstPartyAppPath(appDir, _audience) {
6593
6691
  }
6594
6692
 
6595
6693
  // src/commands/app-scaffold-platform-versions.ts
6596
- import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
6694
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
6597
6695
  import path2 from "path";
6598
6696
  var versionCache = /* @__PURE__ */ new Map();
6599
6697
  function platformPackagePin(packageName, options = {}) {
@@ -6601,7 +6699,7 @@ function platformPackagePin(packageName, options = {}) {
6601
6699
  options.repoRoot ?? process.env.TOPOLO_WORKSPACE_ROOT ?? void 0,
6602
6700
  options.cwd ?? process.cwd()
6603
6701
  );
6604
- if (!workspaceRoot) {
6702
+ if (!workspaceRoot || !existsSync3(path2.join(workspaceRoot, "topolo-platform", "packages"))) {
6605
6703
  throw new Error(
6606
6704
  `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".`
6607
6705
  );
@@ -7148,6 +7246,7 @@ function readme(application, target, options) {
7148
7246
  "- `@topolo-io/app-launcher` provides shared app switching and handoff helpers.",
7149
7247
  "- `@topolo-io/ui-kit` provides every control, table, panel, workbench, inspector, icon, transition, and bug reporter primitive used by the generated product surface.",
7150
7248
  "- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
7249
+ '- 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.',
7151
7250
  "- `@topolo-io/api-key-management`, `@topolo-io/chat-widget`, `@topolo-io/notifications`, and `@topolo-io/resource-context` complete the AppShell capability baseline.",
7152
7251
  "- Every Topolo-owned browser dependency is emitted at `latest`; product-domain packages are added only when the app actually adopts that domain.",
7153
7252
  "- `.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.",
@@ -8714,7 +8813,7 @@ function developerWorkerTs(application) {
8714
8813
  }
8715
8814
 
8716
8815
  // src/commands/app-scaffold-generate.ts
8717
- import { existsSync as existsSync3, mkdirSync, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync } from "fs";
8816
+ import { existsSync as existsSync4, mkdirSync, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync } from "fs";
8718
8817
  import { spawnSync } from "child_process";
8719
8818
  import { tmpdir } from "os";
8720
8819
  import path3 from "path";
@@ -8728,7 +8827,7 @@ function resolveGeneratorPath() {
8728
8827
  ];
8729
8828
  for (const candidate of candidates) {
8730
8829
  const p = decodeURIComponent(candidate.pathname);
8731
- if (existsSync3(p)) return p;
8830
+ if (existsSync4(p)) return p;
8732
8831
  }
8733
8832
  throw new Error(
8734
8833
  `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:
@@ -9914,7 +10013,7 @@ function applicationScaffoldPayload(application, options = {}) {
9914
10013
  const requestedId = normalizeApplicationId(application);
9915
10014
  const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
9916
10015
  const slug = toSlug(id);
9917
- const catalogApplication = APPLICATIONS[id];
10016
+ const catalogApplication = options.catalogApplication ?? null;
9918
10017
  const browser = options.browser !== false;
9919
10018
  const api = options.api ?? target === "platform";
9920
10019
  const audience = normalizeAudience(
@@ -11550,7 +11649,6 @@ function registerApplicationReconcileCommand(parent, ownerType, dependencies = {
11550
11649
  }
11551
11650
 
11552
11651
  // src/commands/apps.ts
11553
- var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
11554
11652
  var CLOUDCONTROL_LOCAL_APPLICATION = {
11555
11653
  id: "cloudcontrol",
11556
11654
  appDir: "TopoloCloudControl",
@@ -11560,6 +11658,21 @@ var CLOUDCONTROL_LOCAL_APPLICATION = {
11560
11658
  services: [],
11561
11659
  deployTargets: []
11562
11660
  };
11661
+ async function directoryClient() {
11662
+ try {
11663
+ const topolo = await buildClient();
11664
+ return topolo ? topolo.client : null;
11665
+ } catch {
11666
+ return null;
11667
+ }
11668
+ }
11669
+ async function resolveScaffoldCatalogApplication(application) {
11670
+ try {
11671
+ return await resolveApplication(application, await directoryClient());
11672
+ } catch {
11673
+ return null;
11674
+ }
11675
+ }
11563
11676
  function registerAppsCommand(program2) {
11564
11677
  const apps = program2.command("apps").description("Search Topolo platform applications available to the current credential").option("-q, --query <query>", "Filter by app id, slug, or name").option("--cursor <cursor>", "Continuation cursor from the previous page").option("--limit <limit>", "Page size (1-100)", "25").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action(async (opts) => {
11565
11678
  const mode = resolveOutputMode(opts.json);
@@ -11597,10 +11710,10 @@ function registerAppsCommand(program2) {
11597
11710
  });
11598
11711
  apps.command("requirements [application]").description(
11599
11712
  "Show the versioned Topolo application-build requirements, optionally filtered to one app."
11600
- ).option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action((application, opts) => {
11713
+ ).option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action(async (application, opts) => {
11601
11714
  const mode = resolveOutputMode(opts.json);
11602
11715
  try {
11603
- const payload = applicationRequirementsPayload(application);
11716
+ const payload = await applicationRequirementsPayload(application, await directoryClient());
11604
11717
  emit(payload, mode, () => formatRequirements(payload));
11605
11718
  } catch (err) {
11606
11719
  fail(err, mode);
@@ -11608,19 +11721,21 @@ function registerAppsCommand(program2) {
11608
11721
  });
11609
11722
  apps.command("audit [application]").description(
11610
11723
  "Audit Topolo applications against the versioned application-build requirements."
11611
- ).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(
11724
+ ).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(
11612
11725
  "--fail-on <status>",
11613
11726
  "Exit non-zero when audit has findings at this severity or worse: missing, needs_review, partial"
11614
- ).action((application, opts) => {
11727
+ ).action(async (application, opts) => {
11615
11728
  const mode = resolveOutputMode(opts.json);
11616
11729
  try {
11617
- const payload = applicationAuditPayload(application, {
11730
+ const payload = await applicationAuditPayload(application, {
11618
11731
  local: opts.local,
11619
- repoRoot: opts.repoRoot
11732
+ repoRoot: opts.repoRoot,
11733
+ client: await directoryClient()
11620
11734
  });
11621
11735
  const gate = opts.failOn ? evaluateApplicationAuditGate(payload, opts.failOn) : null;
11622
- const output2 = gate ? { ...payload, conformanceGate: gate } : payload;
11623
- emit(output2, mode, () => formatAudit(output2));
11736
+ const compact = opts.detail ? payload : compactApplicationAudit(payload);
11737
+ const output2 = gate ? { ...compact, conformanceGate: gate } : compact;
11738
+ emit(output2, mode, () => formatAudit(gate ? { ...payload, conformanceGate: gate } : payload));
11624
11739
  if (gate && !gate.passed) {
11625
11740
  if (mode === "json") {
11626
11741
  process.stderr.write(JSON.stringify({ error: gate.message, conformanceGate: gate }, null, 2) + "\n");
@@ -11636,10 +11751,13 @@ function registerAppsCommand(program2) {
11636
11751
  });
11637
11752
  apps.command("record-evidence <application>").description(
11638
11753
  "Stamp <repo>/.topolo/evidence.json with the latest CI gate results so the audit can flip verification-gates / observability-and-audit / canonical-docs from partial to met when the recorded run is fresh (< 24h)."
11639
- ).option("--json", "Output JSON", true).option("--no-json", "Output human-readable summary").option("--app-root <path>", "Path to the application repo root (defaults to cwd)").option("--build", "Mark the latest build as successful").option("--typecheck", "Mark the latest typecheck as successful").option("--test", "Mark the latest test run as successful").option("--docs-validate", "Mark the latest TopoloDocs npm run validate as successful").option("--smoke <url>", "Mark the latest live smoke as successful against this URL").option("--commit <sha>", "Override the commit hash (defaults to git HEAD)").action((application, opts) => {
11754
+ ).option("--json", "Output JSON", true).option("--no-json", "Output human-readable summary").option("--app-root <path>", "Path to the application repo root (defaults to cwd)").option("--build", "Mark the latest build as successful").option("--typecheck", "Mark the latest typecheck as successful").option("--test", "Mark the latest test run as successful").option("--docs-validate", "Mark the latest TopoloDocs npm run validate as successful").option("--smoke <url>", "Mark the latest live smoke as successful against this URL").option("--commit <sha>", "Override the commit hash (defaults to git HEAD)").action(async (application, opts) => {
11640
11755
  const mode = resolveOutputMode(opts.json);
11641
11756
  try {
11642
- const payload = recordApplicationEvidence(application, opts);
11757
+ const payload = await recordApplicationEvidence(application, {
11758
+ ...opts,
11759
+ client: await directoryClient()
11760
+ });
11643
11761
  emit(payload, mode, () => formatEvidence(payload));
11644
11762
  } catch (err) {
11645
11763
  fail(err, mode);
@@ -11661,6 +11779,7 @@ function registerAppsCommand(program2) {
11661
11779
  try {
11662
11780
  const api = resolveScaffoldApiOption(opts.api, opts.withApi);
11663
11781
  const payload = applicationScaffoldPayload(application, {
11782
+ catalogApplication: await resolveScaffoldCatalogApplication(application),
11664
11783
  appDir: opts.appDir,
11665
11784
  name: opts.name,
11666
11785
  packageName: opts.packageName,
@@ -11722,14 +11841,11 @@ function parsePageSize2(value) {
11722
11841
  }
11723
11842
  return parsed;
11724
11843
  }
11725
- function resolveApplication(application) {
11726
- const id = application.trim().toLowerCase();
11727
- if (!APPLICATION_IDS.includes(id)) {
11728
- throw new Error(`Unknown application "${application}". ${formatKnownValues(APPLICATION_IDS, "applications")}`);
11729
- }
11730
- return APPLICATIONS[id];
11844
+ async function resolveApplication(application, client = null) {
11845
+ const resolved = await resolveApplicationDirectoryEntry(client, application);
11846
+ return resolved.application;
11731
11847
  }
11732
- function applicationRequirementsPayload(application) {
11848
+ async function applicationRequirementsPayload(application, client = null) {
11733
11849
  if (!application) {
11734
11850
  return {
11735
11851
  version: APPLICATION_REQUIREMENTS_VERSION,
@@ -11738,7 +11854,7 @@ function applicationRequirementsPayload(application) {
11738
11854
  requirements: [...APPLICATION_REQUIREMENTS]
11739
11855
  };
11740
11856
  }
11741
- const app = resolveApplication(application);
11857
+ const app = await resolveApplication(application, client);
11742
11858
  return {
11743
11859
  version: APPLICATION_REQUIREMENTS_VERSION,
11744
11860
  application: app,
@@ -11746,20 +11862,23 @@ function applicationRequirementsPayload(application) {
11746
11862
  requirements: requirementsForApplication(app)
11747
11863
  };
11748
11864
  }
11749
- function applicationAuditPayload(application, options = {}) {
11865
+ async function applicationAuditPayload(application, options = {}) {
11866
+ const client = options.client ?? null;
11750
11867
  const requestedId = application?.trim().toLowerCase();
11751
11868
  const report = requestedId === "cloudcontrol" && options.local ? {
11752
11869
  version: APPLICATION_REQUIREMENTS_VERSION,
11753
11870
  applications: [auditApplicationRequirements(CLOUDCONTROL_LOCAL_APPLICATION)],
11754
11871
  migrationQueue: []
11755
- } : application ? auditAllApplicationRequirements([resolveApplication(application).id]) : auditAllApplicationRequirements();
11872
+ } : application ? auditApplicationEntries([await resolveApplication(application, client)]) : auditApplicationEntries(
11873
+ (await listApplicationDirectory(client)).applications.map((app) => app.application)
11874
+ );
11756
11875
  if (!options.local) return report;
11757
11876
  return enrichApplicationAuditWithLocalEvidence(report, { repoRoot: options.repoRoot });
11758
11877
  }
11759
- function recordApplicationEvidence(application, options = {}) {
11760
- const appId = resolveApplication(application).id;
11878
+ async function recordApplicationEvidence(application, options = {}) {
11879
+ const appId = (await resolveApplication(application, options.client ?? null)).id;
11761
11880
  const root = path6.resolve(options.appRoot ?? process.cwd());
11762
- if (!existsSync4(root)) {
11881
+ if (!existsSync5(root)) {
11763
11882
  throw new Error(`Application root does not exist: ${root}`);
11764
11883
  }
11765
11884
  let commit = options.commit;
@@ -11773,7 +11892,7 @@ function recordApplicationEvidence(application, options = {}) {
11773
11892
  const evidenceDir = path6.join(root, ".topolo");
11774
11893
  const evidencePath = path6.join(evidenceDir, "evidence.json");
11775
11894
  let before = null;
11776
- if (existsSync4(evidencePath)) {
11895
+ if (existsSync5(evidencePath)) {
11777
11896
  try {
11778
11897
  const raw = JSON.parse(readFileSync6(evidencePath, "utf8"));
11779
11898
  if (raw && typeof raw === "object" && raw.version === 1) {
@@ -11888,22 +12007,7 @@ function applicationUrl(application) {
11888
12007
  return application.apiBaseUrl ?? application.launchUrl ?? application.productionUrl ?? null;
11889
12008
  }
11890
12009
  function displayCatalogId(entry) {
11891
- for (const value of [entry.slug, entry.name, entry.appId]) {
11892
- if (typeof value !== "string" || !value.trim()) continue;
11893
- const candidates = catalogIdCandidates(value);
11894
- const withoutTopolo = candidates.find((candidate) => !candidate.startsWith("topolo-"));
11895
- if (withoutTopolo) return withoutTopolo;
11896
- if (candidates[0]) return candidates[0];
11897
- }
11898
- return entry.appId;
11899
- }
11900
- function catalogIdCandidates(value) {
11901
- const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
11902
- const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
11903
- return [.../* @__PURE__ */ new Set([
11904
- normalized,
11905
- withoutTopolo
11906
- ])].filter(Boolean);
12010
+ return friendlyAppId(entry);
11907
12011
  }
11908
12012
  function formatRequirements(payload) {
11909
12013
  const lines = [
@@ -12067,12 +12171,6 @@ function normalizeFailOn(value) {
12067
12171
  }
12068
12172
  throw new Error("Invalid --fail-on value. Use missing, needs_review, or partial.");
12069
12173
  }
12070
- function formatKnownValues(values, label) {
12071
- const sorted = [...values].sort();
12072
- const preview = sorted.slice(0, 25).join(", ");
12073
- const suffix = sorted.length > 25 ? `, ... +${sorted.length - 25} more` : "";
12074
- return `Run discovery for the full ${label} list. Known snapshot sample: ${preview}${suffix}`;
12075
- }
12076
12174
  function statusesAtOrWorse(threshold) {
12077
12175
  if (threshold === "missing") return ["missing"];
12078
12176
  if (threshold === "needs_review") return ["missing", "needs_review"];
@@ -12274,6 +12372,7 @@ function registerPlatformCommand(program2) {
12274
12372
  }
12275
12373
  const api = resolveScaffoldApiOption(opts.api, opts.withApi);
12276
12374
  const payload = applicationScaffoldPayload(application, {
12375
+ catalogApplication: await resolveScaffoldCatalogApplication(application),
12277
12376
  appDir: opts.appDir,
12278
12377
  name: opts.name,
12279
12378
  packageName: opts.packageName,
@@ -12525,15 +12624,7 @@ function isStagingUrl(value) {
12525
12624
  return url.includes(".stg.topolo.us") || url.includes("localhost") || url.includes("127.0.0.1") || url.includes("topolo-staging");
12526
12625
  }
12527
12626
  function displayAppId(entry) {
12528
- for (const value of [entry.slug, entry.name, entry.appId]) {
12529
- if (typeof value !== "string" || !value.trim()) continue;
12530
- for (const candidate of appIdCandidates(value)) {
12531
- if (Object.prototype.hasOwnProperty.call(DEFAULT_APP_URLS, candidate)) {
12532
- return candidate;
12533
- }
12534
- }
12535
- }
12536
- return appIdCandidates(entry.slug || entry.appId)[0] || entry.appId;
12627
+ return friendlyAppId(entry);
12537
12628
  }
12538
12629
  function defaultServiceUrl(id, entry) {
12539
12630
  return resolveCatalogServiceUrl(entry, { serviceKey: id });
@@ -13073,7 +13164,7 @@ function formatCheckResult(result) {
13073
13164
 
13074
13165
  // src/commands/skills.ts
13075
13166
  import { spawnSync as spawnSync2 } from "child_process";
13076
- import { existsSync as existsSync5 } from "fs";
13167
+ import { existsSync as existsSync6 } from "fs";
13077
13168
  import { mkdir as mkdir4, copyFile, readFile as readFile7, writeFile as writeFile7, rm } from "fs/promises";
13078
13169
  import { homedir as homedir2 } from "os";
13079
13170
  import { dirname as dirname4, join as join5, resolve as resolve3 } from "path";
@@ -13085,7 +13176,7 @@ function skillsSourceDir() {
13085
13176
  const distCandidate = resolve3(here, "../skills");
13086
13177
  const srcCandidate = resolve3(here, "../../skills");
13087
13178
  for (const candidate of [distCandidate, srcCandidate]) {
13088
- if (existsSync5(join5(candidate, "topolo", "SKILL.md"))) return candidate;
13179
+ if (existsSync6(join5(candidate, "topolo", "SKILL.md"))) return candidate;
13089
13180
  }
13090
13181
  return distCandidate;
13091
13182
  }
@@ -13130,7 +13221,7 @@ function registerViaOfficialCli(cli, mcp, agentName) {
13130
13221
  async function registerClaudeViaConfig(mcp) {
13131
13222
  const path9 = join5(homedir2(), ".claude.json");
13132
13223
  let config = {};
13133
- if (existsSync5(path9)) {
13224
+ if (existsSync6(path9)) {
13134
13225
  try {
13135
13226
  config = JSON.parse(await readFile7(path9, "utf8"));
13136
13227
  } catch {
@@ -13151,7 +13242,7 @@ async function registerClaudeViaConfig(mcp) {
13151
13242
  async function registerCodexViaConfig(mcp) {
13152
13243
  const path9 = join5(homedir2(), ".codex", "config.toml");
13153
13244
  let text = "";
13154
- if (existsSync5(path9)) text = await readFile7(path9, "utf8");
13245
+ if (existsSync6(path9)) text = await readFile7(path9, "utf8");
13155
13246
  text = text.replace(
13156
13247
  /(^|\n)\[mcp_servers\.topolo(?:\.[a-z]+)?\][^\[]*/g,
13157
13248
  (match) => match.startsWith("\n") ? "\n" : ""
@@ -13206,7 +13297,7 @@ function registerSkillsCommand(program2) {
13206
13297
  const mode = resolveOutputMode(opts.json);
13207
13298
  try {
13208
13299
  const sourceDir = skillsSourceDir();
13209
- if (!existsSync5(join5(sourceDir, "topolo", "SKILL.md"))) {
13300
+ if (!existsSync6(join5(sourceDir, "topolo", "SKILL.md"))) {
13210
13301
  throw new Error(
13211
13302
  "Bundled skill file not found. Reinstall the CLI: npm install -g @topolo/cli"
13212
13303
  );
@@ -13235,7 +13326,7 @@ function registerSkillsCommand(program2) {
13235
13326
  const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
13236
13327
  return {
13237
13328
  agent,
13238
- skillInstalled: existsSync5(join5(skillDir, "SKILL.md")),
13329
+ skillInstalled: existsSync6(join5(skillDir, "SKILL.md")),
13239
13330
  skillPath: join5(skillDir, "SKILL.md"),
13240
13331
  cliAvailable: agentBinaryExists(agent)
13241
13332
  };
@@ -13252,7 +13343,7 @@ function registerSkillsCommand(program2) {
13252
13343
  for (const agent of resolveTargets(opts)) {
13253
13344
  const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
13254
13345
  const skillFile = join5(skillDir, "SKILL.md");
13255
- const skillRemoved = existsSync5(skillFile);
13346
+ const skillRemoved = existsSync6(skillFile);
13256
13347
  if (skillRemoved) await rm(skillFile, { force: true });
13257
13348
  if (agentBinaryExists(agent)) {
13258
13349
  spawnSync2(agent, ["mcp", "remove", "topolo"], { stdio: "ignore" });
@@ -13504,12 +13595,14 @@ function registerContextCommand(program2) {
13504
13595
 
13505
13596
  // src/commands/resources.ts
13506
13597
  function registerResourcesCommand(program2) {
13507
- 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) => {
13598
+ 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) => {
13508
13599
  const mode = resolveOutputMode(opts.json);
13509
13600
  try {
13510
13601
  const topolo = await buildClient();
13511
13602
  if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
13512
- const result = await topolo.client.listBindableResources(application);
13603
+ const selector = application?.trim() || opts.service?.trim();
13604
+ if (!selector) throw new Error("application or --service is required");
13605
+ const result = await topolo.client.listBindableResources(selector);
13513
13606
  emit(result, mode, () => formatResources(result));
13514
13607
  } catch (err) {
13515
13608
  fail(err, mode);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
- "version": "0.6.10",
4
- "topoloPublishedVersion": "0.6.10",
3
+ "version": "0.7.1",
4
+ "topoloPublishedVersion": "0.7.1",
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": {
@@ -4,7 +4,7 @@ This file teaches an agent how to interact with the Topolo platform via the `top
4
4
 
5
5
  ## What the platform is
6
6
 
7
- Topolo is a multi-app SaaS platform. The CLI and MCP server both use `@topolo/sdk`; bundled app IDs come from the generated registry, and newly registered API/technical services can be resolved from the live Auth catalog.
7
+ Topolo is a multi-app SaaS platform. The CLI and MCP server both use `@topolo/sdk`; every application resolves from the live credential-scoped registry, so newly registered apps and API/technical services work immediately — there is no bundled application catalog to rely on or wait for.
8
8
 
9
9
  - Use `topolo apps --json` or the MCP `topolo_search_applications` tool to discover platform applications, including browser-only Worker apps.
10
10
  - Use `topolo apps requirements <application_id> --json` or the MCP `topolo_list_application_requirements` tool before creating or expanding a Topolo app.
@@ -12,7 +12,7 @@ Topolo is a multi-app SaaS platform. The CLI and MCP server both use `@topolo/sd
12
12
  - Use `topolo services --json` or the MCP `topolo_search_applications` tool to discover callable app ids and effective URLs.
13
13
  - Use `topolo actions --service <app> --json` or MCP `topolo_search_actions` to discover callable operations granted to the current credential; invoke automatic reads with `topolo_read_action` and confirmation-gated operations with `topolo_call_action`.
14
14
  - Use `topolo actions capabilities`, `actions examples`, `actions validate`, and `actions plan` (or their MCP equivalents) to obtain schemas, examples, verification, and rollback before execution.
15
- - The generic `api` command and MCP `topolo_api_call` tool can reach bundled SDK services and live Auth-catalog API/technical services when no action contract exists yet.
15
+ - The generic `api` command and MCP `topolo_api_call` tool can reach any live Auth-catalog application or API/technical service when no action contract exists yet; discover ids with `topolo apps --json` first.
16
16
 
17
17
  Every call is scoped to the organization embedded in the credential. There is no `--org` flag anywhere — attempting to target another org will fail.
18
18
 
@@ -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