@claude-collective/cli 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.3] - 2026-01-30
9
+
10
+ ### Added
11
+
12
+ - `--output` option for eject command to specify custom output directory
13
+ - Remote schema fetching for skill validation from GitHub
14
+
15
+ ### Fixed
16
+
17
+ - Plugin manifest no longer includes agents field (Claude Code discovers automatically)
18
+ - Source loader now supports source-provided skills matrix
19
+
20
+ [0.1.3]: https://github.com/claude-collective/cli/releases/tag/v0.1.3
21
+
8
22
  ## [0.1.2] - 2026-01-30
9
23
 
10
24
  ### Changed
package/dist/cli/index.js CHANGED
@@ -891,9 +891,6 @@ function generateStackPluginManifest(options) {
891
891
  if (options.keywords && options.keywords.length > 0) {
892
892
  manifest.keywords = options.keywords;
893
893
  }
894
- if (options.hasAgents) {
895
- manifest.agents = "./agents/";
896
- }
897
894
  if (options.hasHooks) {
898
895
  manifest.hooks = "./hooks/hooks.json";
899
896
  }
@@ -3329,12 +3326,97 @@ async function runWizard(matrix, options = {}) {
3329
3326
  // src/cli/lib/source-loader.ts
3330
3327
  import path21 from "path";
3331
3328
 
3332
- // src/cli/lib/matrix-loader.ts
3329
+ // src/cli/lib/local-skill-loader.ts
3333
3330
  import { parse as parseYaml7 } from "yaml";
3334
3331
  import path19 from "path";
3332
+ var LOCAL_CATEGORY = "local";
3333
+ var LOCAL_AUTHOR = "@local";
3334
+ var TEST_SKILL_PREFIX = "test-";
3335
+ async function discoverLocalSkills(projectDir) {
3336
+ const localSkillsPath = path19.join(projectDir, LOCAL_SKILLS_PATH);
3337
+ if (!await directoryExists(localSkillsPath)) {
3338
+ verbose(`Local skills directory not found: ${localSkillsPath}`);
3339
+ return null;
3340
+ }
3341
+ const skills = [];
3342
+ const skillDirs = await listDirectories(localSkillsPath);
3343
+ for (const skillDirName of skillDirs) {
3344
+ if (!skillDirName.startsWith(TEST_SKILL_PREFIX)) {
3345
+ verbose(
3346
+ `Skipping local skill '${skillDirName}': Does not have test- prefix (temporary filter)`
3347
+ );
3348
+ continue;
3349
+ }
3350
+ const skill = await extractLocalSkill(localSkillsPath, skillDirName);
3351
+ if (skill) {
3352
+ skills.push(skill);
3353
+ }
3354
+ }
3355
+ verbose(`Discovered ${skills.length} local skills from ${localSkillsPath}`);
3356
+ return {
3357
+ skills,
3358
+ localSkillsPath
3359
+ };
3360
+ }
3361
+ async function extractLocalSkill(localSkillsPath, skillDirName) {
3362
+ const skillDir = path19.join(localSkillsPath, skillDirName);
3363
+ const metadataPath = path19.join(skillDir, "metadata.yaml");
3364
+ const skillMdPath = path19.join(skillDir, "SKILL.md");
3365
+ if (!await fileExists(metadataPath)) {
3366
+ verbose(`Skipping local skill '${skillDirName}': No metadata.yaml found`);
3367
+ return null;
3368
+ }
3369
+ if (!await fileExists(skillMdPath)) {
3370
+ verbose(`Skipping local skill '${skillDirName}': No SKILL.md found`);
3371
+ return null;
3372
+ }
3373
+ const metadataContent = await readFile(metadataPath);
3374
+ const metadata = parseYaml7(metadataContent);
3375
+ if (!metadata.cli_name) {
3376
+ verbose(
3377
+ `Skipping local skill '${skillDirName}': Missing required 'cli_name' in metadata.yaml`
3378
+ );
3379
+ return null;
3380
+ }
3381
+ const skillMdContent = await readFile(skillMdPath);
3382
+ const frontmatter = parseFrontmatter(skillMdContent);
3383
+ if (!frontmatter) {
3384
+ verbose(
3385
+ `Skipping local skill '${skillDirName}': Invalid SKILL.md frontmatter`
3386
+ );
3387
+ return null;
3388
+ }
3389
+ const relativePath = `${LOCAL_SKILLS_PATH}/${skillDirName}/`;
3390
+ const skillId = frontmatter.name;
3391
+ const extracted = {
3392
+ id: skillId,
3393
+ directoryPath: skillDirName,
3394
+ name: `${metadata.cli_name} ${LOCAL_AUTHOR}`,
3395
+ description: metadata.cli_description || frontmatter.description,
3396
+ usageGuidance: void 0,
3397
+ category: LOCAL_CATEGORY,
3398
+ categoryExclusive: false,
3399
+ author: LOCAL_AUTHOR,
3400
+ tags: [],
3401
+ compatibleWith: [],
3402
+ conflictsWith: [],
3403
+ requires: [],
3404
+ requiresSetup: [],
3405
+ providesSetupFor: [],
3406
+ path: relativePath,
3407
+ local: true,
3408
+ localPath: relativePath
3409
+ };
3410
+ verbose(`Extracted local skill: ${skillId}`);
3411
+ return extracted;
3412
+ }
3413
+
3414
+ // src/cli/lib/matrix-loader.ts
3415
+ import { parse as parseYaml8 } from "yaml";
3416
+ import path20 from "path";
3335
3417
  async function loadSkillsMatrix(configPath) {
3336
3418
  const content = await readFile(configPath);
3337
- const config = parseYaml7(content);
3419
+ const config = parseYaml8(content);
3338
3420
  validateMatrixStructure(config, configPath);
3339
3421
  verbose(`Loaded skills matrix: ${configPath}`);
3340
3422
  return config;
@@ -3390,15 +3472,15 @@ async function extractAllSkills(skillsDir) {
3390
3472
  const skills = [];
3391
3473
  const metadataFiles = await glob("**/metadata.yaml", skillsDir);
3392
3474
  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);
3475
+ const skillDir = path20.dirname(metadataFile);
3476
+ const skillMdPath = path20.join(skillsDir, skillDir, "SKILL.md");
3477
+ const metadataPath = path20.join(skillsDir, metadataFile);
3396
3478
  if (!await fileExists(skillMdPath)) {
3397
3479
  verbose(`Skipping ${metadataFile}: No SKILL.md found`);
3398
3480
  continue;
3399
3481
  }
3400
3482
  const metadataContent = await readFile(metadataPath);
3401
- const metadata = parseYaml7(metadataContent);
3483
+ const metadata = parseYaml8(metadataContent);
3402
3484
  const skillMdContent = await readFile(skillMdPath);
3403
3485
  const frontmatter = parseFrontmatter(skillMdContent);
3404
3486
  if (!frontmatter) {
@@ -3686,91 +3768,6 @@ function resolveSuggestedStacks(matrix, aliases) {
3686
3768
  });
3687
3769
  }
3688
3770
 
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
3771
  // src/cli/lib/source-loader.ts
3775
3772
  async function loadSkillsMatrixFromSource(options = {}) {
3776
3773
  const {
@@ -3810,9 +3807,17 @@ async function loadFromLocal(source, sourceConfig) {
3810
3807
  skillsPath = PROJECT_ROOT;
3811
3808
  }
3812
3809
  verbose(`Loading skills from local path: ${skillsPath}`);
3813
- const matrixPath = path21.join(PROJECT_ROOT, SKILLS_MATRIX_PATH);
3810
+ const sourceMatrixPath = path21.join(skillsPath, SKILLS_MATRIX_PATH);
3811
+ const cliMatrixPath = path21.join(PROJECT_ROOT, SKILLS_MATRIX_PATH);
3812
+ let matrixPath;
3813
+ if (await fileExists(sourceMatrixPath)) {
3814
+ matrixPath = sourceMatrixPath;
3815
+ verbose(`Matrix from source: ${matrixPath}`);
3816
+ } else {
3817
+ matrixPath = cliMatrixPath;
3818
+ verbose(`Matrix from CLI (source has no matrix): ${matrixPath}`);
3819
+ }
3814
3820
  const skillsDir = path21.join(skillsPath, SKILLS_DIR_PATH);
3815
- verbose(`Matrix from CLI: ${matrixPath}`);
3816
3821
  verbose(`Skills from source: ${skillsDir}`);
3817
3822
  const matrix = await loadSkillsMatrix(matrixPath);
3818
3823
  const skills = await extractAllSkills(skillsDir);
@@ -3828,9 +3833,17 @@ async function loadFromRemote(source, sourceConfig, forceRefresh) {
3828
3833
  verbose(`Fetching skills from remote source: ${source}`);
3829
3834
  const fetchResult = await fetchFromSource(source, { forceRefresh });
3830
3835
  verbose(`Fetched to: ${fetchResult.path}`);
3831
- const matrixPath = path21.join(PROJECT_ROOT, SKILLS_MATRIX_PATH);
3836
+ const sourceMatrixPath = path21.join(fetchResult.path, SKILLS_MATRIX_PATH);
3837
+ const cliMatrixPath = path21.join(PROJECT_ROOT, SKILLS_MATRIX_PATH);
3838
+ let matrixPath;
3839
+ if (await fileExists(sourceMatrixPath)) {
3840
+ matrixPath = sourceMatrixPath;
3841
+ verbose(`Matrix from source: ${matrixPath}`);
3842
+ } else {
3843
+ matrixPath = cliMatrixPath;
3844
+ verbose(`Matrix from CLI (source has no matrix): ${matrixPath}`);
3845
+ }
3832
3846
  const skillsDir = path21.join(fetchResult.path, SKILLS_DIR_PATH);
3833
- verbose(`Matrix from CLI: ${matrixPath}`);
3834
3847
  verbose(`Skills from source: ${skillsDir}`);
3835
3848
  const matrix = await loadSkillsMatrix(matrixPath);
3836
3849
  const skills = await extractAllSkills(skillsDir);
@@ -5182,6 +5195,9 @@ var KEBAB_CASE_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
5182
5195
  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
5196
  var schemaCache2 = /* @__PURE__ */ new Map();
5184
5197
  var validatorCache2 = /* @__PURE__ */ new Map();
5198
+ var REMOTE_SCHEMAS = {
5199
+ "skill-frontmatter.schema.json": "https://raw.githubusercontent.com/claude-collective/skills/main/src/schemas/skill-frontmatter.schema.json"
5200
+ };
5185
5201
  async function loadSchema2(schemaName) {
5186
5202
  if (schemaCache2.has(schemaName)) {
5187
5203
  return schemaCache2.get(schemaName);
@@ -5198,8 +5214,20 @@ async function loadSchema2(schemaName) {
5198
5214
  return schema;
5199
5215
  }
5200
5216
  }
5217
+ const remoteUrl = REMOTE_SCHEMAS[schemaName];
5218
+ if (remoteUrl) {
5219
+ try {
5220
+ const response = await fetch(remoteUrl);
5221
+ if (response.ok) {
5222
+ const schema = await response.json();
5223
+ schemaCache2.set(schemaName, schema);
5224
+ return schema;
5225
+ }
5226
+ } catch {
5227
+ }
5228
+ }
5201
5229
  throw new Error(
5202
- `Schema not found: ${schemaName}. Searched: ${locations.join(", ")}`
5230
+ `Schema not found: ${schemaName}. Searched: ${locations.join(", ")}${remoteUrl ? ` and ${remoteUrl}` : ""}`
5203
5231
  );
5204
5232
  }
5205
5233
  async function getValidator2(schemaName) {
@@ -5909,6 +5937,7 @@ import { Command as Command10 } from "commander";
5909
5937
  import * as p12 from "@clack/prompts";
5910
5938
  import pc12 from "picocolors";
5911
5939
  import path30 from "path";
5940
+ import os4 from "os";
5912
5941
  var EJECT_TYPES = ["templates", "skills", "config", "all"];
5913
5942
  var DEFAULT_CONFIG_CONTENT = `# Claude Collective Configuration
5914
5943
  # Agent-skill mappings for this project
@@ -5959,44 +5988,65 @@ author: "@local"
5959
5988
  cli_name: Example Skill
5960
5989
  cli_description: Short description for CLI
5961
5990
  `;
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({
5991
+ 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).option(
5992
+ "-o, --output <dir>",
5993
+ "Output directory (default: .claude/ in current directory)"
5994
+ ).configureOutput({
5963
5995
  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;
5996
+ }).showHelpAfterError(true).action(
5997
+ async (type, options) => {
5998
+ const projectDir = process.cwd();
5999
+ if (!type) {
6000
+ p12.log.error(
6001
+ "Please specify what to eject: templates, skills, config, or all"
6002
+ );
6003
+ process.exit(EXIT_CODES.INVALID_ARGS);
6004
+ }
6005
+ if (!EJECT_TYPES.includes(type)) {
6006
+ p12.log.error(`Unknown eject type: ${type}`);
6007
+ p12.log.info(`Valid types: ${EJECT_TYPES.join(", ")}`);
6008
+ process.exit(EXIT_CODES.INVALID_ARGS);
6009
+ }
6010
+ let outputBase;
6011
+ if (options.output) {
6012
+ const expandedPath = options.output.startsWith("~") ? path30.join(os4.homedir(), options.output.slice(1)) : options.output;
6013
+ outputBase = path30.resolve(projectDir, expandedPath);
6014
+ if (await fileExists(outputBase)) {
6015
+ p12.log.error(`Output path exists as a file: ${outputBase}`);
6016
+ p12.log.info("Please specify a directory path, not a file.");
6017
+ process.exit(EXIT_CODES.INVALID_ARGS);
6018
+ }
6019
+ } else {
6020
+ outputBase = path30.join(projectDir, ".claude");
6021
+ }
6022
+ p12.intro(pc12.cyan("Claude Collective Eject"));
6023
+ if (options.output) {
6024
+ p12.log.info(`Output directory: ${pc12.cyan(outputBase)}`);
6025
+ }
6026
+ const ejectType = type;
6027
+ const directOutput = !!options.output;
6028
+ switch (ejectType) {
6029
+ case "templates":
6030
+ await ejectTemplates(outputBase, options.force, directOutput);
6031
+ break;
6032
+ case "skills":
6033
+ await ejectSkills(outputBase, options.force, directOutput);
6034
+ break;
6035
+ case "config":
6036
+ await ejectConfig(outputBase, options.force, directOutput);
6037
+ break;
6038
+ case "all":
6039
+ await ejectTemplates(outputBase, options.force, directOutput);
6040
+ await ejectSkills(outputBase, options.force, directOutput);
6041
+ await ejectConfig(outputBase, options.force, directOutput);
6042
+ break;
6043
+ }
6044
+ p12.outro(pc12.green("Eject complete!"));
5994
6045
  }
5995
- p12.outro(pc12.green("Eject complete!"));
5996
- });
5997
- async function ejectTemplates(projectDir, force) {
6046
+ );
6047
+ async function ejectTemplates(outputBase, force, directOutput = false) {
5998
6048
  const sourceDir = path30.join(PROJECT_ROOT, DIRS.templates);
5999
- const destDir = path30.join(projectDir, ".claude", "templates");
6049
+ const destDir = directOutput ? outputBase : path30.join(outputBase, "templates");
6000
6050
  if (await directoryExists(destDir) && !force) {
6001
6051
  p12.log.warn(
6002
6052
  `Templates already exist at ${destDir}. Use --force to overwrite.`
@@ -6010,8 +6060,8 @@ async function ejectTemplates(projectDir, force) {
6010
6060
  pc12.dim("You can now customize agent.liquid and partials locally.")
6011
6061
  );
6012
6062
  }
6013
- async function ejectSkills(projectDir, force) {
6014
- const destDir = path30.join(projectDir, ".claude", "skill-templates");
6063
+ async function ejectSkills(outputBase, force, directOutput = false) {
6064
+ const destDir = directOutput ? outputBase : path30.join(outputBase, "skill-templates");
6015
6065
  if (await directoryExists(destDir) && !force) {
6016
6066
  p12.log.warn(
6017
6067
  `Skill templates already exist at ${destDir}. Use --force to overwrite.`
@@ -6030,10 +6080,12 @@ async function ejectSkills(projectDir, force) {
6030
6080
  DEFAULT_METADATA_CONTENT
6031
6081
  );
6032
6082
  p12.log.success(`Skill templates ejected to ${pc12.cyan(destDir)}`);
6033
- p12.log.info(pc12.dim("Copy example-skill/ to .claude/skills/ and customize."));
6083
+ p12.log.info(
6084
+ pc12.dim("Copy example-skill/ to your skills/ directory and customize.")
6085
+ );
6034
6086
  }
6035
- async function ejectConfig(projectDir, force) {
6036
- const destPath = path30.join(projectDir, ".claude", "config.yaml");
6087
+ async function ejectConfig(outputBase, force, directOutput = false) {
6088
+ const destPath = path30.join(outputBase, "config.yaml");
6037
6089
  if (await fileExists(destPath) && !force) {
6038
6090
  p12.log.warn(
6039
6091
  `Config already exists at ${destPath}. Use --force to overwrite.`