@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/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 (projectDir) {
295
- const projectConfig = await loadProjectConfig(projectDir);
296
- if (projectConfig?.source) {
297
- verbose(`Source from project config: ${projectConfig.source}`);
298
- return { source: projectConfig.source, sourceOrigin: "project" };
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, projectRoot, engine) {
1020
+ async function compileAgentForPlugin(name, agent, fallbackRoot, engine) {
1015
1021
  verbose(`Compiling agent: ${name}`);
1016
- const agentDir = path11.join(projectRoot, DIRS.agents, agent.path || name);
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(projectRoot, DIRS.agents, category);
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 agentRoot = agentSourcePath || projectRoot;
1142
+ const localAgentRoot = agentSourcePath || projectRoot;
1136
1143
  verbose(`Compiling stack plugin: ${stackId}`);
1137
1144
  verbose(` Stack/skills source: ${projectRoot}`);
1138
- verbose(` Agent partials source: ${agentRoot}`);
1145
+ verbose(` Local agent source: ${localAgentRoot}`);
1146
+ verbose(` CLI agent source: ${PROJECT_ROOT}`);
1139
1147
  const stack = await loadStack(stackId, projectRoot, "dev");
1140
- const agents = await loadAllAgents(agentRoot);
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
- agentRoot
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(name, agent, agentRoot, engine);
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/matrix-loader.ts
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 = parseYaml7(content);
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 = path19.dirname(metadataFile);
3394
- const skillMdPath = path19.join(skillsDir, skillDir, "SKILL.md");
3395
- const metadataPath = path19.join(skillsDir, metadataFile);
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 = parseYaml7(metadataContent);
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 matrixPath = path21.join(PROJECT_ROOT, SKILLS_MATRIX_PATH);
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 matrixPath = path21.join(PROJECT_ROOT, SKILLS_MATRIX_PATH);
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
- p7.log.info(
4511
- pc7.yellow(
4512
- `[dry-run] Would install stack "${result.selectedStack.id}" as a native plugin`
4513
- )
4514
- );
4515
- p7.log.info(
4516
- pc7.yellow(
4517
- `[dry-run] claude plugin install ./compiled-stack/${result.selectedStack.id} --scope project`
4518
- )
4519
- );
4520
- p7.log.info(
4521
- pc7.yellow(
4522
- `[dry-run] Stack includes ${result.selectedSkills.length} skills and agents bundled together`
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
- s.start(
4554
- `Compiling and installing stack "${result.selectedStack.id}"...`
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
- s.stop(`Installed stack plugin: ${installResult.pluginName}`);
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
- console.log("");
4571
- console.log(pc7.dim("Agents included:"));
4572
- for (const agentName of installResult.agents) {
4573
- console.log(` ${pc7.cyan(agentName)}`);
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 agents = await loadAllAgents(sourceResult.sourcePath);
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
- var EJECT_TYPES = ["templates", "skills", "config", "all"];
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 DEFAULT_SKILL_MD_CONTENT = `---
5939
- name: example-skill
5940
- description: Short description of the skill
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(async (type, options) => {
5965
- const projectDir = process.cwd();
5966
- if (!type) {
5967
- p12.log.error(
5968
- "Please specify what to eject: templates, skills, config, or all"
5969
- );
5970
- process.exit(EXIT_CODES.INVALID_ARGS);
5971
- }
5972
- if (!EJECT_TYPES.includes(type)) {
5973
- p12.log.error(`Unknown eject type: ${type}`);
5974
- p12.log.info(`Valid types: ${EJECT_TYPES.join(", ")}`);
5975
- process.exit(EXIT_CODES.INVALID_ARGS);
5976
- }
5977
- p12.intro(pc12.cyan("Claude Collective Eject"));
5978
- const ejectType = type;
5979
- switch (ejectType) {
5980
- case "templates":
5981
- await ejectTemplates(projectDir, options.force);
5982
- break;
5983
- case "skills":
5984
- await ejectSkills(projectDir, options.force);
5985
- break;
5986
- case "config":
5987
- await ejectConfig(projectDir, options.force);
5988
- break;
5989
- case "all":
5990
- await ejectTemplates(projectDir, options.force);
5991
- await ejectSkills(projectDir, options.force);
5992
- await ejectConfig(projectDir, options.force);
5993
- break;
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
- p12.outro(pc12.green("Eject complete!"));
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(projectDir, ".claude", "templates");
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 ejectSkills(projectDir, force) {
6014
- const destDir = path30.join(projectDir, ".claude", "skill-templates");
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.`