@topolo/sdk 0.4.1 → 0.4.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
@@ -35,7 +35,15 @@ const topolo = createTopolo({
35
35
  const me = await topolo.identity.whoami();
36
36
  console.log(me.organization?.slug);
37
37
 
38
- const { contacts } = await topolo.crm.listContacts({ q: 'acme' });
38
+ const actions = await topolo.client.listActions({ service: 'mail' });
39
+ const sendMessage = actions.find((action) => action.name === 'messages.send');
40
+ if (sendMessage) {
41
+ await topolo.client.callAction(sendMessage.actionId, {
42
+ mailboxId: 'primary',
43
+ subject: 'Hello',
44
+ text: '...',
45
+ }, { confirm: true });
46
+ }
39
47
  ```
40
48
 
41
49
  ## Design invariants
@@ -73,14 +81,16 @@ Every request sends:
73
81
  Combined with the credential identity, this makes every platform API call
74
82
  traceable to a specific agent invocation.
75
83
 
76
- ## Modules (Phase 1)
84
+ ## Modules
77
85
 
78
- | Module | Methods |
86
+ | Surface | Methods |
79
87
  | ------------ | ------------------------------------- |
80
88
  | `identity` | `whoami()` |
81
- | `crm` | `listContacts()`, `getContact(id)` |
89
+ | `client` | `listServices()`, `listActions()`, `getAction()`, `callAction()` |
90
+ | app modules | compatibility helpers for stable app-specific contracts |
82
91
 
83
- Additional modules land per-app as each backend contract stabilizes.
92
+ Agents should use the action catalog for app-specific operations so new
93
+ services and actions become usable without an SDK, CLI, or MCP release.
84
94
 
85
95
  ## Application catalog
86
96
 
@@ -89,12 +99,29 @@ PlatformApplications metadata, including Worker apps that do not expose a
89
99
  callable service. `DEFAULT_SERVICE_URLS` is the bundled callable API service
90
100
  snapshot generated from `topolo.cloudcontrol.json`.
91
101
 
92
- For agent-scale fleets, the bundle is a floor, not a cap. `TopoloClient.request`
93
- accepts bundled service IDs and live Auth-catalog service IDs/slugs. When a
94
- service is not in the bundled snapshot, the client resolves the selected
95
- environment's live catalog and only routes entries marked as API/technical
96
- service surfaces. Browser/runtime surfaces are not treated as generic API
97
- targets.
102
+ For agent-scale fleets, the bundle is packaging metadata, not an authorization
103
+ source. `TopoloClient.listServices()` always calls the live TopoloAuth catalog
104
+ with the current credential and returns only services that credential can use in
105
+ its organization, including the granted permissions. `TopoloClient.request()`
106
+ uses that credential-scoped catalog before routing any non-Auth service, so a
107
+ bundled service ID is only a convenient alias after Auth confirms access.
108
+
109
+ `listBundledServices()` is available for packaging/audit tooling that needs the
110
+ generated snapshot. Do not use it to decide what an end user or agent may call.
111
+
112
+ ## Action catalog
113
+
114
+ `TopoloClient.listActions()` reads TopoloAuth's live action catalog for the
115
+ current credential. Applications publish their action definitions through
116
+ Topolo Developers, and Auth returns only actions whose existing service
117
+ permission is granted to the credential. The action entry includes the service,
118
+ HTTP method/path, required permission, input/output schemas, read-only and
119
+ destructive hints, and the stable MCP/agent tool name.
120
+
121
+ `TopoloClient.callAction(actionRef, input, options)` resolves an action by
122
+ `actionId`, `name`, or `toolName`, maps path parameters and query/body values
123
+ from `input`, and routes the request through the same auth, audit-header, and
124
+ write-confirmation flow as `request()`.
98
125
 
99
126
  `APPLICATION_REQUIREMENTS` is the versioned app-build contract agents should
100
127
  read before creating or expanding a Topolo app. `requirementsForApplication()`
@@ -159,12 +186,12 @@ Defaults target production. Override per-service for staging/dev:
159
186
  createTopolo({
160
187
  /* ... */
161
188
  serviceUrls: {
162
- crm: 'https://edge-crm-stg.topolo.workers.dev',
189
+ mail: 'https://mail.stg.topolo.us',
163
190
  },
164
191
  });
165
192
  ```
166
193
 
167
- Or via env: `TOPOLO_SERVICE_URL_CRM=...` (falls through `resolveServiceUrl`).
194
+ Or via env: `TOPOLO_SERVICE_URL_MAIL=...` (falls through `resolveServiceUrl`).
168
195
 
169
196
  ## Development
170
197
 
package/dist/index.cjs CHANGED
@@ -2123,6 +2123,7 @@ var TopoloClient = class _TopoloClient {
2123
2123
  fetchImpl;
2124
2124
  debug;
2125
2125
  serviceCatalogCache = null;
2126
+ actionCatalogCache = null;
2126
2127
  /**
2127
2128
  * Process-wide cache for slug → service-id lookups. Keyed by
2128
2129
  * `${authBaseUrl}|${slug}` so multiple `TopoloClient` instances pointing at
@@ -2258,12 +2259,16 @@ var TopoloClient = class _TopoloClient {
2258
2259
  if (!serviceKey) {
2259
2260
  throw new TopoloSdkError("unknown_service", "service is required");
2260
2261
  }
2261
- const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
2262
- if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
2263
- const staticUrl = DEFAULT_SERVICE_URLS[serviceKey];
2264
- if (staticUrl) return { serviceKey, baseUrl: staticUrl };
2262
+ if (serviceKey === "auth") {
2263
+ const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
2264
+ if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
2265
+ return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS.auth };
2266
+ }
2265
2267
  const live = await this.resolveLiveCallableService(serviceKey);
2266
- if (live) return live;
2268
+ if (live) {
2269
+ const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
2270
+ return overrideUrl ? { ...live, baseUrl: overrideUrl } : live;
2271
+ }
2267
2272
  throw new TopoloSdkError(
2268
2273
  "unknown_service",
2269
2274
  `Unknown Topolo service "${service}". Run service discovery first and pass a callable service id or slug.`
@@ -2273,10 +2278,7 @@ var TopoloClient = class _TopoloClient {
2273
2278
  const catalog = await this.listServices();
2274
2279
  const match = catalog.find((entry) => catalogEntryMatches(entry, serviceKey));
2275
2280
  if (!match?.baseUrl || !isCallableCatalogEntry(match)) return null;
2276
- return {
2277
- serviceKey: normalizeLookupKey(match.slug || match.name || match.serviceId) || serviceKey,
2278
- baseUrl: match.baseUrl
2279
- };
2281
+ return { serviceKey, baseUrl: match.baseUrl };
2280
2282
  }
2281
2283
  /**
2282
2284
  * Resolve a service-slug → opaque service-id via caller config or TopoloAuth.
@@ -2338,53 +2340,112 @@ var TopoloClient = class _TopoloClient {
2338
2340
  /**
2339
2341
  * List the platform service catalog from the live registry
2340
2342
  * (`GET /api/services/catalog` on TopoloAuth). Credential-aware: the
2341
- * caller's credential is attached, so an authenticated caller in an
2342
- * entitled org also receives the `developer_only` services its role
2343
- * permits; an unauthenticated/agent caller receives `active` only.
2343
+ * caller's credential is attached, so the result only includes services the
2344
+ * credential's organization and effective permissions can use.
2344
2345
  *
2345
- * Resilient: on any network/HTTP failure this falls back to the
2346
- * build-time generated `APPLICATIONS` snapshot so offline/degraded
2347
- * callers still get a usable (if static) catalog. The live result is
2348
- * authoritative; the snapshot is a floor, not a cap.
2346
+ * Security boundary: this never falls back to the generated global snapshot.
2347
+ * The live Auth catalog is the authority because it is scoped to the caller's
2348
+ * organization and effective permissions.
2349
2349
  */
2350
2350
  async listServices() {
2351
2351
  const now = Date.now();
2352
2352
  if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
2353
2353
  return this.serviceCatalogCache.entries;
2354
2354
  }
2355
- try {
2356
- const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
2357
- const raw = res.data?.services ?? res.services;
2358
- if (Array.isArray(raw) && raw.length > 0) {
2359
- const entries = [];
2360
- for (const item of raw) {
2361
- const s = item;
2362
- const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
2363
- if (!serviceId) continue;
2364
- entries.push({
2365
- serviceId,
2366
- slug: typeof s.app_slug === "string" ? s.app_slug : null,
2367
- name: typeof s.name === "string" ? s.name : null,
2368
- description: typeof s.description === "string" ? s.description : null,
2369
- baseUrl: typeof s.base_url === "string" ? s.base_url : null,
2370
- status: typeof s.status === "string" ? s.status : null,
2371
- surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
2372
- launchable: s.launchable === true
2373
- });
2374
- }
2375
- if (entries.length > 0) {
2376
- this.serviceCatalogCache = {
2377
- entries,
2378
- expiresAt: now + CATALOG_CACHE_TTL_MS
2379
- };
2380
- return entries;
2381
- }
2382
- }
2383
- } catch {
2355
+ const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
2356
+ const raw = res.data?.services ?? res.services;
2357
+ if (!Array.isArray(raw)) {
2358
+ throw new TopoloSdkError(
2359
+ "service_catalog_unavailable",
2360
+ "TopoloAuth did not return a service catalog for the current credential."
2361
+ );
2362
+ }
2363
+ const entries = [];
2364
+ for (const item of raw) {
2365
+ const s = item;
2366
+ const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
2367
+ if (!serviceId) continue;
2368
+ const permissions = Array.isArray(s.granted_permissions) ? s.granted_permissions.filter((permission) => typeof permission === "string" && permission.trim().length > 0) : [];
2369
+ entries.push({
2370
+ serviceId,
2371
+ slug: typeof s.app_slug === "string" ? s.app_slug : null,
2372
+ name: typeof s.name === "string" ? s.name : null,
2373
+ description: typeof s.description === "string" ? s.description : null,
2374
+ baseUrl: typeof s.base_url === "string" ? s.base_url : null,
2375
+ status: typeof s.status === "string" ? s.status : null,
2376
+ surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
2377
+ launchable: s.launchable === true,
2378
+ permissions
2379
+ });
2384
2380
  }
2385
- return this.generatedCatalogFallback();
2381
+ this.serviceCatalogCache = {
2382
+ entries,
2383
+ expiresAt: now + CATALOG_CACHE_TTL_MS
2384
+ };
2385
+ return entries;
2386
+ }
2387
+ /**
2388
+ * List callable actions from the live Auth catalog. The response is scoped to
2389
+ * the caller's organization and effective permissions; no bundled/static app
2390
+ * action list is used as an authorization source.
2391
+ */
2392
+ async listActions(options = {}) {
2393
+ const now = Date.now();
2394
+ if (!options.service && this.actionCatalogCache && this.actionCatalogCache.expiresAt > now) {
2395
+ return this.actionCatalogCache.entries;
2396
+ }
2397
+ const res = await this.request({
2398
+ service: "auth",
2399
+ method: "GET",
2400
+ path: "/api/actions/catalog",
2401
+ ...options.service ? { query: { service: options.service } } : {}
2402
+ });
2403
+ const raw = res.data?.actions ?? res.actions;
2404
+ if (!Array.isArray(raw)) {
2405
+ throw new TopoloSdkError(
2406
+ "action_catalog_unavailable",
2407
+ "TopoloAuth did not return an action catalog for the current credential."
2408
+ );
2409
+ }
2410
+ const entries = raw.map((item) => normalizeActionCatalogEntry(item)).filter((entry) => Boolean(entry));
2411
+ if (!options.service) {
2412
+ this.actionCatalogCache = {
2413
+ entries,
2414
+ expiresAt: now + CATALOG_CACHE_TTL_MS
2415
+ };
2416
+ }
2417
+ return entries;
2418
+ }
2419
+ async getAction(action) {
2420
+ const needle = normalizeActionLookupKey(action);
2421
+ const actions = await this.listActions();
2422
+ const match = actions.find((entry) => actionEntryMatches(entry, needle));
2423
+ if (!match) {
2424
+ throw new TopoloSdkError(
2425
+ "unknown_action",
2426
+ `Unknown Topolo action "${action}". Run action discovery first and pass an action id, name, or tool name.`
2427
+ );
2428
+ }
2429
+ return match;
2430
+ }
2431
+ async callAction(action, input = {}, options = {}) {
2432
+ const entry = await this.getAction(action);
2433
+ const mapped = mapActionInput(entry, input);
2434
+ const service = entry.serviceSlug || entry.serviceId;
2435
+ return this.request({
2436
+ service,
2437
+ method: entry.method,
2438
+ path: mapped.path,
2439
+ ...mapped.query ? { query: mapped.query } : {},
2440
+ ...mapped.body !== void 0 ? { body: mapped.body } : {},
2441
+ confirm: options.confirm === true
2442
+ });
2386
2443
  }
2387
- generatedCatalogFallback() {
2444
+ /**
2445
+ * Bundled generated registry for tooling that audits platform packaging, not
2446
+ * for deciding what the current credential may use.
2447
+ */
2448
+ listBundledServices() {
2388
2449
  const apps = Object.values(APPLICATIONS);
2389
2450
  return apps.map((app) => ({
2390
2451
  serviceId: app.id,
@@ -2392,9 +2453,10 @@ var TopoloClient = class _TopoloClient {
2392
2453
  name: app.name ?? app.id,
2393
2454
  description: null,
2394
2455
  baseUrl: app.productionUrl ?? null,
2395
- status: "active",
2456
+ status: "bundled",
2396
2457
  surfaceKind: "application",
2397
- launchable: Boolean(app.productionUrl)
2458
+ launchable: Boolean(app.productionUrl),
2459
+ permissions: []
2398
2460
  }));
2399
2461
  }
2400
2462
  /**
@@ -2464,7 +2526,7 @@ function serviceKeyCandidates(slug) {
2464
2526
  ])];
2465
2527
  }
2466
2528
  function normalizeLookupKey(value) {
2467
- return String(value || "").trim().toLowerCase();
2529
+ return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
2468
2530
  }
2469
2531
  function catalogEntryMatches(entry, needle) {
2470
2532
  return catalogEntryLookupKeys(entry).includes(needle);
@@ -2475,12 +2537,8 @@ function catalogEntryLookupKeys(entry) {
2475
2537
  for (const value of values) {
2476
2538
  const normalized = normalizeLookupKey(value);
2477
2539
  if (!normalized) continue;
2478
- keys.add(normalized);
2479
- keys.add(normalized.replace(/-/g, "_"));
2480
- if (normalized.startsWith("topolo-")) {
2481
- const withoutTopolo = normalized.slice("topolo-".length);
2482
- keys.add(withoutTopolo);
2483
- keys.add(withoutTopolo.replace(/-/g, "_"));
2540
+ for (const candidate of serviceKeyCandidates(normalized)) {
2541
+ keys.add(candidate);
2484
2542
  }
2485
2543
  }
2486
2544
  return [...keys];
@@ -2488,7 +2546,101 @@ function catalogEntryLookupKeys(entry) {
2488
2546
  function isCallableCatalogEntry(entry) {
2489
2547
  const kind = normalizeLookupKey(entry.surfaceKind || "");
2490
2548
  if (kind === "api" || kind === "technical_service" || kind === "service") return true;
2491
- return Boolean(entry.slug && Object.prototype.hasOwnProperty.call(DEFAULT_SERVICE_URLS, catalogEntryLookupKeys(entry)[0] ?? ""));
2549
+ return Boolean(
2550
+ entry.baseUrl && catalogEntryLookupKeys(entry).some((key) => Object.prototype.hasOwnProperty.call(DEFAULT_SERVICE_URLS, key))
2551
+ );
2552
+ }
2553
+ function normalizeActionCatalogEntry(item) {
2554
+ if (!item || typeof item !== "object" || Array.isArray(item)) return null;
2555
+ const a = item;
2556
+ const actionId = pickActionString(a.action_id) ?? pickActionString(a.actionId);
2557
+ const name = pickActionString(a.name);
2558
+ const serviceId = pickActionString(a.service_id) ?? pickActionString(a.serviceId);
2559
+ const method = pickActionString(a.method)?.toUpperCase();
2560
+ const path = pickActionString(a.path);
2561
+ if (!actionId || !name || !serviceId || !method || !path || !isHttpMethod(method)) {
2562
+ return null;
2563
+ }
2564
+ return {
2565
+ actionId,
2566
+ name,
2567
+ toolName: pickActionString(a.tool_name) ?? pickActionString(a.toolName) ?? null,
2568
+ title: pickActionString(a.title) ?? name,
2569
+ description: pickActionString(a.description) ?? "",
2570
+ serviceId,
2571
+ serviceSlug: pickActionString(a.service_slug) ?? pickActionString(a.serviceSlug) ?? null,
2572
+ serviceName: pickActionString(a.service_name) ?? pickActionString(a.serviceName) ?? null,
2573
+ method,
2574
+ path,
2575
+ permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
2576
+ requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
2577
+ inputSchema: plainObject(a.input_schema ?? a.inputSchema),
2578
+ outputSchema: plainObject(a.output_schema ?? a.outputSchema),
2579
+ readOnly: Boolean(a.read_only ?? a.readOnly),
2580
+ destructive: Boolean(a.destructive),
2581
+ requiresConfirmation: Boolean(a.requires_confirmation ?? a.requiresConfirmation),
2582
+ status: pickActionString(a.status),
2583
+ sourceRevision: pickActionString(a.source_revision) ?? pickActionString(a.sourceRevision)
2584
+ };
2585
+ }
2586
+ function isHttpMethod(value) {
2587
+ return value === "GET" || value === "POST" || value === "PUT" || value === "PATCH" || value === "DELETE";
2588
+ }
2589
+ function pickActionString(value) {
2590
+ return typeof value === "string" && value.trim() ? value.trim() : null;
2591
+ }
2592
+ function plainObject(value) {
2593
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2594
+ }
2595
+ function normalizeActionLookupKey(value) {
2596
+ return String(value || "").trim().toLowerCase();
2597
+ }
2598
+ function actionEntryMatches(entry, needle) {
2599
+ if (!needle) return false;
2600
+ return [entry.actionId, entry.name, entry.toolName].filter((value) => typeof value === "string" && value.length > 0).some((value) => value.toLowerCase() === needle);
2601
+ }
2602
+ function mapActionInput(action, input) {
2603
+ const remaining = { ...input || {} };
2604
+ const path = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
2605
+ const value = remaining[key];
2606
+ if (value === void 0 || value === null || value === "") {
2607
+ throw new TopoloSdkError(
2608
+ "invalid_action_input",
2609
+ `Action "${action.name}" requires path parameter "${key}".`
2610
+ );
2611
+ }
2612
+ delete remaining[key];
2613
+ return encodeURIComponent(String(value));
2614
+ });
2615
+ const explicitQuery = plainObject(remaining.query);
2616
+ delete remaining.query;
2617
+ const explicitBody = Object.prototype.hasOwnProperty.call(remaining, "body") ? remaining.body : void 0;
2618
+ delete remaining.body;
2619
+ if (action.method === "GET") {
2620
+ return {
2621
+ path,
2622
+ query: objectToQuery({ ...remaining, ...explicitQuery })
2623
+ };
2624
+ }
2625
+ const query = objectToQuery(explicitQuery);
2626
+ const body = explicitBody !== void 0 ? explicitBody : remaining;
2627
+ return {
2628
+ path,
2629
+ ...query ? { query } : {},
2630
+ body
2631
+ };
2632
+ }
2633
+ function objectToQuery(value) {
2634
+ const query = {};
2635
+ for (const [key, raw] of Object.entries(value)) {
2636
+ if (raw === void 0 || raw === null) continue;
2637
+ if (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean") {
2638
+ query[key] = raw;
2639
+ } else {
2640
+ query[key] = JSON.stringify(raw);
2641
+ }
2642
+ }
2643
+ return Object.keys(query).length > 0 ? query : void 0;
2492
2644
  }
2493
2645
  function describeError(body, fallback) {
2494
2646
  if (body && typeof body === "object") {
package/dist/index.d.cts CHANGED
@@ -108,6 +108,28 @@ interface TopoloServiceCatalogEntry {
108
108
  status: string | null;
109
109
  surfaceKind: string | null;
110
110
  launchable: boolean;
111
+ permissions: string[];
112
+ }
113
+ interface TopoloActionCatalogEntry {
114
+ actionId: string;
115
+ name: string;
116
+ toolName: string | null;
117
+ title: string;
118
+ description: string;
119
+ serviceId: string;
120
+ serviceSlug: string | null;
121
+ serviceName: string | null;
122
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
123
+ path: string;
124
+ permissionName: string;
125
+ requiredPermission: string;
126
+ inputSchema: Record<string, unknown>;
127
+ outputSchema: Record<string, unknown>;
128
+ readOnly: boolean;
129
+ destructive: boolean;
130
+ requiresConfirmation: boolean;
131
+ status: string | null;
132
+ sourceRevision: string | null;
111
133
  }
112
134
  declare class TopoloClient {
113
135
  private readonly credential;
@@ -119,6 +141,7 @@ declare class TopoloClient {
119
141
  private readonly fetchImpl;
120
142
  private readonly debug?;
121
143
  private serviceCatalogCache;
144
+ private actionCatalogCache;
122
145
  /**
123
146
  * Process-wide cache for slug → service-id lookups. Keyed by
124
147
  * `${authBaseUrl}|${slug}` so multiple `TopoloClient` instances pointing at
@@ -157,17 +180,31 @@ declare class TopoloClient {
157
180
  /**
158
181
  * List the platform service catalog from the live registry
159
182
  * (`GET /api/services/catalog` on TopoloAuth). Credential-aware: the
160
- * caller's credential is attached, so an authenticated caller in an
161
- * entitled org also receives the `developer_only` services its role
162
- * permits; an unauthenticated/agent caller receives `active` only.
183
+ * caller's credential is attached, so the result only includes services the
184
+ * credential's organization and effective permissions can use.
163
185
  *
164
- * Resilient: on any network/HTTP failure this falls back to the
165
- * build-time generated `APPLICATIONS` snapshot so offline/degraded
166
- * callers still get a usable (if static) catalog. The live result is
167
- * authoritative; the snapshot is a floor, not a cap.
186
+ * Security boundary: this never falls back to the generated global snapshot.
187
+ * The live Auth catalog is the authority because it is scoped to the caller's
188
+ * organization and effective permissions.
168
189
  */
169
190
  listServices(): Promise<TopoloServiceCatalogEntry[]>;
170
- private generatedCatalogFallback;
191
+ /**
192
+ * List callable actions from the live Auth catalog. The response is scoped to
193
+ * the caller's organization and effective permissions; no bundled/static app
194
+ * action list is used as an authorization source.
195
+ */
196
+ listActions(options?: {
197
+ service?: string;
198
+ }): Promise<TopoloActionCatalogEntry[]>;
199
+ getAction(action: string): Promise<TopoloActionCatalogEntry>;
200
+ callAction<T = unknown>(action: string, input?: Record<string, unknown>, options?: {
201
+ confirm?: boolean;
202
+ }): Promise<T>;
203
+ /**
204
+ * Bundled generated registry for tooling that audits platform packaging, not
205
+ * for deciding what the current credential may use.
206
+ */
207
+ listBundledServices(): TopoloServiceCatalogEntry[];
171
208
  /**
172
209
  * Introspect the current credential. Returns the caller's resolved identity,
173
210
  * organization, and permission set. Used by CLI `whoami` and by MCP to gate
@@ -1533,7 +1570,7 @@ declare function auditAllApplicationRequirements(applicationIds?: readonly Appli
1533
1570
  * `scripts/generate-services.mjs`; edit the JSON, not the generated file.
1534
1571
  *
1535
1572
  * Any entry can be overridden per-call via `TopoloClient` options, or globally
1536
- * via env vars like `TOPOLO_SERVICE_URL_CRM` or `TOPOLO_SERVICE_URL_WEB_API`
1573
+ * via env vars like `TOPOLO_SERVICE_URL_MAIL` or `TOPOLO_SERVICE_URL_WEB_API`
1537
1574
  * (dots in service IDs become underscores in env vars).
1538
1575
  *
1539
1576
  * IMPORTANT: The SDK NEVER accepts `orgId` as a parameter. Every request is
@@ -2048,4 +2085,4 @@ declare function createTopolo(options: TopoloClientOptions): {
2048
2085
  };
2049
2086
  type Topolo = ReturnType<typeof createTopolo>;
2050
2087
 
2051
- export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceId, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveServiceUrl, validateTopoloWidgetResponse };
2088
+ export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceId, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, type TopoloActionCatalogEntry, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveServiceUrl, validateTopoloWidgetResponse };
package/dist/index.d.ts CHANGED
@@ -108,6 +108,28 @@ interface TopoloServiceCatalogEntry {
108
108
  status: string | null;
109
109
  surfaceKind: string | null;
110
110
  launchable: boolean;
111
+ permissions: string[];
112
+ }
113
+ interface TopoloActionCatalogEntry {
114
+ actionId: string;
115
+ name: string;
116
+ toolName: string | null;
117
+ title: string;
118
+ description: string;
119
+ serviceId: string;
120
+ serviceSlug: string | null;
121
+ serviceName: string | null;
122
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
123
+ path: string;
124
+ permissionName: string;
125
+ requiredPermission: string;
126
+ inputSchema: Record<string, unknown>;
127
+ outputSchema: Record<string, unknown>;
128
+ readOnly: boolean;
129
+ destructive: boolean;
130
+ requiresConfirmation: boolean;
131
+ status: string | null;
132
+ sourceRevision: string | null;
111
133
  }
112
134
  declare class TopoloClient {
113
135
  private readonly credential;
@@ -119,6 +141,7 @@ declare class TopoloClient {
119
141
  private readonly fetchImpl;
120
142
  private readonly debug?;
121
143
  private serviceCatalogCache;
144
+ private actionCatalogCache;
122
145
  /**
123
146
  * Process-wide cache for slug → service-id lookups. Keyed by
124
147
  * `${authBaseUrl}|${slug}` so multiple `TopoloClient` instances pointing at
@@ -157,17 +180,31 @@ declare class TopoloClient {
157
180
  /**
158
181
  * List the platform service catalog from the live registry
159
182
  * (`GET /api/services/catalog` on TopoloAuth). Credential-aware: the
160
- * caller's credential is attached, so an authenticated caller in an
161
- * entitled org also receives the `developer_only` services its role
162
- * permits; an unauthenticated/agent caller receives `active` only.
183
+ * caller's credential is attached, so the result only includes services the
184
+ * credential's organization and effective permissions can use.
163
185
  *
164
- * Resilient: on any network/HTTP failure this falls back to the
165
- * build-time generated `APPLICATIONS` snapshot so offline/degraded
166
- * callers still get a usable (if static) catalog. The live result is
167
- * authoritative; the snapshot is a floor, not a cap.
186
+ * Security boundary: this never falls back to the generated global snapshot.
187
+ * The live Auth catalog is the authority because it is scoped to the caller's
188
+ * organization and effective permissions.
168
189
  */
169
190
  listServices(): Promise<TopoloServiceCatalogEntry[]>;
170
- private generatedCatalogFallback;
191
+ /**
192
+ * List callable actions from the live Auth catalog. The response is scoped to
193
+ * the caller's organization and effective permissions; no bundled/static app
194
+ * action list is used as an authorization source.
195
+ */
196
+ listActions(options?: {
197
+ service?: string;
198
+ }): Promise<TopoloActionCatalogEntry[]>;
199
+ getAction(action: string): Promise<TopoloActionCatalogEntry>;
200
+ callAction<T = unknown>(action: string, input?: Record<string, unknown>, options?: {
201
+ confirm?: boolean;
202
+ }): Promise<T>;
203
+ /**
204
+ * Bundled generated registry for tooling that audits platform packaging, not
205
+ * for deciding what the current credential may use.
206
+ */
207
+ listBundledServices(): TopoloServiceCatalogEntry[];
171
208
  /**
172
209
  * Introspect the current credential. Returns the caller's resolved identity,
173
210
  * organization, and permission set. Used by CLI `whoami` and by MCP to gate
@@ -1533,7 +1570,7 @@ declare function auditAllApplicationRequirements(applicationIds?: readonly Appli
1533
1570
  * `scripts/generate-services.mjs`; edit the JSON, not the generated file.
1534
1571
  *
1535
1572
  * Any entry can be overridden per-call via `TopoloClient` options, or globally
1536
- * via env vars like `TOPOLO_SERVICE_URL_CRM` or `TOPOLO_SERVICE_URL_WEB_API`
1573
+ * via env vars like `TOPOLO_SERVICE_URL_MAIL` or `TOPOLO_SERVICE_URL_WEB_API`
1537
1574
  * (dots in service IDs become underscores in env vars).
1538
1575
  *
1539
1576
  * IMPORTANT: The SDK NEVER accepts `orgId` as a parameter. Every request is
@@ -2048,4 +2085,4 @@ declare function createTopolo(options: TopoloClientOptions): {
2048
2085
  };
2049
2086
  type Topolo = ReturnType<typeof createTopolo>;
2050
2087
 
2051
- export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceId, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveServiceUrl, validateTopoloWidgetResponse };
2088
+ export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceId, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, type TopoloActionCatalogEntry, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveServiceUrl, validateTopoloWidgetResponse };