@topolo/cli 0.1.22 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +174 -76
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
9
9
  import { createInterface } from "readline/promises";
10
10
  import { stdin as input } from "process";
11
11
 
12
- // node_modules/@topolo/sdk/dist/index.js
12
+ // ../topolo-sdk/dist/index.js
13
13
  function applyAuthHeaders(headers, credential) {
14
14
  if (credential.kind === "api_key") {
15
15
  headers.set("X-Api-Key", credential.apiKey);
@@ -1522,12 +1522,12 @@ var APPLICATION_REQUIREMENTS = [
1522
1522
  summary: "Applications must derive identity, organization, scopes, and service entitlement from TopoloAuth rather than inventing parallel auth state.",
1523
1523
  appliesTo: ["browser", "api", "tooling", "agent_surface"],
1524
1524
  evidence: [
1525
- "@topolo/auth-client or @topolo-io/worker-runtime is used where applicable",
1525
+ "@topolo/auth-client or @topolo/auth-middleware is used where applicable",
1526
1526
  "No public command, tool, or SDK method accepts orgId",
1527
1527
  "Service slug and Auth service registration are documented for protected services"
1528
1528
  ],
1529
1529
  implementation: [
1530
- "Use shared auth/runtime packages instead of copying token parsing, gateway, or browser storage flows",
1530
+ "Use shared auth packages instead of copying token parsing or browser storage flows",
1531
1531
  "Derive organization from the credential on every request",
1532
1532
  "Route OAuth, refresh, logout, and SSO paths according to the shared Auth contract"
1533
1533
  ]
@@ -2068,58 +2068,6 @@ var TopoloClient = class _TopoloClient {
2068
2068
  });
2069
2069
  return value;
2070
2070
  }
2071
- /**
2072
- * List the platform service catalog from the live registry
2073
- * (`GET /api/services/catalog` on TopoloAuth). Credential-aware: the
2074
- * caller's credential is attached, so an authenticated caller in an
2075
- * entitled org also receives the `developer_only` services its role
2076
- * permits; an unauthenticated/agent caller receives `active` only.
2077
- *
2078
- * Resilient: on any network/HTTP failure this falls back to the
2079
- * build-time generated `APPLICATIONS` snapshot so offline/degraded
2080
- * callers still get a usable (if static) catalog. The live result is
2081
- * authoritative; the snapshot is a floor, not a cap.
2082
- */
2083
- async listServices() {
2084
- try {
2085
- const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
2086
- const raw = res.data?.services ?? res.services;
2087
- if (Array.isArray(raw) && raw.length > 0) {
2088
- const entries = [];
2089
- for (const item of raw) {
2090
- const s = item;
2091
- const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
2092
- if (!serviceId) continue;
2093
- entries.push({
2094
- serviceId,
2095
- slug: typeof s.app_slug === "string" ? s.app_slug : null,
2096
- name: typeof s.name === "string" ? s.name : null,
2097
- description: typeof s.description === "string" ? s.description : null,
2098
- baseUrl: typeof s.base_url === "string" ? s.base_url : null,
2099
- status: typeof s.status === "string" ? s.status : null,
2100
- surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
2101
- launchable: s.launchable === true
2102
- });
2103
- }
2104
- if (entries.length > 0) return entries;
2105
- }
2106
- } catch {
2107
- }
2108
- return this.generatedCatalogFallback();
2109
- }
2110
- generatedCatalogFallback() {
2111
- const apps = Object.values(APPLICATIONS);
2112
- return apps.map((app) => ({
2113
- serviceId: app.id,
2114
- slug: app.id,
2115
- name: app.name ?? app.id,
2116
- description: null,
2117
- baseUrl: app.productionUrl ?? null,
2118
- status: "active",
2119
- surfaceKind: "application",
2120
- launchable: Boolean(app.productionUrl)
2121
- }));
2122
- }
2123
2071
  /**
2124
2072
  * Introspect the current credential. Returns the caller's resolved identity,
2125
2073
  * organization, and permission set. Used by CLI `whoami` and by MCP to gate
@@ -3108,6 +3056,8 @@ import {
3108
3056
  } from "fs";
3109
3057
  import path from "path";
3110
3058
  var EVIDENCE_FRESHNESS_MS = 36 * 60 * 60 * 1e3;
3059
+ var REQUIRED_AUTH_CLIENT_VERSION = "1.1.8";
3060
+ var REQUIRED_UI_KIT_VERSION = "0.4.63";
3111
3061
  function readEvidenceFile(appRoot) {
3112
3062
  const candidate = path.join(appRoot, ".topolo", "evidence.json");
3113
3063
  if (!isFile(candidate)) return null;
@@ -3269,32 +3219,56 @@ function enrichFinding(application, finding2, evidence) {
3269
3219
  evidence: ["service=auth"]
3270
3220
  });
3271
3221
  }
3222
+ const usesWorkerRuntime = authEvidence.includes("source=@topolo-io/worker-runtime") || authEvidence.includes("source=topolo-worker-runtime") || authEvidence.includes("source=createTopoloWorkerRuntime");
3272
3223
  const usesAuthMiddleware = authEvidence.includes("source=@topolo-io/auth-middleware") || authEvidence.includes("source=topolo-auth-middleware") || authEvidence.includes("source=TopoloAuthMiddleware");
3273
3224
  const usesAuthClient = authEvidence.includes("source=@topolo-io/auth-client") || authEvidence.includes("source=topolo-auth-client");
3274
3225
  const usesTopoloClient = authEvidence.includes("source=TopoloClient");
3275
3226
  const usesAuthValidateFetch = authEvidence.includes("source=auth-validate-fetch");
3276
- if (usesAuthMiddleware || usesAuthClient || usesTopoloClient || usesAuthValidateFetch) {
3227
+ if (authEvidence.includes(`dependency-version=@topolo-io/auth-client<${REQUIRED_AUTH_CLIENT_VERSION}`)) {
3228
+ return mergeFinding(finding2, {
3229
+ status: "needs_review",
3230
+ reason: `The app depends on @topolo-io/auth-client below ${REQUIRED_AUTH_CLIENT_VERSION}; public-route auth boot and session-channel fixes require the current shared client.`,
3231
+ evidence: authEvidence,
3232
+ nextActions: [
3233
+ `Update @topolo-io/auth-client to ${REQUIRED_AUTH_CLIENT_VERSION} or the workspace package that resolves to it`,
3234
+ "Regenerate the lockfile without reverting unrelated app changes",
3235
+ "Rerun this audit after the package update"
3236
+ ]
3237
+ });
3238
+ }
3239
+ if (authEvidence.includes("auth-boot=blocking-cookie-refresh") && !authEvidence.includes("auth-boot=background-cookie-refresh")) {
3240
+ return mergeFinding(finding2, {
3241
+ status: "needs_review",
3242
+ reason: "The app appears to block all browser routes on auth cookie refresh. Public landing and login routes must render immediately and restore the cookie session in the background.",
3243
+ evidence: authEvidence,
3244
+ nextActions: [
3245
+ "Use authClient.initialize({ cookieRefresh: 'background' }) or initializeSession({ cookieRefresh: 'background' }) on public landing/login routes",
3246
+ "Keep cookieRefresh: 'blocking' for protected workspace routes",
3247
+ "Subscribe to auth:session-updated so background restore updates the visible user state"
3248
+ ]
3249
+ });
3250
+ }
3251
+ if (usesWorkerRuntime || usesAuthMiddleware || usesAuthClient || usesTopoloClient || usesAuthValidateFetch) {
3277
3252
  return mergeFinding(finding2, {
3278
3253
  status: "met",
3279
- reason: "Canonical Topolo auth wiring is present (auth-middleware verifyRequest, auth-client browser module, or TopoloClient).",
3254
+ reason: "Canonical Topolo auth wiring is present (worker-runtime, auth-middleware verifyRequest, auth-client browser session controller, or TopoloClient).",
3280
3255
  evidence: authEvidence
3281
3256
  });
3282
3257
  }
3283
3258
  return mergeFinding(finding2, {
3284
3259
  status: "partial",
3285
- reason: "Shared Topolo auth dependency is declared but the canonical wiring (verifyRequest / createBrowserAuthModule / useTopoloAuth / TopoloClient) is not visible.",
3260
+ reason: "Shared Topolo auth dependency is declared but the canonical wiring (verifyRequest / createBrowserAuthSessionController / useTopoloAuth / TopoloClient) is not visible.",
3286
3261
  evidence: authEvidence
3287
3262
  });
3288
3263
  }
3289
3264
  case "service-registration-and-scopes": {
3290
- const applicationId = String(application.id);
3291
3265
  const declaredServices = application.services ?? [];
3292
3266
  if (declaredServices.length === 0) {
3293
- if (applicationId === "cli" || applicationId === "mcp" || applicationId === "cloudcontrol") {
3267
+ if (application.id === "cloudcontrol") {
3294
3268
  return mergeFinding(finding2, {
3295
3269
  status: "met",
3296
3270
  reason: "This application is itself a tooling/agent surface and does not register a callable platform service.",
3297
- evidence: [`tooling=${applicationId}`],
3271
+ evidence: [`tooling=${application.id}`],
3298
3272
  nextActions: []
3299
3273
  });
3300
3274
  }
@@ -3373,6 +3347,18 @@ function enrichFinding(application, finding2, evidence) {
3373
3347
  });
3374
3348
  }
3375
3349
  if (shellEvidence.length === 0) return finding2;
3350
+ if (shellEvidence.includes(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`)) {
3351
+ return mergeFinding(finding2, {
3352
+ status: "needs_review",
3353
+ reason: `The app depends on @topolo-io/ui-kit below ${REQUIRED_UI_KIT_VERSION}; launcher catalog cache/dedupe fixes require the current shared kit.`,
3354
+ evidence: shellEvidence,
3355
+ nextActions: [
3356
+ `Update @topolo-io/ui-kit to ${REQUIRED_UI_KIT_VERSION}`,
3357
+ "Regenerate the lockfile without reverting unrelated app changes",
3358
+ "Rerun this audit after the package update"
3359
+ ]
3360
+ });
3361
+ }
3376
3362
  if (shellEvidence.includes("forbidden=workspace-shell")) {
3377
3363
  return mergeFinding(finding2, {
3378
3364
  status: "needs_review",
@@ -3439,7 +3425,7 @@ function enrichFinding(application, finding2, evidence) {
3439
3425
  }
3440
3426
  const text = evidence.source.text;
3441
3427
  const hasOrganizationIdColumn = orgEvidence.includes("source=organization_id") || orgEvidence.includes("source=organizationId") || orgEvidence.includes("source=tenantId") || orgEvidence.includes("source=tenant_id");
3442
- const hasCredentialBoundary = orgEvidence.includes("source=organization-auth-boundary") || text.includes("verifyRequest") || text.includes("createBrowserAuthModule") || text.includes("useTopoloAuth");
3428
+ const hasCredentialBoundary = orgEvidence.includes("source=organization-auth-boundary") || text.includes("verifyRequest") || text.includes("createBrowserAuthSessionController") || text.includes("useTopoloAuth");
3443
3429
  if (hasOrganizationIdColumn && hasCredentialBoundary) {
3444
3430
  return mergeFinding(finding2, {
3445
3431
  status: "met",
@@ -3454,7 +3440,6 @@ function enrichFinding(application, finding2, evidence) {
3454
3440
  });
3455
3441
  }
3456
3442
  case "agent-and-operator-surfaces": {
3457
- const applicationId = String(application.id);
3458
3443
  const declaredServices = application.services ?? [];
3459
3444
  const text = evidence.source.text;
3460
3445
  const items = [];
@@ -3464,11 +3449,11 @@ function enrichFinding(application, finding2, evidence) {
3464
3449
  items.push("source=confirm-write-gate");
3465
3450
  }
3466
3451
  if (text.includes("@topolo/sdk")) items.push("source=@topolo/sdk");
3467
- if (applicationId === "cli" || applicationId === "mcp" || applicationId === "cloudcontrol") {
3452
+ if (application.id === "cloudcontrol") {
3468
3453
  return mergeFinding(finding2, {
3469
3454
  status: "met",
3470
- reason: "This application is itself an agent/operator surface (CLI / MCP / CloudControl).",
3471
- evidence: [`tooling=${applicationId}`, ...items],
3455
+ reason: "This application is itself an agent/operator surface (CloudControl).",
3456
+ evidence: [`tooling=${application.id}`, ...items],
3472
3457
  nextActions: []
3473
3458
  });
3474
3459
  }
@@ -3715,15 +3700,24 @@ function sharedAuthEvidence(evidence) {
3715
3700
  const text = evidence.source.text;
3716
3701
  const items = [];
3717
3702
  if (deps.has("@topolo-io/auth-client")) items.push("dependency=@topolo-io/auth-client");
3703
+ if (packageDependencyVersions(evidence, "@topolo-io/auth-client").some((version) => isStaleDependencyVersion(version, REQUIRED_AUTH_CLIENT_VERSION))) {
3704
+ items.push(`dependency-version=@topolo-io/auth-client<${REQUIRED_AUTH_CLIENT_VERSION}`);
3705
+ }
3718
3706
  if (deps.has("@topolo-io/auth-middleware")) items.push("dependency=@topolo-io/auth-middleware");
3707
+ if (deps.has("@topolo-io/worker-runtime")) items.push("dependency=@topolo-io/worker-runtime");
3719
3708
  if (deps.has("@topolo/sdk")) items.push("dependency=@topolo/sdk");
3720
3709
  if (text.includes("@topolo-io/auth-client")) items.push("source=@topolo-io/auth-client");
3721
3710
  if (text.includes("@topolo-io/auth-middleware")) items.push("source=@topolo-io/auth-middleware");
3711
+ if (text.includes("@topolo-io/worker-runtime")) items.push("source=@topolo-io/worker-runtime");
3712
+ if (text.includes("topolo-worker-runtime")) items.push("source=topolo-worker-runtime");
3713
+ if (text.includes("createTopoloWorkerRuntime")) items.push("source=createTopoloWorkerRuntime");
3722
3714
  if (text.includes("topolo-auth-client")) items.push("source=topolo-auth-client");
3723
3715
  if (text.includes("topolo-auth-middleware")) items.push("source=topolo-auth-middleware");
3724
3716
  if (text.includes("TopoloAuthMiddleware")) items.push("source=TopoloAuthMiddleware");
3725
3717
  if (text.includes("TopoloClient")) items.push("source=TopoloClient");
3726
3718
  if (text.includes("TopoloOAuth")) items.push("source=TopoloOAuth");
3719
+ if (/\.initialize(?:Session)?\(\s*\)/.test(text)) items.push("auth-boot=blocking-cookie-refresh");
3720
+ if (/cookieRefresh:\s*['"]background['"]/.test(text)) items.push("auth-boot=background-cookie-refresh");
3727
3721
  if (/Authorization.*Bearer/i.test(text) && (text.includes("/validate") || text.includes("TOPOLO_AUTH_URL") || text.includes("AUTH_BASE_URL") || text.includes("auth.topolo.app"))) {
3728
3722
  items.push("source=auth-validate-fetch");
3729
3723
  }
@@ -3745,6 +3739,9 @@ function sharedShellEvidence(evidence) {
3745
3739
  const text = evidence.source.text;
3746
3740
  const items = [];
3747
3741
  if (deps.has("@topolo-io/ui-kit")) items.push("dependency=@topolo-io/ui-kit");
3742
+ if (packageDependencyVersions(evidence, "@topolo-io/ui-kit").some((version) => isStaleDependencyVersion(version, REQUIRED_UI_KIT_VERSION))) {
3743
+ items.push(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`);
3744
+ }
3748
3745
  if (text.includes("@topolo-io/ui-kit")) items.push("source=@topolo-io/ui-kit");
3749
3746
  if (text.includes("TopoloAppShell")) items.push("source=TopoloAppShell");
3750
3747
  if (text.includes(["Topolo", "Workspace", "Shell"].join(""))) items.push("forbidden=workspace-shell");
@@ -3851,6 +3848,36 @@ function dependencyNames(evidence) {
3851
3848
  ])
3852
3849
  );
3853
3850
  }
3851
+ function packageDependencyVersions(evidence, packageName) {
3852
+ return unique(
3853
+ evidence.packageJsons.flatMap((record) => {
3854
+ const specs = [
3855
+ record.packageJson.dependencies?.[packageName],
3856
+ record.packageJson.devDependencies?.[packageName],
3857
+ record.packageJson.peerDependencies?.[packageName]
3858
+ ];
3859
+ return specs.filter((spec) => typeof spec === "string");
3860
+ })
3861
+ );
3862
+ }
3863
+ function isStaleDependencyVersion(spec, minimumVersion) {
3864
+ if (spec.startsWith("workspace:")) return false;
3865
+ const current = parseDependencyVersion(spec);
3866
+ const minimum = parseDependencyVersion(minimumVersion);
3867
+ if (!current || !minimum) return false;
3868
+ const currentParts = current;
3869
+ const minimumParts = minimum;
3870
+ for (const index of [0, 1, 2]) {
3871
+ if (currentParts[index] < minimumParts[index]) return true;
3872
+ if (currentParts[index] > minimumParts[index]) return false;
3873
+ }
3874
+ return false;
3875
+ }
3876
+ function parseDependencyVersion(spec) {
3877
+ const match = spec.match(/(\d+)\.(\d+)\.(\d+)/);
3878
+ if (!match) return null;
3879
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
3880
+ }
3854
3881
  function matchingSystems(repoRoot, application) {
3855
3882
  const docsRoot = topoloDocsRoot(repoRoot);
3856
3883
  const systemsRoot = path.join(docsRoot, "src", "content", "systems");
@@ -4624,7 +4651,7 @@ function packageJson(application, target, options) {
4624
4651
  dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
4625
4652
  scripts.deploy = `node -e 'console.error("\\nX Bare deploy is disabled. Use deploy:staging or deploy:production.\\n"); process.exit(1)'`;
4626
4653
  scripts["deploy:staging"] = "DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build && wrangler deploy --env staging";
4627
- scripts["deploy:production"] = "DEPLOY_ENVIRONMENT=production npm run build && wrangler deploy";
4654
+ scripts["deploy:production"] = "DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app npm run build && wrangler deploy";
4628
4655
  } else {
4629
4656
  dependencies["@topolo-io/widget-server"] = topoloPackageVersion("@topolo-io/widget-server", target);
4630
4657
  scripts.deploy = "npm run build && wrangler deploy";
@@ -5244,7 +5271,9 @@ function platformAppTsx(application, options) {
5244
5271
  " if (!auth) return;",
5245
5272
  " let active = true;",
5246
5273
  " const syncAuth = async () => {",
5247
- " setLoading(true);",
5274
+ " const publicRoute = route.kind === 'root' || route.kind === 'login';",
5275
+ " if (publicRoute) setLoading(false);",
5276
+ " else setLoading(true);",
5248
5277
  " setAuthError('');",
5249
5278
  "",
5250
5279
  " if (route.kind === 'callback') {",
@@ -5260,7 +5289,9 @@ function platformAppTsx(application, options) {
5260
5289
  " return;",
5261
5290
  " }",
5262
5291
  "",
5263
- " const nextUser = await auth.authClient.initialize();",
5292
+ " const nextUser = await auth.authClient.initialize({",
5293
+ " cookieRefresh: publicRoute ? 'background' : 'blocking',",
5294
+ " });",
5264
5295
  " if (!active) return;",
5265
5296
  " setUser(nextUser);",
5266
5297
  " setLoading(false);",
@@ -5277,6 +5308,16 @@ function platformAppTsx(application, options) {
5277
5308
  " }, [auth, route.kind]);",
5278
5309
  "",
5279
5310
  " useEffect(() => {",
5311
+ " if (!auth) return;",
5312
+ " const handleSessionUpdated = () => {",
5313
+ " const nextUser = auth.authClient.getHydratedCurrentUser() || auth.authClient.getTokenIdentity();",
5314
+ " if (nextUser) setUser(nextUser);",
5315
+ " };",
5316
+ " window.addEventListener('auth:session-updated', handleSessionUpdated);",
5317
+ " return () => window.removeEventListener('auth:session-updated', handleSessionUpdated);",
5318
+ " }, [auth]);",
5319
+ "",
5320
+ " useEffect(() => {",
5280
5321
  " if (!loading && user && route.kind === 'login') {",
5281
5322
  " window.location.replace('/');",
5282
5323
  " }",
@@ -6881,8 +6922,31 @@ function readRequiredString(value, message) {
6881
6922
  }
6882
6923
  return resolved;
6883
6924
  }
6925
+ async function applyProvisionedServiceId(manifestPath, serviceId) {
6926
+ let raw;
6927
+ try {
6928
+ raw = await readFile2(manifestPath, "utf8");
6929
+ } catch {
6930
+ return { changed: false, previous: null };
6931
+ }
6932
+ let json2;
6933
+ try {
6934
+ json2 = JSON.parse(raw);
6935
+ } catch {
6936
+ return { changed: false, previous: null };
6937
+ }
6938
+ const previous = typeof json2.serviceId === "string" ? json2.serviceId : null;
6939
+ if (previous === serviceId) {
6940
+ return { changed: false, previous };
6941
+ }
6942
+ json2.serviceId = serviceId;
6943
+ await writeFile3(manifestPath, `${JSON.stringify(json2, null, 2)}
6944
+ `, "utf8");
6945
+ return { changed: true, previous };
6946
+ }
6884
6947
 
6885
6948
  // src/commands/apps.ts
6949
+ import { join as join3 } from "path";
6886
6950
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
6887
6951
  function registerAppsCommand(program2) {
6888
6952
  const apps = program2.command("apps").description("List every Topolo platform application known to the SDK catalog").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action((opts) => {
@@ -6981,15 +7045,25 @@ function registerAppsCommand(program2) {
6981
7045
  });
6982
7046
  const writeResult = opts.write ? await writeApplicationScaffold(payload, { repoRoot: opts.repoRoot, force: opts.force }) : null;
6983
7047
  const provisionResult = opts.provision ? await provisionApplicationScaffold(payload) : null;
7048
+ let serviceIdWriteBack = null;
7049
+ if (writeResult && provisionResult?.authService?.id) {
7050
+ const manifestFile = writeResult.files.find((f) => f.path.endsWith("topolo.app.json"));
7051
+ if (manifestFile) {
7052
+ const manifestPath = join3(writeResult.root, manifestFile.path);
7053
+ const result = await applyProvisionedServiceId(manifestPath, provisionResult.authService.id);
7054
+ serviceIdWriteBack = { ...result, path: manifestFile.path };
7055
+ }
7056
+ }
6984
7057
  const output = {
6985
7058
  ...payload,
6986
7059
  ...writeResult ? { write: writeResult } : {},
6987
- ...provisionResult ? { provision: provisionResult } : {}
7060
+ ...provisionResult ? { provision: provisionResult } : {},
7061
+ ...serviceIdWriteBack ? { serviceIdWriteBack } : {}
6988
7062
  };
6989
7063
  emit(
6990
7064
  output,
6991
7065
  mode,
6992
- () => formatScaffold(payload, writeResult, provisionResult)
7066
+ () => formatScaffold(payload, writeResult, provisionResult, serviceIdWriteBack)
6993
7067
  );
6994
7068
  } catch (err) {
6995
7069
  fail(err, mode);
@@ -7223,7 +7297,7 @@ function formatAudit(payload) {
7223
7297
  }
7224
7298
  return lines.join("\n");
7225
7299
  }
7226
- function formatScaffold(payload, writeResult = null, provisionResult = null) {
7300
+ function formatScaffold(payload, writeResult = null, provisionResult = null, serviceIdWriteBack = null) {
7227
7301
  const lines = [
7228
7302
  `Version: ${payload.version}`,
7229
7303
  `Target: ${payload.target}`,
@@ -7283,6 +7357,11 @@ function formatScaffold(payload, writeResult = null, provisionResult = null) {
7283
7357
  );
7284
7358
  }
7285
7359
  }
7360
+ if (serviceIdWriteBack) {
7361
+ lines.push(
7362
+ serviceIdWriteBack.changed ? ` - service id written back to ${serviceIdWriteBack.path} (was ${serviceIdWriteBack.previous ?? "unset"})` : ` - service id already current in ${serviceIdWriteBack.path}`
7363
+ );
7364
+ }
7286
7365
  return lines.join("\n");
7287
7366
  }
7288
7367
  function resolveScaffoldAudience(value) {
@@ -7915,7 +7994,7 @@ function normalizeList(values, fallback = []) {
7915
7994
 
7916
7995
  // src/commands/integrations.ts
7917
7996
  import { readdir, readFile as readFile3, stat } from "fs/promises";
7918
- import { join as join3, relative as relative2 } from "path";
7997
+ import { join as join4, relative as relative2 } from "path";
7919
7998
  function registerIntegrationsCommand(program2) {
7920
7999
  const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
7921
8000
  integrations.command("prompt").description("Print an agent prompt for adding a Topolo integration.").argument("<integration>", "Integration prompt id, currently login-with-topolo").option("--framework <framework>", "auto, authjs, express, or generic", "auto").option("--json", "Emit JSON output").action((integration, options) => {
@@ -7973,7 +8052,7 @@ ${file.content}`).join("\n");
7973
8052
  makeCheck(
7974
8053
  "package",
7975
8054
  "@topolo/login dependency",
7976
- packageJson2?.dependencies?.["@topolo/login"] || packageJson2?.devDependencies?.["@topolo/login"] ? ["package.json declares @topolo/login"] : [],
8055
+ topoloLoginPackageEvidence(packageJson2, files),
7977
8056
  true
7978
8057
  ),
7979
8058
  makeCheck(
@@ -8025,6 +8104,25 @@ function makeCheck(id, label, evidence, required) {
8025
8104
  function matchEvidence(haystack, needles) {
8026
8105
  return needles.filter((needle) => haystack.toLowerCase().includes(needle.toLowerCase()));
8027
8106
  }
8107
+ function topoloLoginPackageEvidence(rootPackageJson, files) {
8108
+ const evidence = [];
8109
+ if (declaresTopoloLogin(rootPackageJson)) evidence.push("package.json declares @topolo/login");
8110
+ for (const file of files) {
8111
+ if (!file.path.endsWith("package.json") || file.path === "package.json") continue;
8112
+ try {
8113
+ if (declaresTopoloLogin(JSON.parse(file.content))) {
8114
+ evidence.push(`${file.path} declares @topolo/login`);
8115
+ }
8116
+ } catch {
8117
+ }
8118
+ }
8119
+ return evidence;
8120
+ }
8121
+ function declaresTopoloLogin(packageJson2) {
8122
+ return Boolean(
8123
+ packageJson2?.dependencies?.["@topolo/login"] || packageJson2?.devDependencies?.["@topolo/login"] || packageJson2?.peerDependencies?.["@topolo/login"]
8124
+ );
8125
+ }
8028
8126
  function detectFramework(packageJson2, haystack) {
8029
8127
  const deps = {
8030
8128
  ...packageJson2?.dependencies ?? {},
@@ -8039,7 +8137,7 @@ function detectFramework(packageJson2, haystack) {
8039
8137
  }
8040
8138
  async function readPackageJson2(repoRoot) {
8041
8139
  try {
8042
- return JSON.parse(await readFile3(join3(repoRoot, "package.json"), "utf8"));
8140
+ return JSON.parse(await readFile3(join4(repoRoot, "package.json"), "utf8"));
8043
8141
  } catch {
8044
8142
  return null;
8045
8143
  }
@@ -8051,7 +8149,7 @@ async function collectFiles(repoRoot) {
8051
8149
  if (result.length > 2e3) return;
8052
8150
  const entries = await readdir(dir, { withFileTypes: true });
8053
8151
  for (const entry of entries) {
8054
- const fullPath = join3(dir, entry.name);
8152
+ const fullPath = join4(dir, entry.name);
8055
8153
  if (entry.isDirectory()) {
8056
8154
  if ([".git", "node_modules", "dist", "build", ".next", "coverage"].includes(entry.name)) continue;
8057
8155
  await walk(fullPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  "commander": "^12.1.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@topolo/sdk": "latest",
25
+ "@topolo/sdk": "workspace:*",
26
26
  "@types/node": "^20.12.0",
27
27
  "@vitest/coverage-v8": "^2.1.0",
28
28
  "tsup": "^8.0.0",