@bluealba/platform-cli 1.0.0 → 1.0.2

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 (42) hide show
  1. package/dist/index.js +351 -18
  2. package/docs/404.mdx +5 -0
  3. package/docs/architecture/api-explorer.mdx +478 -0
  4. package/docs/architecture/architecture-diagrams.mdx +12 -0
  5. package/docs/architecture/authentication-system.mdx +903 -0
  6. package/docs/architecture/authorization-system.mdx +886 -0
  7. package/docs/architecture/bootstrap.mdx +1442 -0
  8. package/docs/architecture/gateway-architecture.mdx +845 -0
  9. package/docs/architecture/multi-tenancy.mdx +1150 -0
  10. package/docs/architecture/overview.mdx +776 -0
  11. package/docs/architecture/scheduler.mdx +818 -0
  12. package/docs/architecture/shell.mdx +885 -0
  13. package/docs/architecture/ui-extension-points.mdx +781 -0
  14. package/docs/architecture/user-states.mdx +794 -0
  15. package/docs/development/overview.mdx +21 -0
  16. package/docs/development/workflow.mdx +914 -0
  17. package/docs/getting-started/core-concepts.mdx +892 -0
  18. package/docs/getting-started/installation.mdx +780 -0
  19. package/docs/getting-started/overview.mdx +83 -0
  20. package/docs/getting-started/quick-start.mdx +940 -0
  21. package/docs/guides/adding-documentation-sites.mdx +1367 -0
  22. package/docs/guides/creating-services.mdx +1736 -0
  23. package/docs/guides/creating-ui-modules.mdx +1860 -0
  24. package/docs/guides/identity-providers.mdx +1007 -0
  25. package/docs/guides/mermaid-diagrams.mdx +212 -0
  26. package/docs/guides/using-feature-flags.mdx +1059 -0
  27. package/docs/guides/working-with-rooms.mdx +566 -0
  28. package/docs/index.mdx +57 -0
  29. package/docs/platform-cli/commands.mdx +604 -0
  30. package/docs/platform-cli/overview.mdx +195 -0
  31. package/package.json +5 -2
  32. package/skills/ba-platform/platform-cli.skill.md +26 -0
  33. package/skills/ba-platform/platform.skill.md +35 -0
  34. package/templates/application-monorepo-template/gitignore +95 -0
  35. package/templates/bootstrap-service-template/gitignore +57 -0
  36. package/templates/bootstrap-service-template/src/main.ts +6 -16
  37. package/templates/customization-ui-module-template/gitignore +73 -0
  38. package/templates/nestjs-service-module-template/gitignore +56 -0
  39. package/templates/platform-init-template/{{platformName}}-core/gitignore +97 -0
  40. package/templates/react-ui-module-template/Dockerfile +1 -1
  41. package/templates/react-ui-module-template/caddy/Caddyfile +1 -1
  42. package/templates/react-ui-module-template/gitignore +72 -0
package/dist/index.js CHANGED
@@ -213,7 +213,7 @@ import { join as join2, dirname as dirname2 } from "path";
213
213
 
214
214
  // src/utils/template-engine.ts
215
215
  import { readdir, readFile, writeFile, mkdir, copyFile, stat, chmod } from "fs/promises";
216
- import { join, dirname, extname } from "path";
216
+ import { join, dirname, extname, basename } from "path";
217
217
  var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
218
218
  ".pem",
219
219
  ".crt",
@@ -239,6 +239,13 @@ function applyVariables(text, variables) {
239
239
  }
240
240
  return result;
241
241
  }
242
+ function transformFilename(relativePath) {
243
+ const base = basename(relativePath);
244
+ if (base === "gitignore") {
245
+ return join(dirname(relativePath), ".gitignore");
246
+ }
247
+ return relativePath;
248
+ }
242
249
  function isBinary(filePath) {
243
250
  return BINARY_EXTENSIONS.has(extname(filePath).toLowerCase());
244
251
  }
@@ -263,7 +270,7 @@ async function applyTemplate(config, onProgress) {
263
270
  const relativePath = srcPath.slice(templateDir4.length + 1);
264
271
  if (relativePath.endsWith(".gitkeep")) continue;
265
272
  if (exclude.includes(relativePath)) continue;
266
- const transformedRelative = applyVariables(relativePath, variables);
273
+ const transformedRelative = transformFilename(applyVariables(relativePath, variables));
267
274
  const destPath = join(outputDir, transformedRelative);
268
275
  await mkdir(dirname(destPath), { recursive: true });
269
276
  if (isBinary(srcPath)) {
@@ -1302,6 +1309,7 @@ async function createUiModule(params, logger) {
1302
1309
  import { spawn as spawn3 } from "child_process";
1303
1310
  import { access as access7 } from "fs/promises";
1304
1311
  import { join as join24, resolve as resolve5 } from "path";
1312
+ import { fetch as undiciFetch2, Agent as Agent2 } from "undici";
1305
1313
 
1306
1314
  // src/commands/local-scripts/docker-compose-orchestrator.ts
1307
1315
  import { spawn } from "child_process";
@@ -2015,6 +2023,40 @@ async function checkContainersPerApp(layout, manifest, allContainers) {
2015
2023
  }
2016
2024
  return groups;
2017
2025
  }
2026
+ async function checkIdpProviders(layout) {
2027
+ const envPath = join24(layout.localDir, ".env");
2028
+ let env;
2029
+ try {
2030
+ env = await readEnvFile(envPath);
2031
+ } catch {
2032
+ return { configured: false, providers: [], error: "could not read core/local/.env" };
2033
+ }
2034
+ const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL");
2035
+ const accessSecret = env.get("PAE_GATEWAY_SERVICE_ACCESS_SECRET");
2036
+ if (!gatewayUrl || !accessSecret) {
2037
+ return { configured: false, providers: [], error: "PAE_GATEWAY_HOST_URL or PAE_GATEWAY_SERVICE_ACCESS_SECRET not set" };
2038
+ }
2039
+ const agent = new Agent2({ connect: { rejectUnauthorized: false } });
2040
+ let response;
2041
+ try {
2042
+ response = await undiciFetch2(`${gatewayUrl}/_/providers`, {
2043
+ method: "GET",
2044
+ headers: {
2045
+ Accept: "application/json",
2046
+ Authorization: `Bearer ${accessSecret}`
2047
+ },
2048
+ dispatcher: agent
2049
+ });
2050
+ } catch {
2051
+ return { configured: false, providers: [], error: "gateway unreachable" };
2052
+ }
2053
+ if (!response.ok) {
2054
+ return { configured: false, providers: [], error: `gateway responded with ${response.status}` };
2055
+ }
2056
+ const data = await response.json();
2057
+ const providers2 = data.map((p) => ({ name: p.name, type: p.type, active: p.active }));
2058
+ return { configured: providers2.length > 0, providers: providers2 };
2059
+ }
2018
2060
  async function gatherStatus() {
2019
2061
  const layout = await findPlatformLayout();
2020
2062
  const [nodeCheck, dockerCheck] = await Promise.all([checkNodeVersion(), checkDocker()]);
@@ -2033,7 +2075,8 @@ async function gatherStatus() {
2033
2075
  },
2034
2076
  containers: [],
2035
2077
  containersByApp: [],
2036
- coreDirName: null
2078
+ coreDirName: null,
2079
+ idp: null
2037
2080
  };
2038
2081
  }
2039
2082
  let manifest;
@@ -2053,7 +2096,8 @@ async function gatherStatus() {
2053
2096
  },
2054
2097
  containers: [],
2055
2098
  containersByApp: [],
2056
- coreDirName: layout.coreDirName
2099
+ coreDirName: layout.coreDirName,
2100
+ idp: null
2057
2101
  };
2058
2102
  }
2059
2103
  const [installedDetails, builtDetails, containers] = await Promise.all([
@@ -2062,6 +2106,8 @@ async function gatherStatus() {
2062
2106
  checkContainers(layout, manifest)
2063
2107
  ]);
2064
2108
  const containersByApp = await checkContainersPerApp(layout, manifest, containers);
2109
+ const running = containers.length > 0 && containers.every((c) => c.state === "running");
2110
+ const idp = running ? await checkIdpProviders(layout) : null;
2065
2111
  const projectInfo = {
2066
2112
  productName: manifest.product.name,
2067
2113
  displayName: manifest.product.displayName,
@@ -2079,11 +2125,12 @@ async function gatherStatus() {
2079
2125
  installedDetails,
2080
2126
  built: builtDetails.every((d) => d.exists),
2081
2127
  builtDetails,
2082
- running: containers.length > 0 && containers.every((c) => c.state === "running")
2128
+ running
2083
2129
  },
2084
2130
  containers,
2085
2131
  containersByApp,
2086
- coreDirName: layout.coreDirName
2132
+ coreDirName: layout.coreDirName,
2133
+ idp
2087
2134
  };
2088
2135
  }
2089
2136
 
@@ -2097,7 +2144,7 @@ var statusCommand = {
2097
2144
 
2098
2145
  // src/commands/manage-platform-admins/manage-platform-admins.command.ts
2099
2146
  import { join as join25 } from "path";
2100
- import { fetch as undiciFetch2, Agent as Agent2 } from "undici";
2147
+ import { fetch as undiciFetch3, Agent as Agent3 } from "undici";
2101
2148
  var MANAGE_PLATFORM_ADMINS_COMMAND_NAME = "manage-platform-admins";
2102
2149
  var managePlatformAdminsCommand = {
2103
2150
  name: MANAGE_PLATFORM_ADMINS_COMMAND_NAME,
@@ -2134,10 +2181,10 @@ async function listPlatformAdmins(logger) {
2134
2181
  const config = await getGatewayConfig(logger);
2135
2182
  if (!config) return [];
2136
2183
  const { gatewayUrl, accessSecret } = config;
2137
- const agent = new Agent2({ connect: { rejectUnauthorized: false } });
2184
+ const agent = new Agent3({ connect: { rejectUnauthorized: false } });
2138
2185
  let response;
2139
2186
  try {
2140
- response = await undiciFetch2(`${gatewayUrl}/applications/platform/rules`, {
2187
+ response = await undiciFetch3(`${gatewayUrl}/applications/platform/rules`, {
2141
2188
  method: "GET",
2142
2189
  headers: {
2143
2190
  Accept: "application/json",
@@ -2165,10 +2212,10 @@ async function addPlatformAdmin(username, logger) {
2165
2212
  const config = await getGatewayConfig(logger);
2166
2213
  if (!config) return false;
2167
2214
  const { gatewayUrl, accessSecret } = config;
2168
- const agent = new Agent2({ connect: { rejectUnauthorized: false } });
2215
+ const agent = new Agent3({ connect: { rejectUnauthorized: false } });
2169
2216
  let response;
2170
2217
  try {
2171
- response = await undiciFetch2(`${gatewayUrl}/applications/platform/rules`, {
2218
+ response = await undiciFetch3(`${gatewayUrl}/applications/platform/rules`, {
2172
2219
  method: "POST",
2173
2220
  headers: {
2174
2221
  "Content-Type": "application/json",
@@ -2204,10 +2251,10 @@ async function removePlatformAdmin(ruleId, logger) {
2204
2251
  const config = await getGatewayConfig(logger);
2205
2252
  if (!config) return false;
2206
2253
  const { gatewayUrl, accessSecret } = config;
2207
- const agent = new Agent2({ connect: { rejectUnauthorized: false } });
2254
+ const agent = new Agent3({ connect: { rejectUnauthorized: false } });
2208
2255
  let response;
2209
2256
  try {
2210
- response = await undiciFetch2(`${gatewayUrl}/applications/platform/rules/${ruleId}`, {
2257
+ response = await undiciFetch3(`${gatewayUrl}/applications/platform/rules/${ruleId}`, {
2211
2258
  method: "DELETE",
2212
2259
  headers: {
2213
2260
  Authorization: `Bearer ${accessSecret}`
@@ -2228,6 +2275,91 @@ async function removePlatformAdmin(ruleId, logger) {
2228
2275
  return true;
2229
2276
  }
2230
2277
 
2278
+ // src/commands/install-ai-plugin/plugins/ba-platform/index.ts
2279
+ var baPlatformPlugin = {
2280
+ name: "ba-platform-plugin",
2281
+ description: "Blue Alba Platform knowledge and CLI skills for AI assistants",
2282
+ version: "1.0.0",
2283
+ skills: [
2284
+ {
2285
+ name: "platform",
2286
+ description: "Comprehensive Blue Alba Platform architecture and concepts knowledge",
2287
+ type: "skill",
2288
+ sourceFile: "skills/ba-platform/platform.skill.md"
2289
+ },
2290
+ {
2291
+ name: "platform-cli",
2292
+ description: "Blue Alba Platform CLI commands and usage knowledge",
2293
+ type: "skill",
2294
+ sourceFile: "skills/ba-platform/platform-cli.skill.md"
2295
+ }
2296
+ ]
2297
+ };
2298
+
2299
+ // src/commands/install-ai-plugin/plugins/registry.ts
2300
+ var pluginCatalog = [baPlatformPlugin];
2301
+ function findPlugin(name) {
2302
+ return pluginCatalog.find((p) => p.name === name);
2303
+ }
2304
+
2305
+ // src/commands/install-ai-plugin/install-ai-plugin.command.ts
2306
+ var INSTALL_AI_PLUGIN_COMMAND_NAME = "install-ai-plugin";
2307
+ var installAiPluginCommand = {
2308
+ name: INSTALL_AI_PLUGIN_COMMAND_NAME,
2309
+ description: "Install AI assistant plugins (skills, MCPs, hooks) for your AI provider"
2310
+ };
2311
+ async function installAiPlugin(params, context) {
2312
+ const { provider, docsSource, logger, confirmUpdate } = context;
2313
+ for (const pluginName of params.plugins) {
2314
+ const plugin = findPlugin(pluginName);
2315
+ if (!plugin) {
2316
+ logger.log(`Error: Plugin "${pluginName}" not found in catalog.`);
2317
+ continue;
2318
+ }
2319
+ const manifest = await provider.readManifest(pluginName);
2320
+ const diff = provider.calculateDiff(manifest, plugin);
2321
+ const hasStructuralChanges = diff.versionChanged || diff.newSkills.length > 0 || diff.removedSkills.length > 0;
2322
+ if (manifest && hasStructuralChanges) {
2323
+ logger.log(
2324
+ `Plugin "${pluginName}" is already installed (v${diff.currentVersion} \u2192 v${diff.targetVersion}).`
2325
+ );
2326
+ if (diff.newSkills.length > 0) {
2327
+ logger.log(` New skills: ${diff.newSkills.map((s) => s.name).join(", ")}`);
2328
+ }
2329
+ if (diff.removedSkills.length > 0) {
2330
+ logger.log(` Removed skills: ${diff.removedSkills.join(", ")}`);
2331
+ }
2332
+ if (diff.existingSkills.length > 0) {
2333
+ logger.log(` Updated skills: ${diff.existingSkills.map((s) => s.name).join(", ")}`);
2334
+ }
2335
+ if (!params.force && confirmUpdate) {
2336
+ const proceed = await confirmUpdate(diff, plugin);
2337
+ if (!proceed) {
2338
+ logger.log(`Skipped "${pluginName}".`);
2339
+ continue;
2340
+ }
2341
+ }
2342
+ for (const skillName of diff.removedSkills) {
2343
+ await provider.removeSkill(skillName, logger);
2344
+ }
2345
+ } else if (!manifest) {
2346
+ logger.log(`Installing plugin "${pluginName}" v${plugin.version}...`);
2347
+ }
2348
+ for (const skill of plugin.skills) {
2349
+ await provider.installSkill(skill, docsSource, logger);
2350
+ }
2351
+ await provider.writeManifest({
2352
+ plugin: pluginName,
2353
+ version: plugin.version,
2354
+ provider: provider.name,
2355
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
2356
+ skills: plugin.skills.map((s) => s.name)
2357
+ });
2358
+ await provider.updatePermissions(docsSource, logger);
2359
+ logger.log(`Plugin "${pluginName}" v${plugin.version} installed successfully.`);
2360
+ }
2361
+ }
2362
+
2231
2363
  // src/commands/registry.ts
2232
2364
  var CommandRegistry = class {
2233
2365
  commands = /* @__PURE__ */ new Map();
@@ -2268,6 +2400,7 @@ for (const cmd of localScriptCommands) {
2268
2400
  }
2269
2401
  registry.register(statusCommand);
2270
2402
  registry.register(managePlatformAdminsCommand);
2403
+ registry.register(installAiPluginCommand);
2271
2404
 
2272
2405
  // src/app-state.ts
2273
2406
  var APP_STATE = {
@@ -2365,12 +2498,12 @@ async function configureIdpUiController(ctx) {
2365
2498
  ctx.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
2366
2499
  return;
2367
2500
  }
2368
- const providers = getAllProviders();
2501
+ const providers2 = getAllProviders();
2369
2502
  const providerType = await ctx.select(
2370
2503
  "Select IDP type:",
2371
- providers.map((p) => ({ label: p.displayName, value: p.type }))
2504
+ providers2.map((p) => ({ label: p.displayName, value: p.type }))
2372
2505
  );
2373
- const provider = providers.find((p) => p.type === providerType);
2506
+ const provider = providers2.find((p) => p.type === providerType);
2374
2507
  const name = await ctx.prompt("Provider name:");
2375
2508
  const issuer = await ctx.prompt("Issuer URL:");
2376
2509
  const clientId = await ctx.prompt("Client ID:");
@@ -2551,6 +2684,20 @@ function formatStatusLines(result) {
2551
2684
  lines.push(` ${tick(ok)} ${c.name.padEnd(35)} ${stateStr}${ports}`);
2552
2685
  }
2553
2686
  }
2687
+ if (result.idp !== null) {
2688
+ lines.push("");
2689
+ lines.push(theme.section("Identity Providers"));
2690
+ if (result.idp.error) {
2691
+ lines.push(` ${CROSS} ${theme.warning(result.idp.error)}`);
2692
+ } else if (result.idp.configured) {
2693
+ for (const p of result.idp.providers) {
2694
+ const activeStr = p.active ? theme.success("active") : theme.warning("inactive");
2695
+ lines.push(` ${CHECK} ${p.name} ${theme.label(`(${p.type})`)} \u2014 ${activeStr}`);
2696
+ }
2697
+ } else {
2698
+ lines.push(` ${CROSS} ${theme.warning("No identity providers configured")}`);
2699
+ }
2700
+ }
2554
2701
  return lines;
2555
2702
  }
2556
2703
 
@@ -2568,6 +2715,13 @@ async function statusUiController(ctx) {
2568
2715
  }
2569
2716
  const { step, appNames, allApps } = computeNextStepInfo(result);
2570
2717
  if (step === null) {
2718
+ if (result.idp && !result.idp.configured && !result.idp.error) {
2719
+ const shouldConfigure = await ctx.confirm("No IDP configured. Configure one now?", true);
2720
+ if (shouldConfigure) {
2721
+ await configureIdpUiController(ctx);
2722
+ continue;
2723
+ }
2724
+ }
2571
2725
  ctx.log("");
2572
2726
  ctx.log("All checks passed!");
2573
2727
  return;
@@ -2758,6 +2912,153 @@ async function handleRemove(ctx) {
2758
2912
  ctx.log(`Successfully removed ${successCount} of ${selected.length} admin(s).`);
2759
2913
  }
2760
2914
 
2915
+ // src/services/install-ai-plugin.service.ts
2916
+ async function installAiPluginService(params, context) {
2917
+ await installAiPlugin(params, context);
2918
+ }
2919
+
2920
+ // src/commands/install-ai-plugin/providers/claude.provider.ts
2921
+ import { homedir as homedir2 } from "os";
2922
+ import { join as join26 } from "path";
2923
+ import { readFile as readFile10, writeFile as writeFile9, mkdir as mkdir3, rm } from "fs/promises";
2924
+ var ClaudeProvider = class {
2925
+ name = "claude";
2926
+ baseDir = join26(homedir2(), ".claude");
2927
+ getInstallPath(skillName) {
2928
+ return join26(this.baseDir, "skills", skillName, "SKILL.md");
2929
+ }
2930
+ manifestPath(pluginName) {
2931
+ return join26(this.baseDir, `${pluginName}.manifest.json`);
2932
+ }
2933
+ async readManifest(pluginName) {
2934
+ try {
2935
+ const content = await readFile10(this.manifestPath(pluginName), "utf-8");
2936
+ return JSON.parse(content);
2937
+ } catch {
2938
+ return null;
2939
+ }
2940
+ }
2941
+ async writeManifest(manifest) {
2942
+ await mkdir3(this.baseDir, { recursive: true });
2943
+ await writeFile9(
2944
+ this.manifestPath(manifest.plugin),
2945
+ JSON.stringify(manifest, null, 2),
2946
+ "utf-8"
2947
+ );
2948
+ }
2949
+ calculateDiff(manifest, plugin) {
2950
+ const installedSkills = manifest?.skills ?? [];
2951
+ const targetSkillNames = plugin.skills.map((s) => s.name);
2952
+ return {
2953
+ newSkills: plugin.skills.filter((s) => !installedSkills.includes(s.name)),
2954
+ removedSkills: installedSkills.filter((name) => !targetSkillNames.includes(name)),
2955
+ existingSkills: plugin.skills.filter((s) => installedSkills.includes(s.name)),
2956
+ versionChanged: manifest?.version !== plugin.version,
2957
+ currentVersion: manifest?.version ?? null,
2958
+ targetVersion: plugin.version
2959
+ };
2960
+ }
2961
+ async installSkill(skill, docsSource, logger) {
2962
+ const sourcePath = docsSource.resolve(skill.sourceFile);
2963
+ let content = await readFile10(sourcePath, "utf-8");
2964
+ const docsPath = docsSource.resolve("docs");
2965
+ content = content.replaceAll("{{docsPath}}", docsPath);
2966
+ const installPath = this.getInstallPath(skill.name);
2967
+ await mkdir3(join26(installPath, ".."), { recursive: true });
2968
+ await writeFile9(installPath, content, "utf-8");
2969
+ logger.log(` Installed skill: ${installPath}`);
2970
+ }
2971
+ async removeSkill(skillName, logger) {
2972
+ const skillDir = join26(this.baseDir, "skills", skillName);
2973
+ try {
2974
+ await rm(skillDir, { recursive: true, force: true });
2975
+ logger.log(` Removed skill: ${skillDir}`);
2976
+ } catch {
2977
+ }
2978
+ }
2979
+ async updatePermissions(docsSource, logger) {
2980
+ const settingsPath = join26(this.baseDir, "settings.json");
2981
+ const docsPath = docsSource.resolve("docs");
2982
+ let settings = {};
2983
+ try {
2984
+ const content = await readFile10(settingsPath, "utf-8");
2985
+ settings = JSON.parse(content);
2986
+ } catch {
2987
+ }
2988
+ const permissions = settings.permissions ?? {};
2989
+ const additionalDirectories = permissions.additionalDirectories ?? [];
2990
+ if (additionalDirectories.includes(docsPath)) {
2991
+ return;
2992
+ }
2993
+ permissions.additionalDirectories = [...additionalDirectories, docsPath];
2994
+ settings.permissions = permissions;
2995
+ await mkdir3(this.baseDir, { recursive: true });
2996
+ await writeFile9(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
2997
+ logger.log(` Granted read access to docs: ${docsPath}`);
2998
+ }
2999
+ };
3000
+
3001
+ // src/commands/install-ai-plugin/providers/index.ts
3002
+ var providers = {
3003
+ claude: () => new ClaudeProvider()
3004
+ };
3005
+ var providerNames = Object.keys(providers);
3006
+ function getProvider(name) {
3007
+ const factory = providers[name];
3008
+ if (!factory) {
3009
+ throw new Error(`Unknown AI provider: "${name}". Available: ${providerNames.join(", ")}`);
3010
+ }
3011
+ return factory();
3012
+ }
3013
+
3014
+ // src/commands/install-ai-plugin/docs-source/local.docs-source.ts
3015
+ import { fileURLToPath as fileURLToPath10 } from "url";
3016
+ import { join as join27, dirname as dirname12 } from "path";
3017
+ var packageRoot = join27(dirname12(fileURLToPath10(import.meta.url)), "..");
3018
+ var LocalDocsSource = class {
3019
+ resolve(relativePath) {
3020
+ return join27(packageRoot, relativePath);
3021
+ }
3022
+ };
3023
+
3024
+ // src/controllers/ui/install-ai-plugin.ui-controller.ts
3025
+ async function installAiPluginUiController(ctx) {
3026
+ const providerName = await ctx.select(
3027
+ "Select AI provider:",
3028
+ providerNames.map((name) => ({
3029
+ label: name.charAt(0).toUpperCase() + name.slice(1),
3030
+ value: name
3031
+ }))
3032
+ );
3033
+ const selectedPlugins = await ctx.multiselect(
3034
+ "Select plugins to install:",
3035
+ pluginCatalog.map((p) => ({
3036
+ label: `${p.name} \u2014 ${p.description}`,
3037
+ value: p.name
3038
+ }))
3039
+ );
3040
+ if (selectedPlugins.length === 0) {
3041
+ ctx.log("No plugins selected.");
3042
+ return;
3043
+ }
3044
+ const provider = getProvider(providerName);
3045
+ const docsSource = new LocalDocsSource();
3046
+ await installAiPluginService(
3047
+ { provider: providerName, plugins: selectedPlugins },
3048
+ {
3049
+ provider,
3050
+ docsSource,
3051
+ logger: ctx,
3052
+ confirmUpdate: async (diff, plugin) => {
3053
+ return ctx.confirm(
3054
+ `Update "${plugin.name}" from v${diff.currentVersion} to v${diff.targetVersion}?`,
3055
+ true
3056
+ );
3057
+ }
3058
+ }
3059
+ );
3060
+ }
3061
+
2761
3062
  // src/controllers/ui/registry.ts
2762
3063
  var uiControllers = /* @__PURE__ */ new Map([
2763
3064
  [CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
@@ -2771,7 +3072,8 @@ var uiControllers = /* @__PURE__ */ new Map([
2771
3072
  [START_COMMAND_NAME, createLocalScriptUiController(START_COMMAND_NAME)],
2772
3073
  [STOP_COMMAND_NAME, createLocalScriptUiController(STOP_COMMAND_NAME)],
2773
3074
  [DESTROY_COMMAND_NAME, createLocalScriptUiController(DESTROY_COMMAND_NAME)],
2774
- [MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsUiController]
3075
+ [MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsUiController],
3076
+ [INSTALL_AI_PLUGIN_COMMAND_NAME, installAiPluginUiController]
2775
3077
  ]);
2776
3078
 
2777
3079
  // src/hooks/use-command-runner.ts
@@ -3333,6 +3635,10 @@ var statusCliController = async (_args) => {
3333
3635
  }
3334
3636
  process.exit(1);
3335
3637
  }
3638
+ if (result.idp && !result.idp.configured && !result.idp.error) {
3639
+ console.log("");
3640
+ console.log(`Hint: Run "platform configure-idp" to set up an identity provider.`);
3641
+ }
3336
3642
  };
3337
3643
 
3338
3644
  // src/controllers/cli/manage-platform-admins.cli-controller.ts
@@ -3398,6 +3704,32 @@ async function managePlatformAdminsCliController(args2) {
3398
3704
  }
3399
3705
  }
3400
3706
 
3707
+ // src/controllers/cli/install-ai-plugin.cli-controller.ts
3708
+ async function installAiPluginCliController(args2) {
3709
+ const { provider: providerName, plugins: pluginsArg, force } = args2;
3710
+ if (!providerName) {
3711
+ console.error('Error: "provider" argument is required (e.g., provider=claude).');
3712
+ process.exit(1);
3713
+ }
3714
+ if (!pluginsArg) {
3715
+ console.error(
3716
+ 'Error: "plugins" argument is required (e.g., plugins=ba-platform-plugin).'
3717
+ );
3718
+ process.exit(1);
3719
+ }
3720
+ const plugins = pluginsArg.split(",").map((s) => s.trim());
3721
+ const provider = getProvider(providerName);
3722
+ const docsSource = new LocalDocsSource();
3723
+ await installAiPluginService(
3724
+ { provider: providerName, plugins, force: force === "true" },
3725
+ {
3726
+ provider,
3727
+ docsSource,
3728
+ logger: { log: console.log }
3729
+ }
3730
+ );
3731
+ }
3732
+
3401
3733
  // src/controllers/cli/registry.ts
3402
3734
  var cliControllers = /* @__PURE__ */ new Map([
3403
3735
  [CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
@@ -3411,7 +3743,8 @@ var cliControllers = /* @__PURE__ */ new Map([
3411
3743
  [START_COMMAND_NAME, createLocalScriptCliController(START_COMMAND_NAME)],
3412
3744
  [STOP_COMMAND_NAME, createLocalScriptCliController(STOP_COMMAND_NAME)],
3413
3745
  [DESTROY_COMMAND_NAME, createLocalScriptCliController(DESTROY_COMMAND_NAME)],
3414
- [MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsCliController]
3746
+ [MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsCliController],
3747
+ [INSTALL_AI_PLUGIN_COMMAND_NAME, installAiPluginCliController]
3415
3748
  ]);
3416
3749
 
3417
3750
  // src/utils/parse-args.ts
package/docs/404.mdx ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: Page Not Found
3
+ ---
4
+
5
+ Sorry, this page doesn't exist. Use the navigation to find what you're looking for.