@bluealba/platform-cli 1.2.0-alpha.2 → 1.2.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -204,7 +204,7 @@ function ConfirmPrompt({ value }) {
204
204
  import { Fzf } from "fzf";
205
205
 
206
206
  // src/commands/create-application/create-application.command.ts
207
- import { join as join12, resolve as resolve2 } from "path";
207
+ import { join as join13, resolve as resolve2 } from "path";
208
208
  import { cwd as cwd4 } from "process";
209
209
  import { mkdir as mkdir3 } from "fs/promises";
210
210
 
@@ -730,6 +730,15 @@ async function getDefaults() {
730
730
  const cache = await readCacheFile();
731
731
  return cache.defaults;
732
732
  }
733
+ var SENSITIVE_KEYS = /* @__PURE__ */ new Set(["clientSecret"]);
734
+ function maskSensitiveFields(obj) {
735
+ return JSON.parse(
736
+ JSON.stringify(
737
+ obj,
738
+ (key, value) => SENSITIVE_KEYS.has(key) && typeof value === "string" && value.length > 0 ? "***" : value
739
+ )
740
+ );
741
+ }
733
742
  var LEGACY_CONFIG_FILE = "standalone.json";
734
743
  async function readLegacyConfig(appDir) {
735
744
  try {
@@ -742,6 +751,50 @@ async function readLegacyConfig(appDir) {
742
751
  }
743
752
  }
744
753
 
754
+ // src/utils/workspace-claude-md.ts
755
+ import { join as join12 } from "path";
756
+ import { writeFile as writeFile6 } from "fs/promises";
757
+ async function writeWorkspaceClaudeMd(rootDir, manifest) {
758
+ const { product, applications } = manifest;
759
+ const coreDirName = `${product.name}-core`;
760
+ const appLines = applications.map(
761
+ (app) => `- \`${product.name}-${app.name}/\` \u2014 ${app.displayName}: ${app.description}`
762
+ );
763
+ const appSection = appLines.length > 0 ? `
764
+ ### Applications
765
+
766
+ ${appLines.join("\n")}
767
+ ` : "";
768
+ const content = `# ${product.displayName} Platform
769
+
770
+ This is a **Blue Alba Platform** workspace for \`@${product.organization}\`.
771
+
772
+ For any platform-related task, use the \`/platform\` skill \u2014 it routes to the correct specialized skill automatically.
773
+
774
+ ## Workspace Layout
775
+
776
+ ### Core
777
+
778
+ - \`${coreDirName}/\` \u2014 Core monorepo (bootstrap service, customization UI, local dev environment)
779
+ - \`${coreDirName}/local/\` \u2014 Docker Compose files, .env, SSL certificates
780
+ - \`${coreDirName}/local/platform-docker-compose.yml\` \u2014 Platform infrastructure (nginx, postgres, gateway)
781
+ - \`${coreDirName}/local/${product.name}-core-docker-compose.yml\` \u2014 Core application services
782
+ - \`${coreDirName}/local/.env\` \u2014 Environment variables (shared by all applications)
783
+ ${appSection}
784
+ ## Common Commands
785
+
786
+ \`\`\`sh
787
+ platform install # Install all monorepos
788
+ platform start # Start the full platform locally via Docker
789
+ platform stop # Stop all running containers
790
+ platform status # Show platform health and service status
791
+ platform create-application # Add a new application to this platform
792
+ platform standalone # Run a single application in isolation
793
+ \`\`\`
794
+ `;
795
+ await writeFile6(join12(rootDir, "CLAUDE.md"), content, "utf-8");
796
+ }
797
+
745
798
  // src/commands/create-application/create-application.command.ts
746
799
  var CREATE_APPLICATION_COMMAND_NAME = "create-application";
747
800
  var createApplicationCommand = {
@@ -779,7 +832,7 @@ async function createApplication(params, logger) {
779
832
  platformName = manifest.product.name;
780
833
  organizationName = manifest.product.organization;
781
834
  const localPath = `../${platformName}-${applicationName}`;
782
- applicationDir = resolve2(join12(rootDir, coreDirName), localPath);
835
+ applicationDir = resolve2(join13(rootDir, coreDirName), localPath);
783
836
  } else {
784
837
  if (!params.platformName || !params.organizationName) {
785
838
  logger.log("Error: platformName and organizationName are required when creating an application outside a platform.");
@@ -787,10 +840,10 @@ async function createApplication(params, logger) {
787
840
  }
788
841
  platformName = params.platformName;
789
842
  organizationName = params.organizationName;
790
- applicationDir = join12(cwd4(), `${platformName}-${applicationName}`);
843
+ applicationDir = join13(cwd4(), `${platformName}-${applicationName}`);
791
844
  }
792
845
  const bootstrapServiceName = `${platformName}-${applicationName}-bootstrap-service`;
793
- const bootstrapServiceDir = join12(applicationDir, "services", bootstrapServiceName);
846
+ const bootstrapServiceDir = join13(applicationDir, "services", bootstrapServiceName);
794
847
  logger.log(`Creating application monorepo "${applicationName}"...`);
795
848
  try {
796
849
  await scaffoldApplicationMonorepo(applicationDir, organizationName, platformName, applicationName, logger);
@@ -798,8 +851,8 @@ async function createApplication(params, logger) {
798
851
  logger.log(`Error: Could not scaffold application monorepo \u2014 ${formatError(err)}`);
799
852
  return;
800
853
  }
801
- await mkdir3(join12(applicationDir, "services"), { recursive: true });
802
- await mkdir3(join12(applicationDir, "ui"), { recursive: true });
854
+ await mkdir3(join13(applicationDir, "services"), { recursive: true });
855
+ await mkdir3(join13(applicationDir, "ui"), { recursive: true });
803
856
  logger.log(`Creating bootstrap service "${bootstrapServiceName}"...`);
804
857
  const bootstrapServiceBaseDir = `${platformName}-${applicationName}/services`;
805
858
  try {
@@ -833,7 +886,7 @@ async function createApplication(params, logger) {
833
886
  }
834
887
  if (hasUserInterface) {
835
888
  const uiName = `${platformName}-${applicationName}-ui`;
836
- const uiDir = join12(applicationDir, "ui", uiName);
889
+ const uiDir = join13(applicationDir, "ui", uiName);
837
890
  const uiBaseDir = `${platformName}-${applicationName}/ui`;
838
891
  try {
839
892
  await scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
@@ -844,7 +897,7 @@ async function createApplication(params, logger) {
844
897
  }
845
898
  if (hasBackendService) {
846
899
  const serviceName = `${platformName}-${applicationName}-service`;
847
- const serviceDir = join12(applicationDir, "services", serviceName);
900
+ const serviceDir = join13(applicationDir, "services", serviceName);
848
901
  const serviceBaseDir = `${platformName}-${applicationName}/services`;
849
902
  try {
850
903
  await scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger);
@@ -853,7 +906,7 @@ async function createApplication(params, logger) {
853
906
  return;
854
907
  }
855
908
  }
856
- const dockerComposePath = join12(applicationDir, "docker-compose.yml");
909
+ const dockerComposePath = join13(applicationDir, "docker-compose.yml");
857
910
  let uiPort;
858
911
  let servicePort;
859
912
  if (insidePlatform && localDir && manifest) {
@@ -889,6 +942,12 @@ async function createApplication(params, logger) {
889
942
  } catch (err) {
890
943
  logger.log(`Warning: Could not update product manifest \u2014 ${formatError(err)}`);
891
944
  }
945
+ try {
946
+ await writeWorkspaceClaudeMd(rootDir, updatedManifest);
947
+ logger.log(`Updated workspace CLAUDE.md.`);
948
+ } catch (err) {
949
+ logger.log(`Warning: Could not update workspace CLAUDE.md \u2014 ${formatError(err)}`);
950
+ }
892
951
  } else {
893
952
  try {
894
953
  await saveDefaults({ platformName, organizationName });
@@ -906,7 +965,7 @@ async function createApplication(params, logger) {
906
965
  }
907
966
 
908
967
  // src/commands/init/init.command.ts
909
- import { join as join18 } from "path";
968
+ import { join as join19 } from "path";
910
969
  import { cwd as cwd5 } from "process";
911
970
 
912
971
  // src/utils/string.ts
@@ -916,8 +975,8 @@ function camelize(name) {
916
975
 
917
976
  // src/commands/init/scaffold-platform.ts
918
977
  import { fileURLToPath as fileURLToPath6 } from "url";
919
- import { join as join13, dirname as dirname8 } from "path";
920
- var templateDir = join13(
978
+ import { join as join14, dirname as dirname8 } from "path";
979
+ var templateDir = join14(
921
980
  dirname8(fileURLToPath6(import.meta.url)),
922
981
  "..",
923
982
  "templates",
@@ -929,8 +988,8 @@ async function scaffoldPlatform(outputDir, variables, logger) {
929
988
 
930
989
  // src/commands/init/scaffold-platform-bootstrap.ts
931
990
  import { fileURLToPath as fileURLToPath7 } from "url";
932
- import { join as join14, dirname as dirname9 } from "path";
933
- var templateDir2 = join14(
991
+ import { join as join15, dirname as dirname9 } from "path";
992
+ var templateDir2 = join15(
934
993
  dirname9(fileURLToPath7(import.meta.url)),
935
994
  "..",
936
995
  "templates",
@@ -942,8 +1001,8 @@ async function scaffoldPlatformBootstrap(outputDir, variables, logger) {
942
1001
 
943
1002
  // src/commands/init/scaffold-customization-ui.ts
944
1003
  import { fileURLToPath as fileURLToPath8 } from "url";
945
- import { join as join15, dirname as dirname10 } from "path";
946
- var templateDir3 = join15(
1004
+ import { join as join16, dirname as dirname10 } from "path";
1005
+ var templateDir3 = join16(
947
1006
  dirname10(fileURLToPath8(import.meta.url)),
948
1007
  "..",
949
1008
  "templates",
@@ -954,10 +1013,10 @@ async function scaffoldCustomizationUi(outputDir, variables, logger) {
954
1013
  }
955
1014
 
956
1015
  // src/commands/init/register-customization-module.ts
957
- import { join as join16 } from "path";
958
- import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
1016
+ import { join as join17 } from "path";
1017
+ import { readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
959
1018
  async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger, platformName = "platform") {
960
- const modulesJsonPath = join16(bootstrapServiceDir, "src", "data", "platform", "modules.json");
1019
+ const modulesJsonPath = join17(bootstrapServiceDir, "src", "data", "platform", "modules.json");
961
1020
  const customizationUiName = `${platformName}-customization-ui`;
962
1021
  const entry = {
963
1022
  name: `@${organizationName}/${customizationUiName}`,
@@ -975,13 +1034,13 @@ async function registerCustomizationModule(bootstrapServiceDir, organizationName
975
1034
  };
976
1035
  const existing = JSON.parse(await readFile6(modulesJsonPath, "utf-8"));
977
1036
  existing.push(entry);
978
- await writeFile6(modulesJsonPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
1037
+ await writeFile7(modulesJsonPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
979
1038
  logger.log(`Registered customization module in ${modulesJsonPath}`);
980
1039
  }
981
1040
 
982
1041
  // src/commands/init/generate-local-env.ts
983
- import { readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
984
- import { join as join17 } from "path";
1042
+ import { readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
1043
+ import { join as join18 } from "path";
985
1044
 
986
1045
  // src/utils/random.ts
987
1046
  import { randomBytes } from "crypto";
@@ -991,14 +1050,14 @@ function generateRandomSecret() {
991
1050
 
992
1051
  // src/commands/init/generate-local-env.ts
993
1052
  async function generateLocalEnv(outputDir, logger, coreDirName = "core") {
994
- const examplePath = join17(outputDir, coreDirName, "local", ".env.example");
995
- const envPath = join17(outputDir, coreDirName, "local", ".env");
1053
+ const examplePath = join18(outputDir, coreDirName, "local", ".env.example");
1054
+ const envPath = join18(outputDir, coreDirName, "local", ".env");
996
1055
  logger.log(`Generating ${coreDirName}/local/.env with random secrets...`);
997
1056
  const content = await readFile7(examplePath, "utf-8");
998
1057
  const result = content.replace(/^(PAE_AUTH_JWT_SECRET|PAE_GATEWAY_SERVICE_ACCESS_SECRET|PAE_DB_PASSWORD)=$/gm, (_, key) => {
999
1058
  return `${key}=${generateRandomSecret()}`;
1000
1059
  });
1001
- await writeFile7(envPath, result, "utf-8");
1060
+ await writeFile8(envPath, result, "utf-8");
1002
1061
  }
1003
1062
 
1004
1063
  // src/commands/init/init.command.ts
@@ -1044,17 +1103,24 @@ async function init(params, logger) {
1044
1103
  logger.log(`Error: Could not generate ${coreDirName}/local/.env \u2014 ${formatError(err)}`);
1045
1104
  return;
1046
1105
  }
1106
+ let manifest;
1047
1107
  try {
1048
- const manifest = createInitialManifest({ organizationName, platformName, platformDisplayName });
1108
+ manifest = createInitialManifest({ organizationName, platformName, platformDisplayName });
1049
1109
  await writeManifest(manifest, outputDir, coreDirName);
1050
1110
  logger.log(`Created product manifest: ${coreDirName}/product.manifest.json`);
1051
1111
  } catch (err) {
1052
1112
  logger.log(`Error: Could not write product manifest \u2014 ${formatError(err)}`);
1053
1113
  return;
1054
1114
  }
1115
+ try {
1116
+ await writeWorkspaceClaudeMd(outputDir, manifest);
1117
+ logger.log(`Created workspace CLAUDE.md`);
1118
+ } catch (err) {
1119
+ logger.log(`Warning: Could not write workspace CLAUDE.md \u2014 ${formatError(err)}`);
1120
+ }
1055
1121
  try {
1056
1122
  await scaffoldPlatformBootstrap(
1057
- join18(outputDir, coreDirName, "services", bootstrapServiceName),
1123
+ join19(outputDir, coreDirName, "services", bootstrapServiceName),
1058
1124
  variables,
1059
1125
  logger
1060
1126
  );
@@ -1064,7 +1130,7 @@ async function init(params, logger) {
1064
1130
  }
1065
1131
  try {
1066
1132
  await scaffoldCustomizationUi(
1067
- join18(outputDir, coreDirName, "ui", customizationUiName),
1133
+ join19(outputDir, coreDirName, "ui", customizationUiName),
1068
1134
  variables,
1069
1135
  logger
1070
1136
  );
@@ -1074,7 +1140,7 @@ async function init(params, logger) {
1074
1140
  }
1075
1141
  try {
1076
1142
  await registerCustomizationModule(
1077
- join18(outputDir, coreDirName, "services", bootstrapServiceName),
1143
+ join19(outputDir, coreDirName, "services", bootstrapServiceName),
1078
1144
  organizationName,
1079
1145
  logger,
1080
1146
  platformName
@@ -1087,7 +1153,7 @@ async function init(params, logger) {
1087
1153
  }
1088
1154
 
1089
1155
  // src/commands/configure-idp/configure-idp.command.ts
1090
- import { join as join19 } from "path";
1156
+ import { join as join20 } from "path";
1091
1157
  import { fetch as undiciFetch, Agent } from "undici";
1092
1158
 
1093
1159
  // src/utils/env-reader.ts
@@ -1171,7 +1237,7 @@ async function configureIdp(params, logger) {
1171
1237
  logger.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
1172
1238
  return;
1173
1239
  }
1174
- const envPath = join19(layout.localDir, ".env");
1240
+ const envPath = join20(layout.localDir, ".env");
1175
1241
  let env;
1176
1242
  try {
1177
1243
  env = await readEnvFile(envPath);
@@ -1223,13 +1289,13 @@ async function configureIdp(params, logger) {
1223
1289
  }
1224
1290
 
1225
1291
  // src/commands/create-service-module/create-service-module.command.ts
1226
- import { join as join21, resolve as resolve3 } from "path";
1292
+ import { join as join22, resolve as resolve3 } from "path";
1227
1293
  import { access as access4 } from "fs/promises";
1228
1294
 
1229
1295
  // src/commands/create-service-module/scaffold-service-module.ts
1230
1296
  import { fileURLToPath as fileURLToPath9 } from "url";
1231
- import { join as join20, dirname as dirname11 } from "path";
1232
- var nestjsServiceModuleTemplateDir2 = join20(
1297
+ import { join as join21, dirname as dirname11 } from "path";
1298
+ var nestjsServiceModuleTemplateDir2 = join21(
1233
1299
  dirname11(fileURLToPath9(import.meta.url)),
1234
1300
  "..",
1235
1301
  "templates",
@@ -1264,7 +1330,7 @@ function buildCustomServiceModuleEntry(organizationName, serviceName, serviceDis
1264
1330
  }
1265
1331
 
1266
1332
  // src/commands/create-service-module/append-docker-compose.ts
1267
- import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
1333
+ import { readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
1268
1334
  async function appendServiceToDockerCompose(dockerComposePath, serviceName, platformName, applicationName, port, logger) {
1269
1335
  const block = `
1270
1336
  ${serviceName}:
@@ -1280,7 +1346,7 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, plat
1280
1346
  `;
1281
1347
  try {
1282
1348
  const existing = await readFile9(dockerComposePath, "utf-8");
1283
- await writeFile8(dockerComposePath, existing + block, "utf-8");
1349
+ await writeFile9(dockerComposePath, existing + block, "utf-8");
1284
1350
  logger.log(`Updated docker-compose: ${dockerComposePath}`);
1285
1351
  } catch (err) {
1286
1352
  const message = err instanceof Error ? err.message : String(err);
@@ -1316,7 +1382,7 @@ async function createServiceModule(params, logger) {
1316
1382
  logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
1317
1383
  return;
1318
1384
  }
1319
- const applicationDir = resolve3(join21(rootDir, coreDirName), appEntry.localPath);
1385
+ const applicationDir = resolve3(join22(rootDir, coreDirName), appEntry.localPath);
1320
1386
  try {
1321
1387
  await access4(applicationDir);
1322
1388
  } catch {
@@ -1325,7 +1391,7 @@ async function createServiceModule(params, logger) {
1325
1391
  }
1326
1392
  const suffix = serviceNameSuffix === void 0 ? "service" : serviceNameSuffix;
1327
1393
  const fullServiceName = suffix ? `${platformName}-${serviceName}-${suffix}` : `${platformName}-${serviceName}`;
1328
- const serviceDir = join21(applicationDir, "services", fullServiceName);
1394
+ const serviceDir = join22(applicationDir, "services", fullServiceName);
1329
1395
  try {
1330
1396
  await access4(serviceDir);
1331
1397
  logger.log(`Error: A service named "${fullServiceName}" already exists in application "${applicationName}".`);
@@ -1346,21 +1412,21 @@ async function createServiceModule(params, logger) {
1346
1412
  logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
1347
1413
  return;
1348
1414
  }
1349
- const bootstrapServiceDir = join21(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1415
+ const bootstrapServiceDir = join22(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1350
1416
  const moduleEntry = buildCustomServiceModuleEntry(organizationName, fullServiceName, serviceDisplayName);
1351
1417
  await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
1352
- const dockerComposePath = join21(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1418
+ const dockerComposePath = join22(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1353
1419
  const port = await getNextAvailablePort(localDir);
1354
1420
  await appendServiceToDockerCompose(dockerComposePath, fullServiceName, platformName, applicationName, port, logger);
1355
1421
  logger.log(`Done! Service module "${fullServiceName}" added to application "${applicationName}".`);
1356
1422
  }
1357
1423
 
1358
1424
  // src/commands/create-ui-module/create-ui-module.command.ts
1359
- import { join as join22, resolve as resolve4 } from "path";
1425
+ import { join as join23, resolve as resolve4 } from "path";
1360
1426
  import { access as access5, readdir as readdir4 } from "fs/promises";
1361
1427
 
1362
1428
  // src/commands/create-ui-module/append-ui-docker-compose.ts
1363
- import { readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
1429
+ import { readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
1364
1430
  async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiNameOverride) {
1365
1431
  const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
1366
1432
  const block = `
@@ -1375,7 +1441,7 @@ async function appendUiToDockerCompose(dockerComposePath, platformName, applicat
1375
1441
  `;
1376
1442
  try {
1377
1443
  const existing = await readFile10(dockerComposePath, "utf-8");
1378
- await writeFile9(dockerComposePath, existing + block, "utf-8");
1444
+ await writeFile10(dockerComposePath, existing + block, "utf-8");
1379
1445
  logger.log(`Updated docker-compose: ${dockerComposePath}`);
1380
1446
  } catch (err) {
1381
1447
  const message = err instanceof Error ? err.message : String(err);
@@ -1411,14 +1477,14 @@ async function createUiModule(params, logger) {
1411
1477
  logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
1412
1478
  return;
1413
1479
  }
1414
- const applicationDir = resolve4(join22(rootDir, coreDirName), appEntry.localPath);
1480
+ const applicationDir = resolve4(join23(rootDir, coreDirName), appEntry.localPath);
1415
1481
  try {
1416
1482
  await access5(applicationDir);
1417
1483
  } catch {
1418
1484
  logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
1419
1485
  return;
1420
1486
  }
1421
- const uiDir = join22(applicationDir, "ui");
1487
+ const uiDir = join23(applicationDir, "ui");
1422
1488
  try {
1423
1489
  const uiEntries = await readdir4(uiDir);
1424
1490
  const existingUiModules = uiEntries.filter((e) => e !== ".gitkeep");
@@ -1430,7 +1496,7 @@ async function createUiModule(params, logger) {
1430
1496
  }
1431
1497
  const uiSuffix = uiModuleSuffix === void 0 ? "ui" : uiModuleSuffix;
1432
1498
  const uiName = uiSuffix ? `${platformName}-${applicationName}-${uiSuffix}` : `${platformName}-${applicationName}`;
1433
- const uiOutputDir = join22(uiDir, uiName);
1499
+ const uiOutputDir = join23(uiDir, uiName);
1434
1500
  const uiBaseDir = `${platformName}-${applicationName}/ui`;
1435
1501
  try {
1436
1502
  await scaffoldUiModule(uiOutputDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
@@ -1438,10 +1504,10 @@ async function createUiModule(params, logger) {
1438
1504
  logger.log(`Error: Could not scaffold UI module \u2014 ${formatError(err)}`);
1439
1505
  return;
1440
1506
  }
1441
- const bootstrapServiceDir = join22(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1507
+ const bootstrapServiceDir = join23(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1442
1508
  const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiName);
1443
1509
  await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
1444
- const dockerComposePath = join22(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1510
+ const dockerComposePath = join23(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1445
1511
  const port = await getNextAvailablePort(localDir);
1446
1512
  await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiName);
1447
1513
  logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
@@ -1450,13 +1516,13 @@ async function createUiModule(params, logger) {
1450
1516
  // src/commands/status/status-checks.ts
1451
1517
  import { spawn as spawn3 } from "child_process";
1452
1518
  import { access as access8 } from "fs/promises";
1453
- import { join as join25, resolve as resolve7 } from "path";
1519
+ import { join as join26, resolve as resolve7 } from "path";
1454
1520
  import { fetch as undiciFetch2, Agent as Agent2 } from "undici";
1455
1521
 
1456
1522
  // src/commands/local-scripts/docker-compose-orchestrator.ts
1457
1523
  import { spawn } from "child_process";
1458
- import { access as access6 } from "fs/promises";
1459
- import { join as join23, resolve as resolve5 } from "path";
1524
+ import { access as access6, readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
1525
+ import { dirname as dirname12, join as join24, resolve as resolve5 } from "path";
1460
1526
  function runDockerCompose(args2, logger, rootDir, signal) {
1461
1527
  return new Promise((resolvePromise) => {
1462
1528
  const child = spawn("docker", ["compose", ...args2], {
@@ -1519,17 +1585,30 @@ function captureDockerCompose(args2, rootDir) {
1519
1585
  child.on("error", (err) => reject(err));
1520
1586
  });
1521
1587
  }
1588
+ async function generateProxyAppCompose(appDir, localDir, platformName, applicationName) {
1589
+ const appComposePath = join24(appDir, "docker-compose.yml");
1590
+ const content = await readFile11(appComposePath, "utf-8");
1591
+ const withAbsContexts = content.replace(
1592
+ /^(\s+context:\s+)\.\/(.*)$/gm,
1593
+ `$1${appDir}/$2`
1594
+ );
1595
+ const appParentDir = dirname12(appDir);
1596
+ const rewritten = withAbsContexts.replace(/\$\{PWD\}/g, appParentDir);
1597
+ const proxyPath = join24(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1598
+ await writeFile11(proxyPath, rewritten, "utf-8");
1599
+ return proxyPath;
1600
+ }
1522
1601
  async function getAppComposePaths(localDir, coreDir, platformName, manifest, logger) {
1523
1602
  const results = [];
1524
1603
  for (const app of manifest.applications) {
1525
- const appDir = resolve5(join23(coreDir), app.localPath);
1526
- const newPath = join23(appDir, "docker-compose.yml");
1527
- const legacyPrefixedPath = join23(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1528
- const legacyUnprefixedPath = join23(localDir, `${app.name}-docker-compose.yml`);
1604
+ const appDir = resolve5(join24(coreDir), app.localPath);
1605
+ const newPath = join24(appDir, "docker-compose.yml");
1606
+ const legacyPrefixedPath = join24(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1607
+ const legacyUnprefixedPath = join24(localDir, `${app.name}-docker-compose.yml`);
1529
1608
  let resolved = null;
1530
1609
  try {
1531
1610
  await access6(newPath);
1532
- resolved = newPath;
1611
+ resolved = await generateProxyAppCompose(appDir, localDir, platformName, app.name);
1533
1612
  } catch {
1534
1613
  try {
1535
1614
  await access6(legacyPrefixedPath);
@@ -1553,8 +1632,8 @@ async function getAppComposePaths(localDir, coreDir, platformName, manifest, log
1553
1632
  async function buildFullComposeArgs(layout, manifest, logger) {
1554
1633
  const { coreDirName, localDir, coreDir } = layout;
1555
1634
  const platformName = manifest.product.name;
1556
- const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
1557
- const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
1635
+ const prefixedCoreCompose = join24(localDir, `${coreDirName}-docker-compose.yml`);
1636
+ const unprefixedCoreCompose = join24(localDir, "core-docker-compose.yml");
1558
1637
  let coreComposePath;
1559
1638
  try {
1560
1639
  await access6(prefixedCoreCompose);
@@ -1564,7 +1643,7 @@ async function buildFullComposeArgs(layout, manifest, logger) {
1564
1643
  }
1565
1644
  const fileArgs = [
1566
1645
  "-f",
1567
- join23(localDir, "platform-docker-compose.yml"),
1646
+ join24(localDir, "platform-docker-compose.yml"),
1568
1647
  "-f",
1569
1648
  coreComposePath
1570
1649
  ];
@@ -1584,8 +1663,8 @@ async function buildSelectedComposeFiles(layout, selectedManifest, includeCore)
1584
1663
  const platformName = selectedManifest.product.name;
1585
1664
  const files = [];
1586
1665
  if (includeCore) {
1587
- const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
1588
- const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
1666
+ const prefixedCoreCompose = join24(localDir, `${coreDirName}-docker-compose.yml`);
1667
+ const unprefixedCoreCompose = join24(localDir, "core-docker-compose.yml");
1589
1668
  let coreComposePath;
1590
1669
  try {
1591
1670
  await access6(prefixedCoreCompose);
@@ -1593,7 +1672,7 @@ async function buildSelectedComposeFiles(layout, selectedManifest, includeCore)
1593
1672
  } catch {
1594
1673
  coreComposePath = unprefixedCoreCompose;
1595
1674
  }
1596
- files.push(join23(localDir, "platform-docker-compose.yml"), coreComposePath);
1675
+ files.push(join24(localDir, "platform-docker-compose.yml"), coreComposePath);
1597
1676
  }
1598
1677
  const appEntries = await getAppComposePaths(localDir, coreDir, platformName, selectedManifest);
1599
1678
  for (const { composePath } of appEntries) {
@@ -1637,7 +1716,7 @@ async function getServicesFromComposeFiles(selectedFiles, allFiles, rootDir) {
1637
1716
  async function startEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
1638
1717
  const { rootDir, localDir } = layout;
1639
1718
  const platformName = manifest.product.name;
1640
- const envFile = join23(localDir, ".env");
1719
+ const envFile = join24(localDir, ".env");
1641
1720
  const isSelective = fullManifest !== void 0;
1642
1721
  const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
1643
1722
  const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
@@ -1659,7 +1738,7 @@ async function startEnvironment(layout, manifest, logger, signal, includeCore =
1659
1738
  async function stopEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
1660
1739
  const { rootDir, localDir } = layout;
1661
1740
  const platformName = manifest.product.name;
1662
- const envFile = join23(localDir, ".env");
1741
+ const envFile = join24(localDir, ".env");
1663
1742
  const isSelective = fullManifest !== void 0;
1664
1743
  const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
1665
1744
  const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
@@ -1681,7 +1760,7 @@ async function stopEnvironment(layout, manifest, logger, signal, includeCore = t
1681
1760
  async function resetEnvironment(layout, manifest, logger, signal) {
1682
1761
  const { rootDir, localDir } = layout;
1683
1762
  const platformName = manifest.product.name;
1684
- const envFile = join23(localDir, ".env");
1763
+ const envFile = join24(localDir, ".env");
1685
1764
  const fileArgs = await buildFullComposeArgs(layout, manifest, logger);
1686
1765
  const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
1687
1766
  await runDockerCompose([...projectArgs, ...fileArgs, "down", "-v"], logger, rootDir, signal);
@@ -1689,7 +1768,7 @@ async function resetEnvironment(layout, manifest, logger, signal) {
1689
1768
  async function destroyEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest, removeImages = true) {
1690
1769
  const { rootDir, localDir } = layout;
1691
1770
  const platformName = manifest.product.name;
1692
- const envFile = join23(localDir, ".env");
1771
+ const envFile = join24(localDir, ".env");
1693
1772
  const isSelective = fullManifest !== void 0;
1694
1773
  const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
1695
1774
  const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
@@ -1716,7 +1795,7 @@ async function destroyEnvironment(layout, manifest, logger, signal, includeCore
1716
1795
  // src/commands/local-scripts/npm-orchestrator.ts
1717
1796
  import { spawn as spawn2 } from "child_process";
1718
1797
  import { access as access7 } from "fs/promises";
1719
- import { resolve as resolve6, join as join24 } from "path";
1798
+ import { resolve as resolve6, join as join25 } from "path";
1720
1799
  function runCommand(command, args2, workDir, logger, signal) {
1721
1800
  return new Promise((resolvePromise) => {
1722
1801
  const child = spawn2(command, args2, {
@@ -1773,7 +1852,7 @@ async function installDependencies(layout, manifest, logger, signal, includeCore
1773
1852
  const { coreDir, coreDirName } = layout;
1774
1853
  const appDirs = [];
1775
1854
  for (const app of manifest.applications) {
1776
- const appDir = resolve6(join24(coreDir), app.localPath);
1855
+ const appDir = resolve6(join25(coreDir), app.localPath);
1777
1856
  if (!await dirExists(appDir)) {
1778
1857
  logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
1779
1858
  continue;
@@ -1799,7 +1878,7 @@ async function buildAll(layout, manifest, logger, signal, includeCore = true) {
1799
1878
  }
1800
1879
  const appDirs = [];
1801
1880
  for (const app of manifest.applications) {
1802
- const appDir = resolve6(join24(coreDir), app.localPath);
1881
+ const appDir = resolve6(join25(coreDir), app.localPath);
1803
1882
  if (!await dirExists(appDir)) {
1804
1883
  logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
1805
1884
  continue;
@@ -1980,13 +2059,13 @@ async function checkInstalled(layout, manifest) {
1980
2059
  const results = [];
1981
2060
  const coreCheck = {
1982
2061
  name: layout.coreDirName,
1983
- path: join25(layout.coreDir, "node_modules"),
1984
- exists: await pathExists(join25(layout.coreDir, "node_modules"))
2062
+ path: join26(layout.coreDir, "node_modules"),
2063
+ exists: await pathExists(join26(layout.coreDir, "node_modules"))
1985
2064
  };
1986
2065
  results.push(coreCheck);
1987
2066
  for (const app of manifest.applications) {
1988
2067
  const appDir = resolve7(layout.coreDir, app.localPath);
1989
- const nodeModulesPath = join25(appDir, "node_modules");
2068
+ const nodeModulesPath = join26(appDir, "node_modules");
1990
2069
  results.push({
1991
2070
  name: app.name,
1992
2071
  path: nodeModulesPath,
@@ -1997,7 +2076,7 @@ async function checkInstalled(layout, manifest) {
1997
2076
  }
1998
2077
  async function checkBuilt(layout, manifest) {
1999
2078
  const results = [];
2000
- const coreTurboPath = join25(layout.coreDir, ".turbo");
2079
+ const coreTurboPath = join26(layout.coreDir, ".turbo");
2001
2080
  results.push({
2002
2081
  name: layout.coreDirName,
2003
2082
  path: coreTurboPath,
@@ -2005,7 +2084,7 @@ async function checkBuilt(layout, manifest) {
2005
2084
  });
2006
2085
  for (const app of manifest.applications) {
2007
2086
  const appDir = resolve7(layout.coreDir, app.localPath);
2008
- const appTurboPath = join25(appDir, ".turbo");
2087
+ const appTurboPath = join26(appDir, ".turbo");
2009
2088
  results.push({
2010
2089
  name: app.name,
2011
2090
  path: appTurboPath,
@@ -2017,13 +2096,13 @@ async function checkBuilt(layout, manifest) {
2017
2096
  async function resolveComposeFiles(layout, manifest) {
2018
2097
  const { localDir, coreDirName } = layout;
2019
2098
  const platformName = manifest.product.name;
2020
- const files = [join25(localDir, "platform-docker-compose.yml")];
2021
- const prefixedCore = join25(localDir, `${coreDirName}-docker-compose.yml`);
2022
- const unprefixedCore = join25(localDir, "core-docker-compose.yml");
2099
+ const files = [join26(localDir, "platform-docker-compose.yml")];
2100
+ const prefixedCore = join26(localDir, `${coreDirName}-docker-compose.yml`);
2101
+ const unprefixedCore = join26(localDir, "core-docker-compose.yml");
2023
2102
  files.push(await pathExists(prefixedCore) ? prefixedCore : unprefixedCore);
2024
2103
  for (const app of manifest.applications) {
2025
- const prefixed = join25(localDir, `${platformName}-${app.name}-docker-compose.yml`);
2026
- const unprefixed = join25(localDir, `${app.name}-docker-compose.yml`);
2104
+ const prefixed = join26(localDir, `${platformName}-${app.name}-docker-compose.yml`);
2105
+ const unprefixed = join26(localDir, `${app.name}-docker-compose.yml`);
2027
2106
  if (await pathExists(prefixed)) files.push(prefixed);
2028
2107
  else if (await pathExists(unprefixed)) files.push(unprefixed);
2029
2108
  }
@@ -2129,15 +2208,15 @@ async function getServicesForComposeFiles(selectedFiles, allFiles, rootDir) {
2129
2208
  async function checkContainersPerApp(layout, manifest, allContainers) {
2130
2209
  const { localDir, coreDirName, rootDir } = layout;
2131
2210
  const platformName = manifest.product.name;
2132
- const prefixedCore = join25(localDir, `${coreDirName}-docker-compose.yml`);
2133
- const unprefixedCore = join25(localDir, "core-docker-compose.yml");
2211
+ const prefixedCore = join26(localDir, `${coreDirName}-docker-compose.yml`);
2212
+ const unprefixedCore = join26(localDir, "core-docker-compose.yml");
2134
2213
  const coreComposePath = await pathExists(prefixedCore) ? prefixedCore : unprefixedCore;
2135
- const platformComposePath = join25(localDir, "platform-docker-compose.yml");
2214
+ const platformComposePath = join26(localDir, "platform-docker-compose.yml");
2136
2215
  const allFiles = [platformComposePath, coreComposePath];
2137
2216
  const appComposeMap = /* @__PURE__ */ new Map();
2138
2217
  for (const app of manifest.applications) {
2139
- const prefixed = join25(localDir, `${platformName}-${app.name}-docker-compose.yml`);
2140
- const unprefixed = join25(localDir, `${app.name}-docker-compose.yml`);
2218
+ const prefixed = join26(localDir, `${platformName}-${app.name}-docker-compose.yml`);
2219
+ const unprefixed = join26(localDir, `${app.name}-docker-compose.yml`);
2141
2220
  if (await pathExists(prefixed)) {
2142
2221
  appComposeMap.set(app.name, prefixed);
2143
2222
  allFiles.push(prefixed);
@@ -2184,7 +2263,7 @@ async function checkContainersPerApp(layout, manifest, allContainers) {
2184
2263
  return groups;
2185
2264
  }
2186
2265
  async function checkIdpProviders(layout) {
2187
- const envPath = join25(layout.localDir, ".env");
2266
+ const envPath = join26(layout.localDir, ".env");
2188
2267
  let env;
2189
2268
  try {
2190
2269
  env = await readEnvFile(envPath);
@@ -2303,7 +2382,7 @@ var statusCommand = {
2303
2382
  };
2304
2383
 
2305
2384
  // src/commands/manage-platform-admins/manage-platform-admins.command.ts
2306
- import { join as join26 } from "path";
2385
+ import { join as join27 } from "path";
2307
2386
  import { fetch as undiciFetch3, Agent as Agent3 } from "undici";
2308
2387
  var MANAGE_PLATFORM_ADMINS_COMMAND_NAME = "manage-platform-admins";
2309
2388
  var managePlatformAdminsCommand = {
@@ -2317,7 +2396,7 @@ async function getGatewayConfig(logger) {
2317
2396
  logger.log("Error: Cannot manage platform admins \u2014 no platform initialized in this directory.");
2318
2397
  return null;
2319
2398
  }
2320
- const envPath = join26(layout.localDir, ".env");
2399
+ const envPath = join27(layout.localDir, ".env");
2321
2400
  let env;
2322
2401
  try {
2323
2402
  env = await readEnvFile(envPath);
@@ -2439,59 +2518,59 @@ async function removePlatformAdmin(ruleId, logger) {
2439
2518
  var baPlatformPlugin = {
2440
2519
  name: "ba-platform-plugin",
2441
2520
  description: "Blue Alba Platform knowledge and CLI skills for AI assistants",
2442
- version: "1.1.0",
2521
+ version: "1.2.0",
2443
2522
  skills: [
2444
2523
  {
2445
2524
  name: "platform",
2446
- description: "Comprehensive Blue Alba Platform architecture and concepts knowledge",
2525
+ description: "Blue Alba Platform orchestrator \u2014 routes to the correct specialized skill or answers general architecture questions. Use this skill for ANY platform-related request.",
2447
2526
  type: "skill",
2448
2527
  sourceFile: "skills/ba-platform/platform.skill.md"
2449
2528
  },
2450
2529
  {
2451
2530
  name: "platform-cli",
2452
- description: "Blue Alba Platform CLI commands and usage knowledge",
2531
+ description: "Blue Alba Platform CLI commands, usage, installation, and headless mode reference",
2453
2532
  type: "skill",
2454
2533
  sourceFile: "skills/ba-platform/platform-cli.skill.md"
2455
2534
  },
2456
2535
  {
2457
2536
  name: "platform-add-operation",
2458
- description: "Defines a new authorization operation in the bootstrap service, assigns it to a role, and adds access guards in the React UI or NestJS backend",
2537
+ description: "Adds an authorization operation (permission/RBAC guard) to a bootstrap service and enforces it in UI or backend routes",
2459
2538
  type: "skill",
2460
2539
  sourceFile: "skills/ba-platform/platform-add-operation.skill.md"
2461
2540
  },
2462
2541
  {
2463
2542
  name: "platform-add-feature-flag",
2464
- description: "Declares a feature flag in the bootstrap service and adds its usage in a React UI component or NestJS service",
2543
+ description: "Declares a feature flag in a bootstrap service and gates a React component or NestJS endpoint behind it",
2465
2544
  type: "skill",
2466
2545
  sourceFile: "skills/ba-platform/platform-add-feature-flag.skill.md"
2467
2546
  },
2468
2547
  {
2469
2548
  name: "platform-add-scheduled-job",
2470
- description: "Adds a scheduled job to a Blue Alba Platform bootstrap service",
2549
+ description: "Adds a cron-scheduled job to a bootstrap service to run a backend endpoint on a recurring schedule",
2471
2550
  type: "skill",
2472
2551
  sourceFile: "skills/ba-platform/platform-add-scheduled-job.skill.md"
2473
2552
  },
2474
2553
  {
2475
2554
  name: "platform-add-menu-item",
2476
- description: "Adds a new page and menu item to a Blue Alba Platform single-spa UI module",
2555
+ description: "Creates a new page component and wires it as a menu item/route in a single-spa UI module",
2477
2556
  type: "skill",
2478
2557
  sourceFile: "skills/ba-platform/platform-add-menu-item.skill.md"
2479
2558
  },
2480
2559
  {
2481
2560
  name: "platform-extend-shell",
2482
- description: "Injects a custom component into a Blue Alba Platform shell extension point",
2561
+ description: "Injects a custom component into a shell extension point (navbar, branding, user section)",
2483
2562
  type: "skill",
2484
2563
  sourceFile: "skills/ba-platform/platform-extend-shell.skill.md"
2485
2564
  },
2486
2565
  {
2487
2566
  name: "platform-add-presence",
2488
- description: "Adds real-time user presence via the Rooms API to a Blue Alba Platform app",
2567
+ description: "Adds real-time user presence (live avatars, who-is-viewing) to a component using the Rooms API",
2489
2568
  type: "skill",
2490
2569
  sourceFile: "skills/ba-platform/platform-add-presence.skill.md"
2491
2570
  },
2492
2571
  {
2493
2572
  name: "platform-scaffold-module",
2494
- description: "Scaffolds a new UI module or service module using the platform CLI",
2573
+ description: "Scaffolds a new UI module or NestJS service module in a platform monorepo using the CLI",
2495
2574
  type: "skill",
2496
2575
  sourceFile: "skills/ba-platform/platform-scaffold-module.skill.md"
2497
2576
  }
@@ -3180,20 +3259,20 @@ async function installAiPluginService(params, context) {
3180
3259
 
3181
3260
  // src/commands/install-ai-plugin/providers/claude.provider.ts
3182
3261
  import { homedir as homedir3 } from "os";
3183
- import { join as join27 } from "path";
3184
- import { readFile as readFile11, writeFile as writeFile10, mkdir as mkdir4, rm } from "fs/promises";
3262
+ import { join as join28 } from "path";
3263
+ import { readFile as readFile12, writeFile as writeFile12, mkdir as mkdir4, rm } from "fs/promises";
3185
3264
  var ClaudeProvider = class {
3186
3265
  name = "claude";
3187
- baseDir = join27(homedir3(), ".claude");
3266
+ baseDir = join28(homedir3(), ".claude");
3188
3267
  getInstallPath(skillName) {
3189
- return join27(this.baseDir, "skills", skillName, "SKILL.md");
3268
+ return join28(this.baseDir, "skills", skillName, "SKILL.md");
3190
3269
  }
3191
3270
  manifestPath(pluginName) {
3192
- return join27(this.baseDir, `${pluginName}.manifest.json`);
3271
+ return join28(this.baseDir, `${pluginName}.manifest.json`);
3193
3272
  }
3194
3273
  async readManifest(pluginName) {
3195
3274
  try {
3196
- const content = await readFile11(this.manifestPath(pluginName), "utf-8");
3275
+ const content = await readFile12(this.manifestPath(pluginName), "utf-8");
3197
3276
  return JSON.parse(content);
3198
3277
  } catch {
3199
3278
  return null;
@@ -3201,7 +3280,7 @@ var ClaudeProvider = class {
3201
3280
  }
3202
3281
  async writeManifest(manifest) {
3203
3282
  await mkdir4(this.baseDir, { recursive: true });
3204
- await writeFile10(
3283
+ await writeFile12(
3205
3284
  this.manifestPath(manifest.plugin),
3206
3285
  JSON.stringify(manifest, null, 2),
3207
3286
  "utf-8"
@@ -3221,16 +3300,16 @@ var ClaudeProvider = class {
3221
3300
  }
3222
3301
  async installSkill(skill, docsSource, logger) {
3223
3302
  const sourcePath = docsSource.resolve(skill.sourceFile);
3224
- let content = await readFile11(sourcePath, "utf-8");
3303
+ let content = await readFile12(sourcePath, "utf-8");
3225
3304
  const docsPath = docsSource.resolve("docs");
3226
3305
  content = content.replaceAll("{{docsPath}}", docsPath);
3227
3306
  const installPath = this.getInstallPath(skill.name);
3228
- await mkdir4(join27(installPath, ".."), { recursive: true });
3229
- await writeFile10(installPath, content, "utf-8");
3307
+ await mkdir4(join28(installPath, ".."), { recursive: true });
3308
+ await writeFile12(installPath, content, "utf-8");
3230
3309
  logger.log(` Installed skill: ${installPath}`);
3231
3310
  }
3232
3311
  async removeSkill(skillName, logger) {
3233
- const skillDir = join27(this.baseDir, "skills", skillName);
3312
+ const skillDir = join28(this.baseDir, "skills", skillName);
3234
3313
  try {
3235
3314
  await rm(skillDir, { recursive: true, force: true });
3236
3315
  logger.log(` Removed skill: ${skillDir}`);
@@ -3238,11 +3317,11 @@ var ClaudeProvider = class {
3238
3317
  }
3239
3318
  }
3240
3319
  async updatePermissions(docsSource, logger) {
3241
- const settingsPath = join27(this.baseDir, "settings.json");
3320
+ const settingsPath = join28(this.baseDir, "settings.json");
3242
3321
  const docsPath = docsSource.resolve("docs");
3243
3322
  let settings = {};
3244
3323
  try {
3245
- const content = await readFile11(settingsPath, "utf-8");
3324
+ const content = await readFile12(settingsPath, "utf-8");
3246
3325
  settings = JSON.parse(content);
3247
3326
  } catch {
3248
3327
  }
@@ -3254,7 +3333,7 @@ var ClaudeProvider = class {
3254
3333
  permissions.additionalDirectories = [...additionalDirectories, docsPath];
3255
3334
  settings.permissions = permissions;
3256
3335
  await mkdir4(this.baseDir, { recursive: true });
3257
- await writeFile10(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
3336
+ await writeFile12(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
3258
3337
  logger.log(` Granted read access to docs: ${docsPath}`);
3259
3338
  }
3260
3339
  };
@@ -3274,11 +3353,11 @@ function getProvider(name) {
3274
3353
 
3275
3354
  // src/commands/install-ai-plugin/docs-source/local.docs-source.ts
3276
3355
  import { fileURLToPath as fileURLToPath10 } from "url";
3277
- import { join as join28, dirname as dirname12 } from "path";
3278
- var packageRoot = join28(dirname12(fileURLToPath10(import.meta.url)), "..");
3356
+ import { join as join29, dirname as dirname13 } from "path";
3357
+ var packageRoot = join29(dirname13(fileURLToPath10(import.meta.url)), "..");
3279
3358
  var LocalDocsSource = class {
3280
3359
  resolve(relativePath) {
3281
- return join28(packageRoot, relativePath);
3360
+ return join29(packageRoot, relativePath);
3282
3361
  }
3283
3362
  };
3284
3363
 
@@ -3322,17 +3401,17 @@ async function installAiPluginUiController(ctx) {
3322
3401
 
3323
3402
  // src/controllers/ui/standalone.ui-controller.ts
3324
3403
  import { rm as rm3 } from "fs/promises";
3325
- import { join as join32 } from "path";
3404
+ import { join as join33 } from "path";
3326
3405
  import { cwd as cwd7 } from "process";
3327
3406
  import { exec } from "child_process";
3328
3407
 
3329
3408
  // src/commands/standalone/app-monorepo-check.ts
3330
3409
  import { access as access9, readdir as readdir5 } from "fs/promises";
3331
- import { join as join29 } from "path";
3410
+ import { join as join30 } from "path";
3332
3411
  import { cwd as cwd6 } from "process";
3333
3412
  async function findAppMonorepoLayout(startDir = cwd6()) {
3334
- const composePath = join29(startDir, "docker-compose.yml");
3335
- const packageJsonPath = join29(startDir, "package.json");
3413
+ const composePath = join30(startDir, "docker-compose.yml");
3414
+ const packageJsonPath = join30(startDir, "package.json");
3336
3415
  try {
3337
3416
  await access9(composePath);
3338
3417
  await access9(packageJsonPath);
@@ -3350,7 +3429,7 @@ async function hasCoreChildDir(dir) {
3350
3429
  for (const entry of entries) {
3351
3430
  if (entry.isDirectory() && (entry.name.endsWith("-core") || entry.name === "core")) {
3352
3431
  try {
3353
- await access9(join29(dir, entry.name, "product.manifest.json"));
3432
+ await access9(join30(dir, entry.name, "product.manifest.json"));
3354
3433
  return true;
3355
3434
  } catch {
3356
3435
  }
@@ -3365,15 +3444,15 @@ async function isInAppMonorepo() {
3365
3444
  }
3366
3445
 
3367
3446
  // src/commands/standalone/standalone-auto-detect.ts
3368
- import { readdir as readdir6, readFile as readFile12 } from "fs/promises";
3369
- import { join as join30, basename as basename2 } from "path";
3447
+ import { readdir as readdir6, readFile as readFile13 } from "fs/promises";
3448
+ import { join as join31, basename as basename2 } from "path";
3370
3449
  async function autoDetectAppIdentity(appDir) {
3371
3450
  const fromBootstrap = await detectFromBootstrap(appDir);
3372
3451
  if (fromBootstrap) return fromBootstrap;
3373
3452
  return detectFromPackageJson(appDir);
3374
3453
  }
3375
3454
  async function detectFromBootstrap(appDir) {
3376
- const servicesDir = join30(appDir, "services");
3455
+ const servicesDir = join31(appDir, "services");
3377
3456
  let entries;
3378
3457
  try {
3379
3458
  entries = await readdir6(servicesDir);
@@ -3383,7 +3462,7 @@ async function detectFromBootstrap(appDir) {
3383
3462
  const bootstrapDirs = entries.filter((e) => e.endsWith("-bootstrap-service"));
3384
3463
  if (bootstrapDirs.length === 0) return null;
3385
3464
  for (const bootstrapDirName of bootstrapDirs) {
3386
- const dataDir = join30(servicesDir, bootstrapDirName, "src", "data");
3465
+ const dataDir = join31(servicesDir, bootstrapDirName, "src", "data");
3387
3466
  let dataDirEntries;
3388
3467
  try {
3389
3468
  dataDirEntries = await readdir6(dataDir);
@@ -3392,9 +3471,9 @@ async function detectFromBootstrap(appDir) {
3392
3471
  }
3393
3472
  for (const subDir of dataDirEntries) {
3394
3473
  if (subDir === "platform") continue;
3395
- const appJsonPath = join30(dataDir, subDir, "application.json");
3474
+ const appJsonPath = join31(dataDir, subDir, "application.json");
3396
3475
  try {
3397
- const content = await readFile12(appJsonPath, "utf-8");
3476
+ const content = await readFile13(appJsonPath, "utf-8");
3398
3477
  const appData = JSON.parse(content);
3399
3478
  if (!appData.name) continue;
3400
3479
  const applicationName = appData.name;
@@ -3412,7 +3491,7 @@ async function detectFromBootstrap(appDir) {
3412
3491
  }
3413
3492
  async function detectFromPackageJson(appDir) {
3414
3493
  try {
3415
- const content = await readFile12(join30(appDir, "package.json"), "utf-8");
3494
+ const content = await readFile13(join31(appDir, "package.json"), "utf-8");
3416
3495
  const pkg = JSON.parse(content);
3417
3496
  const rawName = pkg.name ?? "";
3418
3497
  const applicationName = rawName.includes("/") ? rawName.split("/")[1] ?? rawName : rawName;
@@ -3435,23 +3514,24 @@ function inferPlatformNameFromAppDir(appDir, applicationName) {
3435
3514
  }
3436
3515
 
3437
3516
  // src/commands/standalone/standalone-orchestrator.ts
3438
- import { join as join31, dirname as dirname13 } from "path";
3439
- import { mkdir as mkdir5, rm as rm2, readdir as readdir7, access as access10, writeFile as writeFile11, readFile as readFile13 } from "fs/promises";
3517
+ import { join as join32, dirname as dirname14 } from "path";
3518
+ import { mkdir as mkdir5, rm as rm2, readdir as readdir7, access as access10, writeFile as writeFile13, readFile as readFile14, open } from "fs/promises";
3519
+ import { constants } from "fs";
3440
3520
  import { fetch as undiciFetch4, Agent as Agent4 } from "undici";
3441
3521
  var TMP_BASE = "/tmp";
3442
3522
  var STANDALONE_PREFIX = "platform-standalone-";
3443
3523
  var DETACH_MARKER = ".standalone-running";
3444
3524
  function getTmpDir(platformName) {
3445
- return join31(TMP_BASE, `${STANDALONE_PREFIX}${platformName}`);
3525
+ return join32(TMP_BASE, `${STANDALONE_PREFIX}${platformName}`);
3446
3526
  }
3447
3527
  async function findRunningSingleton() {
3448
3528
  try {
3449
3529
  const entries = await readdir7(TMP_BASE, { withFileTypes: true });
3450
3530
  for (const entry of entries) {
3451
3531
  if (entry.isDirectory() && entry.name.startsWith(STANDALONE_PREFIX)) {
3452
- const candidateDir = join31(TMP_BASE, entry.name);
3532
+ const candidateDir = join32(TMP_BASE, entry.name);
3453
3533
  try {
3454
- await access10(join31(candidateDir, DETACH_MARKER));
3534
+ await access10(join32(candidateDir, DETACH_MARKER));
3455
3535
  return candidateDir;
3456
3536
  } catch {
3457
3537
  }
@@ -3495,7 +3575,7 @@ async function configureIdpFromConfig(config, localDir, logger) {
3495
3575
  logger.log("No IDP configured \u2014 run 'platform standalone-config set' to add an IDP. Skipping IDP setup.");
3496
3576
  return;
3497
3577
  }
3498
- const envPath = join31(localDir, ".env");
3578
+ const envPath = join32(localDir, ".env");
3499
3579
  let env;
3500
3580
  try {
3501
3581
  env = await readEnvFile(envPath);
@@ -3550,7 +3630,7 @@ async function configureAdminUsersFromConfig(config, localDir, logger) {
3550
3630
  if (!config.adminUsers || config.adminUsers.length === 0) {
3551
3631
  return;
3552
3632
  }
3553
- const envPath = join31(localDir, ".env");
3633
+ const envPath = join32(localDir, ".env");
3554
3634
  let env;
3555
3635
  try {
3556
3636
  env = await readEnvFile(envPath);
@@ -3593,19 +3673,19 @@ async function configureAdminUsersFromConfig(config, localDir, logger) {
3593
3673
  }
3594
3674
  }
3595
3675
  function buildLayout(tmpDir, coreDirName) {
3596
- const coreDir = join31(tmpDir, coreDirName);
3676
+ const coreDir = join32(tmpDir, coreDirName);
3597
3677
  return {
3598
3678
  rootDir: tmpDir,
3599
3679
  coreDir,
3600
3680
  coreDirName,
3601
- localDir: join31(coreDir, "local")
3681
+ localDir: join32(coreDir, "local")
3602
3682
  };
3603
3683
  }
3604
- async function generateProxyAppCompose(appDir, localDir, platformName, applicationName, logger) {
3605
- const appComposePath = join31(appDir, "docker-compose.yml");
3684
+ async function generateProxyAppCompose2(appDir, localDir, platformName, applicationName, logger) {
3685
+ const appComposePath = join32(appDir, "docker-compose.yml");
3606
3686
  let content;
3607
3687
  try {
3608
- content = await readFile13(appComposePath, "utf-8");
3688
+ content = await readFile14(appComposePath, "utf-8");
3609
3689
  } catch {
3610
3690
  logger.log(`Warning: No docker-compose.yml found at ${appComposePath} \u2014 app services won't start.`);
3611
3691
  return;
@@ -3614,10 +3694,10 @@ async function generateProxyAppCompose(appDir, localDir, platformName, applicati
3614
3694
  /^(\s+context:\s+)\.\/(.*)$/gm,
3615
3695
  `$1${appDir}/$2`
3616
3696
  );
3617
- const appParentDir = dirname13(appDir);
3697
+ const appParentDir = dirname14(appDir);
3618
3698
  const rewritten = withAbsContexts.replace(/\$\{PWD\}/g, appParentDir);
3619
- const proxyPath = join31(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
3620
- await writeFile11(proxyPath, rewritten, "utf-8");
3699
+ const proxyPath = join32(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
3700
+ await writeFile13(proxyPath, rewritten, "utf-8");
3621
3701
  logger.log(`Generated app compose with absolute build paths.`);
3622
3702
  }
3623
3703
  function buildComposeManifest(manifest) {
@@ -3643,7 +3723,23 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
3643
3723
  }
3644
3724
  logger.log(`Starting standalone platform for "${applicationDisplayName}"...`);
3645
3725
  await mkdir5(tmpDir, { recursive: true });
3646
- await writeFile11(join31(tmpDir, DETACH_MARKER), platformName, "utf-8");
3726
+ let markerFd;
3727
+ try {
3728
+ markerFd = await open(
3729
+ join32(tmpDir, DETACH_MARKER),
3730
+ constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY
3731
+ );
3732
+ await markerFd.writeFile(platformName, "utf-8");
3733
+ } catch (err) {
3734
+ if (err.code === "EEXIST") {
3735
+ logger.log(`Error: A standalone platform is already starting at ${tmpDir}.`);
3736
+ logger.log(`Run "platform standalone-stop" to stop it before starting a new one.`);
3737
+ return;
3738
+ }
3739
+ throw err;
3740
+ } finally {
3741
+ await markerFd?.close();
3742
+ }
3647
3743
  const variables = {
3648
3744
  organizationName,
3649
3745
  platformName,
@@ -3668,9 +3764,9 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
3668
3764
  }
3669
3765
  );
3670
3766
  await writeManifest(npmManifest, tmpDir, coreDirName);
3671
- const bootstrapServiceDir = join31(tmpDir, coreDirName, "services", bootstrapServiceName);
3767
+ const bootstrapServiceDir = join32(tmpDir, coreDirName, "services", bootstrapServiceName);
3672
3768
  await scaffoldPlatformBootstrap(bootstrapServiceDir, variables, logger);
3673
- const customizationUiDir = join31(tmpDir, coreDirName, "ui", customizationUiName);
3769
+ const customizationUiDir = join32(tmpDir, coreDirName, "ui", customizationUiName);
3674
3770
  await scaffoldCustomizationUi(customizationUiDir, variables, logger);
3675
3771
  await registerCustomizationModule(bootstrapServiceDir, organizationName, logger, platformName);
3676
3772
  const layout = buildLayout(tmpDir, coreDirName);
@@ -3680,7 +3776,7 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
3680
3776
  logger.log("Building...");
3681
3777
  await buildAll(layout, npmManifest, logger, signal, true);
3682
3778
  if (signal?.aborted) return;
3683
- await generateProxyAppCompose(appDir, layout.localDir, platformName, applicationName, logger);
3779
+ await generateProxyAppCompose2(appDir, layout.localDir, platformName, applicationName, logger);
3684
3780
  const composeManifest = buildComposeManifest(npmManifest);
3685
3781
  await writeManifest(composeManifest, tmpDir, coreDirName);
3686
3782
  logger.log("Resetting previous environment (removing stale containers and volumes)...");
@@ -3689,7 +3785,7 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
3689
3785
  logger.log("Starting containers...");
3690
3786
  await startEnvironment(layout, composeManifest, logger, signal);
3691
3787
  if (signal?.aborted) return;
3692
- const env = await readEnvFile(join31(layout.localDir, ".env")).catch(() => /* @__PURE__ */ new Map());
3788
+ const env = await readEnvFile(join32(layout.localDir, ".env")).catch(() => /* @__PURE__ */ new Map());
3693
3789
  const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL") ?? "https://localhost:443";
3694
3790
  const gatewayReady = await waitForGateway(gatewayUrl, logger);
3695
3791
  if (!gatewayReady) {
@@ -3709,16 +3805,27 @@ Standalone platform is running in the background.`);
3709
3805
  logger.log("\nPlatform is running. Press Ctrl+C to stop and clean up.");
3710
3806
  const keepAlive = setInterval(() => {
3711
3807
  }, 60 * 6e4);
3808
+ let cleanupInProgress = false;
3712
3809
  const shutdown = () => {
3810
+ if (cleanupInProgress) {
3811
+ logger.log("\nForce exiting. Containers may need manual cleanup:");
3812
+ logger.log(` docker compose -p ${platformName}-platform down -v --remove-orphans`);
3813
+ process.exit(1);
3814
+ }
3815
+ cleanupInProgress = true;
3713
3816
  clearInterval(keepAlive);
3714
- void (async () => {
3817
+ (async () => {
3715
3818
  logger.log("\nShutting down standalone platform...");
3716
3819
  await destroyEnvironment(layout, composeManifest, logger);
3717
3820
  await cleanupTmpDir(tmpDir, logger);
3718
- })();
3821
+ process.exit(0);
3822
+ })().catch((err) => {
3823
+ logger.log(`Error during cleanup: ${err.message}`);
3824
+ process.exit(1);
3825
+ });
3719
3826
  };
3720
- process.once("SIGINT", shutdown);
3721
- process.once("SIGTERM", shutdown);
3827
+ process.on("SIGINT", shutdown);
3828
+ process.on("SIGTERM", shutdown);
3722
3829
  }
3723
3830
  async function stopStandalone(logger) {
3724
3831
  const runningDir = await findRunningSingleton();
@@ -3732,7 +3839,7 @@ async function stopStandalone(logger) {
3732
3839
  for (const entry of entries) {
3733
3840
  if (entry.isDirectory() && entry.name.endsWith("-core")) {
3734
3841
  try {
3735
- await access10(join31(runningDir, entry.name, "product.manifest.json"));
3842
+ await access10(join32(runningDir, entry.name, "product.manifest.json"));
3736
3843
  coreDirName = entry.name;
3737
3844
  break;
3738
3845
  } catch {
@@ -3847,7 +3954,7 @@ async function standaloneUiController(ctx) {
3847
3954
  const deleteLegacy = await ctx.confirm("Delete the local standalone.json?", true);
3848
3955
  if (deleteLegacy) {
3849
3956
  try {
3850
- await rm3(join32(appDir, "standalone.json"));
3957
+ await rm3(join33(appDir, "standalone.json"));
3851
3958
  ctx.log("Deleted standalone.json.");
3852
3959
  } catch {
3853
3960
  ctx.log("Warning: Could not delete standalone.json.");
@@ -3979,7 +4086,7 @@ async function standaloneConfigShowUiController(ctx) {
3979
4086
  if (config) {
3980
4087
  ctx.log(`Standalone config for ${detected.platformName}/${detected.applicationName}:
3981
4088
  `);
3982
- ctx.log(JSON.stringify(config, null, 2));
4089
+ ctx.log(JSON.stringify(maskSensitiveFields(config), null, 2));
3983
4090
  } else {
3984
4091
  ctx.log(`No config found for ${detected.platformName}/${detected.applicationName}.`);
3985
4092
  ctx.log(`Run 'platform standalone' or 'platform standalone-config set' to configure.`);
@@ -3998,7 +4105,7 @@ async function showAll(ctx) {
3998
4105
  if (Object.keys(defaults).length === 0) {
3999
4106
  ctx.log("(none)");
4000
4107
  } else {
4001
- ctx.log(JSON.stringify(defaults, null, 2));
4108
+ ctx.log(JSON.stringify(maskSensitiveFields(defaults), null, 2));
4002
4109
  }
4003
4110
  ctx.log("\n--- Apps ---");
4004
4111
  if (apps.length === 0) {
@@ -4904,7 +5011,7 @@ async function standaloneConfigShowCliController(args2) {
4904
5011
  const [pn, an] = parts;
4905
5012
  const config = await resolveAppConfig(pn, an);
4906
5013
  if (config) {
4907
- console.log(JSON.stringify(config, null, 2));
5014
+ console.log(JSON.stringify(maskSensitiveFields(config), null, 2));
4908
5015
  } else {
4909
5016
  console.error(`No config found for ${appArg}.`);
4910
5017
  process.exit(1);
@@ -4915,12 +5022,12 @@ async function standaloneConfigShowCliController(args2) {
4915
5022
  if (detected) {
4916
5023
  const config = await resolveAppConfig(detected.platformName, detected.applicationName);
4917
5024
  if (config) {
4918
- console.log(JSON.stringify(config, null, 2));
5025
+ console.log(JSON.stringify(maskSensitiveFields(config), null, 2));
4919
5026
  return;
4920
5027
  }
4921
5028
  }
4922
5029
  const cache = await readCacheFile();
4923
- console.log(JSON.stringify(cache, null, 2));
5030
+ console.log(JSON.stringify(maskSensitiveFields(cache), null, 2));
4924
5031
  }
4925
5032
  async function standaloneConfigSetCliController(args2) {
4926
5033
  if (args2["defaults"] === "true") {