@topolo/cli 0.1.30 → 0.1.31

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 +807 -766
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -92,7 +92,7 @@ var SERVICES = {
92
92
  mail: "https://mail.topolo.app",
93
93
  mdm: "https://mdm.topolo.app",
94
94
  messages: "https://messages.topolo.app",
95
- nexus: "https://nexus-api.topolo.app",
95
+ nexus: "https://nexus.topolo.app",
96
96
  notify: "https://notify.topolo.app",
97
97
  observability_watch: "https://observability-watch.topolo.app",
98
98
  one: "https://www.topolo.app",
@@ -641,8 +641,10 @@ var APPLICATIONS = {
641
641
  {
642
642
  "name": "topolo-feed-api",
643
643
  "kind": "worker",
644
- "apiUrl": null,
645
- "domains": [],
644
+ "apiUrl": "https://feed-api.topolo.app",
645
+ "domains": [
646
+ "feed-api.topolo.app"
647
+ ],
646
648
  "routes": [],
647
649
  "serviceId": null
648
650
  },
@@ -1013,9 +1015,9 @@ var APPLICATIONS = {
1013
1015
  {
1014
1016
  "name": "topolo-nexus-gateway",
1015
1017
  "kind": "worker",
1016
- "apiUrl": "https://nexus-api.topolo.app",
1018
+ "apiUrl": "https://nexus.topolo.app",
1017
1019
  "domains": [
1018
- "nexus-api.topolo.app"
1020
+ "nexus.topolo.app"
1019
1021
  ],
1020
1022
  "routes": [],
1021
1023
  "serviceId": "nexus"
@@ -1375,8 +1377,10 @@ var APPLICATIONS = {
1375
1377
  {
1376
1378
  "name": "topolo-socialize-api",
1377
1379
  "kind": "worker",
1378
- "apiUrl": null,
1379
- "domains": [],
1380
+ "apiUrl": "https://socialize-api.topolo.app",
1381
+ "domains": [
1382
+ "socialize-api.topolo.app"
1383
+ ],
1380
1384
  "routes": [],
1381
1385
  "serviceId": null
1382
1386
  },
@@ -2220,10 +2224,14 @@ var TopoloClient = class _TopoloClient {
2220
2224
  if (!serviceKey) {
2221
2225
  throw new TopoloSdkError("unknown_service", "service is required");
2222
2226
  }
2223
- if (serviceKey === "auth") {
2227
+ if (serviceKey === "auth" || serviceKey === "developers") {
2224
2228
  const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
2225
2229
  if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
2226
- return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS.auth };
2230
+ if (serviceKey === "developers") {
2231
+ const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl("auth", this.serviceUrls));
2232
+ if (derived) return { serviceKey, baseUrl: derived };
2233
+ }
2234
+ return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey] };
2227
2235
  }
2228
2236
  const live = await this.resolveLiveCallableService(serviceKey);
2229
2237
  if (live) {
@@ -2263,7 +2271,7 @@ var TopoloClient = class _TopoloClient {
2263
2271
  if (!normalized) return null;
2264
2272
  const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
2265
2273
  if (configuredServiceId) return configuredServiceId;
2266
- const authBase = resolveServiceUrl("auth", this.serviceUrls);
2274
+ const authBase = resolveServiceUrl("developers", this.serviceUrls);
2267
2275
  const cacheKey = `${authBase}|${normalized}`;
2268
2276
  const now = Date.now();
2269
2277
  const cached = _TopoloClient.serviceIdCache.get(cacheKey);
@@ -2317,7 +2325,7 @@ var TopoloClient = class _TopoloClient {
2317
2325
  if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
2318
2326
  return this.serviceCatalogCache.entries;
2319
2327
  }
2320
- const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
2328
+ const res = await this.request({ service: "developers", method: "GET", path: "/api/services/catalog" });
2321
2329
  const raw = res.data?.services ?? res.services;
2322
2330
  if (!Array.isArray(raw)) {
2323
2331
  throw new TopoloSdkError(
@@ -2548,6 +2556,17 @@ function serviceKeyCandidates2(slug) {
2548
2556
  function normalizeLookupKey(value) {
2549
2557
  return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
2550
2558
  }
2559
+ function deriveDevelopersFromAuth(authUrl) {
2560
+ if (!authUrl) return null;
2561
+ try {
2562
+ const url = new URL(authUrl);
2563
+ if (!/^auth\./i.test(url.hostname)) return null;
2564
+ url.hostname = url.hostname.replace(/^auth\./i, "developers.");
2565
+ return url.origin;
2566
+ } catch {
2567
+ return null;
2568
+ }
2569
+ }
2551
2570
  function catalogEntryMatches(entry, needle) {
2552
2571
  return catalogEntryLookupKeys(entry).includes(needle);
2553
2572
  }
@@ -3311,7 +3330,7 @@ async function pollUntilApproved(params) {
3311
3330
  throw new Error("Device code expired. Run `topolo auth login` again.");
3312
3331
  }
3313
3332
  function sleep(ms) {
3314
- return new Promise((resolve2) => setTimeout(resolve2, ms));
3333
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
3315
3334
  }
3316
3335
 
3317
3336
  // src/version.ts
@@ -4694,640 +4713,150 @@ function isFile(filePath) {
4694
4713
 
4695
4714
  // src/commands/app-scaffold.ts
4696
4715
  import { randomBytes } from "crypto";
4716
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
4717
+ import { dirname as dirname3, resolve as resolve2 } from "path";
4718
+
4719
+ // src/commands/app-scaffold-normalizers.ts
4697
4720
  import { constants } from "fs";
4698
- import { access, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
4699
- import { dirname as dirname3, relative, resolve } from "path";
4700
- var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
4701
- var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
4702
- var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
4703
- var CURRENT_VERIFICATION_DATE = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4704
- function generateOpaqueServiceId() {
4705
- return `srv_${randomBytes(9).toString("base64url")}`;
4721
+ import { access } from "fs/promises";
4722
+ import { relative, resolve } from "path";
4723
+ function normalizeTarget(value) {
4724
+ if (!value) return "platform";
4725
+ if (value === "platform" || value === "developer") return value;
4726
+ throw new Error("Invalid scaffold target. Use platform or developer.");
4706
4727
  }
4707
- function applicationScaffoldPayload(application, options = {}) {
4708
- const target = normalizeTarget(options.target);
4709
- const portfolio = target === "platform" ? normalizePortfolio(options.portfolio) : null;
4710
- const requestedId = normalizeApplicationId(application);
4711
- const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
4712
- const slug = toSlug(id);
4713
- const catalogApplication = APPLICATIONS[id];
4714
- const browser = options.browser !== false;
4715
- const api = options.api ?? target === "platform";
4716
- const audience = normalizeAudience(
4717
- options.audience,
4718
- target === "platform" ? portfolio : null
4719
- );
4720
- const tenancy = normalizeTenancy(options.tenancy, audience);
4721
- const surfaceModel = normalizeSurfaceModel(options.surfaceModel);
4722
- const containerAppId = normalizeContainerAppId(options.containerAppId, surfaceModel);
4723
- const profile = normalizeScaffoldProfile(options.profile);
4724
- const resources = resolveScaffoldResources(profile, options);
4725
- const ownerType = target === "platform" ? "topolo_first_party" : "third_party";
4726
- const distributionModel = target === "platform" ? "marketplace" : "organization_internal";
4727
- const appDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
4728
- const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
4729
- const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${slug}`;
4730
- const serviceSlug = api ? target === "platform" ? `topolo-${slug}` : id : null;
4731
- const defaultServiceId = target === "platform" ? generateOpaqueServiceId() : catalogApplication?.services[0] ?? id;
4732
- const serviceId = api ? options.service ?? defaultServiceId : null;
4733
- const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${slug}.topolo.app` : null;
4734
- const scopes = scaffoldScopes({ browser, api });
4735
- const capabilities = scaffoldCapabilities({ browser, api, distributionModel });
4736
- const ciWorkflowPath = target === "platform" ? ".github/workflows/topolo-conformance.yml" : null;
4737
- const ciWorkflow = target === "platform" ? topoloConformanceWorkflow({ id, appDir, audience }) : null;
4738
- const applicationPayload = {
4739
- id,
4740
- name,
4741
- appDir,
4742
- packageName,
4743
- productionUrl,
4744
- serviceSlug,
4745
- serviceId
4746
- };
4747
- return {
4748
- version: APPLICATION_REQUIREMENTS_VERSION,
4749
- target,
4750
- ownerType,
4751
- portfolio,
4752
- audience,
4753
- tenancy,
4754
- surfaceModel,
4755
- containerAppId,
4756
- profile,
4757
- resources,
4758
- distributionModel,
4759
- capabilities,
4760
- application: applicationPayload,
4761
- scopes,
4762
- requirements: APPLICATION_REQUIREMENTS.filter(
4763
- (requirement) => requirement.appliesTo.some((scope) => scopes.includes(scope))
4764
- ),
4765
- packages: scaffoldPackages({ browser, api, target }),
4766
- files: scaffoldFiles({
4767
- payload: {
4768
- ownerType,
4769
- portfolio,
4770
- audience,
4771
- tenancy,
4772
- surfaceModel,
4773
- containerAppId,
4774
- profile,
4775
- resources,
4776
- distributionModel,
4777
- capabilities
4778
- },
4779
- application: applicationPayload,
4780
- target,
4781
- browser,
4782
- api,
4783
- ciWorkflow
4784
- }),
4785
- commands: scaffoldCommands({
4786
- id,
4787
- target,
4788
- audience,
4789
- tenancy,
4790
- portfolio,
4791
- profile
4792
- }),
4793
- ciWorkflowPath,
4794
- ciWorkflow
4795
- };
4728
+ function normalizePortfolio(value) {
4729
+ if (!value) return "platform";
4730
+ if (value === "platform" || value === "personal") return value;
4731
+ throw new Error("Invalid scaffold portfolio. Use platform or personal.");
4796
4732
  }
4797
- async function writeApplicationScaffold(payload, options = {}) {
4798
- const root = resolve(options.repoRoot ?? process.cwd());
4799
- const plannedFiles = payload.files.map((file) => ({
4800
- file,
4801
- absolutePath: resolveInside(root, file.path)
4802
- }));
4803
- const existingFiles = [];
4804
- for (const planned of plannedFiles) {
4805
- if (await pathExists(planned.absolutePath)) {
4806
- existingFiles.push(planned.file.path);
4807
- }
4733
+ function normalizeAudience(value, portfolio) {
4734
+ if (!value) {
4735
+ return portfolio === "personal" ? "personal" : "business";
4808
4736
  }
4809
- if (existingFiles.length > 0 && !options.force) {
4810
- throw new Error(
4811
- [
4812
- "Refusing to overwrite existing scaffold files:",
4813
- ...existingFiles.map((filePath) => `- ${filePath}`),
4814
- "Pass --force to overwrite these files."
4815
- ].join("\n")
4816
- );
4737
+ if (value === "business" || value === "personal") {
4738
+ return value;
4817
4739
  }
4818
- const files = [];
4819
- for (const planned of plannedFiles) {
4820
- const existed = await pathExists(planned.absolutePath);
4821
- await mkdir2(dirname3(planned.absolutePath), { recursive: true });
4822
- await writeFile2(planned.absolutePath, planned.file.content, "utf8");
4823
- files.push({
4824
- path: planned.file.path,
4825
- status: existed ? "overwritten" : "created"
4826
- });
4740
+ throw new Error("Invalid scaffold audience. Use business or personal.");
4741
+ }
4742
+ function normalizeTenancy(value, audience) {
4743
+ const resolved = value ?? (audience === "personal" ? "household" : "organization");
4744
+ if (resolved !== "organization" && resolved !== "household") {
4745
+ throw new Error("Invalid scaffold tenancy. Use organization or household.");
4827
4746
  }
4828
- return { root, files };
4747
+ if (audience === "business" && resolved !== "organization") {
4748
+ throw new Error("Business scaffolds must use organization tenancy.");
4749
+ }
4750
+ if (audience === "personal" && resolved !== "household") {
4751
+ throw new Error("Personal scaffolds must use household tenancy.");
4752
+ }
4753
+ return resolved;
4829
4754
  }
4830
- function scaffoldScopes(options) {
4831
- const scopes = ["all"];
4832
- if (options.browser) scopes.push("browser");
4833
- if (options.api) {
4834
- scopes.push("api", "agent_surface");
4755
+ function normalizeSurfaceModel(value) {
4756
+ if (!value) return "web_app";
4757
+ if (value === "web_app" || value === "mobile_container" || value === "mobile_module" || value === "messaging_surface") {
4758
+ return value;
4835
4759
  }
4836
- return scopes;
4760
+ throw new Error(
4761
+ "Invalid scaffold surface model. Use web_app, mobile_container, mobile_module, or messaging_surface."
4762
+ );
4837
4763
  }
4838
- function scaffoldPackages(options) {
4839
- if (options.target === "developer") {
4840
- return options.browser ? ["@topolo/login"] : [];
4764
+ function normalizeScaffoldProfile(value) {
4765
+ if (!value) return null;
4766
+ if (value === "spatial-assets") return value;
4767
+ throw new Error("Invalid scaffold profile. Use spatial-assets.");
4768
+ }
4769
+ function resolveScaffoldResources(profile, options) {
4770
+ const profileResources = profile === "spatial-assets" ? { d1: true, r2: true, queue: true, workflow: true, containerProcessing: true } : { d1: false, r2: false, queue: false, workflow: false, containerProcessing: false };
4771
+ return {
4772
+ d1: Boolean(profileResources.d1 || options.withD1),
4773
+ r2: Boolean(profileResources.r2 || options.withR2),
4774
+ queue: Boolean(profileResources.queue || options.withQueue),
4775
+ workflow: Boolean(profileResources.workflow || options.withWorkflow),
4776
+ containerProcessing: Boolean(profileResources.containerProcessing || options.withContainerProcessing)
4777
+ };
4778
+ }
4779
+ function normalizeContainerAppId(value, surfaceModel) {
4780
+ const trimmed = String(value || "").trim();
4781
+ if (surfaceModel === "mobile_module") {
4782
+ if (!trimmed) {
4783
+ throw new Error("Mobile-module scaffolds require --container-app-id.");
4784
+ }
4785
+ return trimmed;
4841
4786
  }
4842
- const packages = /* @__PURE__ */ new Set();
4843
- if (options.browser) {
4844
- packages.add("@topolo-io/app-funnel");
4845
- packages.add("@topolo-io/app-launcher");
4846
- packages.add("@topolo-io/app-shell");
4847
- packages.add("@topolo-io/auth-client");
4848
- packages.add("@topolo-io/ui-kit");
4787
+ return trimmed || null;
4788
+ }
4789
+ function normalizeApplicationId(application) {
4790
+ const raw = application.trim().toLowerCase();
4791
+ const normalized = raw.replace(/-/g, "_");
4792
+ if (Object.prototype.hasOwnProperty.call(APPLICATIONS, raw)) {
4793
+ return raw;
4849
4794
  }
4850
- if (options.api) {
4851
- packages.add("@topolo-io/worker-runtime");
4852
- packages.add("@topolo/sdk");
4795
+ if (Object.prototype.hasOwnProperty.call(APPLICATIONS, normalized)) {
4796
+ return normalized;
4853
4797
  }
4854
- return Array.from(packages);
4798
+ if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(raw) || /^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/.test(raw)) {
4799
+ return normalized;
4800
+ }
4801
+ throw new Error(
4802
+ "Application id must be kebab-case or snake_case using lowercase letters, numbers, hyphens, and underscores."
4803
+ );
4855
4804
  }
4856
- function scaffoldCapabilities(options) {
4857
- const capabilities = [];
4858
- if (options.browser) {
4859
- capabilities.push("login", "launcher");
4805
+ function normalizeFirstPartyApplicationId(applicationId) {
4806
+ if (applicationId.startsWith("topolo_") && applicationId.length > "topolo_".length) {
4807
+ return applicationId.slice("topolo_".length);
4860
4808
  }
4861
- if (options.distributionModel === "marketplace") capabilities.push("marketplace");
4862
- if (options.api) capabilities.push("api", "widget");
4863
- return capabilities;
4809
+ return applicationId;
4864
4810
  }
4865
- function firstPartyAppPath(appDir, audience) {
4866
- if (appDir === "TopoloCli" || appDir === "TopoloCLI") return "Apps/system/TopoloCLI";
4867
- if (appDir === "TopoloDocs") return "Apps/system/TopoloDocs";
4868
- if (appDir === "TopoloAuth") return "Apps/system/TopoloAuth";
4869
- return `${audience === "personal" ? "Apps/personal" : "Apps/business"}/${appDir}`;
4811
+ function toSlug(value) {
4812
+ return value.replace(/_/g, "-");
4870
4813
  }
4871
- function scaffoldFiles(options) {
4872
- const { application, target } = options;
4873
- const slug = toSlug(application.id);
4874
- const base = target === "platform" ? firstPartyAppPath(application.appDir, options.payload.audience) : application.appDir;
4875
- const browserBase = target === "platform" ? `${base}/apps/web` : base;
4876
- const apiBase = target === "platform" ? `${base}/apps/api` : base;
4877
- const files = [
4878
- {
4879
- path: `${base}/package.json`,
4880
- purpose: "Package metadata and verification commands.",
4881
- requiredSignals: [
4882
- "name matches the packageName in the SDK application catalog",
4883
- "scripts include build plus typecheck where the stack supports them"
4884
- ],
4885
- content: packageJson(application, target, {
4886
- browser: options.browser,
4887
- api: options.api,
4888
- resources: options.payload.resources
4889
- })
4890
- },
4891
- {
4892
- path: `${base}/README.md`,
4893
- purpose: "Starter README with install, run, and verification commands.",
4894
- requiredSignals: [
4895
- "documents the generated app target",
4896
- "points maintainers to the shared Topolo packages used by the scaffold"
4897
- ],
4898
- content: readme(application, target, {
4899
- api: options.api,
4900
- audience: options.payload.audience,
4901
- tenancy: options.payload.tenancy,
4902
- portfolio: options.payload.portfolio,
4903
- profile: options.payload.profile,
4904
- resources: options.payload.resources
4905
- })
4906
- },
4907
- {
4908
- path: `${base}/.gitignore`,
4909
- purpose: "Generated-app ignore rules.",
4910
- requiredSignals: [
4911
- "ignores dependency installs and build output",
4912
- "keeps local environment files out of source control"
4913
- ],
4914
- content: gitignore()
4915
- },
4916
- {
4917
- path: `${base}/tsconfig.json`,
4918
- purpose: "TypeScript configuration for the generated app.",
4919
- requiredSignals: [
4920
- "uses strict mode",
4921
- "includes browser DOM types for React and Worker types for API-backed apps"
4922
- ],
4923
- content: tsconfigJson(options.api, target)
4924
- },
4925
- {
4926
- path: `${browserBase}/vite.config.ts`,
4927
- purpose: "Vite browser build configuration.",
4928
- requiredSignals: [
4929
- "builds the authenticated browser surface",
4930
- "keeps generated apps on the standard React/Vite baseline"
4931
- ],
4932
- content: viteConfig(target)
4933
- },
4934
- {
4935
- path: `${browserBase}/index.html`,
4936
- purpose: "Browser entry document.",
4937
- requiredSignals: [
4938
- "mounts the React app at #root",
4939
- "uses the generated application display name"
4940
- ],
4941
- content: indexHtml(application.name)
4942
- },
4943
- {
4944
- path: `${browserBase}/src/main.tsx`,
4945
- purpose: "React app entrypoint.",
4946
- requiredSignals: [
4947
- "renders the generated app component",
4948
- "imports the generated app stylesheet"
4949
- ],
4950
- content: mainTsx()
4951
- },
4952
- {
4953
- path: `${browserBase}/src/env.d.ts`,
4954
- purpose: "Vite environment variable typing.",
4955
- requiredSignals: [
4956
- "loads vite/client import.meta.env types",
4957
- "declares the Topolo environment variables consumed by the starter"
4958
- ],
4959
- content: envDts(options.api)
4960
- },
4961
- ...options.payload.resources.workflow ? [
4962
- {
4963
- path: `${base}/src/cloudflare-workers.d.ts`,
4964
- purpose: "Minimal Cloudflare Workflow entrypoint typing for scaffolded Workers.",
4965
- requiredSignals: [
4966
- "declares cloudflare:workers WorkflowEntrypoint for TypeScript",
4967
- "keeps Workflow imports deployable without adding a new dependency"
4968
- ],
4969
- content: cloudflareWorkersDts()
4970
- }
4971
- ] : [],
4972
- {
4973
- path: `${browserBase}/src/styles.css`,
4974
- purpose: "Minimal app-local styles outside the shared Topolo shell.",
4975
- requiredSignals: [
4976
- "does not fork the shared shell chrome",
4977
- "keeps app-specific layout scoped under generated classes"
4978
- ],
4979
- content: stylesCss()
4980
- }
4981
- ];
4982
- if (target === "platform") {
4983
- files.push(
4984
- {
4985
- path: `${base}/topolo.app.json`,
4986
- purpose: "Canonical app manifest that drives generated Wrangler and CloudControl metadata.",
4987
- requiredSignals: [
4988
- "declares web/api targets with canonical names and app-local target directories",
4989
- "keeps Cloudflare account, domain, service id, and bindings in one manifest source of truth"
4990
- ],
4991
- content: topoloAppManifestJson(application, {
4992
- api: options.api,
4993
- resources: options.payload.resources,
4994
- profile: options.payload.profile
4995
- })
4996
- },
4997
- {
4998
- path: `${base}/topolo.cloudcontrol.json`,
4999
- purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
5000
- requiredSignals: [
5001
- "production URLs and Cloudflare targets are explicit",
5002
- "resource bindings and required secrets are declared without secret values"
5003
- ],
5004
- content: cloudcontrolJson(application, {
5005
- api: options.api,
5006
- audience: options.payload.audience,
5007
- profile: options.payload.profile,
5008
- resources: options.payload.resources
5009
- })
5010
- },
5011
- {
5012
- path: `${base}/.github/workflows/topolo-conformance.yml`,
5013
- purpose: "PR/push gate that runs the local Topolo app audit with docs evidence.",
5014
- requiredSignals: [
5015
- "checks out this repo under its Apps lane directory",
5016
- "checks out TopoloDocs under Apps/system/TopoloDocs",
5017
- "checks out topolo-sdk and TopoloCli from main so the gate does not wait for an npm publish",
5018
- "runs topolo apps audit with --local and --fail-on needs_review"
5019
- ],
5020
- content: options.ciWorkflow ?? ""
5021
- },
5022
- {
5023
- path: `Apps/system/TopoloDocs/src/content/systems/topolo-${slug}.json`,
5024
- purpose: "Canonical system registry entry.",
5025
- requiredSignals: [
5026
- `repo_paths includes ${base}`,
5027
- "doc_paths includes internal handbook coverage",
5028
- "security_assurance records the auth, tenancy, deployment, and verification boundary"
5029
- ],
5030
- content: systemJson(application, {
5031
- api: options.api,
5032
- audience: options.payload.audience,
5033
- tenancy: options.payload.tenancy,
5034
- portfolio: options.payload.portfolio,
5035
- profile: options.payload.profile,
5036
- resources: options.payload.resources
5037
- })
5038
- },
5039
- {
5040
- path: `Apps/system/TopoloDocs/src/content/internal/apps/${slug}.mdx`,
5041
- purpose: "Internal operator handbook.",
5042
- requiredSignals: [
5043
- "documents ownership, routes, auth boundary, deployment, operations, and verification",
5044
- "last_verified is updated when behavior changes"
5045
- ],
5046
- content: internalDocs(application, {
5047
- api: options.api,
5048
- audience: options.payload.audience,
5049
- tenancy: options.payload.tenancy,
5050
- portfolio: options.payload.portfolio,
5051
- profile: options.payload.profile,
5052
- resources: options.payload.resources
5053
- })
5054
- },
5055
- {
5056
- path: `Apps/system/TopoloDocs/src/content/public/applications/${slug}.mdx`,
5057
- purpose: "Public application overview.",
5058
- requiredSignals: [
5059
- "documents the product surface without internal-only details",
5060
- "links back to the canonical system registry entry"
5061
- ],
5062
- content: publicDocs(application, {
5063
- api: options.api,
5064
- audience: options.payload.audience,
5065
- tenancy: options.payload.tenancy,
5066
- portfolio: options.payload.portfolio,
5067
- profile: options.payload.profile,
5068
- resources: options.payload.resources
5069
- })
5070
- }
5071
- );
5072
- if (options.api && application.serviceId) {
5073
- files.push(
5074
- {
5075
- path: `Apps/system/TopoloAuth/catalog/service-manifests/${application.serviceId}.json`,
5076
- purpose: "Auth service manifest for first-party permissions, role bundles, and API-key scopes.",
5077
- requiredSignals: [
5078
- "serviceId matches the generated Worker service id",
5079
- "permissions, role bundles, and API-key scopes exist before the app is catalog-visible",
5080
- "systems metadata maps the service back to the generated TopoloDocs entries"
5081
- ],
5082
- content: serviceManifestJson(application, {
5083
- audience: options.payload.audience,
5084
- tenancy: options.payload.tenancy
5085
- })
5086
- }
5087
- );
5088
- }
5089
- } else {
5090
- files.push(
5091
- {
5092
- path: `${base}/topolo.app.json`,
5093
- purpose: "Third-party Topolo application manifest.",
5094
- requiredSignals: [
5095
- "declares the external app id, callback URLs, and requested platform scopes",
5096
- "does not include internal Topolo CloudControl or canonical-docs ownership"
5097
- ],
5098
- content: developerManifest(application, {
5099
- api: options.api,
5100
- audience: options.payload.audience,
5101
- tenancy: options.payload.tenancy,
5102
- surfaceModel: options.payload.surfaceModel,
5103
- containerAppId: options.payload.containerAppId,
5104
- distributionModel: options.payload.distributionModel,
5105
- capabilities: options.payload.capabilities
5106
- })
5107
- },
5108
- {
5109
- path: `${base}/.env.example`,
5110
- purpose: "Developer environment template for local auth configuration.",
5111
- requiredSignals: [
5112
- "names the Topolo Auth URL",
5113
- "keeps OAuth client ids and service ids out of source control by default"
5114
- ],
5115
- content: envExample(application)
5116
- }
5117
- );
4814
+ function toConstantCase(value) {
4815
+ return value.split(/[-_]/).filter(Boolean).join("_").toUpperCase();
4816
+ }
4817
+ function toPascalCase(value) {
4818
+ const acronyms = /* @__PURE__ */ new Set(["api", "cli", "crm", "mcp", "mdm", "p2p"]);
4819
+ return value.split(/[-_]/).filter(Boolean).map((part) => acronyms.has(part.toLowerCase()) ? part.toUpperCase() : part.slice(0, 1).toUpperCase() + part.slice(1)).join("");
4820
+ }
4821
+ function toTitle(value) {
4822
+ return value.replace(/^topolo[-_\s]*/i, "Topolo ").split(/[-_\s]+/).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
4823
+ }
4824
+ function json(value) {
4825
+ return JSON.stringify(value, null, 2);
4826
+ }
4827
+ async function pathExists(path4) {
4828
+ try {
4829
+ await access(path4, constants.F_OK);
4830
+ return true;
4831
+ } catch {
4832
+ return false;
5118
4833
  }
5119
- if (options.browser) {
5120
- if (target === "platform") {
5121
- files.push({
5122
- path: `${browserBase}/src/service-identity.ts`,
5123
- purpose: "Central service identity resolver shared by browser and Worker runtime surfaces.",
5124
- requiredSignals: [
5125
- "exports the generated app id, display name, and stable Auth service slug",
5126
- "requires the provisioned Auth service id from the Worker deployment config"
5127
- ],
5128
- content: serviceIdentityTs(application)
5129
- });
5130
- }
5131
- files.push({
5132
- path: `${browserBase}/src/App.tsx`,
5133
- purpose: "Authenticated browser surface.",
5134
- requiredSignals: [
5135
- "bootstraps routes, auth provider, and shared shell through @topolo-io/app-shell",
5136
- "uses @topolo-io/auth-client through the app-shell wrapper for app-origin auth flows",
5137
- "keeps launcher and account-menu behavior on the shared platform surface",
5138
- "passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
5139
- ],
5140
- content: appTsx(application, target, {
5141
- audience: options.payload.audience,
5142
- portfolio: options.payload.portfolio
5143
- })
5144
- });
5145
- }
5146
- if (options.api) {
5147
- files.push(
5148
- {
5149
- path: `${apiBase}/wrangler.toml`,
5150
- purpose: "Cloudflare Worker runtime configuration.",
5151
- requiredSignals: [
5152
- "bindings match topolo.cloudcontrol.json or topolo.app.json",
5153
- "secrets are referenced by name only"
5154
- ],
5155
- content: target === "platform" ? platformApiWranglerToml(application, options.payload.resources, options.payload.profile) : wranglerToml(application, target, options.payload.resources, options.payload.profile)
5156
- },
5157
- {
5158
- path: `${apiBase}/src/worker.ts`,
5159
- purpose: "Protected service boundary.",
5160
- requiredSignals: [
5161
- "uses @topolo-io/worker-runtime for Worker auth, gatewaying, headers, and request helpers",
5162
- "derives organization from the credential rather than client-supplied orgId",
5163
- "emits or propagates X-Topolo-Request-Id and X-Topolo-Client"
5164
- ],
5165
- content: workerTs(application, target, {
5166
- audience: options.payload.audience,
5167
- tenancy: options.payload.tenancy,
5168
- profile: options.payload.profile,
5169
- resources: options.payload.resources
5170
- })
5171
- }
5172
- );
5173
- if (target === "platform") {
5174
- files.push(
5175
- {
5176
- path: `${apiBase}/src/service-identity.ts`,
5177
- purpose: "Runtime service identity resolver for the API Worker.",
5178
- requiredSignals: [
5179
- "resolves the concrete Auth service id from Worker vars",
5180
- "keeps the API Worker independent from browser-only import.meta usage"
5181
- ],
5182
- content: apiServiceIdentityTs(application)
5183
- },
5184
- {
5185
- path: `${browserBase}/src/worker.ts`,
5186
- purpose: "Browser Worker that serves assets and forwards API paths to the API Worker binding.",
5187
- requiredSignals: [
5188
- "keeps the frontend Worker thin",
5189
- "routes /api/* to the API service binding instead of owning backend routes locally"
5190
- ],
5191
- content: webWorkerTs(application)
5192
- },
5193
- {
5194
- path: `${browserBase}/wrangler.toml`,
5195
- purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
5196
- requiredSignals: [
5197
- "serves Vite assets through a Worker with assets",
5198
- "binds the frontend Worker to the API Worker for /api/* requests"
5199
- ],
5200
- content: platformWebWranglerToml(application)
5201
- }
5202
- );
5203
- }
5204
- }
5205
- return files;
5206
4834
  }
5207
- function scaffoldCommands(options) {
5208
- const scaffoldId = toSlug(options.id);
5209
- if (options.target === "developer") {
5210
- const audienceFlags = options.audience === "personal" ? " --audience personal --tenancy household" : "";
5211
- return [
5212
- `topolo apps scaffold ${scaffoldId}${audienceFlags} --write --repo-root .`,
5213
- `cd topolo-${scaffoldId}`,
5214
- "npm install",
5215
- "npm run typecheck",
5216
- "npm run build"
5217
- ];
4835
+ function resolveInside(root, filePath) {
4836
+ const resolved = resolve(root, filePath);
4837
+ const relativePath = relative(root, resolved);
4838
+ if (relativePath.startsWith("..") || relativePath === "" || relativePath.startsWith("/")) {
4839
+ throw new Error(`Scaffold file path escapes the target root: ${filePath}`);
5218
4840
  }
5219
- const portfolioFlags = options.portfolio === "personal" ? " --portfolio personal" : "";
5220
- const profileFlags = options.profile ? ` --profile ${options.profile}` : "";
5221
- return [
5222
- `topolo platform apps scaffold ${scaffoldId}${portfolioFlags}${profileFlags} --write --provision --repo-root /Users/ashes/Projects/Topolo/Platform --json`,
5223
- "topolo apps requirements --json",
5224
- `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo/Platform --fail-on needs_review --json`,
5225
- `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo/Platform --fail-on partial --json`
5226
- ];
4841
+ return resolved;
5227
4842
  }
5228
- function topoloConformanceWorkflow(options) {
5229
- const appPath = firstPartyAppPath(options.appDir, options.audience);
5230
- const docsCheckoutStep = options.appDir === "TopoloDocs" ? [] : [
5231
- "",
5232
- " - name: Checkout TopoloDocs",
5233
- ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
5234
- " uses: actions/checkout@v4",
5235
- " with:",
5236
- " repository: Topolo-io/TopoloDocs",
5237
- " path: Apps/system/TopoloDocs",
5238
- " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
5239
- ];
5240
- const cliCheckoutStep = options.appDir === "TopoloCLI" || options.appDir === "TopoloCli" ? [] : [
5241
- "",
5242
- " - name: Checkout Topolo CLI",
5243
- ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
5244
- " uses: actions/checkout@v4",
5245
- " with:",
5246
- " repository: Topolo-io/TopoloCli",
5247
- " path: Apps/system/TopoloCLI",
5248
- " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
5249
- ];
5250
- return [
5251
- "name: Topolo App Conformance",
5252
- "",
5253
- "on:",
5254
- " pull_request:",
5255
- " push:",
5256
- " branches:",
5257
- " - main",
5258
- " workflow_dispatch:",
5259
- "",
5260
- "permissions:",
5261
- " contents: read",
5262
- "",
5263
- "jobs:",
5264
- " conformance:",
5265
- " runs-on: ubuntu-latest",
5266
- " env:",
5267
- ` HAS_TOPOLO_PACKAGES_APP: \${{ secrets.TOPOLO_PACKAGES_APP_ID != '' && secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY != '' }}`,
5268
- ` HAS_TOPOLO_CI_REPO_TOKEN: \${{ secrets.TOPOLO_CI_REPO_TOKEN != '' }}`,
5269
- " steps:",
5270
- " - name: Checkout application",
5271
- " uses: actions/checkout@v4",
5272
- " with:",
5273
- ` path: ${appPath}`,
5274
- "",
5275
- " - name: Mint Topolo cross-repo token",
5276
- " id: app-token",
5277
- ` if: env.HAS_TOPOLO_PACKAGES_APP == 'true'`,
5278
- " uses: actions/create-github-app-token@v1",
5279
- " with:",
5280
- ` app-id: \${{ secrets.TOPOLO_PACKAGES_APP_ID }}`,
5281
- ` private-key: \${{ secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY }}`,
5282
- " owner: Topolo-io",
5283
- ...docsCheckoutStep,
5284
- "",
5285
- " - name: Checkout Topolo SDK",
5286
- ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
5287
- " uses: actions/checkout@v4",
5288
- " with:",
5289
- " repository: Topolo-io/topolo-sdk",
5290
- " path: Apps/packages/topolo-sdk",
5291
- " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}",
5292
- ...cliCheckoutStep,
5293
- "",
5294
- " - name: Skip conformance without cross-repo token",
5295
- ` if: steps.app-token.outputs.token == '' && env.HAS_TOPOLO_CI_REPO_TOKEN != 'true'`,
5296
- " run: |",
5297
- ' echo "::warning::Skipping Topolo app conformance because no Topolo cross-repo token is available to this workflow context."',
5298
- "",
5299
- " - name: Setup Node.js",
5300
- ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
5301
- " uses: actions/setup-node@v4",
5302
- " with:",
5303
- " node-version: 20",
5304
- "",
5305
- " - name: Build Topolo SDK",
5306
- ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
5307
- " working-directory: Apps/packages/topolo-sdk",
5308
- " run: |",
5309
- " npm install --no-audit --no-fund",
5310
- " npm run build",
5311
- "",
5312
- " - name: Build Topolo CLI",
5313
- ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
5314
- " working-directory: Apps/system/TopoloCLI",
5315
- " run: |",
5316
- " npm install --no-audit --no-fund",
5317
- " npm run build",
5318
- "",
5319
- " - name: Run Topolo app conformance audit",
5320
- ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
5321
- " run: |",
5322
- ` node Apps/system/TopoloCLI/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`,
5323
- "",
5324
- " - name: Record green CI evidence",
5325
- ` if: \${{ success() && (steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true') }}`,
5326
- ` working-directory: ${appPath}`,
5327
- " run: |",
5328
- ` node "$GITHUB_WORKSPACE/Apps/system/TopoloCLI/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"`
5329
- ].join("\n");
4843
+ function escapeHtml(value) {
4844
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4845
+ }
4846
+ function escapeSingleQuoted(value) {
4847
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
4848
+ }
4849
+ function escapeFrontmatter(value) {
4850
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
4851
+ }
4852
+ function firstPartyAppPath(appDir, audience) {
4853
+ if (appDir === "TopoloCli" || appDir === "TopoloCLI") return "Apps/system/TopoloCLI";
4854
+ if (appDir === "TopoloDocs") return "Apps/system/TopoloDocs";
4855
+ if (appDir === "TopoloAuth") return "Apps/system/TopoloAuth";
4856
+ return `${audience === "personal" ? "Apps/personal" : "Apps/business"}/${appDir}`;
5330
4857
  }
4858
+
4859
+ // src/commands/app-scaffold-package-template.ts
5331
4860
  function packageJson(application, target, options) {
5332
4861
  const dependencies = {};
5333
4862
  const devDependencies = {
@@ -5423,11 +4952,12 @@ function hasScaffoldResources(resources) {
5423
4952
  function resourceConfig(application, profile) {
5424
4953
  const slug = toSlug(application.id);
5425
4954
  const pascal = toPascalCase(application.id);
4955
+ const constant = toConstantCase(application.id);
5426
4956
  if (profile === "spatial-assets") {
5427
4957
  return {
5428
- d1Binding: "APP_DB",
5429
- d1ProductionName: `topolo-${slug}-production-db`,
5430
- d1StagingName: `topolo-${slug}-staging-db`,
4958
+ d1Binding: `${constant}_CORE_DB`,
4959
+ d1ProductionName: `topolo-${slug}-core-prod`,
4960
+ d1StagingName: `topolo-${slug}-core-staging`,
5431
4961
  r2Binding: "SPACES_ASSETS",
5432
4962
  r2ProductionName: `topolo-${slug}-assets`,
5433
4963
  r2StagingName: `topolo-${slug}-assets-staging`,
@@ -5443,9 +4973,9 @@ function resourceConfig(application, profile) {
5443
4973
  };
5444
4974
  }
5445
4975
  return {
5446
- d1Binding: "APP_DB",
5447
- d1ProductionName: `topolo-${slug}-prod`,
5448
- d1StagingName: `topolo-${slug}-staging`,
4976
+ d1Binding: `${constant}_CORE_DB`,
4977
+ d1ProductionName: `topolo-${slug}-core-prod`,
4978
+ d1StagingName: `topolo-${slug}-core-staging`,
5449
4979
  r2Binding: "APP_ASSETS",
5450
4980
  r2ProductionName: `topolo-${slug}-assets`,
5451
4981
  r2StagingName: `topolo-${slug}-assets-staging`,
@@ -5460,6 +4990,8 @@ function resourceConfig(application, profile) {
5460
4990
  containerStagingName: `topolo-${slug}-processor-staging`
5461
4991
  };
5462
4992
  }
4993
+
4994
+ // src/commands/app-scaffold-browser-template.ts
5463
4995
  function readme(application, target, options) {
5464
4996
  const displayName = toTitle(application.name);
5465
4997
  const slug = toSlug(application.id);
@@ -5904,6 +5436,8 @@ function stylesCss() {
5904
5436
  ""
5905
5437
  ].join("\n");
5906
5438
  }
5439
+
5440
+ // src/commands/app-scaffold-app-template.ts
5907
5441
  function appTsx(application, target, options) {
5908
5442
  if (target === "developer") return developerAppTsx(application);
5909
5443
  return platformAppTsx(application, options);
@@ -6331,6 +5865,13 @@ function developerAppTsx(application) {
6331
5865
  ""
6332
5866
  ].join("\n");
6333
5867
  }
5868
+
5869
+ // src/commands/app-scaffold-cloudflare-constants.ts
5870
+ var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
5871
+ var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
5872
+ var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
5873
+
5874
+ // src/commands/app-scaffold-worker-template.ts
6334
5875
  function webWorkerTs(application) {
6335
5876
  const serviceName = toTitle(application.name);
6336
5877
  return [
@@ -6448,7 +5989,7 @@ function platformApiWranglerToml(application, resources, profile) {
6448
5989
  "",
6449
5990
  "[vars]",
6450
5991
  'AUTH_BASE_URL = "https://auth.topolo.app"',
6451
- 'NEXUS_GATEWAY_URL = "https://nexus-api.topolo.app"',
5992
+ 'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
6452
5993
  `NEXUS_APP_ID = "${slug}"`,
6453
5994
  `SERVICE_ID = "${application.serviceId ?? ""}"`,
6454
5995
  `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
@@ -6463,7 +6004,7 @@ function platformApiWranglerToml(application, resources, profile) {
6463
6004
  "",
6464
6005
  "[env.staging.vars]",
6465
6006
  'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
6466
- 'NEXUS_GATEWAY_URL = "https://nexus-api.stg.topolo.us"',
6007
+ 'NEXUS_GATEWAY_URL = "https://nexus.stg.topolo.us"',
6467
6008
  `NEXUS_APP_ID = "${slug}"`,
6468
6009
  `SERVICE_ID = "${application.serviceId ?? ""}"`,
6469
6010
  `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
@@ -6478,7 +6019,7 @@ function platformApiWranglerToml(application, resources, profile) {
6478
6019
  "",
6479
6020
  "[env.production.vars]",
6480
6021
  'AUTH_BASE_URL = "https://auth.topolo.app"',
6481
- 'NEXUS_GATEWAY_URL = "https://nexus-api.topolo.app"',
6022
+ 'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
6482
6023
  `NEXUS_APP_ID = "${slug}"`,
6483
6024
  `SERVICE_ID = "${application.serviceId ?? ""}"`,
6484
6025
  `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
@@ -6574,7 +6115,7 @@ function wranglerToml(application, target, resources, profile) {
6574
6115
  "",
6575
6116
  "[vars]",
6576
6117
  'AUTH_BASE_URL = "https://auth.topolo.app"',
6577
- 'NEXUS_GATEWAY_URL = "https://nexus-api.topolo.app"',
6118
+ 'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
6578
6119
  runtimeIdentityVar,
6579
6120
  ""
6580
6121
  );
@@ -6629,7 +6170,7 @@ function wranglerToml(application, target, resources, profile) {
6629
6170
  "",
6630
6171
  "[env.staging.vars]",
6631
6172
  'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
6632
- 'NEXUS_GATEWAY_URL = "https://nexus-api.stg.topolo.us"',
6173
+ 'NEXUS_GATEWAY_URL = "https://nexus.stg.topolo.us"',
6633
6174
  runtimeIdentityVar,
6634
6175
  ""
6635
6176
  );
@@ -6929,6 +6470,632 @@ function developerWorkerTs(application) {
6929
6470
  ""
6930
6471
  ].join("\n");
6931
6472
  }
6473
+
6474
+ // src/commands/app-scaffold-workflows.ts
6475
+ function scaffoldCommands(options) {
6476
+ const scaffoldId = toSlug(options.id);
6477
+ if (options.target === "developer") {
6478
+ const audienceFlags = options.audience === "personal" ? " --audience personal --tenancy household" : "";
6479
+ return [
6480
+ `topolo apps scaffold ${scaffoldId}${audienceFlags} --write --repo-root .`,
6481
+ `cd topolo-${scaffoldId}`,
6482
+ "npm install",
6483
+ "npm run typecheck",
6484
+ "npm run build"
6485
+ ];
6486
+ }
6487
+ const portfolioFlags = options.portfolio === "personal" ? " --portfolio personal" : "";
6488
+ const profileFlags = options.profile ? ` --profile ${options.profile}` : "";
6489
+ return [
6490
+ `topolo platform apps scaffold ${scaffoldId}${portfolioFlags}${profileFlags} --write --provision --repo-root /Users/ashes/Projects/Topolo/Platform --json`,
6491
+ "topolo apps requirements --json",
6492
+ `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo/Platform --fail-on needs_review --json`,
6493
+ `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo/Platform --fail-on partial --json`
6494
+ ];
6495
+ }
6496
+ function topoloConformanceWorkflow(options) {
6497
+ const appPath = firstPartyAppPath(options.appDir, options.audience);
6498
+ const docsCheckoutStep = options.appDir === "TopoloDocs" ? [] : [
6499
+ "",
6500
+ " - name: Checkout TopoloDocs",
6501
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6502
+ " uses: actions/checkout@v4",
6503
+ " with:",
6504
+ " repository: Topolo-io/TopoloDocs",
6505
+ " path: Apps/system/TopoloDocs",
6506
+ " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
6507
+ ];
6508
+ const cliCheckoutStep = options.appDir === "TopoloCLI" || options.appDir === "TopoloCli" ? [] : [
6509
+ "",
6510
+ " - name: Checkout Topolo CLI",
6511
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6512
+ " uses: actions/checkout@v4",
6513
+ " with:",
6514
+ " repository: Topolo-io/TopoloCli",
6515
+ " path: Apps/system/TopoloCLI",
6516
+ " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
6517
+ ];
6518
+ return [
6519
+ "name: Topolo App Conformance",
6520
+ "",
6521
+ "on:",
6522
+ " pull_request:",
6523
+ " push:",
6524
+ " branches:",
6525
+ " - main",
6526
+ " workflow_dispatch:",
6527
+ "",
6528
+ "permissions:",
6529
+ " contents: read",
6530
+ "",
6531
+ "jobs:",
6532
+ " conformance:",
6533
+ " runs-on: ubuntu-latest",
6534
+ " env:",
6535
+ ` HAS_TOPOLO_PACKAGES_APP: \${{ secrets.TOPOLO_PACKAGES_APP_ID != '' && secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY != '' }}`,
6536
+ ` HAS_TOPOLO_CI_REPO_TOKEN: \${{ secrets.TOPOLO_CI_REPO_TOKEN != '' }}`,
6537
+ " steps:",
6538
+ " - name: Checkout application",
6539
+ " uses: actions/checkout@v4",
6540
+ " with:",
6541
+ ` path: ${appPath}`,
6542
+ "",
6543
+ " - name: Mint Topolo cross-repo token",
6544
+ " id: app-token",
6545
+ ` if: env.HAS_TOPOLO_PACKAGES_APP == 'true'`,
6546
+ " uses: actions/create-github-app-token@v1",
6547
+ " with:",
6548
+ ` app-id: \${{ secrets.TOPOLO_PACKAGES_APP_ID }}`,
6549
+ ` private-key: \${{ secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY }}`,
6550
+ " owner: Topolo-io",
6551
+ ...docsCheckoutStep,
6552
+ "",
6553
+ " - name: Checkout Topolo SDK",
6554
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6555
+ " uses: actions/checkout@v4",
6556
+ " with:",
6557
+ " repository: Topolo-io/topolo-sdk",
6558
+ " path: Apps/packages/topolo-sdk",
6559
+ " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}",
6560
+ ...cliCheckoutStep,
6561
+ "",
6562
+ " - name: Skip conformance without cross-repo token",
6563
+ ` if: steps.app-token.outputs.token == '' && env.HAS_TOPOLO_CI_REPO_TOKEN != 'true'`,
6564
+ " run: |",
6565
+ ' echo "::warning::Skipping Topolo app conformance because no Topolo cross-repo token is available to this workflow context."',
6566
+ "",
6567
+ " - name: Setup Node.js",
6568
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6569
+ " uses: actions/setup-node@v4",
6570
+ " with:",
6571
+ " node-version: 20",
6572
+ "",
6573
+ " - name: Build Topolo SDK",
6574
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6575
+ " working-directory: Apps/packages/topolo-sdk",
6576
+ " run: |",
6577
+ " npm install --no-audit --no-fund",
6578
+ " npm run build",
6579
+ "",
6580
+ " - name: Build Topolo CLI",
6581
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6582
+ " working-directory: Apps/system/TopoloCLI",
6583
+ " run: |",
6584
+ " npm install --no-audit --no-fund",
6585
+ " npm run build",
6586
+ "",
6587
+ " - name: Run Topolo app conformance audit",
6588
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6589
+ " run: |",
6590
+ ` node Apps/system/TopoloCLI/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`,
6591
+ "",
6592
+ " - name: Record green CI evidence",
6593
+ ` if: \${{ success() && (steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true') }}`,
6594
+ ` working-directory: ${appPath}`,
6595
+ " run: |",
6596
+ ` node "$GITHUB_WORKSPACE/Apps/system/TopoloCLI/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"`
6597
+ ].join("\n");
6598
+ }
6599
+
6600
+ // src/commands/app-scaffold.ts
6601
+ var CURRENT_VERIFICATION_DATE = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6602
+ function generateOpaqueServiceId() {
6603
+ return `srv_${randomBytes(9).toString("base64url")}`;
6604
+ }
6605
+ function applicationScaffoldPayload(application, options = {}) {
6606
+ const target = normalizeTarget(options.target);
6607
+ const portfolio = target === "platform" ? normalizePortfolio(options.portfolio) : null;
6608
+ const requestedId = normalizeApplicationId(application);
6609
+ const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
6610
+ const slug = toSlug(id);
6611
+ const catalogApplication = APPLICATIONS[id];
6612
+ const browser = options.browser !== false;
6613
+ const api = options.api ?? target === "platform";
6614
+ const audience = normalizeAudience(
6615
+ options.audience,
6616
+ target === "platform" ? portfolio : null
6617
+ );
6618
+ const tenancy = normalizeTenancy(options.tenancy, audience);
6619
+ const surfaceModel = normalizeSurfaceModel(options.surfaceModel);
6620
+ const containerAppId = normalizeContainerAppId(options.containerAppId, surfaceModel);
6621
+ const profile = normalizeScaffoldProfile(options.profile);
6622
+ const resources = resolveScaffoldResources(profile, options);
6623
+ const ownerType = target === "platform" ? "topolo_first_party" : "third_party";
6624
+ const distributionModel = target === "platform" ? "marketplace" : "organization_internal";
6625
+ const appDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
6626
+ const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
6627
+ const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${slug}`;
6628
+ const serviceSlug = api ? target === "platform" ? `topolo-${slug}` : id : null;
6629
+ const defaultServiceId = target === "platform" ? generateOpaqueServiceId() : catalogApplication?.services[0] ?? id;
6630
+ const serviceId = api ? options.service ?? defaultServiceId : null;
6631
+ const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${slug}.topolo.app` : null;
6632
+ const scopes = scaffoldScopes({ browser, api });
6633
+ const capabilities = scaffoldCapabilities({ browser, api, distributionModel });
6634
+ const ciWorkflowPath = target === "platform" ? ".github/workflows/topolo-conformance.yml" : null;
6635
+ const ciWorkflow = target === "platform" ? topoloConformanceWorkflow({ id, appDir, audience }) : null;
6636
+ const applicationPayload = {
6637
+ id,
6638
+ name,
6639
+ appDir,
6640
+ packageName,
6641
+ productionUrl,
6642
+ serviceSlug,
6643
+ serviceId
6644
+ };
6645
+ return {
6646
+ version: APPLICATION_REQUIREMENTS_VERSION,
6647
+ target,
6648
+ ownerType,
6649
+ portfolio,
6650
+ audience,
6651
+ tenancy,
6652
+ surfaceModel,
6653
+ containerAppId,
6654
+ profile,
6655
+ resources,
6656
+ distributionModel,
6657
+ capabilities,
6658
+ application: applicationPayload,
6659
+ scopes,
6660
+ requirements: APPLICATION_REQUIREMENTS.filter(
6661
+ (requirement) => requirement.appliesTo.some((scope) => scopes.includes(scope))
6662
+ ),
6663
+ packages: scaffoldPackages({ browser, api, target }),
6664
+ files: scaffoldFiles({
6665
+ payload: {
6666
+ ownerType,
6667
+ portfolio,
6668
+ audience,
6669
+ tenancy,
6670
+ surfaceModel,
6671
+ containerAppId,
6672
+ profile,
6673
+ resources,
6674
+ distributionModel,
6675
+ capabilities
6676
+ },
6677
+ application: applicationPayload,
6678
+ target,
6679
+ browser,
6680
+ api,
6681
+ ciWorkflow
6682
+ }),
6683
+ commands: scaffoldCommands({
6684
+ id,
6685
+ target,
6686
+ audience,
6687
+ tenancy,
6688
+ portfolio,
6689
+ profile
6690
+ }),
6691
+ ciWorkflowPath,
6692
+ ciWorkflow
6693
+ };
6694
+ }
6695
+ async function writeApplicationScaffold(payload, options = {}) {
6696
+ const root = resolve2(options.repoRoot ?? process.cwd());
6697
+ const plannedFiles = payload.files.map((file) => ({
6698
+ file,
6699
+ absolutePath: resolveInside(root, file.path)
6700
+ }));
6701
+ const existingFiles = [];
6702
+ for (const planned of plannedFiles) {
6703
+ if (await pathExists(planned.absolutePath)) {
6704
+ existingFiles.push(planned.file.path);
6705
+ }
6706
+ }
6707
+ if (existingFiles.length > 0 && !options.force) {
6708
+ throw new Error(
6709
+ [
6710
+ "Refusing to overwrite existing scaffold files:",
6711
+ ...existingFiles.map((filePath) => `- ${filePath}`),
6712
+ "Pass --force to overwrite these files."
6713
+ ].join("\n")
6714
+ );
6715
+ }
6716
+ const files = [];
6717
+ for (const planned of plannedFiles) {
6718
+ const existed = await pathExists(planned.absolutePath);
6719
+ await mkdir2(dirname3(planned.absolutePath), { recursive: true });
6720
+ await writeFile2(planned.absolutePath, planned.file.content, "utf8");
6721
+ files.push({
6722
+ path: planned.file.path,
6723
+ status: existed ? "overwritten" : "created"
6724
+ });
6725
+ }
6726
+ return { root, files };
6727
+ }
6728
+ function scaffoldScopes(options) {
6729
+ const scopes = ["all"];
6730
+ if (options.browser) scopes.push("browser");
6731
+ if (options.api) {
6732
+ scopes.push("api", "agent_surface");
6733
+ }
6734
+ return scopes;
6735
+ }
6736
+ function scaffoldPackages(options) {
6737
+ if (options.target === "developer") {
6738
+ return options.browser ? ["@topolo/login"] : [];
6739
+ }
6740
+ const packages = /* @__PURE__ */ new Set();
6741
+ if (options.browser) {
6742
+ packages.add("@topolo-io/app-funnel");
6743
+ packages.add("@topolo-io/app-launcher");
6744
+ packages.add("@topolo-io/app-shell");
6745
+ packages.add("@topolo-io/auth-client");
6746
+ packages.add("@topolo-io/ui-kit");
6747
+ }
6748
+ if (options.api) {
6749
+ packages.add("@topolo-io/worker-runtime");
6750
+ packages.add("@topolo/sdk");
6751
+ }
6752
+ return Array.from(packages);
6753
+ }
6754
+ function scaffoldCapabilities(options) {
6755
+ const capabilities = [];
6756
+ if (options.browser) {
6757
+ capabilities.push("login", "launcher");
6758
+ }
6759
+ if (options.distributionModel === "marketplace") capabilities.push("marketplace");
6760
+ if (options.api) capabilities.push("api", "widget");
6761
+ return capabilities;
6762
+ }
6763
+ function scaffoldFiles(options) {
6764
+ const { application, target } = options;
6765
+ const slug = toSlug(application.id);
6766
+ const base = target === "platform" ? firstPartyAppPath(application.appDir, options.payload.audience) : application.appDir;
6767
+ const browserBase = target === "platform" ? `${base}/apps/web` : base;
6768
+ const apiBase = target === "platform" ? `${base}/apps/api` : base;
6769
+ const files = [
6770
+ {
6771
+ path: `${base}/package.json`,
6772
+ purpose: "Package metadata and verification commands.",
6773
+ requiredSignals: [
6774
+ "name matches the packageName in the SDK application catalog",
6775
+ "scripts include build plus typecheck where the stack supports them"
6776
+ ],
6777
+ content: packageJson(application, target, {
6778
+ browser: options.browser,
6779
+ api: options.api,
6780
+ resources: options.payload.resources
6781
+ })
6782
+ },
6783
+ {
6784
+ path: `${base}/README.md`,
6785
+ purpose: "Starter README with install, run, and verification commands.",
6786
+ requiredSignals: [
6787
+ "documents the generated app target",
6788
+ "points maintainers to the shared Topolo packages used by the scaffold"
6789
+ ],
6790
+ content: readme(application, target, {
6791
+ api: options.api,
6792
+ audience: options.payload.audience,
6793
+ tenancy: options.payload.tenancy,
6794
+ portfolio: options.payload.portfolio,
6795
+ profile: options.payload.profile,
6796
+ resources: options.payload.resources
6797
+ })
6798
+ },
6799
+ {
6800
+ path: `${base}/.gitignore`,
6801
+ purpose: "Generated-app ignore rules.",
6802
+ requiredSignals: [
6803
+ "ignores dependency installs and build output",
6804
+ "keeps local environment files out of source control"
6805
+ ],
6806
+ content: gitignore()
6807
+ },
6808
+ {
6809
+ path: `${base}/tsconfig.json`,
6810
+ purpose: "TypeScript configuration for the generated app.",
6811
+ requiredSignals: [
6812
+ "uses strict mode",
6813
+ "includes browser DOM types for React and Worker types for API-backed apps"
6814
+ ],
6815
+ content: tsconfigJson(options.api, target)
6816
+ },
6817
+ {
6818
+ path: `${browserBase}/vite.config.ts`,
6819
+ purpose: "Vite browser build configuration.",
6820
+ requiredSignals: [
6821
+ "builds the authenticated browser surface",
6822
+ "keeps generated apps on the standard React/Vite baseline"
6823
+ ],
6824
+ content: viteConfig(target)
6825
+ },
6826
+ {
6827
+ path: `${browserBase}/index.html`,
6828
+ purpose: "Browser entry document.",
6829
+ requiredSignals: [
6830
+ "mounts the React app at #root",
6831
+ "uses the generated application display name"
6832
+ ],
6833
+ content: indexHtml(application.name)
6834
+ },
6835
+ {
6836
+ path: `${browserBase}/src/main.tsx`,
6837
+ purpose: "React app entrypoint.",
6838
+ requiredSignals: [
6839
+ "renders the generated app component",
6840
+ "imports the generated app stylesheet"
6841
+ ],
6842
+ content: mainTsx()
6843
+ },
6844
+ {
6845
+ path: `${browserBase}/src/env.d.ts`,
6846
+ purpose: "Vite environment variable typing.",
6847
+ requiredSignals: [
6848
+ "loads vite/client import.meta.env types",
6849
+ "declares the Topolo environment variables consumed by the starter"
6850
+ ],
6851
+ content: envDts(options.api)
6852
+ },
6853
+ ...options.payload.resources.workflow ? [
6854
+ {
6855
+ path: `${base}/src/cloudflare-workers.d.ts`,
6856
+ purpose: "Minimal Cloudflare Workflow entrypoint typing for scaffolded Workers.",
6857
+ requiredSignals: [
6858
+ "declares cloudflare:workers WorkflowEntrypoint for TypeScript",
6859
+ "keeps Workflow imports deployable without adding a new dependency"
6860
+ ],
6861
+ content: cloudflareWorkersDts()
6862
+ }
6863
+ ] : [],
6864
+ {
6865
+ path: `${browserBase}/src/styles.css`,
6866
+ purpose: "Minimal app-local styles outside the shared Topolo shell.",
6867
+ requiredSignals: [
6868
+ "does not fork the shared shell chrome",
6869
+ "keeps app-specific layout scoped under generated classes"
6870
+ ],
6871
+ content: stylesCss()
6872
+ }
6873
+ ];
6874
+ if (target === "platform") {
6875
+ files.push(
6876
+ {
6877
+ path: `${base}/topolo.app.json`,
6878
+ purpose: "Canonical app manifest that drives generated Wrangler and CloudControl metadata.",
6879
+ requiredSignals: [
6880
+ "declares web/api targets with canonical names and app-local target directories",
6881
+ "keeps Cloudflare account, domain, service id, and bindings in one manifest source of truth"
6882
+ ],
6883
+ content: topoloAppManifestJson(application, {
6884
+ api: options.api,
6885
+ resources: options.payload.resources,
6886
+ profile: options.payload.profile
6887
+ })
6888
+ },
6889
+ {
6890
+ path: `${base}/topolo.cloudcontrol.json`,
6891
+ purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
6892
+ requiredSignals: [
6893
+ "production URLs and Cloudflare targets are explicit",
6894
+ "resource bindings and required secrets are declared without secret values"
6895
+ ],
6896
+ content: cloudcontrolJson(application, {
6897
+ api: options.api,
6898
+ audience: options.payload.audience,
6899
+ profile: options.payload.profile,
6900
+ resources: options.payload.resources
6901
+ })
6902
+ },
6903
+ {
6904
+ path: `${base}/.github/workflows/topolo-conformance.yml`,
6905
+ purpose: "PR/push gate that runs the local Topolo app audit with docs evidence.",
6906
+ requiredSignals: [
6907
+ "checks out this repo under its Apps lane directory",
6908
+ "checks out TopoloDocs under Apps/system/TopoloDocs",
6909
+ "checks out topolo-sdk and TopoloCli from main so the gate does not wait for an npm publish",
6910
+ "runs topolo apps audit with --local and --fail-on needs_review"
6911
+ ],
6912
+ content: options.ciWorkflow ?? ""
6913
+ },
6914
+ {
6915
+ path: `Apps/system/TopoloDocs/src/content/systems/topolo-${slug}.json`,
6916
+ purpose: "Canonical system registry entry.",
6917
+ requiredSignals: [
6918
+ `repo_paths includes ${base}`,
6919
+ "doc_paths includes internal handbook coverage",
6920
+ "security_assurance records the auth, tenancy, deployment, and verification boundary"
6921
+ ],
6922
+ content: systemJson(application, {
6923
+ api: options.api,
6924
+ audience: options.payload.audience,
6925
+ tenancy: options.payload.tenancy,
6926
+ portfolio: options.payload.portfolio,
6927
+ profile: options.payload.profile,
6928
+ resources: options.payload.resources
6929
+ })
6930
+ },
6931
+ {
6932
+ path: `Apps/system/TopoloDocs/src/content/internal/apps/${slug}.mdx`,
6933
+ purpose: "Internal operator handbook.",
6934
+ requiredSignals: [
6935
+ "documents ownership, routes, auth boundary, deployment, operations, and verification",
6936
+ "last_verified is updated when behavior changes"
6937
+ ],
6938
+ content: internalDocs(application, {
6939
+ api: options.api,
6940
+ audience: options.payload.audience,
6941
+ tenancy: options.payload.tenancy,
6942
+ portfolio: options.payload.portfolio,
6943
+ profile: options.payload.profile,
6944
+ resources: options.payload.resources
6945
+ })
6946
+ },
6947
+ {
6948
+ path: `Apps/system/TopoloDocs/src/content/public/applications/${slug}.mdx`,
6949
+ purpose: "Public application overview.",
6950
+ requiredSignals: [
6951
+ "documents the product surface without internal-only details",
6952
+ "links back to the canonical system registry entry"
6953
+ ],
6954
+ content: publicDocs(application, {
6955
+ api: options.api,
6956
+ audience: options.payload.audience,
6957
+ tenancy: options.payload.tenancy,
6958
+ portfolio: options.payload.portfolio,
6959
+ profile: options.payload.profile,
6960
+ resources: options.payload.resources
6961
+ })
6962
+ }
6963
+ );
6964
+ if (options.api && application.serviceId) {
6965
+ files.push(
6966
+ {
6967
+ path: `Apps/system/TopoloAuth/catalog/service-manifests/${application.serviceId}.json`,
6968
+ purpose: "Auth service manifest for first-party permissions, role bundles, and API-key scopes.",
6969
+ requiredSignals: [
6970
+ "serviceId matches the generated Worker service id",
6971
+ "permissions, role bundles, and API-key scopes exist before the app is catalog-visible",
6972
+ "systems metadata maps the service back to the generated TopoloDocs entries"
6973
+ ],
6974
+ content: serviceManifestJson(application, {
6975
+ audience: options.payload.audience,
6976
+ tenancy: options.payload.tenancy
6977
+ })
6978
+ }
6979
+ );
6980
+ }
6981
+ } else {
6982
+ files.push(
6983
+ {
6984
+ path: `${base}/topolo.app.json`,
6985
+ purpose: "Third-party Topolo application manifest.",
6986
+ requiredSignals: [
6987
+ "declares the external app id, callback URLs, and requested platform scopes",
6988
+ "does not include internal Topolo CloudControl or canonical-docs ownership"
6989
+ ],
6990
+ content: developerManifest(application, {
6991
+ api: options.api,
6992
+ audience: options.payload.audience,
6993
+ tenancy: options.payload.tenancy,
6994
+ surfaceModel: options.payload.surfaceModel,
6995
+ containerAppId: options.payload.containerAppId,
6996
+ distributionModel: options.payload.distributionModel,
6997
+ capabilities: options.payload.capabilities
6998
+ })
6999
+ },
7000
+ {
7001
+ path: `${base}/.env.example`,
7002
+ purpose: "Developer environment template for local auth configuration.",
7003
+ requiredSignals: [
7004
+ "names the Topolo Auth URL",
7005
+ "keeps OAuth client ids and service ids out of source control by default"
7006
+ ],
7007
+ content: envExample(application)
7008
+ }
7009
+ );
7010
+ }
7011
+ if (options.browser) {
7012
+ if (target === "platform") {
7013
+ files.push({
7014
+ path: `${browserBase}/src/service-identity.ts`,
7015
+ purpose: "Central service identity resolver shared by browser and Worker runtime surfaces.",
7016
+ requiredSignals: [
7017
+ "exports the generated app id, display name, and stable Auth service slug",
7018
+ "requires the provisioned Auth service id from the Worker deployment config"
7019
+ ],
7020
+ content: serviceIdentityTs(application)
7021
+ });
7022
+ }
7023
+ files.push({
7024
+ path: `${browserBase}/src/App.tsx`,
7025
+ purpose: "Authenticated browser surface.",
7026
+ requiredSignals: [
7027
+ "bootstraps routes, auth provider, and shared shell through @topolo-io/app-shell",
7028
+ "uses @topolo-io/auth-client through the app-shell wrapper for app-origin auth flows",
7029
+ "keeps launcher and account-menu behavior on the shared platform surface",
7030
+ "passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
7031
+ ],
7032
+ content: appTsx(application, target, {
7033
+ audience: options.payload.audience,
7034
+ portfolio: options.payload.portfolio
7035
+ })
7036
+ });
7037
+ }
7038
+ if (options.api) {
7039
+ files.push(
7040
+ {
7041
+ path: `${apiBase}/wrangler.toml`,
7042
+ purpose: "Cloudflare Worker runtime configuration.",
7043
+ requiredSignals: [
7044
+ "bindings match topolo.cloudcontrol.json or topolo.app.json",
7045
+ "secrets are referenced by name only"
7046
+ ],
7047
+ content: target === "platform" ? platformApiWranglerToml(application, options.payload.resources, options.payload.profile) : wranglerToml(application, target, options.payload.resources, options.payload.profile)
7048
+ },
7049
+ {
7050
+ path: `${apiBase}/src/worker.ts`,
7051
+ purpose: "Protected service boundary.",
7052
+ requiredSignals: [
7053
+ "uses @topolo-io/worker-runtime for Worker auth, gatewaying, headers, and request helpers",
7054
+ "derives organization from the credential rather than client-supplied orgId",
7055
+ "emits or propagates X-Topolo-Request-Id and X-Topolo-Client"
7056
+ ],
7057
+ content: workerTs(application, target, {
7058
+ audience: options.payload.audience,
7059
+ tenancy: options.payload.tenancy,
7060
+ profile: options.payload.profile,
7061
+ resources: options.payload.resources
7062
+ })
7063
+ }
7064
+ );
7065
+ if (target === "platform") {
7066
+ files.push(
7067
+ {
7068
+ path: `${apiBase}/src/service-identity.ts`,
7069
+ purpose: "Runtime service identity resolver for the API Worker.",
7070
+ requiredSignals: [
7071
+ "resolves the concrete Auth service id from Worker vars",
7072
+ "keeps the API Worker independent from browser-only import.meta usage"
7073
+ ],
7074
+ content: apiServiceIdentityTs(application)
7075
+ },
7076
+ {
7077
+ path: `${browserBase}/src/worker.ts`,
7078
+ purpose: "Browser Worker that serves assets and forwards API paths to the API Worker binding.",
7079
+ requiredSignals: [
7080
+ "keeps the frontend Worker thin",
7081
+ "routes /api/* to the API service binding instead of owning backend routes locally"
7082
+ ],
7083
+ content: webWorkerTs(application)
7084
+ },
7085
+ {
7086
+ path: `${browserBase}/wrangler.toml`,
7087
+ purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
7088
+ requiredSignals: [
7089
+ "serves Vite assets through a Worker with assets",
7090
+ "binds the frontend Worker to the API Worker for /api/* requests"
7091
+ ],
7092
+ content: platformWebWranglerToml(application)
7093
+ }
7094
+ );
7095
+ }
7096
+ }
7097
+ return files;
7098
+ }
6932
7099
  function topoloAppManifestJson(application, options) {
6933
7100
  const slug = toSlug(application.id);
6934
7101
  const resource = resourceConfig(application, options.profile);
@@ -6993,8 +7160,8 @@ function topoloAppManifestJson(application, options) {
6993
7160
  production: "https://auth.topolo.app"
6994
7161
  },
6995
7162
  NEXUS_GATEWAY_URL: {
6996
- staging: "https://nexus-api.stg.topolo.us",
6997
- production: "https://nexus-api.topolo.app"
7163
+ staging: "https://nexus.stg.topolo.us",
7164
+ production: "https://nexus.topolo.app"
6998
7165
  },
6999
7166
  NEXUS_APP_ID: slug
7000
7167
  },
@@ -7697,132 +7864,6 @@ function envExample(application) {
7697
7864
  ""
7698
7865
  ].join("\n");
7699
7866
  }
7700
- function normalizeTarget(value) {
7701
- if (!value) return "platform";
7702
- if (value === "platform" || value === "developer") return value;
7703
- throw new Error("Invalid scaffold target. Use platform or developer.");
7704
- }
7705
- function normalizePortfolio(value) {
7706
- if (!value) return "platform";
7707
- if (value === "platform" || value === "personal") return value;
7708
- throw new Error("Invalid scaffold portfolio. Use platform or personal.");
7709
- }
7710
- function normalizeAudience(value, portfolio) {
7711
- if (!value) {
7712
- return portfolio === "personal" ? "personal" : "business";
7713
- }
7714
- if (value === "business" || value === "personal") {
7715
- return value;
7716
- }
7717
- throw new Error("Invalid scaffold audience. Use business or personal.");
7718
- }
7719
- function normalizeTenancy(value, audience) {
7720
- const resolved = value ?? (audience === "personal" ? "household" : "organization");
7721
- if (resolved !== "organization" && resolved !== "household") {
7722
- throw new Error("Invalid scaffold tenancy. Use organization or household.");
7723
- }
7724
- if (audience === "business" && resolved !== "organization") {
7725
- throw new Error("Business scaffolds must use organization tenancy.");
7726
- }
7727
- if (audience === "personal" && resolved !== "household") {
7728
- throw new Error("Personal scaffolds must use household tenancy.");
7729
- }
7730
- return resolved;
7731
- }
7732
- function normalizeSurfaceModel(value) {
7733
- if (!value) return "web_app";
7734
- if (value === "web_app" || value === "mobile_container" || value === "mobile_module" || value === "messaging_surface") {
7735
- return value;
7736
- }
7737
- throw new Error(
7738
- "Invalid scaffold surface model. Use web_app, mobile_container, mobile_module, or messaging_surface."
7739
- );
7740
- }
7741
- function normalizeScaffoldProfile(value) {
7742
- if (!value) return null;
7743
- if (value === "spatial-assets") return value;
7744
- throw new Error("Invalid scaffold profile. Use spatial-assets.");
7745
- }
7746
- function resolveScaffoldResources(profile, options) {
7747
- const profileResources = profile === "spatial-assets" ? { d1: true, r2: true, queue: true, workflow: true, containerProcessing: true } : { d1: false, r2: false, queue: false, workflow: false, containerProcessing: false };
7748
- return {
7749
- d1: Boolean(profileResources.d1 || options.withD1),
7750
- r2: Boolean(profileResources.r2 || options.withR2),
7751
- queue: Boolean(profileResources.queue || options.withQueue),
7752
- workflow: Boolean(profileResources.workflow || options.withWorkflow),
7753
- containerProcessing: Boolean(profileResources.containerProcessing || options.withContainerProcessing)
7754
- };
7755
- }
7756
- function normalizeContainerAppId(value, surfaceModel) {
7757
- const trimmed = String(value || "").trim();
7758
- if (surfaceModel === "mobile_module") {
7759
- if (!trimmed) {
7760
- throw new Error("Mobile-module scaffolds require --container-app-id.");
7761
- }
7762
- return trimmed;
7763
- }
7764
- return trimmed || null;
7765
- }
7766
- function normalizeApplicationId(application) {
7767
- const raw = application.trim().toLowerCase();
7768
- const normalized = raw.replace(/-/g, "_");
7769
- if (Object.prototype.hasOwnProperty.call(APPLICATIONS, raw)) {
7770
- return raw;
7771
- }
7772
- if (Object.prototype.hasOwnProperty.call(APPLICATIONS, normalized)) {
7773
- return normalized;
7774
- }
7775
- if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(raw) || /^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/.test(raw)) {
7776
- return normalized;
7777
- }
7778
- throw new Error(
7779
- "Application id must be kebab-case or snake_case using lowercase letters, numbers, hyphens, and underscores."
7780
- );
7781
- }
7782
- function normalizeFirstPartyApplicationId(applicationId) {
7783
- if (applicationId.startsWith("topolo_") && applicationId.length > "topolo_".length) {
7784
- return applicationId.slice("topolo_".length);
7785
- }
7786
- return applicationId;
7787
- }
7788
- function toSlug(value) {
7789
- return value.replace(/_/g, "-");
7790
- }
7791
- function toPascalCase(value) {
7792
- const acronyms = /* @__PURE__ */ new Set(["api", "cli", "crm", "mcp", "mdm", "p2p"]);
7793
- return value.split(/[-_]/).filter(Boolean).map((part) => acronyms.has(part.toLowerCase()) ? part.toUpperCase() : part.slice(0, 1).toUpperCase() + part.slice(1)).join("");
7794
- }
7795
- function toTitle(value) {
7796
- return value.replace(/^topolo[-_\s]*/i, "Topolo ").split(/[-_\s]+/).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
7797
- }
7798
- function json(value) {
7799
- return JSON.stringify(value, null, 2);
7800
- }
7801
- async function pathExists(path4) {
7802
- try {
7803
- await access(path4, constants.F_OK);
7804
- return true;
7805
- } catch {
7806
- return false;
7807
- }
7808
- }
7809
- function resolveInside(root, filePath) {
7810
- const resolved = resolve(root, filePath);
7811
- const relativePath = relative(root, resolved);
7812
- if (relativePath.startsWith("..") || relativePath === "" || relativePath.startsWith("/")) {
7813
- throw new Error(`Scaffold file path escapes the target root: ${filePath}`);
7814
- }
7815
- return resolved;
7816
- }
7817
- function escapeHtml(value) {
7818
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
7819
- }
7820
- function escapeSingleQuoted(value) {
7821
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
7822
- }
7823
- function escapeFrontmatter(value) {
7824
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
7825
- }
7826
7867
 
7827
7868
  // src/commands/app-scaffold-provision.ts
7828
7869
  import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
@@ -9182,7 +9223,7 @@ function normalizeList(values, fallback = []) {
9182
9223
 
9183
9224
  // src/commands/integrations.ts
9184
9225
  import { readdir, readFile as readFile3, stat } from "fs/promises";
9185
- import { join as join4, relative as relative2 } from "path";
9226
+ import { join as join4, relative as relative3 } from "path";
9186
9227
  function registerIntegrationsCommand(program2) {
9187
9228
  const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
9188
9229
  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) => {
@@ -9347,7 +9388,7 @@ async function collectFiles(repoRoot) {
9347
9388
  const info = await stat(fullPath);
9348
9389
  if (info.size > 25e4) continue;
9349
9390
  result.push({
9350
- path: relative2(root, fullPath),
9391
+ path: relative3(root, fullPath),
9351
9392
  content: await readFile3(fullPath, "utf8")
9352
9393
  });
9353
9394
  }