@claude-collective/cli 0.1.2 → 0.2.0
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/CHANGELOG.md +34 -0
- package/dist/cli/index.js +397 -284
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -209,6 +209,8 @@ function isValidGlobalConfig(obj) {
|
|
|
209
209
|
return false;
|
|
210
210
|
if (config.author !== void 0 && typeof config.author !== "string")
|
|
211
211
|
return false;
|
|
212
|
+
if (config.marketplace !== void 0 && typeof config.marketplace !== "string")
|
|
213
|
+
return false;
|
|
212
214
|
return true;
|
|
213
215
|
}
|
|
214
216
|
function isValidProjectConfig(obj) {
|
|
@@ -216,6 +218,8 @@ function isValidProjectConfig(obj) {
|
|
|
216
218
|
const config = obj;
|
|
217
219
|
if (config.source !== void 0 && typeof config.source !== "string")
|
|
218
220
|
return false;
|
|
221
|
+
if (config.marketplace !== void 0 && typeof config.marketplace !== "string")
|
|
222
|
+
return false;
|
|
219
223
|
return true;
|
|
220
224
|
}
|
|
221
225
|
function getGlobalConfigPath() {
|
|
@@ -279,32 +283,35 @@ async function saveProjectConfig(projectDir, config) {
|
|
|
279
283
|
verbose(`Saved project config to ${configPath}`);
|
|
280
284
|
}
|
|
281
285
|
async function resolveSource(flagValue, projectDir) {
|
|
286
|
+
const projectConfig = projectDir ? await loadProjectConfig(projectDir) : null;
|
|
287
|
+
const globalConfig = await loadGlobalConfig();
|
|
288
|
+
const marketplace = projectConfig?.marketplace || globalConfig?.marketplace;
|
|
282
289
|
if (flagValue !== void 0) {
|
|
283
290
|
if (flagValue === "" || flagValue.trim() === "") {
|
|
284
291
|
throw new Error("--source flag cannot be empty");
|
|
285
292
|
}
|
|
286
293
|
verbose(`Source from --source flag: ${flagValue}`);
|
|
287
|
-
return { source: flagValue, sourceOrigin: "flag" };
|
|
294
|
+
return { source: flagValue, sourceOrigin: "flag", marketplace };
|
|
288
295
|
}
|
|
289
296
|
const envValue = process.env[SOURCE_ENV_VAR];
|
|
290
297
|
if (envValue) {
|
|
291
298
|
verbose(`Source from ${SOURCE_ENV_VAR} env var: ${envValue}`);
|
|
292
|
-
return { source: envValue, sourceOrigin: "env" };
|
|
299
|
+
return { source: envValue, sourceOrigin: "env", marketplace };
|
|
293
300
|
}
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
301
|
+
if (projectConfig?.source) {
|
|
302
|
+
verbose(`Source from project config: ${projectConfig.source}`);
|
|
303
|
+
return {
|
|
304
|
+
source: projectConfig.source,
|
|
305
|
+
sourceOrigin: "project",
|
|
306
|
+
marketplace
|
|
307
|
+
};
|
|
300
308
|
}
|
|
301
|
-
const globalConfig = await loadGlobalConfig();
|
|
302
309
|
if (globalConfig?.source) {
|
|
303
310
|
verbose(`Source from global config: ${globalConfig.source}`);
|
|
304
|
-
return { source: globalConfig.source, sourceOrigin: "global" };
|
|
311
|
+
return { source: globalConfig.source, sourceOrigin: "global", marketplace };
|
|
305
312
|
}
|
|
306
313
|
verbose(`Using default source: ${DEFAULT_SOURCE}`);
|
|
307
|
-
return { source: DEFAULT_SOURCE, sourceOrigin: "default" };
|
|
314
|
+
return { source: DEFAULT_SOURCE, sourceOrigin: "default", marketplace };
|
|
308
315
|
}
|
|
309
316
|
function formatSourceOrigin(origin) {
|
|
310
317
|
switch (origin) {
|
|
@@ -529,7 +536,8 @@ async function loadAllAgents(projectRoot) {
|
|
|
529
536
|
description: config.description,
|
|
530
537
|
model: config.model,
|
|
531
538
|
tools: config.tools,
|
|
532
|
-
path: agentPath
|
|
539
|
+
path: agentPath,
|
|
540
|
+
sourceRoot: projectRoot
|
|
533
541
|
};
|
|
534
542
|
verbose(`Loaded agent: ${config.id} from ${file}`);
|
|
535
543
|
}
|
|
@@ -794,7 +802,8 @@ async function resolveAgents(agents, skills, compileConfig, projectRoot) {
|
|
|
794
802
|
model: definition.model,
|
|
795
803
|
tools: definition.tools,
|
|
796
804
|
skills: resolvedSkills,
|
|
797
|
-
path: definition.path
|
|
805
|
+
path: definition.path,
|
|
806
|
+
sourceRoot: definition.sourceRoot
|
|
798
807
|
};
|
|
799
808
|
}
|
|
800
809
|
return resolved;
|
|
@@ -891,9 +900,6 @@ function generateStackPluginManifest(options) {
|
|
|
891
900
|
if (options.keywords && options.keywords.length > 0) {
|
|
892
901
|
manifest.keywords = options.keywords;
|
|
893
902
|
}
|
|
894
|
-
if (options.hasAgents) {
|
|
895
|
-
manifest.agents = "./agents/";
|
|
896
|
-
}
|
|
897
903
|
if (options.hasHooks) {
|
|
898
904
|
manifest.hooks = "./hooks/hooks.json";
|
|
899
905
|
}
|
|
@@ -1011,9 +1017,10 @@ async function determineStackVersion(stack, pluginDir) {
|
|
|
1011
1017
|
contentHash: newHash
|
|
1012
1018
|
};
|
|
1013
1019
|
}
|
|
1014
|
-
async function compileAgentForPlugin(name, agent,
|
|
1020
|
+
async function compileAgentForPlugin(name, agent, fallbackRoot, engine) {
|
|
1015
1021
|
verbose(`Compiling agent: ${name}`);
|
|
1016
|
-
const
|
|
1022
|
+
const agentSourceRoot = agent.sourceRoot || fallbackRoot;
|
|
1023
|
+
const agentDir = path11.join(agentSourceRoot, DIRS.agents, agent.path || name);
|
|
1017
1024
|
const intro4 = await readFile(path11.join(agentDir, "intro.md"));
|
|
1018
1025
|
const workflow = await readFile(path11.join(agentDir, "workflow.md"));
|
|
1019
1026
|
const examples = await readFileOptional(
|
|
@@ -1030,7 +1037,7 @@ async function compileAgentForPlugin(name, agent, projectRoot, engine) {
|
|
|
1030
1037
|
);
|
|
1031
1038
|
const agentPath = agent.path || name;
|
|
1032
1039
|
const category = agentPath.split("/")[0];
|
|
1033
|
-
const categoryDir = path11.join(
|
|
1040
|
+
const categoryDir = path11.join(agentSourceRoot, DIRS.agents, category);
|
|
1034
1041
|
let outputFormat = await readFileOptional(
|
|
1035
1042
|
path11.join(agentDir, "output-format.md"),
|
|
1036
1043
|
""
|
|
@@ -1132,19 +1139,25 @@ function generateHooksJson(hooks) {
|
|
|
1132
1139
|
}
|
|
1133
1140
|
async function compileStackPlugin(options) {
|
|
1134
1141
|
const { stackId, outputDir, projectRoot, agentSourcePath } = options;
|
|
1135
|
-
const
|
|
1142
|
+
const localAgentRoot = agentSourcePath || projectRoot;
|
|
1136
1143
|
verbose(`Compiling stack plugin: ${stackId}`);
|
|
1137
1144
|
verbose(` Stack/skills source: ${projectRoot}`);
|
|
1138
|
-
verbose(`
|
|
1145
|
+
verbose(` Local agent source: ${localAgentRoot}`);
|
|
1146
|
+
verbose(` CLI agent source: ${PROJECT_ROOT}`);
|
|
1139
1147
|
const stack = await loadStack(stackId, projectRoot, "dev");
|
|
1140
|
-
const
|
|
1148
|
+
const cliAgents = await loadAllAgents(PROJECT_ROOT);
|
|
1149
|
+
const localAgents = await loadAllAgents(localAgentRoot);
|
|
1150
|
+
const agents = { ...cliAgents, ...localAgents };
|
|
1151
|
+
verbose(
|
|
1152
|
+
` Loaded ${Object.keys(localAgents).length} local agents, ${Object.keys(cliAgents).length} CLI agents`
|
|
1153
|
+
);
|
|
1141
1154
|
const skills = await loadSkillsByIds(stack.skills || [], projectRoot);
|
|
1142
1155
|
const compileConfig = stackToCompileConfig(stackId, stack);
|
|
1143
1156
|
const resolvedAgents = await resolveAgents(
|
|
1144
1157
|
agents,
|
|
1145
1158
|
skills,
|
|
1146
1159
|
compileConfig,
|
|
1147
|
-
|
|
1160
|
+
projectRoot
|
|
1148
1161
|
);
|
|
1149
1162
|
const pluginDir = path11.join(outputDir, stackId);
|
|
1150
1163
|
const agentsDir = path11.join(pluginDir, "agents");
|
|
@@ -1171,7 +1184,12 @@ async function compileStackPlugin(options) {
|
|
|
1171
1184
|
const compiledAgentNames = [];
|
|
1172
1185
|
const allSkillPlugins = [];
|
|
1173
1186
|
for (const [name, agent] of Object.entries(resolvedAgents)) {
|
|
1174
|
-
const output = await compileAgentForPlugin(
|
|
1187
|
+
const output = await compileAgentForPlugin(
|
|
1188
|
+
name,
|
|
1189
|
+
agent,
|
|
1190
|
+
PROJECT_ROOT,
|
|
1191
|
+
engine
|
|
1192
|
+
);
|
|
1175
1193
|
await writeFile(path11.join(agentsDir, `${name}.md`), output);
|
|
1176
1194
|
compiledAgentNames.push(name);
|
|
1177
1195
|
for (const skill of agent.skills) {
|
|
@@ -2362,6 +2380,82 @@ import pc7 from "picocolors";
|
|
|
2362
2380
|
import path25 from "path";
|
|
2363
2381
|
import { stringify as stringifyYaml4 } from "yaml";
|
|
2364
2382
|
|
|
2383
|
+
// src/cli/utils/exec.ts
|
|
2384
|
+
import { spawn } from "child_process";
|
|
2385
|
+
async function execCommand(command, args, options) {
|
|
2386
|
+
return new Promise((resolve, reject) => {
|
|
2387
|
+
const proc = spawn(command, args, {
|
|
2388
|
+
cwd: options?.cwd,
|
|
2389
|
+
env: { ...process.env, ...options?.env },
|
|
2390
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2391
|
+
});
|
|
2392
|
+
let stdout = "";
|
|
2393
|
+
let stderr = "";
|
|
2394
|
+
proc.stdout.on("data", (data) => {
|
|
2395
|
+
stdout += data.toString();
|
|
2396
|
+
});
|
|
2397
|
+
proc.stderr.on("data", (data) => {
|
|
2398
|
+
stderr += data.toString();
|
|
2399
|
+
});
|
|
2400
|
+
proc.on("close", (code) => {
|
|
2401
|
+
resolve({
|
|
2402
|
+
stdout,
|
|
2403
|
+
stderr,
|
|
2404
|
+
exitCode: code ?? 1
|
|
2405
|
+
});
|
|
2406
|
+
});
|
|
2407
|
+
proc.on("error", (err) => {
|
|
2408
|
+
reject(err);
|
|
2409
|
+
});
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
async function claudePluginInstall(pluginPath, scope, projectDir) {
|
|
2413
|
+
const args = ["plugin", "install", pluginPath, "--scope", scope];
|
|
2414
|
+
const result = await execCommand("claude", args, { cwd: projectDir });
|
|
2415
|
+
if (result.exitCode !== 0) {
|
|
2416
|
+
const errorMessage = result.stderr || result.stdout || "Unknown error";
|
|
2417
|
+
throw new Error(`Plugin installation failed: ${errorMessage.trim()}`);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
async function isClaudeCLIAvailable() {
|
|
2421
|
+
try {
|
|
2422
|
+
const result = await execCommand("claude", ["--version"], {});
|
|
2423
|
+
return result.exitCode === 0;
|
|
2424
|
+
} catch {
|
|
2425
|
+
return false;
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
async function claudePluginMarketplaceList() {
|
|
2429
|
+
const result = await execCommand(
|
|
2430
|
+
"claude",
|
|
2431
|
+
["plugin", "marketplace", "list", "--json"],
|
|
2432
|
+
{}
|
|
2433
|
+
);
|
|
2434
|
+
if (result.exitCode !== 0) {
|
|
2435
|
+
return [];
|
|
2436
|
+
}
|
|
2437
|
+
try {
|
|
2438
|
+
return JSON.parse(result.stdout);
|
|
2439
|
+
} catch {
|
|
2440
|
+
return [];
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
async function claudePluginMarketplaceExists(name) {
|
|
2444
|
+
const marketplaces = await claudePluginMarketplaceList();
|
|
2445
|
+
return marketplaces.some((m) => m.name === name);
|
|
2446
|
+
}
|
|
2447
|
+
async function claudePluginMarketplaceAdd(githubRepo, name) {
|
|
2448
|
+
const args = ["plugin", "marketplace", "add", githubRepo, "--name", name];
|
|
2449
|
+
const result = await execCommand("claude", args, {});
|
|
2450
|
+
if (result.exitCode !== 0) {
|
|
2451
|
+
const errorMessage = result.stderr || result.stdout || "Unknown error";
|
|
2452
|
+
if (errorMessage.includes("already installed")) {
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
throw new Error(`Failed to add marketplace: ${errorMessage.trim()}`);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2365
2459
|
// src/cli/lib/wizard.ts
|
|
2366
2460
|
import * as p5 from "@clack/prompts";
|
|
2367
2461
|
import pc6 from "picocolors";
|
|
@@ -2793,6 +2887,7 @@ function createInitialState(options = {}) {
|
|
|
2793
2887
|
lastSelectedCategory: null,
|
|
2794
2888
|
lastSelectedSubcategory: null,
|
|
2795
2889
|
lastSelectedSkill: null,
|
|
2890
|
+
lastSelectedApproach: null,
|
|
2796
2891
|
expertMode: options.hasLocalSkills ?? false,
|
|
2797
2892
|
installMode: "plugin"
|
|
2798
2893
|
};
|
|
@@ -2942,7 +3037,8 @@ async function stepApproach(state) {
|
|
|
2942
3037
|
},
|
|
2943
3038
|
formatExpertModeOption(state.expertMode),
|
|
2944
3039
|
formatInstallModeOption(state.installMode)
|
|
2945
|
-
]
|
|
3040
|
+
],
|
|
3041
|
+
initialValue: state.lastSelectedApproach || void 0
|
|
2946
3042
|
});
|
|
2947
3043
|
return result;
|
|
2948
3044
|
}
|
|
@@ -3138,13 +3234,16 @@ async function runWizard(matrix, options = {}) {
|
|
|
3138
3234
|
return null;
|
|
3139
3235
|
}
|
|
3140
3236
|
if (result === EXPERT_MODE_VALUE) {
|
|
3237
|
+
state.lastSelectedApproach = EXPERT_MODE_VALUE;
|
|
3141
3238
|
state.expertMode = !state.expertMode;
|
|
3142
3239
|
break;
|
|
3143
3240
|
}
|
|
3144
3241
|
if (result === INSTALL_MODE_VALUE) {
|
|
3242
|
+
state.lastSelectedApproach = INSTALL_MODE_VALUE;
|
|
3145
3243
|
state.installMode = state.installMode === "plugin" ? "local" : "plugin";
|
|
3146
3244
|
break;
|
|
3147
3245
|
}
|
|
3246
|
+
state.lastSelectedApproach = null;
|
|
3148
3247
|
if (result === "stack") {
|
|
3149
3248
|
pushHistory(state);
|
|
3150
3249
|
state.currentStep = "stack";
|
|
@@ -3329,12 +3428,97 @@ async function runWizard(matrix, options = {}) {
|
|
|
3329
3428
|
// src/cli/lib/source-loader.ts
|
|
3330
3429
|
import path21 from "path";
|
|
3331
3430
|
|
|
3332
|
-
// src/cli/lib/
|
|
3431
|
+
// src/cli/lib/local-skill-loader.ts
|
|
3333
3432
|
import { parse as parseYaml7 } from "yaml";
|
|
3334
3433
|
import path19 from "path";
|
|
3434
|
+
var LOCAL_CATEGORY = "local";
|
|
3435
|
+
var LOCAL_AUTHOR = "@local";
|
|
3436
|
+
var TEST_SKILL_PREFIX = "test-";
|
|
3437
|
+
async function discoverLocalSkills(projectDir) {
|
|
3438
|
+
const localSkillsPath = path19.join(projectDir, LOCAL_SKILLS_PATH);
|
|
3439
|
+
if (!await directoryExists(localSkillsPath)) {
|
|
3440
|
+
verbose(`Local skills directory not found: ${localSkillsPath}`);
|
|
3441
|
+
return null;
|
|
3442
|
+
}
|
|
3443
|
+
const skills = [];
|
|
3444
|
+
const skillDirs = await listDirectories(localSkillsPath);
|
|
3445
|
+
for (const skillDirName of skillDirs) {
|
|
3446
|
+
if (!skillDirName.startsWith(TEST_SKILL_PREFIX)) {
|
|
3447
|
+
verbose(
|
|
3448
|
+
`Skipping local skill '${skillDirName}': Does not have test- prefix (temporary filter)`
|
|
3449
|
+
);
|
|
3450
|
+
continue;
|
|
3451
|
+
}
|
|
3452
|
+
const skill = await extractLocalSkill(localSkillsPath, skillDirName);
|
|
3453
|
+
if (skill) {
|
|
3454
|
+
skills.push(skill);
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
verbose(`Discovered ${skills.length} local skills from ${localSkillsPath}`);
|
|
3458
|
+
return {
|
|
3459
|
+
skills,
|
|
3460
|
+
localSkillsPath
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3463
|
+
async function extractLocalSkill(localSkillsPath, skillDirName) {
|
|
3464
|
+
const skillDir = path19.join(localSkillsPath, skillDirName);
|
|
3465
|
+
const metadataPath = path19.join(skillDir, "metadata.yaml");
|
|
3466
|
+
const skillMdPath = path19.join(skillDir, "SKILL.md");
|
|
3467
|
+
if (!await fileExists(metadataPath)) {
|
|
3468
|
+
verbose(`Skipping local skill '${skillDirName}': No metadata.yaml found`);
|
|
3469
|
+
return null;
|
|
3470
|
+
}
|
|
3471
|
+
if (!await fileExists(skillMdPath)) {
|
|
3472
|
+
verbose(`Skipping local skill '${skillDirName}': No SKILL.md found`);
|
|
3473
|
+
return null;
|
|
3474
|
+
}
|
|
3475
|
+
const metadataContent = await readFile(metadataPath);
|
|
3476
|
+
const metadata = parseYaml7(metadataContent);
|
|
3477
|
+
if (!metadata.cli_name) {
|
|
3478
|
+
verbose(
|
|
3479
|
+
`Skipping local skill '${skillDirName}': Missing required 'cli_name' in metadata.yaml`
|
|
3480
|
+
);
|
|
3481
|
+
return null;
|
|
3482
|
+
}
|
|
3483
|
+
const skillMdContent = await readFile(skillMdPath);
|
|
3484
|
+
const frontmatter = parseFrontmatter(skillMdContent);
|
|
3485
|
+
if (!frontmatter) {
|
|
3486
|
+
verbose(
|
|
3487
|
+
`Skipping local skill '${skillDirName}': Invalid SKILL.md frontmatter`
|
|
3488
|
+
);
|
|
3489
|
+
return null;
|
|
3490
|
+
}
|
|
3491
|
+
const relativePath = `${LOCAL_SKILLS_PATH}/${skillDirName}/`;
|
|
3492
|
+
const skillId = frontmatter.name;
|
|
3493
|
+
const extracted = {
|
|
3494
|
+
id: skillId,
|
|
3495
|
+
directoryPath: skillDirName,
|
|
3496
|
+
name: `${metadata.cli_name} ${LOCAL_AUTHOR}`,
|
|
3497
|
+
description: metadata.cli_description || frontmatter.description,
|
|
3498
|
+
usageGuidance: void 0,
|
|
3499
|
+
category: LOCAL_CATEGORY,
|
|
3500
|
+
categoryExclusive: false,
|
|
3501
|
+
author: LOCAL_AUTHOR,
|
|
3502
|
+
tags: [],
|
|
3503
|
+
compatibleWith: [],
|
|
3504
|
+
conflictsWith: [],
|
|
3505
|
+
requires: [],
|
|
3506
|
+
requiresSetup: [],
|
|
3507
|
+
providesSetupFor: [],
|
|
3508
|
+
path: relativePath,
|
|
3509
|
+
local: true,
|
|
3510
|
+
localPath: relativePath
|
|
3511
|
+
};
|
|
3512
|
+
verbose(`Extracted local skill: ${skillId}`);
|
|
3513
|
+
return extracted;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
// src/cli/lib/matrix-loader.ts
|
|
3517
|
+
import { parse as parseYaml8 } from "yaml";
|
|
3518
|
+
import path20 from "path";
|
|
3335
3519
|
async function loadSkillsMatrix(configPath) {
|
|
3336
3520
|
const content = await readFile(configPath);
|
|
3337
|
-
const config =
|
|
3521
|
+
const config = parseYaml8(content);
|
|
3338
3522
|
validateMatrixStructure(config, configPath);
|
|
3339
3523
|
verbose(`Loaded skills matrix: ${configPath}`);
|
|
3340
3524
|
return config;
|
|
@@ -3390,15 +3574,15 @@ async function extractAllSkills(skillsDir) {
|
|
|
3390
3574
|
const skills = [];
|
|
3391
3575
|
const metadataFiles = await glob("**/metadata.yaml", skillsDir);
|
|
3392
3576
|
for (const metadataFile of metadataFiles) {
|
|
3393
|
-
const skillDir =
|
|
3394
|
-
const skillMdPath =
|
|
3395
|
-
const metadataPath =
|
|
3577
|
+
const skillDir = path20.dirname(metadataFile);
|
|
3578
|
+
const skillMdPath = path20.join(skillsDir, skillDir, "SKILL.md");
|
|
3579
|
+
const metadataPath = path20.join(skillsDir, metadataFile);
|
|
3396
3580
|
if (!await fileExists(skillMdPath)) {
|
|
3397
3581
|
verbose(`Skipping ${metadataFile}: No SKILL.md found`);
|
|
3398
3582
|
continue;
|
|
3399
3583
|
}
|
|
3400
3584
|
const metadataContent = await readFile(metadataPath);
|
|
3401
|
-
const metadata =
|
|
3585
|
+
const metadata = parseYaml8(metadataContent);
|
|
3402
3586
|
const skillMdContent = await readFile(skillMdPath);
|
|
3403
3587
|
const frontmatter = parseFrontmatter(skillMdContent);
|
|
3404
3588
|
if (!frontmatter) {
|
|
@@ -3686,91 +3870,6 @@ function resolveSuggestedStacks(matrix, aliases) {
|
|
|
3686
3870
|
});
|
|
3687
3871
|
}
|
|
3688
3872
|
|
|
3689
|
-
// src/cli/lib/local-skill-loader.ts
|
|
3690
|
-
import { parse as parseYaml8 } from "yaml";
|
|
3691
|
-
import path20 from "path";
|
|
3692
|
-
var LOCAL_CATEGORY = "local";
|
|
3693
|
-
var LOCAL_AUTHOR = "@local";
|
|
3694
|
-
var TEST_SKILL_PREFIX = "test-";
|
|
3695
|
-
async function discoverLocalSkills(projectDir) {
|
|
3696
|
-
const localSkillsPath = path20.join(projectDir, LOCAL_SKILLS_PATH);
|
|
3697
|
-
if (!await directoryExists(localSkillsPath)) {
|
|
3698
|
-
verbose(`Local skills directory not found: ${localSkillsPath}`);
|
|
3699
|
-
return null;
|
|
3700
|
-
}
|
|
3701
|
-
const skills = [];
|
|
3702
|
-
const skillDirs = await listDirectories(localSkillsPath);
|
|
3703
|
-
for (const skillDirName of skillDirs) {
|
|
3704
|
-
if (!skillDirName.startsWith(TEST_SKILL_PREFIX)) {
|
|
3705
|
-
verbose(
|
|
3706
|
-
`Skipping local skill '${skillDirName}': Does not have test- prefix (temporary filter)`
|
|
3707
|
-
);
|
|
3708
|
-
continue;
|
|
3709
|
-
}
|
|
3710
|
-
const skill = await extractLocalSkill(localSkillsPath, skillDirName);
|
|
3711
|
-
if (skill) {
|
|
3712
|
-
skills.push(skill);
|
|
3713
|
-
}
|
|
3714
|
-
}
|
|
3715
|
-
verbose(`Discovered ${skills.length} local skills from ${localSkillsPath}`);
|
|
3716
|
-
return {
|
|
3717
|
-
skills,
|
|
3718
|
-
localSkillsPath
|
|
3719
|
-
};
|
|
3720
|
-
}
|
|
3721
|
-
async function extractLocalSkill(localSkillsPath, skillDirName) {
|
|
3722
|
-
const skillDir = path20.join(localSkillsPath, skillDirName);
|
|
3723
|
-
const metadataPath = path20.join(skillDir, "metadata.yaml");
|
|
3724
|
-
const skillMdPath = path20.join(skillDir, "SKILL.md");
|
|
3725
|
-
if (!await fileExists(metadataPath)) {
|
|
3726
|
-
verbose(`Skipping local skill '${skillDirName}': No metadata.yaml found`);
|
|
3727
|
-
return null;
|
|
3728
|
-
}
|
|
3729
|
-
if (!await fileExists(skillMdPath)) {
|
|
3730
|
-
verbose(`Skipping local skill '${skillDirName}': No SKILL.md found`);
|
|
3731
|
-
return null;
|
|
3732
|
-
}
|
|
3733
|
-
const metadataContent = await readFile(metadataPath);
|
|
3734
|
-
const metadata = parseYaml8(metadataContent);
|
|
3735
|
-
if (!metadata.cli_name) {
|
|
3736
|
-
verbose(
|
|
3737
|
-
`Skipping local skill '${skillDirName}': Missing required 'cli_name' in metadata.yaml`
|
|
3738
|
-
);
|
|
3739
|
-
return null;
|
|
3740
|
-
}
|
|
3741
|
-
const skillMdContent = await readFile(skillMdPath);
|
|
3742
|
-
const frontmatter = parseFrontmatter(skillMdContent);
|
|
3743
|
-
if (!frontmatter) {
|
|
3744
|
-
verbose(
|
|
3745
|
-
`Skipping local skill '${skillDirName}': Invalid SKILL.md frontmatter`
|
|
3746
|
-
);
|
|
3747
|
-
return null;
|
|
3748
|
-
}
|
|
3749
|
-
const relativePath = `${LOCAL_SKILLS_PATH}/${skillDirName}/`;
|
|
3750
|
-
const skillId = frontmatter.name;
|
|
3751
|
-
const extracted = {
|
|
3752
|
-
id: skillId,
|
|
3753
|
-
directoryPath: skillDirName,
|
|
3754
|
-
name: `${metadata.cli_name} ${LOCAL_AUTHOR}`,
|
|
3755
|
-
description: metadata.cli_description || frontmatter.description,
|
|
3756
|
-
usageGuidance: void 0,
|
|
3757
|
-
category: LOCAL_CATEGORY,
|
|
3758
|
-
categoryExclusive: false,
|
|
3759
|
-
author: LOCAL_AUTHOR,
|
|
3760
|
-
tags: [],
|
|
3761
|
-
compatibleWith: [],
|
|
3762
|
-
conflictsWith: [],
|
|
3763
|
-
requires: [],
|
|
3764
|
-
requiresSetup: [],
|
|
3765
|
-
providesSetupFor: [],
|
|
3766
|
-
path: relativePath,
|
|
3767
|
-
local: true,
|
|
3768
|
-
localPath: relativePath
|
|
3769
|
-
};
|
|
3770
|
-
verbose(`Extracted local skill: ${skillId}`);
|
|
3771
|
-
return extracted;
|
|
3772
|
-
}
|
|
3773
|
-
|
|
3774
3873
|
// src/cli/lib/source-loader.ts
|
|
3775
3874
|
async function loadSkillsMatrixFromSource(options = {}) {
|
|
3776
3875
|
const {
|
|
@@ -3810,9 +3909,17 @@ async function loadFromLocal(source, sourceConfig) {
|
|
|
3810
3909
|
skillsPath = PROJECT_ROOT;
|
|
3811
3910
|
}
|
|
3812
3911
|
verbose(`Loading skills from local path: ${skillsPath}`);
|
|
3813
|
-
const
|
|
3912
|
+
const sourceMatrixPath = path21.join(skillsPath, SKILLS_MATRIX_PATH);
|
|
3913
|
+
const cliMatrixPath = path21.join(PROJECT_ROOT, SKILLS_MATRIX_PATH);
|
|
3914
|
+
let matrixPath;
|
|
3915
|
+
if (await fileExists(sourceMatrixPath)) {
|
|
3916
|
+
matrixPath = sourceMatrixPath;
|
|
3917
|
+
verbose(`Matrix from source: ${matrixPath}`);
|
|
3918
|
+
} else {
|
|
3919
|
+
matrixPath = cliMatrixPath;
|
|
3920
|
+
verbose(`Matrix from CLI (source has no matrix): ${matrixPath}`);
|
|
3921
|
+
}
|
|
3814
3922
|
const skillsDir = path21.join(skillsPath, SKILLS_DIR_PATH);
|
|
3815
|
-
verbose(`Matrix from CLI: ${matrixPath}`);
|
|
3816
3923
|
verbose(`Skills from source: ${skillsDir}`);
|
|
3817
3924
|
const matrix = await loadSkillsMatrix(matrixPath);
|
|
3818
3925
|
const skills = await extractAllSkills(skillsDir);
|
|
@@ -3821,16 +3928,25 @@ async function loadFromLocal(source, sourceConfig) {
|
|
|
3821
3928
|
matrix: mergedMatrix,
|
|
3822
3929
|
sourceConfig,
|
|
3823
3930
|
sourcePath: skillsPath,
|
|
3824
|
-
isLocal: true
|
|
3931
|
+
isLocal: true,
|
|
3932
|
+
marketplace: sourceConfig.marketplace
|
|
3825
3933
|
};
|
|
3826
3934
|
}
|
|
3827
3935
|
async function loadFromRemote(source, sourceConfig, forceRefresh) {
|
|
3828
3936
|
verbose(`Fetching skills from remote source: ${source}`);
|
|
3829
3937
|
const fetchResult = await fetchFromSource(source, { forceRefresh });
|
|
3830
3938
|
verbose(`Fetched to: ${fetchResult.path}`);
|
|
3831
|
-
const
|
|
3939
|
+
const sourceMatrixPath = path21.join(fetchResult.path, SKILLS_MATRIX_PATH);
|
|
3940
|
+
const cliMatrixPath = path21.join(PROJECT_ROOT, SKILLS_MATRIX_PATH);
|
|
3941
|
+
let matrixPath;
|
|
3942
|
+
if (await fileExists(sourceMatrixPath)) {
|
|
3943
|
+
matrixPath = sourceMatrixPath;
|
|
3944
|
+
verbose(`Matrix from source: ${matrixPath}`);
|
|
3945
|
+
} else {
|
|
3946
|
+
matrixPath = cliMatrixPath;
|
|
3947
|
+
verbose(`Matrix from CLI (source has no matrix): ${matrixPath}`);
|
|
3948
|
+
}
|
|
3832
3949
|
const skillsDir = path21.join(fetchResult.path, SKILLS_DIR_PATH);
|
|
3833
|
-
verbose(`Matrix from CLI: ${matrixPath}`);
|
|
3834
3950
|
verbose(`Skills from source: ${skillsDir}`);
|
|
3835
3951
|
const matrix = await loadSkillsMatrix(matrixPath);
|
|
3836
3952
|
const skills = await extractAllSkills(skillsDir);
|
|
@@ -3839,7 +3955,8 @@ async function loadFromRemote(source, sourceConfig, forceRefresh) {
|
|
|
3839
3955
|
matrix: mergedMatrix,
|
|
3840
3956
|
sourceConfig,
|
|
3841
3957
|
sourcePath: fetchResult.path,
|
|
3842
|
-
isLocal: false
|
|
3958
|
+
isLocal: false,
|
|
3959
|
+
marketplace: sourceConfig.marketplace
|
|
3843
3960
|
};
|
|
3844
3961
|
}
|
|
3845
3962
|
var LOCAL_CATEGORY_TOP = {
|
|
@@ -4085,54 +4202,6 @@ For autonomous operation, add to .claude/settings.json:
|
|
|
4085
4202
|
// src/cli/lib/stack-installer.ts
|
|
4086
4203
|
import os3 from "os";
|
|
4087
4204
|
import path24 from "path";
|
|
4088
|
-
|
|
4089
|
-
// src/cli/utils/exec.ts
|
|
4090
|
-
import { spawn } from "child_process";
|
|
4091
|
-
async function execCommand(command, args, options) {
|
|
4092
|
-
return new Promise((resolve, reject) => {
|
|
4093
|
-
const proc = spawn(command, args, {
|
|
4094
|
-
cwd: options?.cwd,
|
|
4095
|
-
env: { ...process.env, ...options?.env },
|
|
4096
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4097
|
-
});
|
|
4098
|
-
let stdout = "";
|
|
4099
|
-
let stderr = "";
|
|
4100
|
-
proc.stdout.on("data", (data) => {
|
|
4101
|
-
stdout += data.toString();
|
|
4102
|
-
});
|
|
4103
|
-
proc.stderr.on("data", (data) => {
|
|
4104
|
-
stderr += data.toString();
|
|
4105
|
-
});
|
|
4106
|
-
proc.on("close", (code) => {
|
|
4107
|
-
resolve({
|
|
4108
|
-
stdout,
|
|
4109
|
-
stderr,
|
|
4110
|
-
exitCode: code ?? 1
|
|
4111
|
-
});
|
|
4112
|
-
});
|
|
4113
|
-
proc.on("error", (err) => {
|
|
4114
|
-
reject(err);
|
|
4115
|
-
});
|
|
4116
|
-
});
|
|
4117
|
-
}
|
|
4118
|
-
async function claudePluginInstall(pluginPath, scope, projectDir) {
|
|
4119
|
-
const args = ["plugin", "install", pluginPath, "--scope", scope];
|
|
4120
|
-
const result = await execCommand("claude", args, { cwd: projectDir });
|
|
4121
|
-
if (result.exitCode !== 0) {
|
|
4122
|
-
const errorMessage = result.stderr || result.stdout || "Unknown error";
|
|
4123
|
-
throw new Error(`Plugin installation failed: ${errorMessage.trim()}`);
|
|
4124
|
-
}
|
|
4125
|
-
}
|
|
4126
|
-
async function isClaudeCLIAvailable() {
|
|
4127
|
-
try {
|
|
4128
|
-
const result = await execCommand("claude", ["--version"], {});
|
|
4129
|
-
return result.exitCode === 0;
|
|
4130
|
-
} catch {
|
|
4131
|
-
return false;
|
|
4132
|
-
}
|
|
4133
|
-
}
|
|
4134
|
-
|
|
4135
|
-
// src/cli/lib/stack-installer.ts
|
|
4136
4205
|
async function compileStackToTemp(options) {
|
|
4137
4206
|
const tempDir = path24.join(os3.tmpdir(), `cc-stack-${Date.now()}`);
|
|
4138
4207
|
await ensureDir(tempDir);
|
|
@@ -4150,13 +4219,27 @@ async function compileStackToTemp(options) {
|
|
|
4150
4219
|
};
|
|
4151
4220
|
}
|
|
4152
4221
|
async function installStackAsPlugin(options) {
|
|
4153
|
-
const { stackId, projectDir, sourcePath, agentSourcePath } = options;
|
|
4222
|
+
const { stackId, projectDir, sourcePath, agentSourcePath, marketplace } = options;
|
|
4154
4223
|
const claudeAvailable = await isClaudeCLIAvailable();
|
|
4155
4224
|
if (!claudeAvailable) {
|
|
4156
4225
|
throw new Error(
|
|
4157
4226
|
"Claude CLI not found. Please install Claude Code first: https://claude.ai/code"
|
|
4158
4227
|
);
|
|
4159
4228
|
}
|
|
4229
|
+
if (marketplace) {
|
|
4230
|
+
verbose(`Installing from marketplace: ${stackId}@${marketplace}`);
|
|
4231
|
+
const pluginRef = `${stackId}@${marketplace}`;
|
|
4232
|
+
await claudePluginInstall(pluginRef, "project", projectDir);
|
|
4233
|
+
return {
|
|
4234
|
+
pluginName: stackId,
|
|
4235
|
+
stackName: stackId,
|
|
4236
|
+
agents: [],
|
|
4237
|
+
skills: [],
|
|
4238
|
+
pluginPath: pluginRef,
|
|
4239
|
+
fromMarketplace: true
|
|
4240
|
+
};
|
|
4241
|
+
}
|
|
4242
|
+
verbose(`Compiling stack locally: ${stackId}`);
|
|
4160
4243
|
const { result, cleanup } = await compileStackToTemp({
|
|
4161
4244
|
stackId,
|
|
4162
4245
|
projectRoot: sourcePath,
|
|
@@ -4169,7 +4252,8 @@ async function installStackAsPlugin(options) {
|
|
|
4169
4252
|
stackName: result.stackName,
|
|
4170
4253
|
agents: result.agents,
|
|
4171
4254
|
skills: result.skillPlugins,
|
|
4172
|
-
pluginPath: result.pluginPath
|
|
4255
|
+
pluginPath: result.pluginPath,
|
|
4256
|
+
fromMarketplace: false
|
|
4173
4257
|
};
|
|
4174
4258
|
} finally {
|
|
4175
4259
|
await cleanup();
|
|
@@ -4507,21 +4591,35 @@ var initCommand = new Command5("init").description("Initialize Claude Collective
|
|
|
4507
4591
|
);
|
|
4508
4592
|
if (dryRun) {
|
|
4509
4593
|
if (result.installMode === "plugin" && result.selectedStack) {
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4594
|
+
const useMarketplace = !!sourceResult.marketplace;
|
|
4595
|
+
if (useMarketplace) {
|
|
4596
|
+
p7.log.info(
|
|
4597
|
+
pc7.yellow(
|
|
4598
|
+
`[dry-run] Would install stack "${result.selectedStack.id}" from marketplace "${sourceResult.marketplace}"`
|
|
4599
|
+
)
|
|
4600
|
+
);
|
|
4601
|
+
p7.log.info(
|
|
4602
|
+
pc7.yellow(
|
|
4603
|
+
`[dry-run] claude plugin install ${result.selectedStack.id}@${sourceResult.marketplace} --scope project`
|
|
4604
|
+
)
|
|
4605
|
+
);
|
|
4606
|
+
} else {
|
|
4607
|
+
p7.log.info(
|
|
4608
|
+
pc7.yellow(
|
|
4609
|
+
`[dry-run] Would compile and install stack "${result.selectedStack.id}" as a native plugin`
|
|
4610
|
+
)
|
|
4611
|
+
);
|
|
4612
|
+
p7.log.info(
|
|
4613
|
+
pc7.yellow(
|
|
4614
|
+
`[dry-run] claude plugin install ./compiled-stack/${result.selectedStack.id} --scope project`
|
|
4615
|
+
)
|
|
4616
|
+
);
|
|
4617
|
+
p7.log.info(
|
|
4618
|
+
pc7.yellow(
|
|
4619
|
+
`[dry-run] Stack includes ${result.selectedSkills.length} skills and agents bundled together`
|
|
4620
|
+
)
|
|
4621
|
+
);
|
|
4622
|
+
}
|
|
4525
4623
|
} else {
|
|
4526
4624
|
if (result.installMode === "plugin") {
|
|
4527
4625
|
p7.log.info(
|
|
@@ -4550,31 +4648,59 @@ var initCommand = new Command5("init").description("Initialize Claude Collective
|
|
|
4550
4648
|
}
|
|
4551
4649
|
if (result.installMode === "plugin") {
|
|
4552
4650
|
if (result.selectedStack) {
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4651
|
+
if (sourceResult.marketplace) {
|
|
4652
|
+
const marketplaceExists = await claudePluginMarketplaceExists(
|
|
4653
|
+
sourceResult.marketplace
|
|
4654
|
+
);
|
|
4655
|
+
if (!marketplaceExists) {
|
|
4656
|
+
s.start(`Registering marketplace "${sourceResult.marketplace}"...`);
|
|
4657
|
+
try {
|
|
4658
|
+
await claudePluginMarketplaceAdd(
|
|
4659
|
+
sourceResult.sourceConfig.source,
|
|
4660
|
+
sourceResult.marketplace
|
|
4661
|
+
);
|
|
4662
|
+
s.stop(`Registered marketplace: ${sourceResult.marketplace}`);
|
|
4663
|
+
} catch (error) {
|
|
4664
|
+
s.stop("Failed to register marketplace");
|
|
4665
|
+
p7.log.error(
|
|
4666
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
4667
|
+
);
|
|
4668
|
+
process.exit(EXIT_CODES.ERROR);
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
}
|
|
4672
|
+
const installMethod = sourceResult.marketplace ? `Installing from marketplace "${sourceResult.marketplace}"` : "Compiling and installing";
|
|
4673
|
+
s.start(`${installMethod} stack "${result.selectedStack.id}"...`);
|
|
4556
4674
|
try {
|
|
4557
4675
|
const installResult = await installStackAsPlugin({
|
|
4558
4676
|
stackId: result.selectedStack.id,
|
|
4559
4677
|
projectDir,
|
|
4560
4678
|
sourcePath: sourceResult.sourcePath,
|
|
4561
|
-
agentSourcePath: sourceResult.sourcePath
|
|
4679
|
+
agentSourcePath: sourceResult.sourcePath,
|
|
4680
|
+
marketplace: sourceResult.marketplace
|
|
4562
4681
|
});
|
|
4563
|
-
|
|
4682
|
+
const installedFrom = installResult.fromMarketplace ? `from marketplace` : `(compiled locally)`;
|
|
4683
|
+
s.stop(
|
|
4684
|
+
`Installed stack plugin: ${installResult.pluginName} ${installedFrom}`
|
|
4685
|
+
);
|
|
4564
4686
|
console.log("");
|
|
4565
4687
|
console.log(pc7.green("Claude Collective initialized successfully!"));
|
|
4566
4688
|
console.log("");
|
|
4567
4689
|
console.log(
|
|
4568
4690
|
`Stack ${pc7.cyan(`"${installResult.stackName}"`)} installed as plugin`
|
|
4569
4691
|
);
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4692
|
+
if (installResult.agents.length > 0) {
|
|
4693
|
+
console.log("");
|
|
4694
|
+
console.log(pc7.dim("Agents included:"));
|
|
4695
|
+
for (const agentName of installResult.agents) {
|
|
4696
|
+
console.log(` ${pc7.cyan(agentName)}`);
|
|
4697
|
+
}
|
|
4698
|
+
console.log("");
|
|
4699
|
+
console.log(
|
|
4700
|
+
pc7.dim(`Skills bundled: ${installResult.skills.length}`)
|
|
4701
|
+
);
|
|
4574
4702
|
}
|
|
4575
4703
|
console.log("");
|
|
4576
|
-
console.log(pc7.dim(`Skills bundled: ${installResult.skills.length}`));
|
|
4577
|
-
console.log("");
|
|
4578
4704
|
p7.outro(pc7.green("Claude Collective is ready to use!"));
|
|
4579
4705
|
await checkPermissions(projectDir);
|
|
4580
4706
|
return;
|
|
@@ -4613,7 +4739,9 @@ var initCommand = new Command5("init").description("Initialize Claude Collective
|
|
|
4613
4739
|
);
|
|
4614
4740
|
s.stop(`Copied ${copiedSkills.length} skills to .claude/skills/`);
|
|
4615
4741
|
s.start("Generating configuration...");
|
|
4616
|
-
const
|
|
4742
|
+
const cliAgents = await loadAllAgents(PROJECT_ROOT);
|
|
4743
|
+
const localAgents = await loadAllAgents(sourceResult.sourcePath);
|
|
4744
|
+
const agents = { ...cliAgents, ...localAgents };
|
|
4617
4745
|
const localSkillsForResolution = {};
|
|
4618
4746
|
for (const copiedSkill of copiedSkills) {
|
|
4619
4747
|
const skill = matrix.skills[copiedSkill.skillId];
|
|
@@ -5182,6 +5310,9 @@ var KEBAB_CASE_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
|
5182
5310
|
var SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
5183
5311
|
var schemaCache2 = /* @__PURE__ */ new Map();
|
|
5184
5312
|
var validatorCache2 = /* @__PURE__ */ new Map();
|
|
5313
|
+
var REMOTE_SCHEMAS = {
|
|
5314
|
+
"skill-frontmatter.schema.json": "https://raw.githubusercontent.com/claude-collective/skills/main/src/schemas/skill-frontmatter.schema.json"
|
|
5315
|
+
};
|
|
5185
5316
|
async function loadSchema2(schemaName) {
|
|
5186
5317
|
if (schemaCache2.has(schemaName)) {
|
|
5187
5318
|
return schemaCache2.get(schemaName);
|
|
@@ -5198,8 +5329,20 @@ async function loadSchema2(schemaName) {
|
|
|
5198
5329
|
return schema;
|
|
5199
5330
|
}
|
|
5200
5331
|
}
|
|
5332
|
+
const remoteUrl = REMOTE_SCHEMAS[schemaName];
|
|
5333
|
+
if (remoteUrl) {
|
|
5334
|
+
try {
|
|
5335
|
+
const response = await fetch(remoteUrl);
|
|
5336
|
+
if (response.ok) {
|
|
5337
|
+
const schema = await response.json();
|
|
5338
|
+
schemaCache2.set(schemaName, schema);
|
|
5339
|
+
return schema;
|
|
5340
|
+
}
|
|
5341
|
+
} catch {
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5201
5344
|
throw new Error(
|
|
5202
|
-
`Schema not found: ${schemaName}. Searched: ${locations.join(", ")}`
|
|
5345
|
+
`Schema not found: ${schemaName}. Searched: ${locations.join(", ")}${remoteUrl ? ` and ${remoteUrl}` : ""}`
|
|
5203
5346
|
);
|
|
5204
5347
|
}
|
|
5205
5348
|
async function getValidator2(schemaName) {
|
|
@@ -5909,7 +6052,8 @@ import { Command as Command10 } from "commander";
|
|
|
5909
6052
|
import * as p12 from "@clack/prompts";
|
|
5910
6053
|
import pc12 from "picocolors";
|
|
5911
6054
|
import path30 from "path";
|
|
5912
|
-
|
|
6055
|
+
import os4 from "os";
|
|
6056
|
+
var EJECT_TYPES = ["templates", "config", "all"];
|
|
5913
6057
|
var DEFAULT_CONFIG_CONTENT = `# Claude Collective Configuration
|
|
5914
6058
|
# Agent-skill mappings for this project
|
|
5915
6059
|
|
|
@@ -5935,68 +6079,59 @@ agent_skills:
|
|
|
5935
6079
|
- drizzle
|
|
5936
6080
|
- better-auth
|
|
5937
6081
|
`;
|
|
5938
|
-
var
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
# Example Skill
|
|
5944
|
-
|
|
5945
|
-
## Overview
|
|
5946
|
-
Describe what this skill teaches the agent.
|
|
5947
|
-
|
|
5948
|
-
## Instructions
|
|
5949
|
-
Specific instructions for the agent.
|
|
5950
|
-
|
|
5951
|
-
## Examples
|
|
5952
|
-
\`\`\`typescript
|
|
5953
|
-
// Example code
|
|
5954
|
-
\`\`\`
|
|
5955
|
-
`;
|
|
5956
|
-
var DEFAULT_METADATA_CONTENT = `# yaml-language-server: $schema=../../schemas/metadata.schema.json
|
|
5957
|
-
category: custom
|
|
5958
|
-
author: "@local"
|
|
5959
|
-
cli_name: Example Skill
|
|
5960
|
-
cli_description: Short description for CLI
|
|
5961
|
-
`;
|
|
5962
|
-
var ejectCommand = new Command10("eject").description("Eject bundled content for local customization").argument("[type]", "What to eject: templates, skills, config, all").option("-f, --force", "Overwrite existing files", false).configureOutput({
|
|
6082
|
+
var ejectCommand = new Command10("eject").description("Eject bundled content for local customization").argument("[type]", "What to eject: templates, config, all").option("-f, --force", "Overwrite existing files", false).option(
|
|
6083
|
+
"-o, --output <dir>",
|
|
6084
|
+
"Output directory (default: .claude/ in current directory)"
|
|
6085
|
+
).configureOutput({
|
|
5963
6086
|
writeErr: (str) => console.error(pc12.red(str))
|
|
5964
|
-
}).showHelpAfterError(true).action(
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
"Please specify what to eject: templates,
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
6087
|
+
}).showHelpAfterError(true).action(
|
|
6088
|
+
async (type, options) => {
|
|
6089
|
+
const projectDir = process.cwd();
|
|
6090
|
+
if (!type) {
|
|
6091
|
+
p12.log.error("Please specify what to eject: templates, config, or all");
|
|
6092
|
+
process.exit(EXIT_CODES.INVALID_ARGS);
|
|
6093
|
+
}
|
|
6094
|
+
if (!EJECT_TYPES.includes(type)) {
|
|
6095
|
+
p12.log.error(`Unknown eject type: ${type}`);
|
|
6096
|
+
p12.log.info(`Valid types: ${EJECT_TYPES.join(", ")}`);
|
|
6097
|
+
process.exit(EXIT_CODES.INVALID_ARGS);
|
|
6098
|
+
}
|
|
6099
|
+
let outputBase;
|
|
6100
|
+
if (options.output) {
|
|
6101
|
+
const expandedPath = options.output.startsWith("~") ? path30.join(os4.homedir(), options.output.slice(1)) : options.output;
|
|
6102
|
+
outputBase = path30.resolve(projectDir, expandedPath);
|
|
6103
|
+
if (await fileExists(outputBase)) {
|
|
6104
|
+
p12.log.error(`Output path exists as a file: ${outputBase}`);
|
|
6105
|
+
p12.log.info("Please specify a directory path, not a file.");
|
|
6106
|
+
process.exit(EXIT_CODES.INVALID_ARGS);
|
|
6107
|
+
}
|
|
6108
|
+
} else {
|
|
6109
|
+
outputBase = path30.join(projectDir, ".claude");
|
|
6110
|
+
}
|
|
6111
|
+
p12.intro(pc12.cyan("Claude Collective Eject"));
|
|
6112
|
+
if (options.output) {
|
|
6113
|
+
p12.log.info(`Output directory: ${pc12.cyan(outputBase)}`);
|
|
6114
|
+
}
|
|
6115
|
+
const ejectType = type;
|
|
6116
|
+
const directOutput = !!options.output;
|
|
6117
|
+
switch (ejectType) {
|
|
6118
|
+
case "templates":
|
|
6119
|
+
await ejectTemplates(outputBase, options.force, directOutput);
|
|
6120
|
+
break;
|
|
6121
|
+
case "config":
|
|
6122
|
+
await ejectConfig(outputBase, options.force, directOutput);
|
|
6123
|
+
break;
|
|
6124
|
+
case "all":
|
|
6125
|
+
await ejectTemplates(outputBase, options.force, directOutput);
|
|
6126
|
+
await ejectConfig(outputBase, options.force, directOutput);
|
|
6127
|
+
break;
|
|
6128
|
+
}
|
|
6129
|
+
p12.outro(pc12.green("Eject complete!"));
|
|
5994
6130
|
}
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
async function ejectTemplates(projectDir, force) {
|
|
6131
|
+
);
|
|
6132
|
+
async function ejectTemplates(outputBase, force, directOutput = false) {
|
|
5998
6133
|
const sourceDir = path30.join(PROJECT_ROOT, DIRS.templates);
|
|
5999
|
-
const destDir = path30.join(
|
|
6134
|
+
const destDir = directOutput ? outputBase : path30.join(outputBase, "templates");
|
|
6000
6135
|
if (await directoryExists(destDir) && !force) {
|
|
6001
6136
|
p12.log.warn(
|
|
6002
6137
|
`Templates already exist at ${destDir}. Use --force to overwrite.`
|
|
@@ -6010,30 +6145,8 @@ async function ejectTemplates(projectDir, force) {
|
|
|
6010
6145
|
pc12.dim("You can now customize agent.liquid and partials locally.")
|
|
6011
6146
|
);
|
|
6012
6147
|
}
|
|
6013
|
-
async function
|
|
6014
|
-
const
|
|
6015
|
-
if (await directoryExists(destDir) && !force) {
|
|
6016
|
-
p12.log.warn(
|
|
6017
|
-
`Skill templates already exist at ${destDir}. Use --force to overwrite.`
|
|
6018
|
-
);
|
|
6019
|
-
return;
|
|
6020
|
-
}
|
|
6021
|
-
await ensureDir(destDir);
|
|
6022
|
-
const exampleSkillDir = path30.join(destDir, "example-skill");
|
|
6023
|
-
await ensureDir(exampleSkillDir);
|
|
6024
|
-
await writeFile(
|
|
6025
|
-
path30.join(exampleSkillDir, "SKILL.md"),
|
|
6026
|
-
DEFAULT_SKILL_MD_CONTENT
|
|
6027
|
-
);
|
|
6028
|
-
await writeFile(
|
|
6029
|
-
path30.join(exampleSkillDir, "metadata.yaml"),
|
|
6030
|
-
DEFAULT_METADATA_CONTENT
|
|
6031
|
-
);
|
|
6032
|
-
p12.log.success(`Skill templates ejected to ${pc12.cyan(destDir)}`);
|
|
6033
|
-
p12.log.info(pc12.dim("Copy example-skill/ to .claude/skills/ and customize."));
|
|
6034
|
-
}
|
|
6035
|
-
async function ejectConfig(projectDir, force) {
|
|
6036
|
-
const destPath = path30.join(projectDir, ".claude", "config.yaml");
|
|
6148
|
+
async function ejectConfig(outputBase, force, directOutput = false) {
|
|
6149
|
+
const destPath = path30.join(outputBase, "config.yaml");
|
|
6037
6150
|
if (await fileExists(destPath) && !force) {
|
|
6038
6151
|
p12.log.warn(
|
|
6039
6152
|
`Config already exists at ${destPath}. Use --force to overwrite.`
|