#!/usr/bin/env node import { createRequire } from "node:module"; process.stdout.on("error", (err: NodeJS.ErrnoException) => { if (err.code === "EPIPE") process.exit(0); throw err; }); import { runWizard } from "./wizard.js"; import { installGlobal } from "./commands/install.js"; import { initProject } from "./commands/init.js"; import { uninstall } from "./commands/uninstall.js"; import { upgrade } from "./commands/upgrade.js"; import { doctor } from "./commands/doctor.js"; import { status } from "./commands/status.js"; import { list } from "./commands/list.js"; import { enable, disable } from "./commands/toggle.js"; import { HOSTS, type HostId, hostById } from "./lib/hosts.js"; import { createLogger, colors } from "./lib/logger.js"; interface ParsedArgs { positional: string[]; flags: Record; list: Record; } function parseArgs(argv: string[]): ParsedArgs { const positional: string[] = []; const flags: Record = {}; const list: Record = {}; for (let i = 0; i < argv.length; i++) { const arg = argv[i]; if (arg.startsWith("--")) { const eq = arg.indexOf("="); let key: string; let value: string | undefined; if (eq !== -1) { key = arg.slice(2, eq); value = arg.slice(eq + 1); } else { key = arg.slice(2); const next = argv[i + 1]; if (next !== undefined && !next.startsWith("-")) { value = next; i++; } } if (value === undefined) { flags[key] = true; } else if (key === "host") { list.host = list.host ?? []; list.host.push(...value.split(",").map((s) => s.trim()).filter(Boolean)); } else { flags[key] = value; } } else if (arg.startsWith("-") && arg.length > 1) { const key = arg.slice(1); flags[key] = true; } else { positional.push(arg); } } return { positional, flags, list }; } function parseHosts(args: ParsedArgs): HostId[] { const raw = args.list.host ?? []; const hosts: HostId[] = []; for (const r of raw) { if (r === "auto") { return ["claude"]; } const meta = hostById(r); if (!meta) { console.error(colors.red(`Unknown host: ${r}`)); console.error(`Valid: ${HOSTS.map((h) => h.id).join(", ")}`); process.exit(2); } hosts.push(meta.id); } return hosts; } function bool(args: ParsedArgs, name: string, fallback: boolean): boolean { const v = args.flags[name]; if (v === undefined) return fallback; if (typeof v === "boolean") return v; if (v === "true" || v === "1" || v === "yes") return true; if (v === "false" || v === "0" || v === "no") return false; return fallback; } function getVersion(): string { try { const require = createRequire(import.meta.url); const pkg = require("../package.json") as { version: string }; return pkg.version; } catch { return "unknown"; } } const HELP = `${colors.bold("gstack")} — installer for Garry Tan's gstack skill pack ${colors.bold("Usage:")} npx @garrytan/gstack interactive wizard npx @garrytan/gstack [opts] ${colors.bold("Commands:")} install Install gstack globally (~/.claude/skills/gstack) init Add gstack to the current project (team mode) uninstall Remove gstack (global; add --project for just this repo) upgrade Pull latest gstack and rebuild doctor Diagnose install issues status Show install version, hosts, and settings list List available skills enable Enable a skill in the current project disable Disable a skill in the current project ${colors.bold("Common options:")} --host Register with host (repeatable, comma-separated). Valid: ${HOSTS.map((h) => h.id).join(", ")} --prefix Use gstack-* skill names --no-prefix Use flat skill names (default) --no-claude-md Don't write gstack section to CLAUDE.md --yes, -y Skip confirmation prompts --reinstall Remove existing install before installing --quiet, -q Suppress non-essential output --tier init only: "required" or "optional" (default: required) --no-commit init only: stage but don't commit changes --project uninstall only: remove from current project, not global --keep-claude-md uninstall only: leave CLAUDE.md section in place ${colors.bold("Examples:")} npx @garrytan/gstack install --host claude,codex npx @garrytan/gstack init --tier optional npx @garrytan/gstack uninstall --project --yes npx @garrytan/gstack doctor `; async function main(): Promise { const args = parseArgs(process.argv.slice(2)); const quiet = bool(args, "quiet", bool(args, "q", false)); if (args.flags.version || args.flags.v) { console.log(getVersion()); return; } if (args.flags.help || args.flags.h) { console.log(HELP); return; } const cmd = args.positional[0]; if (!cmd) { await runWizard(); return; } const hosts = parseHosts(args); const prefix = args.flags.prefix === true ? true : args.flags["no-prefix"] === true ? false : false; const writeClaudeMd = !bool(args, "no-claude-md", false); const yes = bool(args, "yes", bool(args, "y", false)); const reinstall = bool(args, "reinstall", false); switch (cmd) { case "install": await installGlobal({ hosts: hosts.length > 0 ? hosts : (["claude"] as HostId[]), prefix, writeClaudeMd, quiet, reinstall, }); break; case "init": { const tierFlag = typeof args.flags.tier === "string" ? args.flags.tier : "required"; if (tierFlag !== "required" && tierFlag !== "optional") { console.error(colors.red(`Invalid --tier: ${tierFlag} (expected "required" or "optional")`)); process.exit(2); } await initProject({ tier: tierFlag, commit: !bool(args, "no-commit", false), quiet, writeClaudeMd, globalArgs: { hosts: hosts.length > 0 ? hosts : (["claude"] as HostId[]), prefix, writeClaudeMd, quiet, reinstall, }, }); break; } case "uninstall": await uninstall({ project: bool(args, "project", false), yes, keepClaudeMd: bool(args, "keep-claude-md", false), quiet, }); break; case "upgrade": await upgrade({ quiet }); break; case "doctor": await doctor({ quiet }); break; case "status": await status({ quiet }); break; case "list": await list({ quiet }); break; case "enable": { const name = args.positional[1]; if (!name) { console.error(colors.red("Usage: gstack enable ")); process.exit(2); } await enable({ skillName: name, quiet }); break; } case "disable": { const name = args.positional[1]; if (!name) { console.error(colors.red("Usage: gstack disable ")); process.exit(2); } await disable({ skillName: name, quiet }); break; } default: { const log = createLogger(false); log.error(`Unknown command: ${cmd}`); console.log(""); console.log(HELP); process.exit(2); } } } main().catch((err) => { const log = createLogger(false); log.error(err instanceof Error ? err.message : String(err)); process.exit(1); });