@ccpluginizer/ccpluginizer 0.3.0 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +9 -12
  2. package/dist/index.js +25 -36
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  > CLI for pluginizing non-plugin Claude Code repos.
4
4
 
5
- Generate, validate, and submit [ccpluginizer marketplace](https://github.com/lifebugz/ccpluginizer) entries from any GitHub repo containing Claude Code-compatible content (skills, agents, commands, hooks, MCP servers).
5
+ Generate and validate [ccpluginizer marketplace](https://github.com/lifebugz/ccpluginizer) entries from any GitHub repo with Claude Code-compatible content (skills, agents, commands, hooks, MCP servers).
6
6
 
7
7
  ## Install
8
8
 
9
- Works with either runtime pick whichever you have.
9
+ Works with either runtime. Pick whichever you have.
10
10
 
11
- **With Bun:**
11
+ With Bun:
12
12
 
13
13
  ```bash
14
14
  bun add -g @ccpluginizer/ccpluginizer
@@ -16,7 +16,7 @@ bun add -g @ccpluginizer/ccpluginizer
16
16
  bunx @ccpluginizer/ccpluginizer scan <owner/repo>
17
17
  ```
18
18
 
19
- **With npm / Node:**
19
+ With npm or Node:
20
20
 
21
21
  ```bash
22
22
  npm install -g @ccpluginizer/ccpluginizer
@@ -24,27 +24,24 @@ npm install -g @ccpluginizer/ccpluginizer
24
24
  npx @ccpluginizer/ccpluginizer scan <owner/repo>
25
25
  ```
26
26
 
27
- The CLI prefers Bun at runtime if available (faster startup) and falls back to Node otherwise. Either works; no configuration needed.
27
+ At runtime the CLI prefers Bun when it's around (it starts faster), and falls back to Node otherwise. You don't need to configure anything.
28
28
 
29
29
  ## Usage
30
30
 
31
31
  ```bash
32
32
  ccpluginizer scan <owner/repo> # Generate a marketplace entry
33
- ccpluginizer submit <owner/repo> # Open a PR to add the repo to the catalog
34
33
  ccpluginizer validate <entry.json> # Validate an entry against the schema
35
34
  ```
36
35
 
36
+ To add a repo to the catalog, run `scan`, commit the JSON to `entries/<name>.json` in the catalog repo, and open a PR. See the catalog's [CONTRIBUTING.md](https://github.com/lifebugz/ccpluginizer/blob/main/CONTRIBUTING.md).
37
+
37
38
  `<owner/repo>` accepts either GitHub shorthand (`elysiajs/skills`) or a full URL (`https://github.com/elysiajs/skills`).
38
39
 
39
40
  ## How it works
40
41
 
41
- ccpluginizer detects skills, agents, commands, hooks, and MCP servers in the source repo, then synthesizes a marketplace entry that uses Claude Code's `strict: false` mode to point at the source. Source code is never copied or redistributed.
42
-
43
- Three detection layers:
42
+ ccpluginizer detects skills, agents, commands, hooks, and MCP servers in the source repo, then synthesizes a marketplace entry that uses Claude Code's `strict: false` mode to point at the source. The catalog never holds a copy of the source itself.
44
43
 
45
- 1. **Convention paths** `.claude/skills/`, `.claude/agents/`, `.claude/commands/`, etc.
46
- 2. **Manifest metadata** — `.claude-plugin/manifest.json` or `.ccpluginizer.json` marker file.
47
- 3. **Heuristic fallback** — looks for `SKILL.md` files with YAML frontmatter, `commands/*.md`, etc.
44
+ Detection runs in three passes. The first looks at convention paths like `.claude/skills/`, `.claude/agents/`, and `.claude/commands/`. The second reads `.claude-plugin/manifest.json` or a `.ccpluginizer.json` marker file if the repo has one. The third is a heuristic fallback for repos that follow neither convention. It scans for `SKILL.md` files with YAML frontmatter, `commands/*.md`, and similar patterns.
48
45
 
49
46
  ## Repository
50
47
 
package/dist/index.js CHANGED
@@ -1725,11 +1725,11 @@ function detectMarkerFile(repoRoot) {
1725
1725
  import { existsSync as existsSync3, readdirSync, statSync } from "node:fs";
1726
1726
  import { join as join4 } from "node:path";
1727
1727
  var FOLDER_KINDS = [
1728
- { folder: "skills", kind: "skills" },
1729
- { folder: "agents", kind: "agents" },
1730
- { folder: "commands", kind: "commands" },
1731
- { folder: "output-styles", kind: "outputStyles" },
1732
- { folder: "themes", kind: "themes" }
1728
+ { folder: "skills", kind: "skills", emit: "directory" },
1729
+ { folder: "agents", kind: "agents", emit: { enumerateFiles: ".md" } },
1730
+ { folder: "commands", kind: "commands", emit: "directory" },
1731
+ { folder: "output-styles", kind: "outputStyles", emit: "directory" },
1732
+ { folder: "themes", kind: "themes", emit: "directory" }
1733
1733
  ];
1734
1734
  var FILE_KINDS = [
1735
1735
  { file: "hooks/hooks.json", kind: "hooks" },
@@ -1745,16 +1745,29 @@ function scanRoot(repoRoot, prefix) {
1745
1745
  const findings = [];
1746
1746
  const baseDir = prefix === "" ? repoRoot : join4(repoRoot, prefix);
1747
1747
  const pathPrefix = prefix === "" ? "./" : `./${prefix}/`;
1748
- for (const { folder, kind } of FOLDER_KINDS) {
1748
+ for (const { folder, kind, emit } of FOLDER_KINDS) {
1749
1749
  const folderPath = join4(baseDir, folder);
1750
- if (existsSync3(folderPath) && statSync(folderPath).isDirectory()) {
1751
- const hasContents = readdirSync(folderPath).length > 0;
1750
+ if (!existsSync3(folderPath) || !statSync(folderPath).isDirectory()) {
1751
+ continue;
1752
+ }
1753
+ const entries = readdirSync(folderPath);
1754
+ if (emit === "directory") {
1752
1755
  findings.push({
1753
1756
  kind,
1754
1757
  paths: [`${pathPrefix}${folder}/`],
1755
- confidence: hasContents ? "high" : "medium",
1758
+ confidence: entries.length > 0 ? "high" : "medium",
1756
1759
  source: "convention"
1757
1760
  });
1761
+ } else {
1762
+ const files = entries.filter((e) => e.endsWith(emit.enumerateFiles));
1763
+ if (files.length > 0) {
1764
+ findings.push({
1765
+ kind,
1766
+ paths: files.map((f) => `${pathPrefix}${folder}/${f}`),
1767
+ confidence: "high",
1768
+ source: "convention"
1769
+ });
1770
+ }
1758
1771
  }
1759
1772
  }
1760
1773
  for (const { file, kind } of FILE_KINDS) {
@@ -2053,7 +2066,7 @@ function mergeManifestMetadata(entry, manifest) {
2053
2066
  ...manifest.homepage !== undefined ? { homepage: manifest.homepage } : {},
2054
2067
  ...manifest.repository !== undefined ? { repository: manifest.repository } : {},
2055
2068
  ...manifest.license !== undefined ? { license: manifest.license } : {},
2056
- ...manifest.author !== undefined ? { author: manifest.author } : {}
2069
+ ...manifest.author !== undefined ? { author: typeof manifest.author === "string" ? { name: manifest.author } : manifest.author } : {}
2057
2070
  };
2058
2071
  }
2059
2072
  function buildEntryFromMarker(marker, sourceRepo, repoRoot) {
@@ -2122,7 +2135,7 @@ function groupByKind(findings) {
2122
2135
  return map;
2123
2136
  }
2124
2137
  function makeGithubSource(repo) {
2125
- return { source: "github", repo };
2138
+ return { source: "url", url: `https://github.com/${repo}.git` };
2126
2139
  }
2127
2140
  function defaultEntryName(sourceRepo) {
2128
2141
  return sourceRepo.replace("/", "-").toLowerCase();
@@ -2205,30 +2218,6 @@ var validateCommand = new R2("validate").meta({ description: "Validate a marketp
2205
2218
  console.log("OK");
2206
2219
  });
2207
2220
 
2208
- // src/commands/submit.ts
2209
- import { writeFileSync as writeFileSync2, mkdtempSync as mkdtempSync2 } from "node:fs";
2210
- import { tmpdir as tmpdir2 } from "node:os";
2211
- import { join as join8 } from "node:path";
2212
- var submitCommand = new R2("submit").meta({ description: "Generate an entry and open a PR against ccpluginizer/marketplace" }).args([{ name: "repo", type: "string", required: true, description: "owner/repo to pluginize" }]).flags({
2213
- dryRun: { type: "boolean", short: "n", description: "Print the PR plan without opening it" }
2214
- }).run(async ({ args, flags }) => {
2215
- const repoPath = await resolveSource(args.repo);
2216
- const sourceRepo = inferSourceRepo(args.repo);
2217
- const entry = synthesizeEntry({ repoRoot: repoPath, sourceRepo });
2218
- const tmpFile = join8(mkdtempSync2(join8(tmpdir2(), "ccp-submit-")), `${entry.name}.json`);
2219
- writeFileSync2(tmpFile, JSON.stringify(entry, null, 2) + `
2220
- `, "utf8");
2221
- if (flags.dryRun === true) {
2222
- console.log(`Would submit:
2223
- entry: ${tmpFile}
2224
- to: ccpluginizer/marketplace`);
2225
- console.log(JSON.stringify(entry, null, 2));
2226
- return;
2227
- }
2228
- console.log(`Generated entry at ${tmpFile}`);
2229
- console.log("Run with --dryRun to preview, or follow the manual PR workflow in CONTRIBUTING.md.");
2230
- });
2231
-
2232
2221
  // src/index.ts
2233
- var app = new R("ccpluginizer").meta({ description: "Pluginize non-plugin Claude Code repos" }).command(scanCommand).command(validateCommand).command(submitCommand);
2222
+ var app = new R("ccpluginizer").meta({ description: "Pluginize non-plugin Claude Code repos" }).command(scanCommand).command(validateCommand);
2234
2223
  await app.execute();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccpluginizer/ccpluginizer",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI for pluginizing non-plugin Claude Code repos",
5
5
  "license": "MIT",
6
6
  "type": "module",