@topolo/cli 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +499 -321
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -67,20 +67,14 @@ var TopoloHttpError = class extends TopoloSdkError {
67
67
  var SERVICES = {
68
68
  agent: "https://topolo-agent-worker.topolo.workers.dev",
69
69
  auth: "https://auth.topolo.app",
70
- books: "https://books.topolo.app",
71
70
  bugfix: "https://bugfix.topolo.app",
72
71
  bytes: "https://r2-media-manager-worker.topolo.workers.dev",
73
72
  calendar: "https://calendar.topolo.app",
74
73
  chat: "https://chat.topolo.app",
75
74
  commerce: "https://topolo-commerce-api.topolo.workers.dev",
76
75
  crm: "https://crm-api.topolo.app",
77
- customer_success: "https://customer-success.topolo.app",
78
76
  feed: "https://topolo-feed-api.topolo.app",
79
- flow: "https://flow.topolo.app",
80
77
  forecast: "https://forecast-worker-production.topolo.workers.dev",
81
- forms: "https://forms.topolo.app",
82
- insights: "https://insights.topolo.app",
83
- inventory: "https://inventory.topolo.app",
84
78
  learn: "https://topololearn-api.topolo.workers.dev",
85
79
  mail: "https://mail.topolo.app",
86
80
  mdm: "https://topolo-mdm-api.topolo.app",
@@ -88,30 +82,16 @@ var SERVICES = {
88
82
  nexus: "https://topolo-nexus-gateway.topolo.workers.dev",
89
83
  one: "https://topoloone-worker.topolo.workers.dev",
90
84
  pay: "https://topolo-pay.topolo.workers.dev",
91
- people: "https://people.topolo.app",
92
85
  "quro.api": "https://api.ol0.me",
93
86
  "quro.redirect": "https://ol0.me",
94
87
  roadmapper: "https://roadmapper-api.topolo.app",
95
- sign: "https://sign.topolo.app",
96
88
  social_studio: "https://topolo-social-studio-api.topolo.workers.dev",
97
89
  socialize: "https://socialize-prod-api.topolo.workers.dev",
98
90
  support: "https://support.topolo.app",
99
- survey: "https://survey.topolo.app",
100
- voice: "https://voice.topolo.app",
101
91
  web: "https://web-api.topolo.app"
102
92
  };
103
93
  var PLATFORM_SERVICE_IDS = {
104
- books: "srv_topolo_books",
105
- crm: "srv_iCwM4jGXcwlj",
106
- customer_success: "srv_topolo_customer_success",
107
- flow: "srv_topolo_flow",
108
- forms: "srv_topolo_forms",
109
- insights: "srv_topolo_insights",
110
- inventory: "srv_topolo_inventory",
111
- people: "srv_topolo_people",
112
- sign: "srv_topolo_sign",
113
- survey: "srv_topolo_survey",
114
- voice: "srv_topolo_voice"
94
+ crm: "srv_iCwM4jGXcwlj"
115
95
  };
116
96
  var APPLICATIONS = {
117
97
  "admin": {
@@ -184,30 +164,6 @@ var APPLICATIONS = {
184
164
  }
185
165
  ]
186
166
  },
187
- "books": {
188
- "id": "books",
189
- "appDir": "TopoloBooks",
190
- "name": "topolo-books",
191
- "packageName": "topolo-books",
192
- "productionUrl": "https://books.topolo.app",
193
- "services": [
194
- "books"
195
- ],
196
- "deployTargets": [
197
- {
198
- "name": "topolo-books",
199
- "kind": "worker",
200
- "apiUrl": "https://books.topolo.app",
201
- "domains": [
202
- "books.topolo.app"
203
- ],
204
- "routes": [
205
- "books.topolo.app"
206
- ],
207
- "serviceId": "books"
208
- }
209
- ]
210
- },
211
167
  "bugfix": {
212
168
  "id": "bugfix",
213
169
  "appDir": "TopoloBugFix",
@@ -409,30 +365,6 @@ var APPLICATIONS = {
409
365
  }
410
366
  ]
411
367
  },
412
- "customer_success": {
413
- "id": "customer_success",
414
- "appDir": "TopoloCustomerSuccess",
415
- "name": "topolo-customer-success",
416
- "packageName": "topolo-customer-success",
417
- "productionUrl": "https://customer-success.topolo.app",
418
- "services": [
419
- "customer_success"
420
- ],
421
- "deployTargets": [
422
- {
423
- "name": "topolo-customer-success",
424
- "kind": "worker",
425
- "apiUrl": "https://customer-success.topolo.app",
426
- "domains": [
427
- "customer-success.topolo.app"
428
- ],
429
- "routes": [
430
- "customer-success.topolo.app"
431
- ],
432
- "serviceId": "customer_success"
433
- }
434
- ]
435
- },
436
368
  "developers": {
437
369
  "id": "developers",
438
370
  "appDir": "TopoloDevelopers",
@@ -509,30 +441,6 @@ var APPLICATIONS = {
509
441
  }
510
442
  ]
511
443
  },
512
- "flow": {
513
- "id": "flow",
514
- "appDir": "TopoloFlow",
515
- "name": "topolo-flow",
516
- "packageName": "topolo-flow",
517
- "productionUrl": "https://flow.topolo.app",
518
- "services": [
519
- "flow"
520
- ],
521
- "deployTargets": [
522
- {
523
- "name": "topolo-flow",
524
- "kind": "worker",
525
- "apiUrl": "https://flow.topolo.app",
526
- "domains": [
527
- "flow.topolo.app"
528
- ],
529
- "routes": [
530
- "flow.topolo.app"
531
- ],
532
- "serviceId": "flow"
533
- }
534
- ]
535
- },
536
444
  "forecast": {
537
445
  "id": "forecast",
538
446
  "appDir": "TopoloForecast",
@@ -561,78 +469,6 @@ var APPLICATIONS = {
561
469
  }
562
470
  ]
563
471
  },
564
- "forms": {
565
- "id": "forms",
566
- "appDir": "TopoloForms",
567
- "name": "topolo-forms",
568
- "packageName": "topolo-forms",
569
- "productionUrl": "https://forms.topolo.app",
570
- "services": [
571
- "forms"
572
- ],
573
- "deployTargets": [
574
- {
575
- "name": "topolo-forms",
576
- "kind": "worker",
577
- "apiUrl": "https://forms.topolo.app",
578
- "domains": [
579
- "forms.topolo.app"
580
- ],
581
- "routes": [
582
- "forms.topolo.app"
583
- ],
584
- "serviceId": "forms"
585
- }
586
- ]
587
- },
588
- "insights": {
589
- "id": "insights",
590
- "appDir": "TopoloInsights",
591
- "name": "topolo-insights",
592
- "packageName": "topolo-insights",
593
- "productionUrl": "https://insights.topolo.app",
594
- "services": [
595
- "insights"
596
- ],
597
- "deployTargets": [
598
- {
599
- "name": "topolo-insights",
600
- "kind": "worker",
601
- "apiUrl": "https://insights.topolo.app",
602
- "domains": [
603
- "insights.topolo.app"
604
- ],
605
- "routes": [
606
- "insights.topolo.app"
607
- ],
608
- "serviceId": "insights"
609
- }
610
- ]
611
- },
612
- "inventory": {
613
- "id": "inventory",
614
- "appDir": "TopoloInventory",
615
- "name": "topolo-inventory",
616
- "packageName": "topolo-inventory",
617
- "productionUrl": "https://inventory.topolo.app",
618
- "services": [
619
- "inventory"
620
- ],
621
- "deployTargets": [
622
- {
623
- "name": "topolo-inventory",
624
- "kind": "worker",
625
- "apiUrl": "https://inventory.topolo.app",
626
- "domains": [
627
- "inventory.topolo.app"
628
- ],
629
- "routes": [
630
- "inventory.topolo.app"
631
- ],
632
- "serviceId": "inventory"
633
- }
634
- ]
635
- },
636
472
  "learn": {
637
473
  "id": "learn",
638
474
  "appDir": "TopoloLearn",
@@ -833,9 +669,7 @@ var APPLICATIONS = {
833
669
  "kind": "worker",
834
670
  "apiUrl": null,
835
671
  "domains": [
836
- "one.topolo.io",
837
- "topolo.io",
838
- "www.topolo.io"
672
+ "one.topolo.io"
839
673
  ],
840
674
  "routes": [],
841
675
  "serviceId": null
@@ -872,30 +706,6 @@ var APPLICATIONS = {
872
706
  }
873
707
  ]
874
708
  },
875
- "people": {
876
- "id": "people",
877
- "appDir": "TopoloPeople",
878
- "name": "topolo-people",
879
- "packageName": "topolo-people",
880
- "productionUrl": "https://people.topolo.app",
881
- "services": [
882
- "people"
883
- ],
884
- "deployTargets": [
885
- {
886
- "name": "topolo-people",
887
- "kind": "worker",
888
- "apiUrl": "https://people.topolo.app",
889
- "domains": [
890
- "people.topolo.app"
891
- ],
892
- "routes": [
893
- "people.topolo.app"
894
- ],
895
- "serviceId": "people"
896
- }
897
- ]
898
- },
899
709
  "quro": {
900
710
  "id": "quro",
901
711
  "appDir": "TopoloQuro",
@@ -967,30 +777,6 @@ var APPLICATIONS = {
967
777
  }
968
778
  ]
969
779
  },
970
- "sign": {
971
- "id": "sign",
972
- "appDir": "TopoloSign",
973
- "name": "topolo-sign",
974
- "packageName": "topolo-sign",
975
- "productionUrl": "https://sign.topolo.app",
976
- "services": [
977
- "sign"
978
- ],
979
- "deployTargets": [
980
- {
981
- "name": "topolo-sign",
982
- "kind": "worker",
983
- "apiUrl": "https://sign.topolo.app",
984
- "domains": [
985
- "sign.topolo.app"
986
- ],
987
- "routes": [
988
- "sign.topolo.app"
989
- ],
990
- "serviceId": "sign"
991
- }
992
- ]
993
- },
994
780
  "social_studio": {
995
781
  "id": "social_studio",
996
782
  "appDir": "TopoloSocialStudio",
@@ -1087,54 +873,6 @@ var APPLICATIONS = {
1087
873
  }
1088
874
  ]
1089
875
  },
1090
- "survey": {
1091
- "id": "survey",
1092
- "appDir": "TopoloSurvey",
1093
- "name": "topolo-survey",
1094
- "packageName": "topolo-survey",
1095
- "productionUrl": "https://survey.topolo.app",
1096
- "services": [
1097
- "survey"
1098
- ],
1099
- "deployTargets": [
1100
- {
1101
- "name": "topolo-survey",
1102
- "kind": "worker",
1103
- "apiUrl": "https://survey.topolo.app",
1104
- "domains": [
1105
- "survey.topolo.app"
1106
- ],
1107
- "routes": [
1108
- "survey.topolo.app"
1109
- ],
1110
- "serviceId": "survey"
1111
- }
1112
- ]
1113
- },
1114
- "voice": {
1115
- "id": "voice",
1116
- "appDir": "TopoloVoice",
1117
- "name": "topolo-voice",
1118
- "packageName": "topolo-voice",
1119
- "productionUrl": "https://voice.topolo.app",
1120
- "services": [
1121
- "voice"
1122
- ],
1123
- "deployTargets": [
1124
- {
1125
- "name": "topolo-voice",
1126
- "kind": "worker",
1127
- "apiUrl": "https://voice.topolo.app",
1128
- "domains": [
1129
- "voice.topolo.app"
1130
- ],
1131
- "routes": [
1132
- "voice.topolo.app"
1133
- ],
1134
- "serviceId": "voice"
1135
- }
1136
- ]
1137
- },
1138
876
  "web": {
1139
877
  "id": "web",
1140
878
  "appDir": "TopoloWeb",
@@ -2204,7 +1942,7 @@ function sleep(ms) {
2204
1942
  }
2205
1943
 
2206
1944
  // src/version.ts
2207
- var CLI_VERSION = "0.1.11";
1945
+ var CLI_VERSION = "0.1.13";
2208
1946
 
2209
1947
  // src/client.ts
2210
1948
  async function buildClient(options) {
@@ -2978,11 +2716,21 @@ var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
2978
2716
  var CURRENT_VERIFICATION_DATE = "2026-04-23";
2979
2717
  function applicationScaffoldPayload(application, options = {}) {
2980
2718
  const target = normalizeTarget(options.target);
2719
+ const portfolio = target === "platform" ? normalizePortfolio(options.portfolio) : null;
2981
2720
  const id = normalizeApplicationId(application);
2982
2721
  const slug = toSlug(id);
2983
2722
  const catalogApplication = APPLICATIONS[id];
2984
2723
  const browser = options.browser !== false;
2985
2724
  const api = options.api ?? target === "platform";
2725
+ const audience = normalizeAudience(
2726
+ options.audience,
2727
+ target === "platform" ? portfolio : null
2728
+ );
2729
+ const tenancy = normalizeTenancy(options.tenancy, audience);
2730
+ const surfaceModel = normalizeSurfaceModel(options.surfaceModel);
2731
+ const containerAppId = normalizeContainerAppId(options.containerAppId, surfaceModel);
2732
+ const ownerType = target === "platform" ? "topolo_first_party" : "third_party";
2733
+ const distributionModel = target === "platform" ? "marketplace" : "organization_internal";
2986
2734
  const appDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
2987
2735
  const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
2988
2736
  const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${slug}`;
@@ -3003,6 +2751,13 @@ function applicationScaffoldPayload(application, options = {}) {
3003
2751
  return {
3004
2752
  version: APPLICATION_REQUIREMENTS_VERSION,
3005
2753
  target,
2754
+ ownerType,
2755
+ portfolio,
2756
+ audience,
2757
+ tenancy,
2758
+ surfaceModel,
2759
+ containerAppId,
2760
+ distributionModel,
3006
2761
  application: applicationPayload,
3007
2762
  scopes,
3008
2763
  requirements: APPLICATION_REQUIREMENTS.filter(
@@ -3010,13 +2765,28 @@ function applicationScaffoldPayload(application, options = {}) {
3010
2765
  ),
3011
2766
  packages: scaffoldPackages({ browser, api, target }),
3012
2767
  files: scaffoldFiles({
2768
+ payload: {
2769
+ ownerType,
2770
+ portfolio,
2771
+ audience,
2772
+ tenancy,
2773
+ surfaceModel,
2774
+ containerAppId,
2775
+ distributionModel
2776
+ },
3013
2777
  application: applicationPayload,
3014
2778
  target,
3015
2779
  browser,
3016
2780
  api,
3017
2781
  ciWorkflow
3018
2782
  }),
3019
- commands: scaffoldCommands(id, target),
2783
+ commands: scaffoldCommands({
2784
+ id,
2785
+ target,
2786
+ audience,
2787
+ tenancy,
2788
+ portfolio
2789
+ }),
3020
2790
  ciWorkflowPath,
3021
2791
  ciWorkflow
3022
2792
  };
@@ -3098,7 +2868,12 @@ function scaffoldFiles(options) {
3098
2868
  "documents the generated app target",
3099
2869
  "points maintainers to the shared Topolo packages used by the scaffold"
3100
2870
  ],
3101
- content: readme(application, target, { api: options.api })
2871
+ content: readme(application, target, {
2872
+ api: options.api,
2873
+ audience: options.payload.audience,
2874
+ tenancy: options.payload.tenancy,
2875
+ portfolio: options.payload.portfolio
2876
+ })
3102
2877
  },
3103
2878
  {
3104
2879
  path: `${base}/.gitignore`,
@@ -3194,7 +2969,12 @@ function scaffoldFiles(options) {
3194
2969
  "doc_paths includes internal handbook coverage",
3195
2970
  "security_assurance records the auth, tenancy, deployment, and verification boundary"
3196
2971
  ],
3197
- content: systemJson(application, { api: options.api })
2972
+ content: systemJson(application, {
2973
+ api: options.api,
2974
+ audience: options.payload.audience,
2975
+ tenancy: options.payload.tenancy,
2976
+ portfolio: options.payload.portfolio
2977
+ })
3198
2978
  },
3199
2979
  {
3200
2980
  path: `PlatformApplications/TopoloDocs/src/content/internal/apps/${slug}.mdx`,
@@ -3203,7 +2983,12 @@ function scaffoldFiles(options) {
3203
2983
  "documents ownership, routes, auth boundary, deployment, operations, and verification",
3204
2984
  "last_verified is updated when behavior changes"
3205
2985
  ],
3206
- content: internalDocs(application, { api: options.api })
2986
+ content: internalDocs(application, {
2987
+ api: options.api,
2988
+ audience: options.payload.audience,
2989
+ tenancy: options.payload.tenancy,
2990
+ portfolio: options.payload.portfolio
2991
+ })
3207
2992
  },
3208
2993
  {
3209
2994
  path: `PlatformApplications/TopoloDocs/src/content/public/applications/${slug}.mdx`,
@@ -3212,7 +2997,12 @@ function scaffoldFiles(options) {
3212
2997
  "documents the product surface without internal-only details",
3213
2998
  "links back to the canonical system registry entry"
3214
2999
  ],
3215
- content: publicDocs(application, { api: options.api })
3000
+ content: publicDocs(application, {
3001
+ api: options.api,
3002
+ audience: options.payload.audience,
3003
+ tenancy: options.payload.tenancy,
3004
+ portfolio: options.payload.portfolio
3005
+ })
3216
3006
  }
3217
3007
  );
3218
3008
  if (options.api && application.serviceId) {
@@ -3224,7 +3014,9 @@ function scaffoldFiles(options) {
3224
3014
  "permissions, role bundles, and API-key scopes exist before the app is catalog-visible",
3225
3015
  "systems metadata maps the service back to the generated TopoloDocs entries"
3226
3016
  ],
3227
- content: serviceManifestJson(application)
3017
+ content: serviceManifestJson(application, {
3018
+ tenancy: options.payload.tenancy
3019
+ })
3228
3020
  });
3229
3021
  }
3230
3022
  } else {
@@ -3236,7 +3028,14 @@ function scaffoldFiles(options) {
3236
3028
  "declares the external app id, callback URLs, and requested platform scopes",
3237
3029
  "does not include internal Topolo CloudControl or canonical-docs ownership"
3238
3030
  ],
3239
- content: developerManifest(application, { api: options.api })
3031
+ content: developerManifest(application, {
3032
+ api: options.api,
3033
+ audience: options.payload.audience,
3034
+ tenancy: options.payload.tenancy,
3035
+ surfaceModel: options.payload.surfaceModel,
3036
+ containerAppId: options.payload.containerAppId,
3037
+ distributionModel: options.payload.distributionModel
3038
+ })
3240
3039
  },
3241
3040
  {
3242
3041
  path: `${base}/.env.example`,
@@ -3259,7 +3058,10 @@ function scaffoldFiles(options) {
3259
3058
  "keeps launcher and account-menu behavior on the shared platform surface",
3260
3059
  "passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
3261
3060
  ],
3262
- content: appTsx(application, target)
3061
+ content: appTsx(application, target, {
3062
+ audience: options.payload.audience,
3063
+ portfolio: options.payload.portfolio
3064
+ })
3263
3065
  });
3264
3066
  }
3265
3067
  if (options.api) {
@@ -3281,28 +3083,33 @@ function scaffoldFiles(options) {
3281
3083
  "derives organization from the credential rather than client-supplied orgId",
3282
3084
  "emits or propagates X-Topolo-Request-Id and X-Topolo-Client"
3283
3085
  ],
3284
- content: workerTs(application, target)
3086
+ content: workerTs(application, target, {
3087
+ audience: options.payload.audience,
3088
+ tenancy: options.payload.tenancy
3089
+ })
3285
3090
  }
3286
3091
  );
3287
3092
  }
3288
3093
  return files;
3289
3094
  }
3290
- function scaffoldCommands(id, target) {
3291
- const scaffoldId = toSlug(id);
3292
- if (target === "developer") {
3095
+ function scaffoldCommands(options) {
3096
+ const scaffoldId = toSlug(options.id);
3097
+ if (options.target === "developer") {
3098
+ const audienceFlags = options.audience === "personal" ? " --audience personal --tenancy household" : "";
3293
3099
  return [
3294
- `topolo apps scaffold ${scaffoldId} --target developer --write --repo-root .`,
3100
+ `topolo apps scaffold ${scaffoldId}${audienceFlags} --write --repo-root .`,
3295
3101
  `cd topolo-${scaffoldId}`,
3296
3102
  "npm install",
3297
3103
  "npm run typecheck",
3298
3104
  "npm run build"
3299
3105
  ];
3300
3106
  }
3107
+ const portfolioFlags = options.portfolio === "personal" ? " --portfolio personal" : "";
3301
3108
  return [
3302
- `topolo apps scaffold ${scaffoldId} --write --repo-root /Users/ashes/Projects/topolo --json`,
3109
+ `topolo platform apps scaffold ${scaffoldId}${portfolioFlags} --write --repo-root /Users/ashes/Projects/topolo --json`,
3303
3110
  "topolo apps requirements --json",
3304
- `topolo apps audit ${id} --local --repo-root /Users/ashes/Projects/topolo --fail-on needs_review --json`,
3305
- `topolo apps audit ${id} --local --repo-root /Users/ashes/Projects/topolo --fail-on partial --json`
3111
+ `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/topolo --fail-on needs_review --json`,
3112
+ `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/topolo --fail-on partial --json`
3306
3113
  ];
3307
3114
  }
3308
3115
  function topoloConformanceWorkflow(options) {
@@ -3430,12 +3237,16 @@ function topoloPackageVersion(packageName, target) {
3430
3237
  }
3431
3238
  function readme(application, target, options) {
3432
3239
  const displayName = toTitle(application.name);
3433
- const targetLabel = target === "platform" ? "first-party platform application" : "third-party Topolo application";
3240
+ const targetLabel = target === "platform" ? options.portfolio === "personal" ? "first-party Topolo personal application" : "first-party Topolo platform application" : options.audience === "personal" ? "third-party Topolo personal application" : "third-party Topolo business application";
3434
3241
  const lines = [
3435
3242
  `# ${displayName}`,
3436
3243
  "",
3437
3244
  `Generated ${targetLabel} scaffold for Topolo.`,
3438
3245
  "",
3246
+ `Audience: ${options.audience}`,
3247
+ `Tenancy: ${options.tenancy}`,
3248
+ ...target === "platform" && options.portfolio ? [`Portfolio: ${options.portfolio}`] : [],
3249
+ "",
3439
3250
  "## Development",
3440
3251
  "",
3441
3252
  "```bash",
@@ -3464,6 +3275,7 @@ function readme(application, target, options) {
3464
3275
  "",
3465
3276
  "- `topolo.app.json` declares callback URLs, requested scopes, and runtime shape for Topolo Developers.",
3466
3277
  "- Browser sign-in uses the public Topolo OAuth authorization-code + PKCE flow.",
3278
+ `- The generated app is seeded as a ${options.audience}/${options.tenancy} developer application.`,
3467
3279
  "- The starter intentionally avoids private first-party packages so it can install outside the Topolo monorepo."
3468
3280
  );
3469
3281
  }
@@ -3651,13 +3463,15 @@ function stylesCss() {
3651
3463
  ""
3652
3464
  ].join("\n");
3653
3465
  }
3654
- function appTsx(application, target) {
3466
+ function appTsx(application, target, options) {
3655
3467
  if (target === "developer") return developerAppTsx(application);
3656
- return platformAppTsx(application);
3468
+ return platformAppTsx(application, options);
3657
3469
  }
3658
- function platformAppTsx(application) {
3470
+ function platformAppTsx(application, options) {
3659
3471
  const appName = toTitle(application.name);
3660
3472
  const serviceId = application.serviceId ?? application.id;
3473
+ const tagline = options.audience === "personal" ? "Topolo personal app" : "Topolo workspace";
3474
+ const workspaceTitle = options.audience === "personal" ? "Personal app" : "Workspace";
3661
3475
  return [
3662
3476
  "import { useEffect, useMemo, useState } from 'react';",
3663
3477
  "import { createBrowserAuthModule, type AuthUser } from '@topolo/auth-client';",
@@ -3673,7 +3487,7 @@ function platformAppTsx(application) {
3673
3487
  "const SERVICE_ID = import.meta.env.VITE_TOPOLO_SERVICE_ID ?? DEFAULT_SERVICE_ID;",
3674
3488
  "const LANDING_CONFIG = {",
3675
3489
  " app_name: APP_NAME,",
3676
- " app_tagline: 'Topolo workspace',",
3490
+ ` app_tagline: '${escapeSingleQuoted(tagline)}',`,
3677
3491
  " hero_title: `Open ${APP_NAME} in Topolo`,",
3678
3492
  " hero_subtitle: 'Shared landing, login, callback, auth, and workspace shell wiring are already included in the generated baseline.',",
3679
3493
  " hero_cta_text: 'Sign in',",
@@ -3683,7 +3497,7 @@ function platformAppTsx(application) {
3683
3497
  "};",
3684
3498
  "const LOGIN_CONFIG = {",
3685
3499
  " app_name: APP_NAME,",
3686
- " app_tagline: 'Topolo workspace',",
3500
+ ` app_tagline: '${escapeSingleQuoted(tagline)}',`,
3687
3501
  " hero_title: `Sign in to ${APP_NAME}`,",
3688
3502
  " hero_subtitle: 'Use Topolo Auth to open the shared workspace shell for this application.',",
3689
3503
  " authenticated_home_path: '/',",
@@ -3857,10 +3671,10 @@ function platformAppTsx(application) {
3857
3671
  " appName={APP_NAME}",
3858
3672
  " user={shellUser}",
3859
3673
  ' activeNavItemId="home"',
3860
- " navItems={[{ id: 'home', label: 'Workspace' }]}",
3674
+ ` navItems={[{ id: 'home', label: '${escapeSingleQuoted(workspaceTitle)}' }]}`,
3861
3675
  " onNavItemSelect={() => undefined}",
3862
3676
  " eyebrow={APP_NAME}",
3863
- ' title="Workspace"',
3677
+ ` title="${escapeDoubleQuoted(workspaceTitle)}"`,
3864
3678
  " authUrl={AUTH_URL}",
3865
3679
  " getAccessToken={getAccessToken}",
3866
3680
  " onSignOut={async () => {",
@@ -4076,7 +3890,7 @@ function wranglerToml(application, target) {
4076
3890
  );
4077
3891
  return lines.join("\n");
4078
3892
  }
4079
- function workerTs(application, target) {
3893
+ function workerTs(application, target, _options) {
4080
3894
  if (target === "developer") return developerWorkerTs(application);
4081
3895
  return platformWorkerTs(application);
4082
3896
  }
@@ -4281,12 +4095,14 @@ function cloudcontrolJson(application, options) {
4281
4095
  }
4282
4096
  function systemJson(application, options) {
4283
4097
  const slug = toSlug(application.id);
4098
+ const suiteLabel = options.audience === "personal" ? "personal" : "platform";
4099
+ const summaryLabel = options.audience === "personal" ? "first-party Topolo personal application" : "first-party Topolo platform application";
4284
4100
  return `${json({
4285
4101
  id: `topolo-${slug}`,
4286
4102
  name: toTitle(application.name),
4287
4103
  slug: `topolo-${slug}`,
4288
4104
  kind: "application",
4289
- summary: `${toTitle(application.name)} is a first-party Topolo application scaffolded on shared Auth, UI Kit, CloudControl, and conformance contracts.`,
4105
+ summary: `${toTitle(application.name)} is a ${summaryLabel} scaffolded on shared Auth, UI Kit, CloudControl, and conformance contracts.`,
4290
4106
  lifecycle: "planned",
4291
4107
  last_verified: CURRENT_VERIFICATION_DATE,
4292
4108
  owners: ["platform-engineering"],
@@ -4303,7 +4119,7 @@ function systemJson(application, options) {
4303
4119
  security_assurance: {
4304
4120
  risk_tier: "internal",
4305
4121
  auth_boundary: "Topolo Auth through @topolo/auth-client for browser sessions and @topolo/auth-middleware for Worker requests.",
4306
- tenant_isolation: "organization_scoped",
4122
+ tenant_isolation: options.tenancy === "household" ? "household_scoped" : "organization_scoped",
4307
4123
  external_inputs: ["browser", ...options.api ? ["api"] : []],
4308
4124
  sensitive_data: ["identity", "org_data"],
4309
4125
  security_review_status: "not_started",
@@ -4311,13 +4127,16 @@ function systemJson(application, options) {
4311
4127
  pentest_status: "not_started",
4312
4128
  evidence_doc: `internal/apps/${slug}`
4313
4129
  },
4314
- dependencies: ["topolo-auth", "topolo-platform"]
4130
+ dependencies: options.audience === "personal" ? ["topolo-auth"] : ["topolo-auth", "topolo-platform"],
4131
+ tags: ["application", "cloudflare", "typescript", suiteLabel]
4315
4132
  })}
4316
4133
  `;
4317
4134
  }
4318
4135
  function internalDocs(application, options) {
4319
4136
  const slug = toSlug(application.id);
4320
4137
  const title = toTitle(application.name);
4138
+ const suiteLabel = options.audience === "personal" ? "personal" : "platform";
4139
+ const tenantLabel = options.tenancy === "household" ? "household" : "organization";
4321
4140
  return [
4322
4141
  "---",
4323
4142
  `title: "${escapeFrontmatter(title)} Handbook"`,
@@ -4342,7 +4161,7 @@ function internalDocs(application, options) {
4342
4161
  "",
4343
4162
  "## What It Is",
4344
4163
  "",
4345
- `${title} is a first-party Topolo application scaffolded from the shared platform application contract.`,
4164
+ `${title} is a first-party Topolo ${suiteLabel} application scaffolded from the shared first-party application contract.`,
4346
4165
  "",
4347
4166
  "## Architecture",
4348
4167
  "",
@@ -4368,7 +4187,7 @@ function internalDocs(application, options) {
4368
4187
  "",
4369
4188
  "## Data Ownership",
4370
4189
  "",
4371
- "The generated scaffold stores no durable application data yet. Future domain tables, Durable Objects, queues, KV namespaces, R2 buckets, and external provider records must be scoped to the authenticated organization at the backend boundary.",
4190
+ `The generated scaffold stores no durable application data yet. Future domain tables, Durable Objects, queues, KV namespaces, R2 buckets, and external provider records must be scoped to the authenticated ${tenantLabel} context at the backend boundary.`,
4372
4191
  "",
4373
4192
  "## Deployments",
4374
4193
  "",
@@ -4390,7 +4209,7 @@ function internalDocs(application, options) {
4390
4209
  "",
4391
4210
  "## Change Log / Verification",
4392
4211
  "",
4393
- `- Scaffold generated on ${CURRENT_VERIFICATION_DATE} from \`topolo apps scaffold\`.`,
4212
+ `- Scaffold generated on ${CURRENT_VERIFICATION_DATE} from \`${options.portfolio ? "topolo platform apps scaffold" : "topolo apps scaffold"}\`.`,
4394
4213
  "- `npm run typecheck`",
4395
4214
  "- `npm run build`",
4396
4215
  "- `npm run validate` in TopoloDocs",
@@ -4400,6 +4219,8 @@ function internalDocs(application, options) {
4400
4219
  function publicDocs(application, options) {
4401
4220
  const slug = toSlug(application.id);
4402
4221
  const title = toTitle(application.name);
4222
+ const suiteLabel = options.audience === "personal" ? "personal" : "business";
4223
+ const tenantLabel = options.tenancy === "household" ? "household" : "organization";
4403
4224
  return [
4404
4225
  "---",
4405
4226
  `title: "${escapeFrontmatter(title)}"`,
@@ -4423,7 +4244,7 @@ function publicDocs(application, options) {
4423
4244
  "",
4424
4245
  "## What It Is",
4425
4246
  "",
4426
- `${title} is part of the Topolo business application suite.`,
4247
+ `${title} is part of the Topolo ${suiteLabel} application suite.`,
4427
4248
  "",
4428
4249
  "## Architecture",
4429
4250
  "",
@@ -4443,7 +4264,7 @@ function publicDocs(application, options) {
4443
4264
  "",
4444
4265
  "## Data Ownership",
4445
4266
  "",
4446
- "The generated baseline has no durable domain data yet. Future data must be organization-scoped at the backend boundary.",
4267
+ `The generated baseline has no durable domain data yet. Future data must be ${tenantLabel}-scoped at the backend boundary.`,
4447
4268
  "",
4448
4269
  "## Deployments",
4449
4270
  "",
@@ -4465,10 +4286,11 @@ function publicDocs(application, options) {
4465
4286
  ""
4466
4287
  ].join("\n");
4467
4288
  }
4468
- function serviceManifestJson(application) {
4289
+ function serviceManifestJson(application, options) {
4469
4290
  const slug = toSlug(application.id);
4470
4291
  const title = toTitle(application.name);
4471
4292
  const permissionPrefix = application.id.replace(/[^a-z0-9_]/g, "_");
4293
+ const resourceLabel = options.tenancy === "household" ? "household" : "workspace";
4472
4294
  return `${json({
4473
4295
  serviceId: application.serviceId,
4474
4296
  systems: [
@@ -4483,13 +4305,13 @@ function serviceManifestJson(application) {
4483
4305
  {
4484
4306
  id: `perm_${permissionPrefix}_workspace_read`,
4485
4307
  name: "workspace:read",
4486
- description: `View ${title} workspace data`,
4308
+ description: `View ${title} ${resourceLabel} data`,
4487
4309
  resourcePattern: null
4488
4310
  },
4489
4311
  {
4490
4312
  id: `perm_${permissionPrefix}_workspace_write`,
4491
4313
  name: "workspace:write",
4492
- description: `Manage ${title} workspace data`,
4314
+ description: `Manage ${title} ${resourceLabel} data`,
4493
4315
  resourcePattern: null
4494
4316
  }
4495
4317
  ],
@@ -4497,13 +4319,13 @@ function serviceManifestJson(application) {
4497
4319
  {
4498
4320
  id: `aks_${permissionPrefix}_workspace_read`,
4499
4321
  name: "workspace.read",
4500
- description: `View ${title} workspace data`,
4322
+ description: `View ${title} ${resourceLabel} data`,
4501
4323
  resourcePattern: null
4502
4324
  },
4503
4325
  {
4504
4326
  id: `aks_${permissionPrefix}_workspace_write`,
4505
4327
  name: "workspace.write",
4506
- description: `Manage ${title} workspace data`,
4328
+ description: `Manage ${title} ${resourceLabel} data`,
4507
4329
  resourcePattern: null
4508
4330
  }
4509
4331
  ],
@@ -4522,6 +4344,11 @@ function developerManifest(application, options) {
4522
4344
  id: application.id,
4523
4345
  name: toTitle(application.name),
4524
4346
  type: "third_party",
4347
+ audience: options.audience,
4348
+ tenancy: options.tenancy,
4349
+ distribution_model: options.distributionModel,
4350
+ surface_model: options.surfaceModel,
4351
+ container_app_id: options.containerAppId,
4525
4352
  package_name: application.packageName,
4526
4353
  service_id: options.api ? application.serviceId : null,
4527
4354
  auth: {
@@ -4554,6 +4381,52 @@ function normalizeTarget(value) {
4554
4381
  if (value === "platform" || value === "developer") return value;
4555
4382
  throw new Error("Invalid scaffold target. Use platform or developer.");
4556
4383
  }
4384
+ function normalizePortfolio(value) {
4385
+ if (!value) return "platform";
4386
+ if (value === "platform" || value === "personal") return value;
4387
+ throw new Error("Invalid scaffold portfolio. Use platform or personal.");
4388
+ }
4389
+ function normalizeAudience(value, portfolio) {
4390
+ if (!value) {
4391
+ return portfolio === "personal" ? "personal" : "business";
4392
+ }
4393
+ if (value === "business" || value === "personal") {
4394
+ return value;
4395
+ }
4396
+ throw new Error("Invalid scaffold audience. Use business or personal.");
4397
+ }
4398
+ function normalizeTenancy(value, audience) {
4399
+ const resolved = value ?? (audience === "personal" ? "household" : "organization");
4400
+ if (resolved !== "organization" && resolved !== "household") {
4401
+ throw new Error("Invalid scaffold tenancy. Use organization or household.");
4402
+ }
4403
+ if (audience === "business" && resolved !== "organization") {
4404
+ throw new Error("Business scaffolds must use organization tenancy.");
4405
+ }
4406
+ if (audience === "personal" && resolved !== "household") {
4407
+ throw new Error("Personal scaffolds must use household tenancy.");
4408
+ }
4409
+ return resolved;
4410
+ }
4411
+ function normalizeSurfaceModel(value) {
4412
+ if (!value) return "web_app";
4413
+ if (value === "web_app" || value === "mobile_container" || value === "mobile_module" || value === "messaging_surface") {
4414
+ return value;
4415
+ }
4416
+ throw new Error(
4417
+ "Invalid scaffold surface model. Use web_app, mobile_container, mobile_module, or messaging_surface."
4418
+ );
4419
+ }
4420
+ function normalizeContainerAppId(value, surfaceModel) {
4421
+ const trimmed = String(value || "").trim();
4422
+ if (surfaceModel === "mobile_module") {
4423
+ if (!trimmed) {
4424
+ throw new Error("Mobile-module scaffolds require --container-app-id.");
4425
+ }
4426
+ return trimmed;
4427
+ }
4428
+ return trimmed || null;
4429
+ }
4557
4430
  function normalizeApplicationId(application) {
4558
4431
  const raw = application.trim().toLowerCase();
4559
4432
  const normalized = raw.replace(/-/g, "_");
@@ -4604,10 +4477,157 @@ function escapeHtml(value) {
4604
4477
  function escapeSingleQuoted(value) {
4605
4478
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
4606
4479
  }
4480
+ function escapeDoubleQuoted(value) {
4481
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
4482
+ }
4607
4483
  function escapeFrontmatter(value) {
4608
4484
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
4609
4485
  }
4610
4486
 
4487
+ // src/commands/app-scaffold-provision.ts
4488
+ var DEFAULT_DEVELOPERS_ORIGIN = "https://developers.topolo.app";
4489
+ var DEVELOPERS_SERVICE_ID = "svc_topolo_developers";
4490
+ async function provisionApplicationScaffold(payload) {
4491
+ if (!payload.application.productionUrl) {
4492
+ throw new Error(
4493
+ "Scaffold provisioning requires a browser-capable application with a production URL."
4494
+ );
4495
+ }
4496
+ if (payload.target === "platform" && !payload.application.serviceId) {
4497
+ throw new Error(
4498
+ "Platform scaffold provisioning requires a service id. Re-run without --no-api or pass --service."
4499
+ );
4500
+ }
4501
+ const credential = await refreshCredentialIfNeeded();
4502
+ if (!credential) {
4503
+ throw new Error(
4504
+ "topolo apps scaffold --provision requires an authenticated Topolo session. Run `topolo auth login` first."
4505
+ );
4506
+ }
4507
+ const client = await buildClient();
4508
+ if (!client) {
4509
+ throw new Error(
4510
+ "Could not build a Topolo client for scaffold provisioning. Run `topolo auth login` first."
4511
+ );
4512
+ }
4513
+ const identity = await client.client.introspect();
4514
+ const config = await readConfig();
4515
+ const serviceUrls = resolveServiceUrls(config.serviceUrls) ?? {};
4516
+ const developersOrigin = sanitizeOrigin(serviceUrls.developers ?? DEFAULT_DEVELOPERS_ORIGIN);
4517
+ const endpoint = payload.target === "platform" ? "/api/developer-console/admin/first-party-app-scaffolds" : "/api/developer-console/apps/scaffold";
4518
+ const headers = new Headers({
4519
+ "Content-Type": "application/json",
4520
+ "X-Service-ID": DEVELOPERS_SERVICE_ID,
4521
+ "X-Topolo-Client": `topolo-cli/${CLI_VERSION}`
4522
+ });
4523
+ const agentName = resolveAgentName(config.agentName);
4524
+ if (agentName) {
4525
+ headers.set("X-Topolo-Agent", agentName);
4526
+ }
4527
+ if (credential.kind === "api_key") {
4528
+ headers.set("X-Api-Key", credential.apiKey);
4529
+ } else {
4530
+ headers.set("Authorization", `Bearer ${credential.accessToken}`);
4531
+ }
4532
+ const response = await fetch(`${developersOrigin}${endpoint}`, {
4533
+ method: "POST",
4534
+ headers,
4535
+ body: JSON.stringify(buildProvisioningRequest(payload))
4536
+ });
4537
+ const envelope = await response.json().catch(() => null);
4538
+ if (!response.ok || !envelope?.success || !envelope.data) {
4539
+ throw new Error(
4540
+ resolveProvisioningError(envelope, response.status, payload.target)
4541
+ );
4542
+ }
4543
+ const app = pickRecord(envelope.data.app);
4544
+ const service = pickRecord(envelope.data.service);
4545
+ const authConfig = pickRecord(envelope.data.authConfig);
4546
+ if (!app) {
4547
+ throw new Error("Developers provisioning did not return an app record.");
4548
+ }
4549
+ const appId = readRequiredString(app.id, "Developers provisioning did not return an app id.");
4550
+ return {
4551
+ target: payload.target,
4552
+ actor: {
4553
+ kind: identity.kind,
4554
+ userId: identity.user.id,
4555
+ email: identity.user.email,
4556
+ role: identity.user.role,
4557
+ organizationId: identity.organization?.id ?? null,
4558
+ organizationSlug: identity.organization?.slug ?? null
4559
+ },
4560
+ developersOrigin,
4561
+ developersApp: {
4562
+ id: appId,
4563
+ slug: readString(app.slug),
4564
+ listingStatus: readString(app.listingStatus),
4565
+ organizationId: readString(app.organizationId)
4566
+ },
4567
+ authService: service ? {
4568
+ id: readRequiredString(
4569
+ service.id,
4570
+ "Auth registration did not return a service id."
4571
+ ),
4572
+ status: readString(service.status),
4573
+ baseUrl: readString(service.base_url) ?? readString(service.baseUrl)
4574
+ } : null,
4575
+ authConfig: authConfig ? {
4576
+ login: authConfig.login === true,
4577
+ landing: authConfig.landing === true,
4578
+ appUi: authConfig.appUi === true
4579
+ } : null
4580
+ };
4581
+ }
4582
+ function buildProvisioningRequest(payload) {
4583
+ return {
4584
+ target: payload.target,
4585
+ ownerType: payload.ownerType,
4586
+ portfolio: payload.portfolio,
4587
+ audience: payload.audience,
4588
+ tenancy: payload.tenancy,
4589
+ surfaceModel: payload.surfaceModel,
4590
+ containerAppId: payload.containerAppId,
4591
+ distributionModel: payload.distributionModel,
4592
+ applicationId: payload.application.id,
4593
+ appDir: payload.application.appDir,
4594
+ packageName: payload.application.packageName,
4595
+ name: payload.application.name,
4596
+ productionUrl: payload.application.productionUrl,
4597
+ serviceId: payload.application.serviceId
4598
+ };
4599
+ }
4600
+ function resolveProvisioningError(envelope, status, target) {
4601
+ const message = (typeof envelope?.error === "string" ? envelope.error : envelope?.error?.message) || envelope?.message || `Scaffold provisioning failed with status ${status}.`;
4602
+ if (status === 401 || status === 403) {
4603
+ if (target === "platform") {
4604
+ return `${message} Platform scaffold provisioning requires a Topolo platform-admin session with Developers commerce access.`;
4605
+ }
4606
+ return `${message} Developer scaffold provisioning requires a signed-in Topolo developer workspace session.`;
4607
+ }
4608
+ return message;
4609
+ }
4610
+ function sanitizeOrigin(value) {
4611
+ return value.replace(/\/+$/, "");
4612
+ }
4613
+ function pickRecord(value) {
4614
+ return value && typeof value === "object" ? value : null;
4615
+ }
4616
+ function readString(value) {
4617
+ if (typeof value !== "string") {
4618
+ return null;
4619
+ }
4620
+ const trimmed = value.trim();
4621
+ return trimmed ? trimmed : null;
4622
+ }
4623
+ function readRequiredString(value, message) {
4624
+ const resolved = readString(value);
4625
+ if (!resolved) {
4626
+ throw new Error(message);
4627
+ }
4628
+ return resolved;
4629
+ }
4630
+
4611
4631
  // src/commands/apps.ts
4612
4632
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
4613
4633
  function registerAppsCommand(program2) {
@@ -4669,14 +4689,16 @@ function registerAppsCommand(program2) {
4669
4689
  }
4670
4690
  });
4671
4691
  apps.command("scaffold <application>").description(
4672
- "Generate the required Topolo new-application scaffold, including a conformance workflow."
4673
- ).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("--target <target>", "Scaffold target: platform or developer").option("--developer", "Shortcut for --target developer").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("--workflow-only", "Output only the generated GitHub Actions workflow in human mode").action(async (application, opts) => {
4692
+ "Generate a third-party Topolo application scaffold for business or personal/household use."
4693
+ ).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(
4694
+ "--surface-model <surfaceModel>",
4695
+ "Developer app surface model: web_app, mobile_container, mobile_module, or messaging_surface"
4696
+ ).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(
4697
+ "--provision",
4698
+ "Provision the generated app into the current Topolo developer workspace using the authenticated session"
4699
+ ).action(async (application, opts) => {
4674
4700
  const mode = opts.json === false || process.argv.includes("--no-json") ? "pretty" : resolveOutputMode(opts.json);
4675
4701
  try {
4676
- const target = resolveScaffoldTarget(opts.target, opts.developer);
4677
- if (opts.workflowOnly && opts.write) {
4678
- throw new Error("--workflow-only cannot be used with --write.");
4679
- }
4680
4702
  const api = resolveScaffoldApiOption(opts.api, opts.withApi);
4681
4703
  const payload = applicationScaffoldPayload(application, {
4682
4704
  appDir: opts.appDir,
@@ -4686,14 +4708,27 @@ function registerAppsCommand(program2) {
4686
4708
  service: opts.service,
4687
4709
  api,
4688
4710
  browser: opts.browser,
4689
- target
4711
+ target: "developer",
4712
+ audience: resolveScaffoldAudience(opts.audience),
4713
+ tenancy: resolveScaffoldTenancy(opts.tenancy),
4714
+ surfaceModel: resolveScaffoldSurfaceModel(opts.surfaceModel),
4715
+ containerAppId: opts.containerAppId
4690
4716
  });
4691
- if (opts.workflowOnly && !payload.ciWorkflow) {
4692
- throw new Error("--workflow-only is only available for platform scaffolds.");
4717
+ if (opts.provision && !opts.write) {
4718
+ throw new Error("--provision requires --write so remote developer records match written scaffold files.");
4693
4719
  }
4694
4720
  const writeResult = opts.write ? await writeApplicationScaffold(payload, { repoRoot: opts.repoRoot, force: opts.force }) : null;
4695
- const output = opts.workflowOnly ? { application: payload.application, ciWorkflowPath: payload.ciWorkflowPath, ciWorkflow: payload.ciWorkflow } : writeResult ? { ...payload, write: writeResult } : payload;
4696
- emit(output, mode, () => opts.workflowOnly ? payload.ciWorkflow ?? "" : formatScaffold(payload, writeResult));
4721
+ const provisionResult = opts.provision ? await provisionApplicationScaffold(payload) : null;
4722
+ const output = {
4723
+ ...payload,
4724
+ ...writeResult ? { write: writeResult } : {},
4725
+ ...provisionResult ? { provision: provisionResult } : {}
4726
+ };
4727
+ emit(
4728
+ output,
4729
+ mode,
4730
+ () => formatScaffold(payload, writeResult, provisionResult)
4731
+ );
4697
4732
  } catch (err) {
4698
4733
  fail(err, mode);
4699
4734
  }
@@ -4854,10 +4889,17 @@ function formatAudit(payload) {
4854
4889
  }
4855
4890
  return lines.join("\n");
4856
4891
  }
4857
- function formatScaffold(payload, writeResult = null) {
4892
+ function formatScaffold(payload, writeResult = null, provisionResult = null) {
4858
4893
  const lines = [
4859
4894
  `Version: ${payload.version}`,
4860
4895
  `Target: ${payload.target}`,
4896
+ `Owner: ${payload.ownerType}`,
4897
+ `Audience: ${payload.audience}`,
4898
+ `Tenancy: ${payload.tenancy}`,
4899
+ `Portfolio: ${payload.portfolio ?? "-"}`,
4900
+ `Surface: ${payload.surfaceModel}`,
4901
+ `Container: ${payload.containerAppId ?? "-"}`,
4902
+ `Distribution: ${payload.distributionModel}`,
4861
4903
  `Application: ${payload.application.id}`,
4862
4904
  `Directory: ${payload.application.appDir}`,
4863
4905
  `Package: ${payload.application.packageName}`,
@@ -4886,13 +4928,54 @@ function formatScaffold(payload, writeResult = null) {
4886
4928
  lines.push(` - ${file.status}: ${file.path}`);
4887
4929
  }
4888
4930
  }
4931
+ if (provisionResult) {
4932
+ lines.push("Provisioning:");
4933
+ lines.push(` - target: ${provisionResult.target}`);
4934
+ lines.push(
4935
+ ` - actor: ${provisionResult.actor.email} (${provisionResult.actor.role}, org=${provisionResult.actor.organizationSlug ?? provisionResult.actor.organizationId ?? "-"})`
4936
+ );
4937
+ lines.push(` - developers origin: ${provisionResult.developersOrigin}`);
4938
+ lines.push(
4939
+ ` - developers app: ${provisionResult.developersApp.id} (listing=${provisionResult.developersApp.listingStatus ?? "-"}, org=${provisionResult.developersApp.organizationId ?? "-"})`
4940
+ );
4941
+ if (provisionResult.authService) {
4942
+ lines.push(
4943
+ ` - auth service: ${provisionResult.authService.id} (status=${provisionResult.authService.status ?? "-"}, url=${provisionResult.authService.baseUrl ?? "-"})`
4944
+ );
4945
+ }
4946
+ if (provisionResult.authConfig) {
4947
+ lines.push(
4948
+ ` - auth config: login=${provisionResult.authConfig.login} landing=${provisionResult.authConfig.landing} app_ui=${provisionResult.authConfig.appUi}`
4949
+ );
4950
+ }
4951
+ }
4889
4952
  return lines.join("\n");
4890
4953
  }
4891
- function resolveScaffoldTarget(target, developer) {
4892
- const normalized = developer ? "developer" : target?.trim().toLowerCase();
4893
- if (!normalized) return "platform";
4894
- if (normalized === "platform" || normalized === "developer") return normalized;
4895
- throw new Error("Invalid --target value. Use platform or developer.");
4954
+ function resolveScaffoldAudience(value) {
4955
+ if (!value) return void 0;
4956
+ const normalized = value.trim().toLowerCase();
4957
+ if (normalized === "business" || normalized === "personal") {
4958
+ return normalized;
4959
+ }
4960
+ throw new Error("Invalid --audience value. Use business or personal.");
4961
+ }
4962
+ function resolveScaffoldTenancy(value) {
4963
+ if (!value) return void 0;
4964
+ const normalized = value.trim().toLowerCase();
4965
+ if (normalized === "organization" || normalized === "household") {
4966
+ return normalized;
4967
+ }
4968
+ throw new Error("Invalid --tenancy value. Use organization or household.");
4969
+ }
4970
+ function resolveScaffoldSurfaceModel(value) {
4971
+ if (!value) return void 0;
4972
+ const normalized = value.trim().toLowerCase();
4973
+ if (normalized === "web_app" || normalized === "mobile_container" || normalized === "mobile_module" || normalized === "messaging_surface") {
4974
+ return normalized;
4975
+ }
4976
+ throw new Error(
4977
+ "Invalid --surface-model value. Use web_app, mobile_container, mobile_module, or messaging_surface."
4978
+ );
4896
4979
  }
4897
4980
  function resolveScaffoldApiOption(api, withApi) {
4898
4981
  const noApi = process.argv.includes("--no-api");
@@ -4916,6 +4999,100 @@ function statusesAtOrWorse(threshold) {
4916
4999
  return ["missing", "needs_review", "partial"];
4917
5000
  }
4918
5001
 
5002
+ // src/commands/platform.ts
5003
+ function registerPlatformCommand(program2) {
5004
+ const platform3 = program2.command("platform").description("Internal Topolo first-party platform and personal app commands");
5005
+ const platformApps = platform3.command("apps").description("Scaffold and operate first-party Topolo platform and personal applications");
5006
+ platformApps.command("scaffold <application>").description(
5007
+ "Generate a first-party Topolo scaffold for the platform or personal portfolio."
5008
+ ).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(
5009
+ "--surface-model <surfaceModel>",
5010
+ "First-party surface model: web_app, mobile_container, mobile_module, or messaging_surface"
5011
+ ).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 the API/service baseline").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
5012
+ "--provision",
5013
+ "Provision the generated first-party app into Topolo Developers and Auth using the authenticated Topolo admin session"
5014
+ ).option("--workflow-only", "Output only the generated GitHub Actions workflow in human mode").action(async (application, opts) => {
5015
+ const mode = opts.json === false || process.argv.includes("--no-json") ? "pretty" : resolveOutputMode(opts.json);
5016
+ try {
5017
+ await requirePlatformAdminSession();
5018
+ if (opts.workflowOnly && opts.write) {
5019
+ throw new Error("--workflow-only cannot be used with --write.");
5020
+ }
5021
+ const api = resolveScaffoldApiOption(opts.api, opts.withApi);
5022
+ const payload = applicationScaffoldPayload(application, {
5023
+ appDir: opts.appDir,
5024
+ name: opts.name,
5025
+ packageName: opts.packageName,
5026
+ productionUrl: opts.productionUrl,
5027
+ service: opts.service,
5028
+ api,
5029
+ browser: opts.browser,
5030
+ target: "platform",
5031
+ portfolio: resolvePlatformPortfolio(opts.portfolio),
5032
+ surfaceModel: resolvePlatformSurfaceModel(opts.surfaceModel),
5033
+ containerAppId: opts.containerAppId
5034
+ });
5035
+ if (opts.workflowOnly && !payload.ciWorkflow) {
5036
+ throw new Error("--workflow-only is only available for first-party scaffolds.");
5037
+ }
5038
+ if (opts.provision && !opts.write) {
5039
+ throw new Error("--provision requires --write so remote first-party records match written scaffold files.");
5040
+ }
5041
+ const writeResult = opts.write ? await writeApplicationScaffold(payload, { repoRoot: opts.repoRoot, force: opts.force }) : null;
5042
+ const provisionResult = opts.provision ? await provisionApplicationScaffold(payload) : null;
5043
+ const output = opts.workflowOnly ? { application: payload.application, ciWorkflowPath: payload.ciWorkflowPath, ciWorkflow: payload.ciWorkflow } : {
5044
+ ...payload,
5045
+ ...writeResult ? { write: writeResult } : {},
5046
+ ...provisionResult ? { provision: provisionResult } : {}
5047
+ };
5048
+ emit(
5049
+ output,
5050
+ mode,
5051
+ () => opts.workflowOnly ? payload.ciWorkflow ?? "" : formatScaffold(payload, writeResult, provisionResult)
5052
+ );
5053
+ } catch (err) {
5054
+ fail(err, mode);
5055
+ }
5056
+ });
5057
+ }
5058
+ function resolvePlatformPortfolio(value) {
5059
+ if (!value) return void 0;
5060
+ const normalized = value.trim().toLowerCase();
5061
+ if (normalized === "platform" || normalized === "personal") {
5062
+ return normalized;
5063
+ }
5064
+ throw new Error("Invalid --portfolio value. Use platform or personal.");
5065
+ }
5066
+ function resolvePlatformSurfaceModel(value) {
5067
+ if (!value) return void 0;
5068
+ const normalized = value.trim().toLowerCase();
5069
+ if (normalized === "web_app" || normalized === "mobile_container" || normalized === "mobile_module" || normalized === "messaging_surface") {
5070
+ return normalized;
5071
+ }
5072
+ throw new Error(
5073
+ "Invalid --surface-model value. Use web_app, mobile_container, mobile_module, or messaging_surface."
5074
+ );
5075
+ }
5076
+ function isPlatformAdminIdentity(identity) {
5077
+ const role = String(identity?.user?.role || "").trim().toLowerCase();
5078
+ const orgSlug = String(identity?.organization?.slug || "").trim().toLowerCase();
5079
+ return orgSlug === "admin" && (role === "platform_super_admin" || role === "platform_admin");
5080
+ }
5081
+ async function requirePlatformAdminSession() {
5082
+ const client = await buildClient();
5083
+ if (!client) {
5084
+ throw new Error(
5085
+ "`topolo platform ...` requires an authenticated Topolo platform-admin session. Run `topolo auth login` first."
5086
+ );
5087
+ }
5088
+ const identity = await client.client.introspect();
5089
+ if (!isPlatformAdminIdentity(identity)) {
5090
+ throw new Error(
5091
+ "`topolo platform ...` is restricted to Topolo platform admins in the Auth `admin` organization."
5092
+ );
5093
+ }
5094
+ }
5095
+
4919
5096
  // src/commands/services.ts
4920
5097
  function registerServicesCommand(program2) {
4921
5098
  program2.command("services").description("List every Topolo service the SDK can route to").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action((opts) => {
@@ -4954,13 +5131,14 @@ function formatRows2(rows) {
4954
5131
  // src/index.ts
4955
5132
  var program = new Command();
4956
5133
  program.name("topolo").description(
4957
- "Topolo platform CLI. Every command is scoped to the authenticated organization \u2014 there is no way to target a different org."
5134
+ "Topolo CLI. Public commands are scoped to the authenticated organization or developer workspace, and first-party Topolo scaffolding lives under the internal `topolo platform` namespace."
4958
5135
  ).version(CLI_VERSION);
4959
5136
  registerAuthCommands(program);
4960
5137
  registerWhoamiCommand(program);
4961
5138
  registerCrmCommands(program);
4962
5139
  registerApiCommand(program);
4963
5140
  registerAppsCommand(program);
5141
+ registerPlatformCommand(program);
4964
5142
  registerServicesCommand(program);
4965
5143
  program.parseAsync(process.argv).catch((err) => {
4966
5144
  process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
- "version": "0.1.11",
4
- "description": "Command-line interface for the Topolo platform. Designed for humans and for agents (Claude, Codex, etc.) that prefer shell invocation.",
3
+ "version": "0.1.13",
4
+ "description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "topolo": "./dist/index.js"