Plan X1 / spec §14 / §16 Phase 4.
apps/daemon/src/plugins/pack.ts ships:
packPlugin({ folder, out? }) \u2192 PackPluginResult
{ outPath, bytes, files, pluginId?, pluginVersion? }
Builds a gzip-compressed tar archive of an author's plugin folder,
ready to install via the installer's HTTPS-tarball path (\u00a73.A6).
Default output path: `<folder>/../<basename>-<manifest.version>.tgz`
when the manifest exposes a version, otherwise <basename>.tgz.
What we put in the archive:
- open-design.json (required \u2014 packPlugin throws when missing)
- SKILL.md / .claude-plugin/plugin.json when present
- Any other plain files in the folder
What we exclude (matches the installer's tarball-extract skiplist):
node_modules / .git / .next / dist / build / out / coverage /
.turbo / .cache / .pnpm-store / .parcel-cache / .svelte-kit /
.nuxt / .astro / .vercel / .vscode / .DS_Store / Thumbs.db
Symlinks are rejected at pack time \u2014 both at walk time and via a
defensive tar filter \u2014 so an author can't pack a symlink and only
discover the rejection at install. The output archive itself is
always excluded from its own contents (avoids the 'pack the
output' spiral when --out lands inside the folder).
CLI: `od plugin pack <folder> [--out <path>] [--json]`. Help block
in printPluginHelp() updated.
Exit codes:
0 archive written
2 CLI usage error
4 pack-time error (missing open-design.json, invalid JSON, etc)
Daemon tests: 1674 \u2192 1683 (+9 cases on plugins-pack: full folder
contents, skiplist enforcement, symlink rejection, default-name
fallback when no version, --out override, round-trip via
tar.extract, missing-manifest rejection, malformed-JSON rejection,
self-archive exclusion).
Co-authored-by: Tom Huang <1043269994@qq.com>