@topolo/sdk 0.4.8 → 0.4.10

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/dist/index.cjs CHANGED
@@ -140,7 +140,7 @@ var SERVICES = {
140
140
  mail: "https://mail.topolo.app",
141
141
  mdm: "https://mdm.topolo.app",
142
142
  messages: "https://messages.topolo.app",
143
- nexus: "https://nexus-api.topolo.app",
143
+ nexus: "https://nexus.topolo.app",
144
144
  notify: "https://notify.topolo.app",
145
145
  observability_watch: "https://observability-watch.topolo.app",
146
146
  one: "https://www.topolo.app",
@@ -691,8 +691,10 @@ var APPLICATIONS = {
691
691
  {
692
692
  "name": "topolo-feed-api",
693
693
  "kind": "worker",
694
- "apiUrl": null,
695
- "domains": [],
694
+ "apiUrl": "https://feed-api.topolo.app",
695
+ "domains": [
696
+ "feed-api.topolo.app"
697
+ ],
696
698
  "routes": [],
697
699
  "serviceId": null
698
700
  },
@@ -1063,9 +1065,9 @@ var APPLICATIONS = {
1063
1065
  {
1064
1066
  "name": "topolo-nexus-gateway",
1065
1067
  "kind": "worker",
1066
- "apiUrl": "https://nexus-api.topolo.app",
1068
+ "apiUrl": "https://nexus.topolo.app",
1067
1069
  "domains": [
1068
- "nexus-api.topolo.app"
1070
+ "nexus.topolo.app"
1069
1071
  ],
1070
1072
  "routes": [],
1071
1073
  "serviceId": "nexus"
@@ -1425,8 +1427,10 @@ var APPLICATIONS = {
1425
1427
  {
1426
1428
  "name": "topolo-socialize-api",
1427
1429
  "kind": "worker",
1428
- "apiUrl": null,
1429
- "domains": [],
1430
+ "apiUrl": "https://socialize-api.topolo.app",
1431
+ "domains": [
1432
+ "socialize-api.topolo.app"
1433
+ ],
1430
1434
  "routes": [],
1431
1435
  "serviceId": null
1432
1436
  },
@@ -2276,10 +2280,14 @@ var TopoloClient = class _TopoloClient {
2276
2280
  if (!serviceKey) {
2277
2281
  throw new TopoloSdkError("unknown_service", "service is required");
2278
2282
  }
2279
- if (serviceKey === "auth") {
2283
+ if (serviceKey === "auth" || serviceKey === "developers") {
2280
2284
  const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
2281
2285
  if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
2282
- return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS.auth };
2286
+ if (serviceKey === "developers") {
2287
+ const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl("auth", this.serviceUrls));
2288
+ if (derived) return { serviceKey, baseUrl: derived };
2289
+ }
2290
+ return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey] };
2283
2291
  }
2284
2292
  const live = await this.resolveLiveCallableService(serviceKey);
2285
2293
  if (live) {
@@ -2319,7 +2327,7 @@ var TopoloClient = class _TopoloClient {
2319
2327
  if (!normalized) return null;
2320
2328
  const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
2321
2329
  if (configuredServiceId) return configuredServiceId;
2322
- const authBase = resolveServiceUrl("auth", this.serviceUrls);
2330
+ const authBase = resolveServiceUrl("developers", this.serviceUrls);
2323
2331
  const cacheKey = `${authBase}|${normalized}`;
2324
2332
  const now = Date.now();
2325
2333
  const cached = _TopoloClient.serviceIdCache.get(cacheKey);
@@ -2373,7 +2381,7 @@ var TopoloClient = class _TopoloClient {
2373
2381
  if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
2374
2382
  return this.serviceCatalogCache.entries;
2375
2383
  }
2376
- const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
2384
+ const res = await this.request({ service: "developers", method: "GET", path: "/api/services/catalog" });
2377
2385
  const raw = res.data?.services ?? res.services;
2378
2386
  if (!Array.isArray(raw)) {
2379
2387
  throw new TopoloSdkError(
@@ -2414,8 +2422,9 @@ var TopoloClient = class _TopoloClient {
2414
2422
  return entries;
2415
2423
  }
2416
2424
  /**
2417
- * List callable actions from the live Auth catalog. The response is scoped to
2418
- * the caller's organization and effective permissions; no bundled/static app
2425
+ * List callable actions from the live registry-canonical catalog (definitions
2426
+ * from the Catalog registry, scoping from Auth). The response is scoped to the
2427
+ * caller's organization and effective permissions; no bundled/static app
2419
2428
  * action list is used as an authorization source.
2420
2429
  */
2421
2430
  async listActions(options = {}) {
@@ -2424,7 +2433,7 @@ var TopoloClient = class _TopoloClient {
2424
2433
  return this.actionCatalogCache.entries;
2425
2434
  }
2426
2435
  const res = await this.request({
2427
- service: "auth",
2436
+ service: "developers",
2428
2437
  method: "GET",
2429
2438
  path: "/api/actions/catalog",
2430
2439
  ...options.service ? { query: { service: options.service } } : {}
@@ -2604,6 +2613,17 @@ function serviceKeyCandidates2(slug) {
2604
2613
  function normalizeLookupKey(value) {
2605
2614
  return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
2606
2615
  }
2616
+ function deriveDevelopersFromAuth(authUrl) {
2617
+ if (!authUrl) return null;
2618
+ try {
2619
+ const url = new URL(authUrl);
2620
+ if (!/^auth\./i.test(url.hostname)) return null;
2621
+ url.hostname = url.hostname.replace(/^auth\./i, "developers.");
2622
+ return url.origin;
2623
+ } catch {
2624
+ return null;
2625
+ }
2626
+ }
2607
2627
  function catalogEntryMatches(entry, needle) {
2608
2628
  return catalogEntryLookupKeys(entry).includes(needle);
2609
2629
  }
@@ -2648,6 +2668,7 @@ function normalizeActionCatalogEntry(item) {
2648
2668
  readOnly: Boolean(a.read_only ?? a.readOnly),
2649
2669
  destructive: Boolean(a.destructive),
2650
2670
  requiresConfirmation: Boolean(a.requires_confirmation ?? a.requiresConfirmation),
2671
+ agentAccess: pickAgentAccess(a.agent_access ?? a.agentAccess),
2651
2672
  status: pickActionString(a.status),
2652
2673
  sourceRevision: pickActionString(a.source_revision) ?? pickActionString(a.sourceRevision)
2653
2674
  };
@@ -2658,6 +2679,9 @@ function isHttpMethod(value) {
2658
2679
  function pickActionString(value) {
2659
2680
  return typeof value === "string" && value.trim() ? value.trim() : null;
2660
2681
  }
2682
+ function pickAgentAccess(value) {
2683
+ return value === "auto" || value === "confirm" || value === "off" ? value : null;
2684
+ }
2661
2685
  function plainObject(value) {
2662
2686
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2663
2687
  }
@@ -2751,7 +2775,6 @@ var CrmModule = class {
2751
2775
  constructor(client) {
2752
2776
  this.client = client;
2753
2777
  }
2754
- client;
2755
2778
  async listContacts(options = {}) {
2756
2779
  const res = await this.client.request({
2757
2780
  service: "crm",
@@ -2799,7 +2822,6 @@ var IdentityModule = class {
2799
2822
  constructor(client) {
2800
2823
  this.client = client;
2801
2824
  }
2802
- client;
2803
2825
  /** Returns the resolved user, organization, and permission set for the
2804
2826
  * current credential. Equivalent to `TopoloClient.introspect()` — exposed
2805
2827
  * on this module for symmetry with the other domain modules. */
@@ -2813,7 +2835,6 @@ var P2PModule = class {
2813
2835
  constructor(client) {
2814
2836
  this.client = client;
2815
2837
  }
2816
- client;
2817
2838
  // Directory ----------------------------------------------------------------
2818
2839
  async listDirectory(options = {}) {
2819
2840
  const res = await this.client.request({
package/dist/index.d.cts CHANGED
@@ -145,6 +145,8 @@ interface TopoloActionCatalogEntry {
145
145
  readOnly: boolean;
146
146
  destructive: boolean;
147
147
  requiresConfirmation: boolean;
148
+ /** Agent access tier. null/absent when the catalog does not specify one (consumers derive a default). */
149
+ agentAccess?: 'auto' | 'confirm' | 'off' | null;
148
150
  status: string | null;
149
151
  sourceRevision: string | null;
150
152
  }
@@ -206,8 +208,9 @@ declare class TopoloClient {
206
208
  */
207
209
  listServices(): Promise<TopoloServiceCatalogEntry[]>;
208
210
  /**
209
- * List callable actions from the live Auth catalog. The response is scoped to
210
- * the caller's organization and effective permissions; no bundled/static app
211
+ * List callable actions from the live registry-canonical catalog (definitions
212
+ * from the Catalog registry, scoping from Auth). The response is scoped to the
213
+ * caller's organization and effective permissions; no bundled/static app
211
214
  * action list is used as an authorization source.
212
215
  */
213
216
  listActions(options?: {
@@ -277,7 +280,7 @@ declare const SERVICES: {
277
280
  readonly mail: "https://mail.topolo.app";
278
281
  readonly mdm: "https://mdm.topolo.app";
279
282
  readonly messages: "https://messages.topolo.app";
280
- readonly nexus: "https://nexus-api.topolo.app";
283
+ readonly nexus: "https://nexus.topolo.app";
281
284
  readonly notify: "https://notify.topolo.app";
282
285
  readonly observability_watch: "https://observability-watch.topolo.app";
283
286
  readonly one: "https://www.topolo.app";
@@ -702,8 +705,8 @@ declare const APPLICATIONS: {
702
705
  }, {
703
706
  readonly name: "topolo-feed-api";
704
707
  readonly kind: "worker";
705
- readonly apiUrl: null;
706
- readonly domains: readonly [];
708
+ readonly apiUrl: "https://feed-api.topolo.app";
709
+ readonly domains: readonly ["feed-api.topolo.app"];
707
710
  readonly routes: readonly [];
708
711
  readonly serviceId: null;
709
712
  }, {
@@ -985,8 +988,8 @@ declare const APPLICATIONS: {
985
988
  }, {
986
989
  readonly name: "topolo-nexus-gateway";
987
990
  readonly kind: "worker";
988
- readonly apiUrl: "https://nexus-api.topolo.app";
989
- readonly domains: readonly ["nexus-api.topolo.app"];
991
+ readonly apiUrl: "https://nexus.topolo.app";
992
+ readonly domains: readonly ["nexus.topolo.app"];
990
993
  readonly routes: readonly [];
991
994
  readonly serviceId: "nexus";
992
995
  }];
@@ -1245,8 +1248,8 @@ declare const APPLICATIONS: {
1245
1248
  }, {
1246
1249
  readonly name: "topolo-socialize-api";
1247
1250
  readonly kind: "worker";
1248
- readonly apiUrl: null;
1249
- readonly domains: readonly [];
1251
+ readonly apiUrl: "https://socialize-api.topolo.app";
1252
+ readonly domains: readonly ["socialize-api.topolo.app"];
1250
1253
  readonly routes: readonly [];
1251
1254
  readonly serviceId: null;
1252
1255
  }, {
package/dist/index.d.ts CHANGED
@@ -145,6 +145,8 @@ interface TopoloActionCatalogEntry {
145
145
  readOnly: boolean;
146
146
  destructive: boolean;
147
147
  requiresConfirmation: boolean;
148
+ /** Agent access tier. null/absent when the catalog does not specify one (consumers derive a default). */
149
+ agentAccess?: 'auto' | 'confirm' | 'off' | null;
148
150
  status: string | null;
149
151
  sourceRevision: string | null;
150
152
  }
@@ -206,8 +208,9 @@ declare class TopoloClient {
206
208
  */
207
209
  listServices(): Promise<TopoloServiceCatalogEntry[]>;
208
210
  /**
209
- * List callable actions from the live Auth catalog. The response is scoped to
210
- * the caller's organization and effective permissions; no bundled/static app
211
+ * List callable actions from the live registry-canonical catalog (definitions
212
+ * from the Catalog registry, scoping from Auth). The response is scoped to the
213
+ * caller's organization and effective permissions; no bundled/static app
211
214
  * action list is used as an authorization source.
212
215
  */
213
216
  listActions(options?: {
@@ -277,7 +280,7 @@ declare const SERVICES: {
277
280
  readonly mail: "https://mail.topolo.app";
278
281
  readonly mdm: "https://mdm.topolo.app";
279
282
  readonly messages: "https://messages.topolo.app";
280
- readonly nexus: "https://nexus-api.topolo.app";
283
+ readonly nexus: "https://nexus.topolo.app";
281
284
  readonly notify: "https://notify.topolo.app";
282
285
  readonly observability_watch: "https://observability-watch.topolo.app";
283
286
  readonly one: "https://www.topolo.app";
@@ -702,8 +705,8 @@ declare const APPLICATIONS: {
702
705
  }, {
703
706
  readonly name: "topolo-feed-api";
704
707
  readonly kind: "worker";
705
- readonly apiUrl: null;
706
- readonly domains: readonly [];
708
+ readonly apiUrl: "https://feed-api.topolo.app";
709
+ readonly domains: readonly ["feed-api.topolo.app"];
707
710
  readonly routes: readonly [];
708
711
  readonly serviceId: null;
709
712
  }, {
@@ -985,8 +988,8 @@ declare const APPLICATIONS: {
985
988
  }, {
986
989
  readonly name: "topolo-nexus-gateway";
987
990
  readonly kind: "worker";
988
- readonly apiUrl: "https://nexus-api.topolo.app";
989
- readonly domains: readonly ["nexus-api.topolo.app"];
991
+ readonly apiUrl: "https://nexus.topolo.app";
992
+ readonly domains: readonly ["nexus.topolo.app"];
990
993
  readonly routes: readonly [];
991
994
  readonly serviceId: "nexus";
992
995
  }];
@@ -1245,8 +1248,8 @@ declare const APPLICATIONS: {
1245
1248
  }, {
1246
1249
  readonly name: "topolo-socialize-api";
1247
1250
  readonly kind: "worker";
1248
- readonly apiUrl: null;
1249
- readonly domains: readonly [];
1251
+ readonly apiUrl: "https://socialize-api.topolo.app";
1252
+ readonly domains: readonly ["socialize-api.topolo.app"];
1250
1253
  readonly routes: readonly [];
1251
1254
  readonly serviceId: null;
1252
1255
  }, {
package/dist/index.js CHANGED
@@ -85,7 +85,7 @@ var SERVICES = {
85
85
  mail: "https://mail.topolo.app",
86
86
  mdm: "https://mdm.topolo.app",
87
87
  messages: "https://messages.topolo.app",
88
- nexus: "https://nexus-api.topolo.app",
88
+ nexus: "https://nexus.topolo.app",
89
89
  notify: "https://notify.topolo.app",
90
90
  observability_watch: "https://observability-watch.topolo.app",
91
91
  one: "https://www.topolo.app",
@@ -636,8 +636,10 @@ var APPLICATIONS = {
636
636
  {
637
637
  "name": "topolo-feed-api",
638
638
  "kind": "worker",
639
- "apiUrl": null,
640
- "domains": [],
639
+ "apiUrl": "https://feed-api.topolo.app",
640
+ "domains": [
641
+ "feed-api.topolo.app"
642
+ ],
641
643
  "routes": [],
642
644
  "serviceId": null
643
645
  },
@@ -1008,9 +1010,9 @@ var APPLICATIONS = {
1008
1010
  {
1009
1011
  "name": "topolo-nexus-gateway",
1010
1012
  "kind": "worker",
1011
- "apiUrl": "https://nexus-api.topolo.app",
1013
+ "apiUrl": "https://nexus.topolo.app",
1012
1014
  "domains": [
1013
- "nexus-api.topolo.app"
1015
+ "nexus.topolo.app"
1014
1016
  ],
1015
1017
  "routes": [],
1016
1018
  "serviceId": "nexus"
@@ -1370,8 +1372,10 @@ var APPLICATIONS = {
1370
1372
  {
1371
1373
  "name": "topolo-socialize-api",
1372
1374
  "kind": "worker",
1373
- "apiUrl": null,
1374
- "domains": [],
1375
+ "apiUrl": "https://socialize-api.topolo.app",
1376
+ "domains": [
1377
+ "socialize-api.topolo.app"
1378
+ ],
1375
1379
  "routes": [],
1376
1380
  "serviceId": null
1377
1381
  },
@@ -2221,10 +2225,14 @@ var TopoloClient = class _TopoloClient {
2221
2225
  if (!serviceKey) {
2222
2226
  throw new TopoloSdkError("unknown_service", "service is required");
2223
2227
  }
2224
- if (serviceKey === "auth") {
2228
+ if (serviceKey === "auth" || serviceKey === "developers") {
2225
2229
  const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
2226
2230
  if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
2227
- return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS.auth };
2231
+ if (serviceKey === "developers") {
2232
+ const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl("auth", this.serviceUrls));
2233
+ if (derived) return { serviceKey, baseUrl: derived };
2234
+ }
2235
+ return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey] };
2228
2236
  }
2229
2237
  const live = await this.resolveLiveCallableService(serviceKey);
2230
2238
  if (live) {
@@ -2264,7 +2272,7 @@ var TopoloClient = class _TopoloClient {
2264
2272
  if (!normalized) return null;
2265
2273
  const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
2266
2274
  if (configuredServiceId) return configuredServiceId;
2267
- const authBase = resolveServiceUrl("auth", this.serviceUrls);
2275
+ const authBase = resolveServiceUrl("developers", this.serviceUrls);
2268
2276
  const cacheKey = `${authBase}|${normalized}`;
2269
2277
  const now = Date.now();
2270
2278
  const cached = _TopoloClient.serviceIdCache.get(cacheKey);
@@ -2318,7 +2326,7 @@ var TopoloClient = class _TopoloClient {
2318
2326
  if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
2319
2327
  return this.serviceCatalogCache.entries;
2320
2328
  }
2321
- const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
2329
+ const res = await this.request({ service: "developers", method: "GET", path: "/api/services/catalog" });
2322
2330
  const raw = res.data?.services ?? res.services;
2323
2331
  if (!Array.isArray(raw)) {
2324
2332
  throw new TopoloSdkError(
@@ -2359,8 +2367,9 @@ var TopoloClient = class _TopoloClient {
2359
2367
  return entries;
2360
2368
  }
2361
2369
  /**
2362
- * List callable actions from the live Auth catalog. The response is scoped to
2363
- * the caller's organization and effective permissions; no bundled/static app
2370
+ * List callable actions from the live registry-canonical catalog (definitions
2371
+ * from the Catalog registry, scoping from Auth). The response is scoped to the
2372
+ * caller's organization and effective permissions; no bundled/static app
2364
2373
  * action list is used as an authorization source.
2365
2374
  */
2366
2375
  async listActions(options = {}) {
@@ -2369,7 +2378,7 @@ var TopoloClient = class _TopoloClient {
2369
2378
  return this.actionCatalogCache.entries;
2370
2379
  }
2371
2380
  const res = await this.request({
2372
- service: "auth",
2381
+ service: "developers",
2373
2382
  method: "GET",
2374
2383
  path: "/api/actions/catalog",
2375
2384
  ...options.service ? { query: { service: options.service } } : {}
@@ -2549,6 +2558,17 @@ function serviceKeyCandidates2(slug) {
2549
2558
  function normalizeLookupKey(value) {
2550
2559
  return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
2551
2560
  }
2561
+ function deriveDevelopersFromAuth(authUrl) {
2562
+ if (!authUrl) return null;
2563
+ try {
2564
+ const url = new URL(authUrl);
2565
+ if (!/^auth\./i.test(url.hostname)) return null;
2566
+ url.hostname = url.hostname.replace(/^auth\./i, "developers.");
2567
+ return url.origin;
2568
+ } catch {
2569
+ return null;
2570
+ }
2571
+ }
2552
2572
  function catalogEntryMatches(entry, needle) {
2553
2573
  return catalogEntryLookupKeys(entry).includes(needle);
2554
2574
  }
@@ -2593,6 +2613,7 @@ function normalizeActionCatalogEntry(item) {
2593
2613
  readOnly: Boolean(a.read_only ?? a.readOnly),
2594
2614
  destructive: Boolean(a.destructive),
2595
2615
  requiresConfirmation: Boolean(a.requires_confirmation ?? a.requiresConfirmation),
2616
+ agentAccess: pickAgentAccess(a.agent_access ?? a.agentAccess),
2596
2617
  status: pickActionString(a.status),
2597
2618
  sourceRevision: pickActionString(a.source_revision) ?? pickActionString(a.sourceRevision)
2598
2619
  };
@@ -2603,6 +2624,9 @@ function isHttpMethod(value) {
2603
2624
  function pickActionString(value) {
2604
2625
  return typeof value === "string" && value.trim() ? value.trim() : null;
2605
2626
  }
2627
+ function pickAgentAccess(value) {
2628
+ return value === "auto" || value === "confirm" || value === "off" ? value : null;
2629
+ }
2606
2630
  function plainObject(value) {
2607
2631
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2608
2632
  }
@@ -2696,7 +2720,6 @@ var CrmModule = class {
2696
2720
  constructor(client) {
2697
2721
  this.client = client;
2698
2722
  }
2699
- client;
2700
2723
  async listContacts(options = {}) {
2701
2724
  const res = await this.client.request({
2702
2725
  service: "crm",
@@ -2744,7 +2767,6 @@ var IdentityModule = class {
2744
2767
  constructor(client) {
2745
2768
  this.client = client;
2746
2769
  }
2747
- client;
2748
2770
  /** Returns the resolved user, organization, and permission set for the
2749
2771
  * current credential. Equivalent to `TopoloClient.introspect()` — exposed
2750
2772
  * on this module for symmetry with the other domain modules. */
@@ -2758,7 +2780,6 @@ var P2PModule = class {
2758
2780
  constructor(client) {
2759
2781
  this.client = client;
2760
2782
  }
2761
- client;
2762
2783
  // Directory ----------------------------------------------------------------
2763
2784
  async listDirectory(options = {}) {
2764
2785
  const res = await this.client.request({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/sdk",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -530,8 +530,10 @@ export const APPLICATIONS = {
530
530
  {
531
531
  "name": "topolo-feed-api",
532
532
  "kind": "worker",
533
- "apiUrl": null,
534
- "domains": [],
533
+ "apiUrl": "https://feed-api.topolo.app",
534
+ "domains": [
535
+ "feed-api.topolo.app"
536
+ ],
535
537
  "routes": [],
536
538
  "serviceId": null
537
539
  },
@@ -902,9 +904,9 @@ export const APPLICATIONS = {
902
904
  {
903
905
  "name": "topolo-nexus-gateway",
904
906
  "kind": "worker",
905
- "apiUrl": "https://nexus-api.topolo.app",
907
+ "apiUrl": "https://nexus.topolo.app",
906
908
  "domains": [
907
- "nexus-api.topolo.app"
909
+ "nexus.topolo.app"
908
910
  ],
909
911
  "routes": [],
910
912
  "serviceId": "nexus"
@@ -1264,8 +1266,10 @@ export const APPLICATIONS = {
1264
1266
  {
1265
1267
  "name": "topolo-socialize-api",
1266
1268
  "kind": "worker",
1267
- "apiUrl": null,
1268
- "domains": [],
1269
+ "apiUrl": "https://socialize-api.topolo.app",
1270
+ "domains": [
1271
+ "socialize-api.topolo.app"
1272
+ ],
1269
1273
  "routes": [],
1270
1274
  "serviceId": null
1271
1275
  },
@@ -401,7 +401,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
401
401
  credential: { kind: 'api_key', apiKey: 'x' },
402
402
  agent: AGENT,
403
403
  fetch: impl,
404
- serviceUrls: { auth: 'https://auth.example.com' } as never,
404
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' } as never,
405
405
  serviceIds: { notify: 'service_notify' },
406
406
  });
407
407
  const id = await client.getServiceIdBySlug('topolo-notify');
@@ -413,7 +413,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
413
413
  const { impl, calls } = stubFetch([
414
414
  { body: { success: true, data: { service_id: 'service_abc123', api_base_url: 'https://x-api' } } },
415
415
  ]);
416
- const client = freshClient(impl, { auth: 'https://auth.example.com' });
416
+ const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
417
417
  const id = await client.getServiceIdBySlug('external-service');
418
418
  expect(id).toBe('service_abc123');
419
419
  expect(calls[0]!.url).toBe('https://auth.example.com/api/services/by-slug/external-service');
@@ -425,7 +425,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
425
425
 
426
426
  it('returns null on 404 and caches the negative result', async () => {
427
427
  const { impl, calls } = stubFetch([{ status: 404, body: { success: false } }]);
428
- const client = freshClient(impl, { auth: 'https://auth.example.com' });
428
+ const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
429
429
  expect(await client.getServiceIdBySlug('nope')).toBeNull();
430
430
  expect(await client.getServiceIdBySlug('nope')).toBeNull();
431
431
  expect(calls.length).toBe(1);
@@ -435,7 +435,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
435
435
  const { impl, calls } = stubFetch([
436
436
  { body: { success: true, data: { service_id: 'service_external' } } },
437
437
  ]);
438
- const client = freshClient(impl, { auth: 'https://auth.example.com' });
438
+ const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
439
439
  expect(await client.getServiceIdBySlug('external-service')).toBe('service_external');
440
440
  expect(await client.getServiceIdBySlug('external-service')).toBe('service_external');
441
441
  expect(calls.length).toBe(1);
@@ -445,7 +445,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
445
445
  const { impl, calls } = stubFetch([
446
446
  { body: { success: true, data: { service_id: 'service_x' } } },
447
447
  ]);
448
- const client = freshClient(impl, { auth: 'https://auth.example.com' });
448
+ const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
449
449
  await client.getServiceIdBySlug(' EXTERNAL-SERVICE ');
450
450
  expect(calls[0]!.url).toBe('https://auth.example.com/api/services/by-slug/external-service');
451
451
  });
@@ -467,7 +467,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
467
467
  credential: { kind: 'api_key', apiKey: 'x' },
468
468
  agent: AGENT,
469
469
  fetch: impl,
470
- serviceUrls: { auth: 'https://auth.example.com' } as never,
470
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' } as never,
471
471
  });
472
472
  expect(await client.getServiceIdBySlug('flaky')).toBeNull();
473
473
  // Second call retries — earlier null was not cached.
@@ -523,7 +523,7 @@ describe('TopoloClient.listServices', () => {
523
523
  credential: { kind: 'access_token', accessToken: 'tok_abc' },
524
524
  agent: AGENT,
525
525
  fetch: impl,
526
- serviceUrls: { auth: 'https://auth.example.com' },
526
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
527
527
  });
528
528
  const out = await client.listServices();
529
529
  expect(out).toEqual([
@@ -574,7 +574,7 @@ describe('TopoloClient.listServices', () => {
574
574
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
575
575
  agent: AGENT,
576
576
  fetch: impl,
577
- serviceUrls: { auth: 'https://auth.example.com' },
577
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
578
578
  });
579
579
  await expect(client.listServices()).rejects.toBeInstanceOf(TopoloHttpError);
580
580
  });
@@ -605,7 +605,7 @@ describe('TopoloClient.listServices', () => {
605
605
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
606
606
  agent: AGENT,
607
607
  fetch: impl,
608
- serviceUrls: { auth: 'https://auth.example.com' },
608
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
609
609
  });
610
610
 
611
611
  await client.request({ service: 'mail', path: '/api/health' });
@@ -640,7 +640,7 @@ describe('TopoloClient.listServices', () => {
640
640
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
641
641
  agent: AGENT,
642
642
  fetch: impl,
643
- serviceUrls: { auth: 'https://auth.example.com' },
643
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
644
644
  });
645
645
 
646
646
  await client.request({ service: 'crm', path: '/health' });
@@ -649,6 +649,21 @@ describe('TopoloClient.listServices', () => {
649
649
  expect(calls[1]!.url).toBe('https://crm-api.topolo.app/health');
650
650
  });
651
651
 
652
+ it('derives the catalog (developers) host from the auth url when developers is unset', async () => {
653
+ const { impl, calls } = stubFetch([{ body: { data: { services: [] } } }]);
654
+ const client = new TopoloClient({
655
+ credential: { kind: 'api_key', apiKey: 'dak_xyz' },
656
+ agent: AGENT,
657
+ fetch: impl,
658
+ // Fleet apps configure only `auth` per-env; the catalog host is derived.
659
+ serviceUrls: { auth: 'https://auth.stg.topolo.us' },
660
+ });
661
+
662
+ await client.listServices();
663
+
664
+ expect(calls[0]!.url).toBe('https://developers.stg.topolo.us/api/services/catalog');
665
+ });
666
+
652
667
  it('prefers live catalog api_base_url for dynamic services', async () => {
653
668
  const { impl, calls } = stubFetch([
654
669
  {
@@ -675,7 +690,7 @@ describe('TopoloClient.listServices', () => {
675
690
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
676
691
  agent: AGENT,
677
692
  fetch: impl,
678
- serviceUrls: { auth: 'https://auth.example.com' },
693
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
679
694
  });
680
695
 
681
696
  await client.request({ service: 'future', path: '/health' });
@@ -708,7 +723,7 @@ describe('TopoloClient.listServices', () => {
708
723
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
709
724
  agent: AGENT,
710
725
  fetch: impl,
711
- serviceUrls: { auth: 'https://auth.example.com' },
726
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
712
727
  });
713
728
 
714
729
  await expect(client.request({ service: 'mail', path: '/api/health' })).rejects.toMatchObject({
@@ -742,7 +757,7 @@ describe('TopoloClient.listServices', () => {
742
757
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
743
758
  agent: AGENT,
744
759
  fetch: impl,
745
- serviceUrls: { auth: 'https://auth.example.com' },
760
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
746
761
  });
747
762
 
748
763
  await expect(client.request({ service: 'future-app', path: '/api/health' })).rejects.toMatchObject({
@@ -777,7 +792,7 @@ describe('TopoloClient.listServices', () => {
777
792
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
778
793
  agent: AGENT,
779
794
  fetch: impl,
780
- serviceUrls: { auth: 'https://auth.example.com' },
795
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
781
796
  });
782
797
 
783
798
  await client.request({ service: 'warehouse', path: '/api/one' });
@@ -798,7 +813,7 @@ describe('TopoloClient action catalog', () => {
798
813
  credential: { kind: 'access_token', accessToken: 'tok_abc' },
799
814
  agent: AGENT,
800
815
  fetch: impl,
801
- serviceUrls: { auth: 'https://auth.example.com' },
816
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
802
817
  });
803
818
  const actions = await client.listActions();
804
819
  expect(actions.map((action) => action.name)).toEqual([
@@ -815,7 +830,7 @@ describe('TopoloClient action catalog', () => {
815
830
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
816
831
  agent: AGENT,
817
832
  fetch: impl,
818
- serviceUrls: { auth: 'https://auth.example.com' },
833
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
819
834
  });
820
835
  await client.callAction('messages.list', { mailboxId: 'primary', q: 'Ada', page: 2 });
821
836
  const call = requestCalls(calls)[0]!;
@@ -833,7 +848,7 @@ describe('TopoloClient action catalog', () => {
833
848
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
834
849
  agent: AGENT,
835
850
  fetch: impl,
836
- serviceUrls: { auth: 'https://auth.example.com' },
851
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
837
852
  });
838
853
  await client.callAction('topolo_mail_messages_get', {
839
854
  mailboxId: 'primary',
@@ -853,7 +868,7 @@ describe('TopoloClient action catalog', () => {
853
868
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
854
869
  agent: AGENT,
855
870
  fetch: impl,
856
- serviceUrls: { auth: 'https://auth.example.com' },
871
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
857
872
  });
858
873
  await expect(
859
874
  client.callAction('messages.send', { mailboxId: 'primary', subject: 'Hello' }),
@@ -866,7 +881,7 @@ describe('TopoloClient action catalog', () => {
866
881
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
867
882
  agent: AGENT,
868
883
  fetch: impl,
869
- serviceUrls: { auth: 'https://auth.example.com' },
884
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
870
885
  });
871
886
  await client.callAction('messages.send', { mailboxId: 'primary', subject: 'Hello' }, { confirm: true });
872
887
  const call = requestCalls(calls)[0]!;
package/src/client.ts CHANGED
@@ -151,6 +151,8 @@ export interface TopoloActionCatalogEntry {
151
151
  readOnly: boolean;
152
152
  destructive: boolean;
153
153
  requiresConfirmation: boolean;
154
+ /** Agent access tier. null/absent when the catalog does not specify one (consumers derive a default). */
155
+ agentAccess?: 'auto' | 'confirm' | 'off' | null;
154
156
  status: string | null;
155
157
  sourceRevision: string | null;
156
158
  }
@@ -337,10 +339,20 @@ export class TopoloClient {
337
339
  throw new TopoloSdkError('unknown_service', 'service is required');
338
340
  }
339
341
 
340
- if (serviceKey === 'auth') {
342
+ // auth + developers are DIRECT services resolved without a catalog lookup:
343
+ // developers serves the catalog itself, so resolving it via the catalog would
344
+ // recurse infinitely.
345
+ if (serviceKey === 'auth' || serviceKey === 'developers') {
341
346
  const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
342
347
  if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
343
- return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS.auth };
348
+ // The catalog host (developers) defaults to the SAME environment as auth.
349
+ // Apps configure only `auth` per-env; derive developers from it (auth.X ->
350
+ // developers.X) so the catalog cutover needs no per-app config change.
351
+ if (serviceKey === 'developers') {
352
+ const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl('auth', this.serviceUrls));
353
+ if (derived) return { serviceKey, baseUrl: derived };
354
+ }
355
+ return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey as 'auth' | 'developers'] };
344
356
  }
345
357
 
346
358
  const live = await this.resolveLiveCallableService(serviceKey);
@@ -386,7 +398,8 @@ export class TopoloClient {
386
398
  const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
387
399
  if (configuredServiceId) return configuredServiceId;
388
400
 
389
- const authBase = resolveServiceUrl('auth', this.serviceUrls);
401
+ // app-catalog greenfield: by-slug resolver lives on the Catalog (developers).
402
+ const authBase = resolveServiceUrl('developers', this.serviceUrls);
390
403
  const cacheKey = `${authBase}|${normalized}`;
391
404
  const now = Date.now();
392
405
  const cached = TopoloClient.serviceIdCache.get(cacheKey);
@@ -450,10 +463,13 @@ export class TopoloClient {
450
463
  return this.serviceCatalogCache.entries;
451
464
  }
452
465
 
466
+ // app-catalog greenfield: the catalog moved to the Catalog (developers)
467
+ // service. Auth no longer owns the registry; it only provides scoping, which
468
+ // the Catalog composes server-side. Consumers pass the per-env developers URL.
453
469
  const res = await this.request<
454
470
  | { data?: { services?: unknown[] } }
455
471
  | { services?: unknown[] }
456
- >({ service: 'auth', method: 'GET', path: '/api/services/catalog' });
472
+ >({ service: 'developers', method: 'GET', path: '/api/services/catalog' });
457
473
  const raw =
458
474
  (res as { data?: { services?: unknown[] } }).data?.services ??
459
475
  (res as { services?: unknown[] }).services;
@@ -525,8 +541,9 @@ export class TopoloClient {
525
541
  }
526
542
 
527
543
  /**
528
- * List callable actions from the live Auth catalog. The response is scoped to
529
- * the caller's organization and effective permissions; no bundled/static app
544
+ * List callable actions from the live registry-canonical catalog (definitions
545
+ * from the Catalog registry, scoping from Auth). The response is scoped to the
546
+ * caller's organization and effective permissions; no bundled/static app
530
547
  * action list is used as an authorization source.
531
548
  */
532
549
  async listActions(options: { service?: string } = {}): Promise<TopoloActionCatalogEntry[]> {
@@ -539,7 +556,7 @@ export class TopoloClient {
539
556
  | { data?: { actions?: unknown[] } }
540
557
  | { actions?: unknown[] }
541
558
  >({
542
- service: 'auth',
559
+ service: 'developers',
543
560
  method: 'GET',
544
561
  path: '/api/actions/catalog',
545
562
  ...(options.service ? { query: { service: options.service } } : {}),
@@ -774,6 +791,24 @@ function normalizeLookupKey(value: string): string {
774
791
  return String(value || '').trim().toLowerCase().replace(/\s+/g, '-');
775
792
  }
776
793
 
794
+ /**
795
+ * Derive the Catalog (developers) base URL from a configured auth base URL by
796
+ * swapping the leading `auth` host label for `developers` (auth.stg.topolo.us ->
797
+ * developers.stg.topolo.us). Returns null if the auth URL is absent or doesn't
798
+ * start with the `auth.` label, so the caller falls back to the bundled default.
799
+ */
800
+ function deriveDevelopersFromAuth(authUrl: string | null | undefined): string | null {
801
+ if (!authUrl) return null;
802
+ try {
803
+ const url = new URL(authUrl);
804
+ if (!/^auth\./i.test(url.hostname)) return null;
805
+ url.hostname = url.hostname.replace(/^auth\./i, 'developers.');
806
+ return url.origin;
807
+ } catch {
808
+ return null;
809
+ }
810
+ }
811
+
777
812
  function catalogEntryMatches(entry: TopoloServiceCatalogEntry, needle: string): boolean {
778
813
  return catalogEntryLookupKeys(entry).includes(needle);
779
814
  }
@@ -823,6 +858,7 @@ function normalizeActionCatalogEntry(item: unknown): TopoloActionCatalogEntry |
823
858
  readOnly: Boolean(a.read_only ?? a.readOnly),
824
859
  destructive: Boolean(a.destructive),
825
860
  requiresConfirmation: Boolean(a.requires_confirmation ?? a.requiresConfirmation),
861
+ agentAccess: pickAgentAccess(a.agent_access ?? a.agentAccess),
826
862
  status: pickActionString(a.status),
827
863
  sourceRevision: pickActionString(a.source_revision) ?? pickActionString(a.sourceRevision),
828
864
  };
@@ -836,6 +872,10 @@ function pickActionString(value: unknown): string | null {
836
872
  return typeof value === 'string' && value.trim() ? value.trim() : null;
837
873
  }
838
874
 
875
+ function pickAgentAccess(value: unknown): 'auto' | 'confirm' | 'off' | null {
876
+ return value === 'auto' || value === 'confirm' || value === 'off' ? value : null;
877
+ }
878
+
839
879
  function plainObject(value: unknown): Record<string, unknown> {
840
880
  return value && typeof value === 'object' && !Array.isArray(value)
841
881
  ? value as Record<string, unknown>
package/src/index.test.ts CHANGED
@@ -63,7 +63,7 @@ describe('createTopolo', () => {
63
63
  describe('package surface re-exports', () => {
64
64
  it('re-exports the error classes from the barrel', () => {
65
65
  expect(new TopoloAuthError('x').name).toBe('TopoloAuthError');
66
- expect(new TopoloHttpError('auth', '/p', 500).name).toBe('TopoloHttpError');
66
+ expect(new TopoloHttpError('auth', '/p', 500, null).name).toBe('TopoloHttpError');
67
67
  expect(new TopoloPermissionError('nope', []).name).toBe('TopoloPermissionError');
68
68
  });
69
69
  });
@@ -30,7 +30,7 @@ export const SERVICES = {
30
30
  mail: "https://mail.topolo.app",
31
31
  mdm: "https://mdm.topolo.app",
32
32
  messages: "https://messages.topolo.app",
33
- nexus: "https://nexus-api.topolo.app",
33
+ nexus: "https://nexus.topolo.app",
34
34
  notify: "https://notify.topolo.app",
35
35
  observability_watch: "https://observability-watch.topolo.app",
36
36
  one: "https://www.topolo.app",