@topolo/cli 0.1.30 → 0.1.32

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 (3) hide show
  1. package/README.md +9 -4
  2. package/dist/index.js +933 -805
  3. package/package.json +14 -14
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
  }
@@ -2693,7 +2712,6 @@ var CrmModule = class {
2693
2712
  constructor(client) {
2694
2713
  this.client = client;
2695
2714
  }
2696
- client;
2697
2715
  async listContacts(options = {}) {
2698
2716
  const res = await this.client.request({
2699
2717
  service: "crm",
@@ -2739,7 +2757,6 @@ var IdentityModule = class {
2739
2757
  constructor(client) {
2740
2758
  this.client = client;
2741
2759
  }
2742
- client;
2743
2760
  /** Returns the resolved user, organization, and permission set for the
2744
2761
  * current credential. Equivalent to `TopoloClient.introspect()` — exposed
2745
2762
  * on this module for symmetry with the other domain modules. */
@@ -3311,7 +3328,7 @@ async function pollUntilApproved(params) {
3311
3328
  throw new Error("Device code expired. Run `topolo auth login` again.");
3312
3329
  }
3313
3330
  function sleep(ms) {
3314
- return new Promise((resolve2) => setTimeout(resolve2, ms));
3331
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
3315
3332
  }
3316
3333
 
3317
3334
  // src/version.ts
@@ -3665,7 +3682,7 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
3665
3682
  function enrichApplicationAuditWithLocalEvidence(report, options = {}) {
3666
3683
  const repoRoot = resolveTopoloRepoRoot(options.repoRoot, options.cwd ?? process.cwd());
3667
3684
  if (!repoRoot) {
3668
- throw new Error("Could not locate the Topolo checkout. Pass --repo-root with the path that contains PlatformApplications.");
3685
+ throw new Error("Could not locate the Topolo checkout. Pass --repo-root with the path that contains apps/ and system-apps/.");
3669
3686
  }
3670
3687
  const applications = report.applications.map(
3671
3688
  (audit) => enrichApplicationAudit(audit, collectEvidence(repoRoot, audit.application))
@@ -3694,8 +3711,10 @@ function resolveTopoloRepoRoot(explicitRoot, cwd = process.cwd()) {
3694
3711
  }
3695
3712
  }
3696
3713
  function normalizeTopoloRoot(candidate) {
3697
- if (isDirectory(path.join(candidate, "PlatformApplications"))) return candidate;
3698
- if (path.basename(candidate) === "PlatformApplications" && isDirectory(path.join(candidate, "TopoloCli"))) {
3714
+ if (isDirectory(path.join(candidate, "apps")) && isDirectory(path.join(candidate, "system-apps"))) {
3715
+ return candidate;
3716
+ }
3717
+ if (path.basename(candidate) === "apps" && isDirectory(path.join(path.dirname(candidate), "system-apps"))) {
3699
3718
  return path.dirname(candidate);
3700
3719
  }
3701
3720
  return null;
@@ -4279,7 +4298,8 @@ function nativeDashboardWidgetEvidence(evidence) {
4279
4298
  return unique(items);
4280
4299
  }
4281
4300
  function collectEvidence(repoRoot, application) {
4282
- const appRoot = path.join(repoRoot, "PlatformApplications", application.appDir);
4301
+ const appRepoPath = applicationSourcePath(application.appDir);
4302
+ const appRoot = path.join(repoRoot, appRepoPath);
4283
4303
  const packageJsonPath = path.join(appRoot, "package.json");
4284
4304
  const packageJson2 = readPackageJson(packageJsonPath);
4285
4305
  const cloudcontrolPath = path.join(appRoot, "topolo.cloudcontrol.json");
@@ -4520,7 +4540,7 @@ function matchingSystems(repoRoot, application) {
4520
4540
  const docsRoot = topoloDocsRoot(repoRoot);
4521
4541
  const systemsRoot = path.join(docsRoot, "src", "content", "systems");
4522
4542
  if (!isDirectory(systemsRoot)) return [];
4523
- const appRepoPath = `PlatformApplications/${application.appDir}`;
4543
+ const appRepoPath = applicationSourcePath(application.appDir);
4524
4544
  return readdirSync(systemsRoot).filter((entry) => entry.endsWith(".json")).map((entry) => readJson(path.join(systemsRoot, entry))).filter((system) => Boolean(system)).filter((system) => {
4525
4545
  const repoPaths = [...system.repo_paths ?? [], ...system.repoPaths ?? []];
4526
4546
  return repoPaths.some(
@@ -4541,9 +4561,14 @@ function existingDocs(repoRoot, systems) {
4541
4561
  );
4542
4562
  }
4543
4563
  function topoloDocsRoot(repoRoot) {
4544
- const platformDocs = path.join(repoRoot, "PlatformApplications", "TopoloDocs");
4545
- if (isDirectory(platformDocs)) return platformDocs;
4546
- return path.join(repoRoot, "TopoloDocs");
4564
+ return path.join(repoRoot, "system-apps", "TopoloDocs", "apps", "web");
4565
+ }
4566
+ function applicationSourcePath(appDir) {
4567
+ if (appDir === "TopoloCli" || appDir === "TopoloCLI") return "topolo-platform/packages/topolo-cli";
4568
+ if (appDir === "TopoloDocs") return "system-apps/TopoloDocs";
4569
+ if (appDir === "TopoloAuth") return "topolo-platform/core/TopoloAuth";
4570
+ if (appDir === "TopoloCloudControl") return "topolo-platform/core/TopoloCloudControl";
4571
+ return `apps/${appDir}`;
4547
4572
  }
4548
4573
  function docExists(contentRoot, docPath) {
4549
4574
  return [
@@ -4671,10 +4696,12 @@ function unique(items) {
4671
4696
  return Array.from(new Set(items));
4672
4697
  }
4673
4698
  function relativeTopoloPath(filePath) {
4674
- const index = filePath.indexOf("PlatformApplications/");
4699
+ const index = filePath.indexOf("apps/");
4675
4700
  if (index >= 0) return filePath.slice(index);
4676
- const docsIndex = filePath.indexOf("TopoloDocs/");
4701
+ const docsIndex = filePath.indexOf("system-apps/TopoloDocs/");
4677
4702
  if (docsIndex >= 0) return filePath.slice(docsIndex);
4703
+ const platformIndex = filePath.indexOf("topolo-platform/");
4704
+ if (platformIndex >= 0) return filePath.slice(platformIndex);
4678
4705
  return filePath;
4679
4706
  }
4680
4707
  function isDirectory(filePath) {
@@ -4694,640 +4721,150 @@ function isFile(filePath) {
4694
4721
 
4695
4722
  // src/commands/app-scaffold.ts
4696
4723
  import { randomBytes } from "crypto";
4724
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
4725
+ import { dirname as dirname3, resolve as resolve2 } from "path";
4726
+
4727
+ // src/commands/app-scaffold-normalizers.ts
4697
4728
  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")}`;
4729
+ import { access } from "fs/promises";
4730
+ import { relative, resolve } from "path";
4731
+ function normalizeTarget(value) {
4732
+ if (!value) return "platform";
4733
+ if (value === "platform" || value === "developer") return value;
4734
+ throw new Error("Invalid scaffold target. Use platform or developer.");
4706
4735
  }
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
- };
4736
+ function normalizePortfolio(value) {
4737
+ if (!value) return "platform";
4738
+ if (value === "platform" || value === "personal") return value;
4739
+ throw new Error("Invalid scaffold portfolio. Use platform or personal.");
4796
4740
  }
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
- }
4741
+ function normalizeAudience(value, portfolio) {
4742
+ if (!value) {
4743
+ return portfolio === "personal" ? "personal" : "business";
4808
4744
  }
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
- );
4745
+ if (value === "business" || value === "personal") {
4746
+ return value;
4817
4747
  }
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
- });
4748
+ throw new Error("Invalid scaffold audience. Use business or personal.");
4749
+ }
4750
+ function normalizeTenancy(value, audience) {
4751
+ const resolved = value ?? (audience === "personal" ? "household" : "organization");
4752
+ if (resolved !== "organization" && resolved !== "household") {
4753
+ throw new Error("Invalid scaffold tenancy. Use organization or household.");
4827
4754
  }
4828
- return { root, files };
4755
+ if (audience === "business" && resolved !== "organization") {
4756
+ throw new Error("Business scaffolds must use organization tenancy.");
4757
+ }
4758
+ if (audience === "personal" && resolved !== "household") {
4759
+ throw new Error("Personal scaffolds must use household tenancy.");
4760
+ }
4761
+ return resolved;
4829
4762
  }
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");
4763
+ function normalizeSurfaceModel(value) {
4764
+ if (!value) return "web_app";
4765
+ if (value === "web_app" || value === "mobile_container" || value === "mobile_module" || value === "messaging_surface") {
4766
+ return value;
4835
4767
  }
4836
- return scopes;
4768
+ throw new Error(
4769
+ "Invalid scaffold surface model. Use web_app, mobile_container, mobile_module, or messaging_surface."
4770
+ );
4837
4771
  }
4838
- function scaffoldPackages(options) {
4839
- if (options.target === "developer") {
4840
- return options.browser ? ["@topolo/login"] : [];
4772
+ function normalizeScaffoldProfile(value) {
4773
+ if (!value) return null;
4774
+ if (value === "spatial-assets") return value;
4775
+ throw new Error("Invalid scaffold profile. Use spatial-assets.");
4776
+ }
4777
+ function resolveScaffoldResources(profile, options) {
4778
+ 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 };
4779
+ return {
4780
+ d1: Boolean(profileResources.d1 || options.withD1),
4781
+ r2: Boolean(profileResources.r2 || options.withR2),
4782
+ queue: Boolean(profileResources.queue || options.withQueue),
4783
+ workflow: Boolean(profileResources.workflow || options.withWorkflow),
4784
+ containerProcessing: Boolean(profileResources.containerProcessing || options.withContainerProcessing)
4785
+ };
4786
+ }
4787
+ function normalizeContainerAppId(value, surfaceModel) {
4788
+ const trimmed = String(value || "").trim();
4789
+ if (surfaceModel === "mobile_module") {
4790
+ if (!trimmed) {
4791
+ throw new Error("Mobile-module scaffolds require --container-app-id.");
4792
+ }
4793
+ return trimmed;
4841
4794
  }
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");
4795
+ return trimmed || null;
4796
+ }
4797
+ function normalizeApplicationId(application) {
4798
+ const raw = application.trim().toLowerCase();
4799
+ const normalized = raw.replace(/-/g, "_");
4800
+ if (Object.prototype.hasOwnProperty.call(APPLICATIONS, raw)) {
4801
+ return raw;
4849
4802
  }
4850
- if (options.api) {
4851
- packages.add("@topolo-io/worker-runtime");
4852
- packages.add("@topolo/sdk");
4803
+ if (Object.prototype.hasOwnProperty.call(APPLICATIONS, normalized)) {
4804
+ return normalized;
4853
4805
  }
4854
- return Array.from(packages);
4806
+ if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(raw) || /^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/.test(raw)) {
4807
+ return normalized;
4808
+ }
4809
+ throw new Error(
4810
+ "Application id must be kebab-case or snake_case using lowercase letters, numbers, hyphens, and underscores."
4811
+ );
4855
4812
  }
4856
- function scaffoldCapabilities(options) {
4857
- const capabilities = [];
4858
- if (options.browser) {
4859
- capabilities.push("login", "launcher");
4813
+ function normalizeFirstPartyApplicationId(applicationId) {
4814
+ if (applicationId.startsWith("topolo_") && applicationId.length > "topolo_".length) {
4815
+ return applicationId.slice("topolo_".length);
4860
4816
  }
4861
- if (options.distributionModel === "marketplace") capabilities.push("marketplace");
4862
- if (options.api) capabilities.push("api", "widget");
4863
- return capabilities;
4817
+ return applicationId;
4864
4818
  }
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}`;
4819
+ function toSlug(value) {
4820
+ return value.replace(/_/g, "-");
4870
4821
  }
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
- );
4822
+ function toConstantCase(value) {
4823
+ return value.split(/[-_]/).filter(Boolean).join("_").toUpperCase();
4824
+ }
4825
+ function toPascalCase(value) {
4826
+ const acronyms = /* @__PURE__ */ new Set(["api", "cli", "crm", "mcp", "mdm", "p2p"]);
4827
+ return value.split(/[-_]/).filter(Boolean).map((part) => acronyms.has(part.toLowerCase()) ? part.toUpperCase() : part.slice(0, 1).toUpperCase() + part.slice(1)).join("");
4828
+ }
4829
+ function toTitle(value) {
4830
+ return value.replace(/^topolo[-_\s]*/i, "Topolo ").split(/[-_\s]+/).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
4831
+ }
4832
+ function json(value) {
4833
+ return JSON.stringify(value, null, 2);
4834
+ }
4835
+ async function pathExists(path4) {
4836
+ try {
4837
+ await access(path4, constants.F_OK);
4838
+ return true;
4839
+ } catch {
4840
+ return false;
5118
4841
  }
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
4842
  }
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
- ];
4843
+ function resolveInside(root, filePath) {
4844
+ const resolved = resolve(root, filePath);
4845
+ const relativePath = relative(root, resolved);
4846
+ if (relativePath.startsWith("..") || relativePath === "" || relativePath.startsWith("/")) {
4847
+ throw new Error(`Scaffold file path escapes the target root: ${filePath}`);
5218
4848
  }
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
- ];
4849
+ return resolved;
5227
4850
  }
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");
4851
+ function escapeHtml(value) {
4852
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4853
+ }
4854
+ function escapeSingleQuoted(value) {
4855
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
4856
+ }
4857
+ function escapeFrontmatter(value) {
4858
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
5330
4859
  }
4860
+ function firstPartyAppPath(appDir, _audience) {
4861
+ if (appDir === "TopoloCli" || appDir === "TopoloCLI") return "topolo-platform/packages/topolo-cli";
4862
+ if (appDir === "TopoloDocs") return "system-apps/TopoloDocs";
4863
+ if (appDir === "TopoloAuth") return "topolo-platform/core/TopoloAuth";
4864
+ return `apps/${appDir}`;
4865
+ }
4866
+
4867
+ // src/commands/app-scaffold-package-template.ts
5331
4868
  function packageJson(application, target, options) {
5332
4869
  const dependencies = {};
5333
4870
  const devDependencies = {
@@ -5342,10 +4879,10 @@ function packageJson(application, target, options) {
5342
4879
  typecheck: "tsc --noEmit"
5343
4880
  };
5344
4881
  if (options.browser) {
5345
- dependencies.react = "^18.3.1";
5346
- dependencies["react-dom"] = "^18.3.1";
5347
- devDependencies["@types/react"] = "^18.3.3";
5348
- devDependencies["@types/react-dom"] = "^18.3.0";
4882
+ dependencies.react = "^19.2.0";
4883
+ dependencies["react-dom"] = "^19.2.0";
4884
+ devDependencies["@types/react"] = "^19.2.2";
4885
+ devDependencies["@types/react-dom"] = "^19.2.2";
5349
4886
  devDependencies["@vitejs/plugin-react"] = "^4.7.0";
5350
4887
  if (target === "platform") {
5351
4888
  dependencies["@topolo-io/auth-client"] = topoloPackageVersion("@topolo-io/auth-client", target);
@@ -5394,28 +4931,28 @@ function topoloPackageVersion(packageName, target) {
5394
4931
  if (target === "developer") {
5395
4932
  if (packageName === "@topolo/login") return "^0.1.0";
5396
4933
  if (packageName === "@topolo-io/auth-client") return "^1.0.0";
5397
- if (packageName === "@topolo/sdk") return "^0.4.1";
4934
+ if (packageName === "@topolo/sdk") return "^0.4.9";
5398
4935
  return "^0.1.0";
5399
4936
  }
5400
- const platformPackageFolders = {
5401
- "@topolo/sdk": "topolo-sdk",
5402
- "@topolo/login": "topolo-login",
5403
- "@topolo-io/app-funnel": "topolo-app-funnel",
5404
- "@topolo-io/app-launcher": "topolo-app-launcher",
5405
- "@topolo-io/app-shell": "topolo-app-shell",
5406
- "@topolo-io/auth-client": "topolo-auth-client",
5407
- "@topolo-io/auth-middleware": "topolo-auth-middleware",
5408
- "@topolo-io/worker-runtime": "topolo-worker-runtime",
5409
- "@topolo-io/design-tokens": "topolo-design-tokens",
5410
- "@topolo-io/onboarding": "topolo-onboarding",
5411
- "@topolo-io/tour": "topolo-tour",
5412
- "@topolo-io/ui-kit": "topolo-ui-kit"
4937
+ const platformPackageVersions = {
4938
+ "@topolo/sdk": "^0.4.9",
4939
+ "@topolo/login": "^0.1.0",
4940
+ "@topolo-io/app-funnel": "^0.1.6",
4941
+ "@topolo-io/app-launcher": "^0.1.15",
4942
+ "@topolo-io/app-shell": "^0.5.54",
4943
+ "@topolo-io/auth-client": "^1.2.1",
4944
+ "@topolo-io/auth-middleware": "^0.3.4",
4945
+ "@topolo-io/worker-runtime": "^0.1.11",
4946
+ "@topolo-io/design-tokens": "^0.1.8",
4947
+ "@topolo-io/onboarding": "^0.9.5",
4948
+ "@topolo-io/tour": "^0.3.4",
4949
+ "@topolo-io/ui-kit": "^0.4.233"
5413
4950
  };
5414
- const folder = platformPackageFolders[packageName];
5415
- if (!folder) {
4951
+ const version = platformPackageVersions[packageName];
4952
+ if (!version) {
5416
4953
  throw new Error(`Unknown Topolo platform package: ${packageName}`);
5417
4954
  }
5418
- return `file:../packages/${folder}`;
4955
+ return version;
5419
4956
  }
5420
4957
  function hasScaffoldResources(resources) {
5421
4958
  return resources.d1 || resources.r2 || resources.queue || resources.workflow || resources.containerProcessing;
@@ -5423,11 +4960,12 @@ function hasScaffoldResources(resources) {
5423
4960
  function resourceConfig(application, profile) {
5424
4961
  const slug = toSlug(application.id);
5425
4962
  const pascal = toPascalCase(application.id);
4963
+ const constant = toConstantCase(application.id);
5426
4964
  if (profile === "spatial-assets") {
5427
4965
  return {
5428
- d1Binding: "APP_DB",
5429
- d1ProductionName: `topolo-${slug}-production-db`,
5430
- d1StagingName: `topolo-${slug}-staging-db`,
4966
+ d1Binding: `${constant}_CORE_DB`,
4967
+ d1ProductionName: `topolo-${slug}-core-prod`,
4968
+ d1StagingName: `topolo-${slug}-core-staging`,
5431
4969
  r2Binding: "SPACES_ASSETS",
5432
4970
  r2ProductionName: `topolo-${slug}-assets`,
5433
4971
  r2StagingName: `topolo-${slug}-assets-staging`,
@@ -5443,9 +4981,9 @@ function resourceConfig(application, profile) {
5443
4981
  };
5444
4982
  }
5445
4983
  return {
5446
- d1Binding: "APP_DB",
5447
- d1ProductionName: `topolo-${slug}-prod`,
5448
- d1StagingName: `topolo-${slug}-staging`,
4984
+ d1Binding: `${constant}_CORE_DB`,
4985
+ d1ProductionName: `topolo-${slug}-core-prod`,
4986
+ d1StagingName: `topolo-${slug}-core-staging`,
5449
4987
  r2Binding: "APP_ASSETS",
5450
4988
  r2ProductionName: `topolo-${slug}-assets`,
5451
4989
  r2StagingName: `topolo-${slug}-assets-staging`,
@@ -5460,6 +4998,8 @@ function resourceConfig(application, profile) {
5460
4998
  containerStagingName: `topolo-${slug}-processor-staging`
5461
4999
  };
5462
5000
  }
5001
+
5002
+ // src/commands/app-scaffold-browser-template.ts
5463
5003
  function readme(application, target, options) {
5464
5004
  const displayName = toTitle(application.name);
5465
5005
  const slug = toSlug(application.id);
@@ -5541,6 +5081,67 @@ function readme(application, target, options) {
5541
5081
  return `${lines.join("\n")}
5542
5082
  `;
5543
5083
  }
5084
+ function agentRules(application, target, options) {
5085
+ const displayName = toTitle(application.name);
5086
+ if (target === "developer") {
5087
+ return [
5088
+ `# AGENTS.md - ${displayName}`,
5089
+ "",
5090
+ "These rules apply to this scaffolded Topolo developer application.",
5091
+ "",
5092
+ "## Scope First",
5093
+ "",
5094
+ "- Confirm whether a change belongs in this app, the public Topolo SDK/Login integration boundary, or the registered `topolo.app.json` manifest before editing.",
5095
+ "- Keep public developer scaffolds independent from private first-party Topolo packages.",
5096
+ "- Keep `topolo.app.json`, callback URLs, requested scopes, and README verification steps aligned with runtime behavior.",
5097
+ "",
5098
+ "## Implementation Rules",
5099
+ "",
5100
+ "- Make focused changes only; do not rewrite generated structure unless the task requires it.",
5101
+ "- For API-backed changes, validate bearer tokens through Topolo Auth and derive tenant context from the verified credential.",
5102
+ "- For browser changes, keep the OAuth authorization-code + PKCE flow intact and do not hardcode user, organization, or household ids.",
5103
+ "- Add or update tests for changed behavior before handoff.",
5104
+ "",
5105
+ "## Verification",
5106
+ "",
5107
+ "- Run `npm run typecheck` and `npm run build` before handoff.",
5108
+ ...options.api ? ["- Run Worker deploy dry-runs or the project deploy check after changing API bindings."] : [],
5109
+ "- Flag any required Topolo Developers registration or provisioning follow-up explicitly.",
5110
+ ""
5111
+ ].join("\n");
5112
+ }
5113
+ const portfolioLabel = options.portfolio === "personal" ? "personal" : "platform";
5114
+ const tenantLabel = options.tenancy === "household" ? "household" : "organization";
5115
+ return [
5116
+ `# AGENTS.md - ${displayName}`,
5117
+ "",
5118
+ "These rules apply to this first-party Topolo scaffold.",
5119
+ "",
5120
+ "## Canonical Ownership",
5121
+ "",
5122
+ "- The workspace-level `/Users/ashes/Projects/Topolo/AGENTS.md` rules still apply.",
5123
+ `- This app is a first-party ${portfolioLabel} app under \`apps/${application.appDir}\` with ${tenantLabel}-scoped product behavior.`,
5124
+ "- Put reusable UI in shared platform packages and reusable API/runtime behavior in shared platform packages; keep this app local only for app-specific product logic.",
5125
+ "- Keep `topolo.app.json`, `topolo.cloudcontrol.json`, TopoloDocs system/public/internal docs, and Auth service manifests aligned with behavior changes.",
5126
+ "- Keep `topolo.app.json` actions current for callable product operations and mark mutating, external-send, billing, export, destructive, and provisioning actions for confirmation.",
5127
+ "",
5128
+ "## Implementation Rules",
5129
+ "",
5130
+ "- Start from `staging`, preserve other agents' work, and do not revert unrelated local changes.",
5131
+ "- Make contained changes at the real ownership boundary; do not patch around shared runtime, auth, launcher, shell, or resource-context issues locally.",
5132
+ "- Browser code should stay on the shared landing/login/callback/AppShell baseline generated by the scaffold.",
5133
+ ...options.api ? ["- API code should stay on `@topolo-io/worker-runtime`, derive auth context from verified credentials, and preserve Nexus usage monitoring."] : [],
5134
+ ...hasScaffoldResources(options.resources) ? ["- Keep Cloudflare resource bindings in Wrangler, CloudControl, Env typing, docs, and README verification output in sync."] : [],
5135
+ "",
5136
+ "## Verification",
5137
+ "",
5138
+ "- Add or update tests for changed behavior; newly added code should be covered.",
5139
+ "- Run the most specific affected tests plus `npm run typecheck` and `npm run build` before handoff.",
5140
+ "- Run `npm run check:deploy-config` after resource binding changes when the script exists.",
5141
+ "- Deploy and verify staging before any production promotion.",
5142
+ ""
5143
+ ].join("\n");
5144
+ }
5544
5145
  function tsconfigJson(api, target) {
5545
5146
  const include = target === "platform" ? ["apps/web/src", ...api ? ["apps/api/src"] : []] : ["src"];
5546
5147
  return `${json({
@@ -5904,6 +5505,8 @@ function stylesCss() {
5904
5505
  ""
5905
5506
  ].join("\n");
5906
5507
  }
5508
+
5509
+ // src/commands/app-scaffold-app-template.ts
5907
5510
  function appTsx(application, target, options) {
5908
5511
  if (target === "developer") return developerAppTsx(application);
5909
5512
  return platformAppTsx(application, options);
@@ -6331,6 +5934,13 @@ function developerAppTsx(application) {
6331
5934
  ""
6332
5935
  ].join("\n");
6333
5936
  }
5937
+
5938
+ // src/commands/app-scaffold-cloudflare-constants.ts
5939
+ var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
5940
+ var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
5941
+ var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
5942
+
5943
+ // src/commands/app-scaffold-worker-template.ts
6334
5944
  function webWorkerTs(application) {
6335
5945
  const serviceName = toTitle(application.name);
6336
5946
  return [
@@ -6448,7 +6058,7 @@ function platformApiWranglerToml(application, resources, profile) {
6448
6058
  "",
6449
6059
  "[vars]",
6450
6060
  'AUTH_BASE_URL = "https://auth.topolo.app"',
6451
- 'NEXUS_GATEWAY_URL = "https://nexus-api.topolo.app"',
6061
+ 'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
6452
6062
  `NEXUS_APP_ID = "${slug}"`,
6453
6063
  `SERVICE_ID = "${application.serviceId ?? ""}"`,
6454
6064
  `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
@@ -6463,7 +6073,7 @@ function platformApiWranglerToml(application, resources, profile) {
6463
6073
  "",
6464
6074
  "[env.staging.vars]",
6465
6075
  'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
6466
- 'NEXUS_GATEWAY_URL = "https://nexus-api.stg.topolo.us"',
6076
+ 'NEXUS_GATEWAY_URL = "https://nexus.stg.topolo.us"',
6467
6077
  `NEXUS_APP_ID = "${slug}"`,
6468
6078
  `SERVICE_ID = "${application.serviceId ?? ""}"`,
6469
6079
  `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
@@ -6478,7 +6088,7 @@ function platformApiWranglerToml(application, resources, profile) {
6478
6088
  "",
6479
6089
  "[env.production.vars]",
6480
6090
  'AUTH_BASE_URL = "https://auth.topolo.app"',
6481
- 'NEXUS_GATEWAY_URL = "https://nexus-api.topolo.app"',
6091
+ 'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
6482
6092
  `NEXUS_APP_ID = "${slug}"`,
6483
6093
  `SERVICE_ID = "${application.serviceId ?? ""}"`,
6484
6094
  `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
@@ -6574,7 +6184,7 @@ function wranglerToml(application, target, resources, profile) {
6574
6184
  "",
6575
6185
  "[vars]",
6576
6186
  'AUTH_BASE_URL = "https://auth.topolo.app"',
6577
- 'NEXUS_GATEWAY_URL = "https://nexus-api.topolo.app"',
6187
+ 'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
6578
6188
  runtimeIdentityVar,
6579
6189
  ""
6580
6190
  );
@@ -6629,7 +6239,7 @@ function wranglerToml(application, target, resources, profile) {
6629
6239
  "",
6630
6240
  "[env.staging.vars]",
6631
6241
  'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
6632
- 'NEXUS_GATEWAY_URL = "https://nexus-api.stg.topolo.us"',
6242
+ 'NEXUS_GATEWAY_URL = "https://nexus.stg.topolo.us"',
6633
6243
  runtimeIdentityVar,
6634
6244
  ""
6635
6245
  );
@@ -6929,6 +6539,650 @@ function developerWorkerTs(application) {
6929
6539
  ""
6930
6540
  ].join("\n");
6931
6541
  }
6542
+
6543
+ // src/commands/app-scaffold-workflows.ts
6544
+ function scaffoldCommands(options) {
6545
+ const scaffoldId = toSlug(options.id);
6546
+ if (options.target === "developer") {
6547
+ const audienceFlags = options.audience === "personal" ? " --audience personal --tenancy household" : "";
6548
+ return [
6549
+ `topolo apps scaffold ${scaffoldId}${audienceFlags} --write --repo-root .`,
6550
+ `cd topolo-${scaffoldId}`,
6551
+ "npm install",
6552
+ "npm run typecheck",
6553
+ "npm run build"
6554
+ ];
6555
+ }
6556
+ const portfolioFlags = options.portfolio === "personal" ? " --portfolio personal" : "";
6557
+ const profileFlags = options.profile ? ` --profile ${options.profile}` : "";
6558
+ return [
6559
+ `topolo platform apps scaffold ${scaffoldId}${portfolioFlags}${profileFlags} --write --provision --repo-root /Users/ashes/Projects/Topolo --json`,
6560
+ "topolo apps requirements --json",
6561
+ `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo --fail-on needs_review --json`,
6562
+ `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/Topolo --fail-on partial --json`
6563
+ ];
6564
+ }
6565
+ function topoloConformanceWorkflow(options) {
6566
+ const appPath = firstPartyAppPath(options.appDir, options.audience);
6567
+ const docsCheckoutStep = options.appDir === "TopoloDocs" ? [] : [
6568
+ "",
6569
+ " - name: Checkout TopoloDocs",
6570
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6571
+ " uses: actions/checkout@v4",
6572
+ " with:",
6573
+ " repository: Topolo-io/TopoloDocs",
6574
+ " path: system-apps/TopoloDocs",
6575
+ " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
6576
+ ];
6577
+ const cliCheckoutStep = options.appDir === "TopoloCLI" || options.appDir === "TopoloCli" ? [] : [
6578
+ "",
6579
+ " - name: Checkout Topolo CLI",
6580
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6581
+ " uses: actions/checkout@v4",
6582
+ " with:",
6583
+ " repository: Topolo-io/TopoloCli",
6584
+ " path: topolo-platform/packages/topolo-cli",
6585
+ " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
6586
+ ];
6587
+ return [
6588
+ "name: Topolo App Conformance",
6589
+ "",
6590
+ "on:",
6591
+ " pull_request:",
6592
+ " push:",
6593
+ " branches:",
6594
+ " - main",
6595
+ " workflow_dispatch:",
6596
+ "",
6597
+ "permissions:",
6598
+ " contents: read",
6599
+ "",
6600
+ "jobs:",
6601
+ " conformance:",
6602
+ " runs-on: ubuntu-latest",
6603
+ " env:",
6604
+ ` HAS_TOPOLO_PACKAGES_APP: \${{ secrets.TOPOLO_PACKAGES_APP_ID != '' && secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY != '' }}`,
6605
+ ` HAS_TOPOLO_CI_REPO_TOKEN: \${{ secrets.TOPOLO_CI_REPO_TOKEN != '' }}`,
6606
+ " steps:",
6607
+ " - name: Checkout application",
6608
+ " uses: actions/checkout@v4",
6609
+ " with:",
6610
+ ` path: ${appPath}`,
6611
+ "",
6612
+ " - name: Mint Topolo cross-repo token",
6613
+ " id: app-token",
6614
+ ` if: env.HAS_TOPOLO_PACKAGES_APP == 'true'`,
6615
+ " uses: actions/create-github-app-token@v1",
6616
+ " with:",
6617
+ ` app-id: \${{ secrets.TOPOLO_PACKAGES_APP_ID }}`,
6618
+ ` private-key: \${{ secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY }}`,
6619
+ " owner: Topolo-io",
6620
+ ...docsCheckoutStep,
6621
+ "",
6622
+ " - name: Checkout Topolo SDK",
6623
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6624
+ " uses: actions/checkout@v4",
6625
+ " with:",
6626
+ " repository: Topolo-io/topolo-sdk",
6627
+ " path: topolo-platform/packages/topolo-sdk",
6628
+ " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}",
6629
+ ...cliCheckoutStep,
6630
+ "",
6631
+ " - name: Skip conformance without cross-repo token",
6632
+ ` if: steps.app-token.outputs.token == '' && env.HAS_TOPOLO_CI_REPO_TOKEN != 'true'`,
6633
+ " run: |",
6634
+ ' echo "::warning::Skipping Topolo app conformance because no Topolo cross-repo token is available to this workflow context."',
6635
+ "",
6636
+ " - name: Setup Node.js",
6637
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6638
+ " uses: actions/setup-node@v4",
6639
+ " with:",
6640
+ " node-version: 20",
6641
+ "",
6642
+ " - name: Build Topolo SDK",
6643
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6644
+ " working-directory: topolo-platform/packages/topolo-sdk",
6645
+ " run: |",
6646
+ " npm install --no-audit --no-fund",
6647
+ " npm run build",
6648
+ "",
6649
+ " - name: Build Topolo CLI",
6650
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6651
+ " working-directory: topolo-platform/packages/topolo-cli",
6652
+ " run: |",
6653
+ " npm install --no-audit --no-fund",
6654
+ " npm run build",
6655
+ "",
6656
+ " - name: Run Topolo app conformance audit",
6657
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
6658
+ " run: |",
6659
+ ` node topolo-platform/packages/topolo-cli/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`,
6660
+ "",
6661
+ " - name: Record green CI evidence",
6662
+ ` if: \${{ success() && (steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true') }}`,
6663
+ ` working-directory: ${appPath}`,
6664
+ " run: |",
6665
+ ` node "$GITHUB_WORKSPACE/topolo-platform/packages/topolo-cli/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"`
6666
+ ].join("\n");
6667
+ }
6668
+
6669
+ // src/commands/app-scaffold.ts
6670
+ var CURRENT_VERIFICATION_DATE = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6671
+ function generateOpaqueServiceId() {
6672
+ return `srv_${randomBytes(9).toString("base64url")}`;
6673
+ }
6674
+ function applicationScaffoldPayload(application, options = {}) {
6675
+ const target = normalizeTarget(options.target);
6676
+ const portfolio = target === "platform" ? normalizePortfolio(options.portfolio) : null;
6677
+ const requestedId = normalizeApplicationId(application);
6678
+ const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
6679
+ const slug = toSlug(id);
6680
+ const catalogApplication = APPLICATIONS[id];
6681
+ const browser = options.browser !== false;
6682
+ const api = options.api ?? target === "platform";
6683
+ const audience = normalizeAudience(
6684
+ options.audience,
6685
+ target === "platform" ? portfolio : null
6686
+ );
6687
+ const tenancy = normalizeTenancy(options.tenancy, audience);
6688
+ const surfaceModel = normalizeSurfaceModel(options.surfaceModel);
6689
+ const containerAppId = normalizeContainerAppId(options.containerAppId, surfaceModel);
6690
+ const profile = normalizeScaffoldProfile(options.profile);
6691
+ const resources = resolveScaffoldResources(profile, options);
6692
+ const ownerType = target === "platform" ? "topolo_first_party" : "third_party";
6693
+ const distributionModel = target === "platform" ? "marketplace" : "organization_internal";
6694
+ const appDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
6695
+ const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
6696
+ const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${slug}`;
6697
+ const serviceSlug = api ? target === "platform" ? `topolo-${slug}` : id : null;
6698
+ const defaultServiceId = target === "platform" ? generateOpaqueServiceId() : catalogApplication?.services[0] ?? id;
6699
+ const serviceId = api ? options.service ?? defaultServiceId : null;
6700
+ const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${slug}.topolo.app` : null;
6701
+ const scopes = scaffoldScopes({ browser, api });
6702
+ const capabilities = scaffoldCapabilities({ browser, api, distributionModel });
6703
+ const ciWorkflowPath = target === "platform" ? ".github/workflows/topolo-conformance.yml" : null;
6704
+ const ciWorkflow = target === "platform" ? topoloConformanceWorkflow({ id, appDir, audience }) : null;
6705
+ const applicationPayload = {
6706
+ id,
6707
+ name,
6708
+ appDir,
6709
+ packageName,
6710
+ productionUrl,
6711
+ serviceSlug,
6712
+ serviceId
6713
+ };
6714
+ return {
6715
+ version: APPLICATION_REQUIREMENTS_VERSION,
6716
+ target,
6717
+ ownerType,
6718
+ portfolio,
6719
+ audience,
6720
+ tenancy,
6721
+ surfaceModel,
6722
+ containerAppId,
6723
+ profile,
6724
+ resources,
6725
+ distributionModel,
6726
+ capabilities,
6727
+ application: applicationPayload,
6728
+ scopes,
6729
+ requirements: APPLICATION_REQUIREMENTS.filter(
6730
+ (requirement) => requirement.appliesTo.some((scope) => scopes.includes(scope))
6731
+ ),
6732
+ packages: scaffoldPackages({ browser, api, target }),
6733
+ files: scaffoldFiles({
6734
+ payload: {
6735
+ ownerType,
6736
+ portfolio,
6737
+ audience,
6738
+ tenancy,
6739
+ surfaceModel,
6740
+ containerAppId,
6741
+ profile,
6742
+ resources,
6743
+ distributionModel,
6744
+ capabilities
6745
+ },
6746
+ application: applicationPayload,
6747
+ target,
6748
+ browser,
6749
+ api,
6750
+ ciWorkflow
6751
+ }),
6752
+ commands: scaffoldCommands({
6753
+ id,
6754
+ target,
6755
+ audience,
6756
+ tenancy,
6757
+ portfolio,
6758
+ profile
6759
+ }),
6760
+ ciWorkflowPath,
6761
+ ciWorkflow
6762
+ };
6763
+ }
6764
+ async function writeApplicationScaffold(payload, options = {}) {
6765
+ const root = resolve2(options.repoRoot ?? process.cwd());
6766
+ const plannedFiles = payload.files.map((file) => ({
6767
+ file,
6768
+ absolutePath: resolveInside(root, file.path)
6769
+ }));
6770
+ const existingFiles = [];
6771
+ for (const planned of plannedFiles) {
6772
+ if (await pathExists(planned.absolutePath)) {
6773
+ existingFiles.push(planned.file.path);
6774
+ }
6775
+ }
6776
+ if (existingFiles.length > 0 && !options.force) {
6777
+ throw new Error(
6778
+ [
6779
+ "Refusing to overwrite existing scaffold files:",
6780
+ ...existingFiles.map((filePath) => `- ${filePath}`),
6781
+ "Pass --force to overwrite these files."
6782
+ ].join("\n")
6783
+ );
6784
+ }
6785
+ const files = [];
6786
+ for (const planned of plannedFiles) {
6787
+ const existed = await pathExists(planned.absolutePath);
6788
+ await mkdir2(dirname3(planned.absolutePath), { recursive: true });
6789
+ await writeFile2(planned.absolutePath, planned.file.content, "utf8");
6790
+ files.push({
6791
+ path: planned.file.path,
6792
+ status: existed ? "overwritten" : "created"
6793
+ });
6794
+ }
6795
+ return { root, files };
6796
+ }
6797
+ function scaffoldScopes(options) {
6798
+ const scopes = ["all"];
6799
+ if (options.browser) scopes.push("browser");
6800
+ if (options.api) {
6801
+ scopes.push("api", "agent_surface");
6802
+ }
6803
+ return scopes;
6804
+ }
6805
+ function scaffoldPackages(options) {
6806
+ if (options.target === "developer") {
6807
+ return options.browser ? ["@topolo/login"] : [];
6808
+ }
6809
+ const packages = /* @__PURE__ */ new Set();
6810
+ if (options.browser) {
6811
+ packages.add("@topolo-io/app-funnel");
6812
+ packages.add("@topolo-io/app-launcher");
6813
+ packages.add("@topolo-io/app-shell");
6814
+ packages.add("@topolo-io/auth-client");
6815
+ packages.add("@topolo-io/design-tokens");
6816
+ packages.add("@topolo-io/onboarding");
6817
+ packages.add("@topolo-io/tour");
6818
+ packages.add("@topolo-io/ui-kit");
6819
+ }
6820
+ if (options.api) {
6821
+ packages.add("@topolo-io/worker-runtime");
6822
+ packages.add("@topolo/sdk");
6823
+ }
6824
+ return Array.from(packages);
6825
+ }
6826
+ function scaffoldCapabilities(options) {
6827
+ const capabilities = [];
6828
+ if (options.browser) {
6829
+ capabilities.push("login", "launcher");
6830
+ }
6831
+ if (options.distributionModel === "marketplace") capabilities.push("marketplace");
6832
+ if (options.api) capabilities.push("api", "widget");
6833
+ return capabilities;
6834
+ }
6835
+ function scaffoldFiles(options) {
6836
+ const { application, target } = options;
6837
+ const slug = toSlug(application.id);
6838
+ const base = target === "platform" ? firstPartyAppPath(application.appDir, options.payload.audience) : application.appDir;
6839
+ const browserBase = target === "platform" ? `${base}/apps/web` : base;
6840
+ const apiBase = target === "platform" ? `${base}/apps/api` : base;
6841
+ const files = [
6842
+ {
6843
+ path: `${base}/package.json`,
6844
+ purpose: "Package metadata and verification commands.",
6845
+ requiredSignals: [
6846
+ "name matches the packageName in the SDK application catalog",
6847
+ "scripts include build plus typecheck where the stack supports them"
6848
+ ],
6849
+ content: packageJson(application, target, {
6850
+ browser: options.browser,
6851
+ api: options.api,
6852
+ resources: options.payload.resources
6853
+ })
6854
+ },
6855
+ {
6856
+ path: `${base}/README.md`,
6857
+ purpose: "Starter README with install, run, and verification commands.",
6858
+ requiredSignals: [
6859
+ "documents the generated app target",
6860
+ "points maintainers to the shared Topolo packages used by the scaffold"
6861
+ ],
6862
+ content: readme(application, target, {
6863
+ api: options.api,
6864
+ audience: options.payload.audience,
6865
+ tenancy: options.payload.tenancy,
6866
+ portfolio: options.payload.portfolio,
6867
+ profile: options.payload.profile,
6868
+ resources: options.payload.resources
6869
+ })
6870
+ },
6871
+ {
6872
+ path: `${base}/.gitignore`,
6873
+ purpose: "Generated-app ignore rules.",
6874
+ requiredSignals: [
6875
+ "ignores dependency installs and build output",
6876
+ "keeps local environment files out of source control"
6877
+ ],
6878
+ content: gitignore()
6879
+ },
6880
+ {
6881
+ path: `${base}/AGENTS.md`,
6882
+ purpose: "Local agent rules for safe work inside the generated project.",
6883
+ requiredSignals: [
6884
+ "states the app-specific ownership boundary",
6885
+ "keeps future agents aligned with Topolo docs, manifest, testing, and staging expectations"
6886
+ ],
6887
+ content: agentRules(application, target, {
6888
+ api: options.api,
6889
+ audience: options.payload.audience,
6890
+ tenancy: options.payload.tenancy,
6891
+ portfolio: options.payload.portfolio,
6892
+ resources: options.payload.resources
6893
+ })
6894
+ },
6895
+ {
6896
+ path: `${base}/tsconfig.json`,
6897
+ purpose: "TypeScript configuration for the generated app.",
6898
+ requiredSignals: [
6899
+ "uses strict mode",
6900
+ "includes browser DOM types for React and Worker types for API-backed apps"
6901
+ ],
6902
+ content: tsconfigJson(options.api, target)
6903
+ },
6904
+ {
6905
+ path: `${browserBase}/vite.config.ts`,
6906
+ purpose: "Vite browser build configuration.",
6907
+ requiredSignals: [
6908
+ "builds the authenticated browser surface",
6909
+ "keeps generated apps on the standard React/Vite baseline"
6910
+ ],
6911
+ content: viteConfig(target)
6912
+ },
6913
+ {
6914
+ path: `${browserBase}/index.html`,
6915
+ purpose: "Browser entry document.",
6916
+ requiredSignals: [
6917
+ "mounts the React app at #root",
6918
+ "uses the generated application display name"
6919
+ ],
6920
+ content: indexHtml(application.name)
6921
+ },
6922
+ {
6923
+ path: `${browserBase}/src/main.tsx`,
6924
+ purpose: "React app entrypoint.",
6925
+ requiredSignals: [
6926
+ "renders the generated app component",
6927
+ "imports the generated app stylesheet"
6928
+ ],
6929
+ content: mainTsx()
6930
+ },
6931
+ {
6932
+ path: `${browserBase}/src/env.d.ts`,
6933
+ purpose: "Vite environment variable typing.",
6934
+ requiredSignals: [
6935
+ "loads vite/client import.meta.env types",
6936
+ "declares the Topolo environment variables consumed by the starter"
6937
+ ],
6938
+ content: envDts(options.api)
6939
+ },
6940
+ ...options.payload.resources.workflow ? [
6941
+ {
6942
+ path: `${base}/src/cloudflare-workers.d.ts`,
6943
+ purpose: "Minimal Cloudflare Workflow entrypoint typing for scaffolded Workers.",
6944
+ requiredSignals: [
6945
+ "declares cloudflare:workers WorkflowEntrypoint for TypeScript",
6946
+ "keeps Workflow imports deployable without adding a new dependency"
6947
+ ],
6948
+ content: cloudflareWorkersDts()
6949
+ }
6950
+ ] : [],
6951
+ {
6952
+ path: `${browserBase}/src/styles.css`,
6953
+ purpose: "Minimal app-local styles outside the shared Topolo shell.",
6954
+ requiredSignals: [
6955
+ "does not fork the shared shell chrome",
6956
+ "keeps app-specific layout scoped under generated classes"
6957
+ ],
6958
+ content: stylesCss()
6959
+ }
6960
+ ];
6961
+ if (target === "platform") {
6962
+ files.push(
6963
+ {
6964
+ path: `${base}/topolo.app.json`,
6965
+ purpose: "Canonical app manifest that drives generated Wrangler and CloudControl metadata.",
6966
+ requiredSignals: [
6967
+ "declares web/api targets with canonical names and app-local target directories",
6968
+ "keeps Cloudflare account, domain, service id, and bindings in one manifest source of truth"
6969
+ ],
6970
+ content: topoloAppManifestJson(application, {
6971
+ api: options.api,
6972
+ resources: options.payload.resources,
6973
+ profile: options.payload.profile
6974
+ })
6975
+ },
6976
+ {
6977
+ path: `${base}/topolo.cloudcontrol.json`,
6978
+ purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
6979
+ requiredSignals: [
6980
+ "production URLs and Cloudflare targets are explicit",
6981
+ "resource bindings and required secrets are declared without secret values"
6982
+ ],
6983
+ content: cloudcontrolJson(application, {
6984
+ api: options.api,
6985
+ audience: options.payload.audience,
6986
+ profile: options.payload.profile,
6987
+ resources: options.payload.resources
6988
+ })
6989
+ },
6990
+ {
6991
+ path: `${base}/.github/workflows/topolo-conformance.yml`,
6992
+ purpose: "PR/push gate that runs the local Topolo app audit with docs evidence.",
6993
+ requiredSignals: [
6994
+ "checks out this repo under its workspace app directory",
6995
+ "checks out TopoloDocs under system-apps/TopoloDocs",
6996
+ "checks out topolo-sdk and TopoloCli from main so the gate does not wait for an npm publish",
6997
+ "runs topolo apps audit with --local and --fail-on needs_review"
6998
+ ],
6999
+ content: options.ciWorkflow ?? ""
7000
+ },
7001
+ {
7002
+ path: `system-apps/TopoloDocs/apps/web/src/content/systems/topolo-${slug}.json`,
7003
+ purpose: "Canonical system registry entry.",
7004
+ requiredSignals: [
7005
+ `repo_paths includes ${base}`,
7006
+ "doc_paths includes internal handbook coverage",
7007
+ "security_assurance records the auth, tenancy, deployment, and verification boundary"
7008
+ ],
7009
+ content: systemJson(application, {
7010
+ api: options.api,
7011
+ audience: options.payload.audience,
7012
+ tenancy: options.payload.tenancy,
7013
+ portfolio: options.payload.portfolio,
7014
+ profile: options.payload.profile,
7015
+ resources: options.payload.resources
7016
+ })
7017
+ },
7018
+ {
7019
+ path: `system-apps/TopoloDocs/apps/web/src/content/internal/apps/${slug}.mdx`,
7020
+ purpose: "Internal operator handbook.",
7021
+ requiredSignals: [
7022
+ "documents ownership, routes, auth boundary, deployment, operations, and verification",
7023
+ "last_verified is updated when behavior changes"
7024
+ ],
7025
+ content: internalDocs(application, {
7026
+ api: options.api,
7027
+ audience: options.payload.audience,
7028
+ tenancy: options.payload.tenancy,
7029
+ portfolio: options.payload.portfolio,
7030
+ profile: options.payload.profile,
7031
+ resources: options.payload.resources
7032
+ })
7033
+ },
7034
+ {
7035
+ path: `system-apps/TopoloDocs/apps/web/src/content/public/applications/${slug}.mdx`,
7036
+ purpose: "Public application overview.",
7037
+ requiredSignals: [
7038
+ "documents the product surface without internal-only details",
7039
+ "links back to the canonical system registry entry"
7040
+ ],
7041
+ content: publicDocs(application, {
7042
+ api: options.api,
7043
+ audience: options.payload.audience,
7044
+ tenancy: options.payload.tenancy,
7045
+ portfolio: options.payload.portfolio,
7046
+ profile: options.payload.profile,
7047
+ resources: options.payload.resources
7048
+ })
7049
+ }
7050
+ );
7051
+ if (options.api && application.serviceId) {
7052
+ files.push(
7053
+ {
7054
+ path: `topolo-platform/core/TopoloAuth/catalog/service-manifests/${application.serviceId}.json`,
7055
+ purpose: "Auth service manifest for first-party permissions, role bundles, and API-key scopes.",
7056
+ requiredSignals: [
7057
+ "serviceId matches the generated Worker service id",
7058
+ "permissions, role bundles, and API-key scopes exist before the app is catalog-visible",
7059
+ "systems metadata maps the service back to the generated TopoloDocs entries"
7060
+ ],
7061
+ content: serviceManifestJson(application, {
7062
+ audience: options.payload.audience,
7063
+ tenancy: options.payload.tenancy
7064
+ })
7065
+ }
7066
+ );
7067
+ }
7068
+ } else {
7069
+ files.push(
7070
+ {
7071
+ path: `${base}/topolo.app.json`,
7072
+ purpose: "Third-party Topolo application manifest.",
7073
+ requiredSignals: [
7074
+ "declares the external app id, callback URLs, and requested platform scopes",
7075
+ "does not include internal Topolo CloudControl or canonical-docs ownership"
7076
+ ],
7077
+ content: developerManifest(application, {
7078
+ api: options.api,
7079
+ audience: options.payload.audience,
7080
+ tenancy: options.payload.tenancy,
7081
+ surfaceModel: options.payload.surfaceModel,
7082
+ containerAppId: options.payload.containerAppId,
7083
+ distributionModel: options.payload.distributionModel,
7084
+ capabilities: options.payload.capabilities
7085
+ })
7086
+ },
7087
+ {
7088
+ path: `${base}/.env.example`,
7089
+ purpose: "Developer environment template for local auth configuration.",
7090
+ requiredSignals: [
7091
+ "names the Topolo Auth URL",
7092
+ "keeps OAuth client ids and service ids out of source control by default"
7093
+ ],
7094
+ content: envExample(application)
7095
+ }
7096
+ );
7097
+ }
7098
+ if (options.browser) {
7099
+ if (target === "platform") {
7100
+ files.push({
7101
+ path: `${browserBase}/src/service-identity.ts`,
7102
+ purpose: "Central service identity resolver shared by browser and Worker runtime surfaces.",
7103
+ requiredSignals: [
7104
+ "exports the generated app id, display name, and stable Auth service slug",
7105
+ "requires the provisioned Auth service id from the Worker deployment config"
7106
+ ],
7107
+ content: serviceIdentityTs(application)
7108
+ });
7109
+ }
7110
+ files.push({
7111
+ path: `${browserBase}/src/App.tsx`,
7112
+ purpose: "Authenticated browser surface.",
7113
+ requiredSignals: [
7114
+ "bootstraps routes, auth provider, and shared shell through @topolo-io/app-shell",
7115
+ "uses @topolo-io/auth-client through the app-shell wrapper for app-origin auth flows",
7116
+ "keeps launcher and account-menu behavior on the shared platform surface",
7117
+ "passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
7118
+ ],
7119
+ content: appTsx(application, target, {
7120
+ audience: options.payload.audience,
7121
+ portfolio: options.payload.portfolio
7122
+ })
7123
+ });
7124
+ }
7125
+ if (options.api) {
7126
+ files.push(
7127
+ {
7128
+ path: `${apiBase}/wrangler.toml`,
7129
+ purpose: "Cloudflare Worker runtime configuration.",
7130
+ requiredSignals: [
7131
+ "bindings match topolo.cloudcontrol.json or topolo.app.json",
7132
+ "secrets are referenced by name only"
7133
+ ],
7134
+ content: target === "platform" ? platformApiWranglerToml(application, options.payload.resources, options.payload.profile) : wranglerToml(application, target, options.payload.resources, options.payload.profile)
7135
+ },
7136
+ {
7137
+ path: `${apiBase}/src/worker.ts`,
7138
+ purpose: "Protected service boundary.",
7139
+ requiredSignals: [
7140
+ "uses @topolo-io/worker-runtime for Worker auth, gatewaying, headers, and request helpers",
7141
+ "derives organization from the credential rather than client-supplied orgId",
7142
+ "emits or propagates X-Topolo-Request-Id and X-Topolo-Client"
7143
+ ],
7144
+ content: workerTs(application, target, {
7145
+ audience: options.payload.audience,
7146
+ tenancy: options.payload.tenancy,
7147
+ profile: options.payload.profile,
7148
+ resources: options.payload.resources
7149
+ })
7150
+ }
7151
+ );
7152
+ if (target === "platform") {
7153
+ files.push(
7154
+ {
7155
+ path: `${apiBase}/src/service-identity.ts`,
7156
+ purpose: "Runtime service identity resolver for the API Worker.",
7157
+ requiredSignals: [
7158
+ "resolves the concrete Auth service id from Worker vars",
7159
+ "keeps the API Worker independent from browser-only import.meta usage"
7160
+ ],
7161
+ content: apiServiceIdentityTs(application)
7162
+ },
7163
+ {
7164
+ path: `${browserBase}/src/worker.ts`,
7165
+ purpose: "Browser Worker that serves assets and forwards API paths to the API Worker binding.",
7166
+ requiredSignals: [
7167
+ "keeps the frontend Worker thin",
7168
+ "routes /api/* to the API service binding instead of owning backend routes locally"
7169
+ ],
7170
+ content: webWorkerTs(application)
7171
+ },
7172
+ {
7173
+ path: `${browserBase}/wrangler.toml`,
7174
+ purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
7175
+ requiredSignals: [
7176
+ "serves Vite assets through a Worker with assets",
7177
+ "binds the frontend Worker to the API Worker for /api/* requests"
7178
+ ],
7179
+ content: platformWebWranglerToml(application)
7180
+ }
7181
+ );
7182
+ }
7183
+ }
7184
+ return files;
7185
+ }
6932
7186
  function topoloAppManifestJson(application, options) {
6933
7187
  const slug = toSlug(application.id);
6934
7188
  const resource = resourceConfig(application, options.profile);
@@ -6993,8 +7247,8 @@ function topoloAppManifestJson(application, options) {
6993
7247
  production: "https://auth.topolo.app"
6994
7248
  },
6995
7249
  NEXUS_GATEWAY_URL: {
6996
- staging: "https://nexus-api.stg.topolo.us",
6997
- production: "https://nexus-api.topolo.app"
7250
+ staging: "https://nexus.stg.topolo.us",
7251
+ production: "https://nexus.topolo.app"
6998
7252
  },
6999
7253
  NEXUS_APP_ID: slug
7000
7254
  },
@@ -7245,7 +7499,7 @@ function cloudcontrolJson(application, options) {
7245
7499
  ];
7246
7500
  return `${json({
7247
7501
  name: `topolo-${slug}`,
7248
- description: `Canonical documentation for ${toTitle(application.name)} lives in Apps/system/TopoloDocs.`,
7502
+ description: `Canonical documentation for ${toTitle(application.name)} lives in system-apps/TopoloDocs.`,
7249
7503
  deploy_method: "wrangler",
7250
7504
  ci_provider: "github_actions",
7251
7505
  repo_identity: {
@@ -7396,7 +7650,7 @@ function internalDocs(application, options) {
7396
7650
  `last_verified: "${CURRENT_VERIFICATION_DATE}"`,
7397
7651
  "source_repos:",
7398
7652
  ` - "${appPath}"`,
7399
- ' - "Apps/system/TopoloDocs"',
7653
+ ' - "system-apps/TopoloDocs"',
7400
7654
  'audience: "internal"',
7401
7655
  "tags:",
7402
7656
  ` - "${slug}"`,
@@ -7429,7 +7683,7 @@ function internalDocs(application, options) {
7429
7683
  ...options.api ? [`- \`${appPath}/apps/api/src/worker.ts\``, `- \`${appPath}/apps/api/wrangler.toml\``] : [],
7430
7684
  `- \`${appPath}/topolo.app.json\``,
7431
7685
  `- \`${appPath}/topolo.cloudcontrol.json\``,
7432
- ...options.api && application.serviceId ? [`- \`Apps/system/TopoloAuth/catalog/service-manifests/${application.serviceId}.json\``] : [],
7686
+ ...options.api && application.serviceId ? [`- \`topolo-platform/core/TopoloAuth/catalog/service-manifests/${application.serviceId}.json\``] : [],
7433
7687
  "",
7434
7688
  "## API Reference",
7435
7689
  "",
@@ -7458,7 +7712,7 @@ function internalDocs(application, options) {
7458
7712
  "## Debugging",
7459
7713
  "",
7460
7714
  "- run `npm run typecheck` and `npm run build` in the application directory",
7461
- "- run `topolo apps audit <app> --local --repo-root /Users/ashes/Projects/Topolo/Platform --fail-on needs_review --json` after SDK catalog registration",
7715
+ "- run `topolo apps audit <app> --local --repo-root /Users/ashes/Projects/Topolo --fail-on needs_review --json` after SDK catalog registration",
7462
7716
  "- inspect `topolo.cloudcontrol.json` before deployment changes",
7463
7717
  ...options.api ? ["- inspect the Worker `SERVICE_ID` binding and matching Auth service manifest when permission grants fail"] : [],
7464
7718
  ...options.api && application.serviceId ? [`- re-run \`topolo platform apps scaffold ${slug} --write --provision\` when embedded app login, launcher visibility, or callback origins drift`] : [],
@@ -7697,132 +7951,6 @@ function envExample(application) {
7697
7951
  ""
7698
7952
  ].join("\n");
7699
7953
  }
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
7954
 
7827
7955
  // src/commands/app-scaffold-provision.ts
7828
7956
  import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
@@ -8137,7 +8265,7 @@ function registerAppsCommand(program2) {
8137
8265
  });
8138
8266
  apps.command("scaffold <application>").description(
8139
8267
  "Generate a third-party Topolo application scaffold for business or personal/household use."
8140
- ).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under PlatformApplications").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth service id for API-backed apps").option("--audience <audience>", "Developer app audience: business or personal").option("--tenancy <tenancy>", "Developer app tenancy: organization or household").option(
8268
+ ).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under the scaffold root").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth service id for API-backed apps").option("--audience <audience>", "Developer app audience: business or personal").option("--tenancy <tenancy>", "Developer app tenancy: organization or household").option(
8141
8269
  "--surface-model <surfaceModel>",
8142
8270
  "Developer app surface model: web_app, mobile_container, mobile_module, or messaging_surface"
8143
8271
  ).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include API/service files for developer scaffolds").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
@@ -8561,7 +8689,7 @@ function registerPlatformCommand(program2) {
8561
8689
  const platformApps = platform3.command("apps").description("Scaffold and operate first-party Topolo platform and personal applications");
8562
8690
  platformApps.command("scaffold <application>").description(
8563
8691
  "Generate a first-party Topolo scaffold for the platform or personal portfolio."
8564
- ).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under PlatformApplications").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth service id for API-backed apps").option("--portfolio <portfolio>", "First-party portfolio: platform or personal").option(
8692
+ ).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under apps/").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth service id for API-backed apps").option("--portfolio <portfolio>", "First-party portfolio: platform or personal").option(
8565
8693
  "--surface-model <surfaceModel>",
8566
8694
  "First-party surface model: web_app, mobile_container, mobile_module, or messaging_surface"
8567
8695
  ).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--profile <profile>", "First-party scaffold profile such as spatial-assets").option("--with-d1", "Declare a D1 binding in the first-party scaffold").option("--with-r2", "Declare an R2 binding in the first-party scaffold").option("--with-queue", "Declare a Queue binding in the first-party scaffold").option("--with-workflow", "Declare a Workflow binding in the first-party scaffold").option("--with-container-processing", "Declare container-processing metadata in CloudControl").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include the API/service baseline").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
@@ -9182,7 +9310,7 @@ function normalizeList(values, fallback = []) {
9182
9310
 
9183
9311
  // src/commands/integrations.ts
9184
9312
  import { readdir, readFile as readFile3, stat } from "fs/promises";
9185
- import { join as join4, relative as relative2 } from "path";
9313
+ import { join as join4, relative as relative3 } from "path";
9186
9314
  function registerIntegrationsCommand(program2) {
9187
9315
  const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
9188
9316
  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 +9475,7 @@ async function collectFiles(repoRoot) {
9347
9475
  const info = await stat(fullPath);
9348
9476
  if (info.size > 25e4) continue;
9349
9477
  result.push({
9350
- path: relative2(root, fullPath),
9478
+ path: relative3(root, fullPath),
9351
9479
  content: await readFile3(fullPath, "utf8")
9352
9480
  });
9353
9481
  }