@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.
- package/dist/index.js +351 -18
- package/docs/404.mdx +5 -0
- package/docs/architecture/api-explorer.mdx +478 -0
- package/docs/architecture/architecture-diagrams.mdx +12 -0
- package/docs/architecture/authentication-system.mdx +903 -0
- package/docs/architecture/authorization-system.mdx +886 -0
- package/docs/architecture/bootstrap.mdx +1442 -0
- package/docs/architecture/gateway-architecture.mdx +845 -0
- package/docs/architecture/multi-tenancy.mdx +1150 -0
- package/docs/architecture/overview.mdx +776 -0
- package/docs/architecture/scheduler.mdx +818 -0
- package/docs/architecture/shell.mdx +885 -0
- package/docs/architecture/ui-extension-points.mdx +781 -0
- package/docs/architecture/user-states.mdx +794 -0
- package/docs/development/overview.mdx +21 -0
- package/docs/development/workflow.mdx +914 -0
- package/docs/getting-started/core-concepts.mdx +892 -0
- package/docs/getting-started/installation.mdx +780 -0
- package/docs/getting-started/overview.mdx +83 -0
- package/docs/getting-started/quick-start.mdx +940 -0
- package/docs/guides/adding-documentation-sites.mdx +1367 -0
- package/docs/guides/creating-services.mdx +1736 -0
- package/docs/guides/creating-ui-modules.mdx +1860 -0
- package/docs/guides/identity-providers.mdx +1007 -0
- package/docs/guides/mermaid-diagrams.mdx +212 -0
- package/docs/guides/using-feature-flags.mdx +1059 -0
- package/docs/guides/working-with-rooms.mdx +566 -0
- package/docs/index.mdx +57 -0
- package/docs/platform-cli/commands.mdx +604 -0
- package/docs/platform-cli/overview.mdx +195 -0
- package/package.json +5 -2
- package/skills/ba-platform/platform-cli.skill.md +26 -0
- package/skills/ba-platform/platform.skill.md +35 -0
- package/templates/application-monorepo-template/gitignore +95 -0
- package/templates/bootstrap-service-template/gitignore +57 -0
- package/templates/bootstrap-service-template/src/main.ts +6 -16
- package/templates/customization-ui-module-template/gitignore +73 -0
- package/templates/nestjs-service-module-template/gitignore +56 -0
- package/templates/platform-init-template/{{platformName}}-core/gitignore +97 -0
- package/templates/react-ui-module-template/Dockerfile +1 -1
- package/templates/react-ui-module-template/caddy/Caddyfile +1 -1
- 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
|
|
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
|
|
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
|
|
2184
|
+
const agent = new Agent3({ connect: { rejectUnauthorized: false } });
|
|
2138
2185
|
let response;
|
|
2139
2186
|
try {
|
|
2140
|
-
response = await
|
|
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
|
|
2215
|
+
const agent = new Agent3({ connect: { rejectUnauthorized: false } });
|
|
2169
2216
|
let response;
|
|
2170
2217
|
try {
|
|
2171
|
-
response = await
|
|
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
|
|
2254
|
+
const agent = new Agent3({ connect: { rejectUnauthorized: false } });
|
|
2208
2255
|
let response;
|
|
2209
2256
|
try {
|
|
2210
|
-
response = await
|
|
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
|
|
2501
|
+
const providers2 = getAllProviders();
|
|
2369
2502
|
const providerType = await ctx.select(
|
|
2370
2503
|
"Select IDP type:",
|
|
2371
|
-
|
|
2504
|
+
providers2.map((p) => ({ label: p.displayName, value: p.type }))
|
|
2372
2505
|
);
|
|
2373
|
-
const provider =
|
|
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
|