#!/usr/bin/env node import { stat } from "node:fs/promises"; import { join, resolve } from "node:path"; import { Command, Option } from "commander"; import { getCliBuildLabel, getCliVersion } from "./cli/buildInfo.js"; import { resolveClawdbotDefaultWorkspace } from "./cli/clawdbotConfig.js"; import { cmdLoginFlow, cmdLogout, cmdToken, cmdWhoami } from "./cli/commands/auth.js"; import { cmdDeleteSkill, cmdHideSkill, cmdUndeleteSkill, cmdUnhideSkill, } from "./cli/commands/delete.js"; import { cmdInspect, cmdVerifySkill } from "./cli/commands/inspect.js"; import { cmdMergeSkill, cmdRenameSkill } from "./cli/commands/ownership.js"; import { cmdDeletePackage, cmdDeletePackageTrustedPublisher, cmdDownloadPackage, cmdExplorePackages, cmdGetPackageTrustedPublisher, cmdInspectPackage, cmdPackageModerationStatus, cmdPackageMigrationStatus, cmdPackageReadiness, cmdPackPackage, cmdPublishPackage, cmdReportPackage, cmdSetPackageTrustedPublisher, cmdTransferPackage, cmdUndeletePackage, cmdValidatePackage, cmdVerifyPackage, } from "./cli/commands/packages.js"; import { cmdPublish } from "./cli/commands/publish.js"; import { cmdCreatePublisher } from "./cli/commands/publishers.js"; import { cmdScan, cmdScanDownload } from "./cli/commands/scan.js"; import { cmdExplore, cmdInstall, cmdList, cmdPin, cmdSearch, cmdUninstall, cmdUnpin, cmdUpdate, } from "./cli/commands/skills.js"; import { cmdStarSkill } from "./cli/commands/star.js"; import { cmdSync } from "./cli/commands/sync.js"; import { cmdTransferAccept, cmdTransferCancel, cmdTransferList, cmdTransferReject, cmdTransferRequest, } from "./cli/commands/transfer.js"; import { cmdUnstarSkill } from "./cli/commands/unstar.js"; import { configureCommanderHelp, styleEnvBlock, styleError, styleTitle } from "./cli/helpStyle.js"; import { DEFAULT_REGISTRY, DEFAULT_SITE } from "./cli/registry.js"; import { fail, formatError } from "./cli/ui.js"; const CLI_HELP_HEADER = styleTitle(`🦞 ClawHub CLI ${getCliBuildLabel()}`); const HELP_DESCRIPTION = "Display help for command"; const program = new Command() .name("clawhub") .description(styleEnvBlock("install, update, search, and publish skills plus OpenClaw packages.")) .version(getCliVersion(), "-V, --cli-version", "Show CLI version") .helpOption("-h, --help", HELP_DESCRIPTION) .option("--workdir ", "Working directory (default: cwd)") .option("--dir ", "Skills directory (relative to workdir, default: skills)") .option("--site ", "Site base URL (for browser login)") .option("--registry ", "Registry API base URL") .option("--no-input", "Disable prompts") .showHelpAfterError() .showSuggestionAfterError() .addHelpCommand("help [command]", HELP_DESCRIPTION) .addHelpText("after", styleEnvBlock("\nEnv:\n CLAWHUB_SITE\n CLAWHUB_REGISTRY\n CLAWHUB_WORKDIR\n (CLAWDHUB_* supported)\n")); configureCommanderHelp(program); addCliHelpHeader(program); program.configureOutput({ outputError: (message, write) => write(styleError(message)), }); function addCliHelpHeader(command) { command.addHelpText("beforeAll", `${CLI_HELP_HEADER}\n`); } function registerCommand(parent, path) { const command = parent.command(path.at(-1) ?? "").helpOption("-h, --help", HELP_DESCRIPTION); configureCommanderHelp(command); return command; } function registerCommandGroup(parent, path) { const command = parent.command(path.at(-1) ?? "").helpOption("-h, --help", HELP_DESCRIPTION); configureCommanderHelp(command); return command; } function applyCommandHelpGroups(parent, groups) { for (const command of parent.commands) { const group = groups[command.name()]; if (group) command.helpGroup(group); } } function validateTopLevelCommand(args) { if (hasTerminalGlobalFlag(args)) return; const commandName = findFirstTopLevelOperand(args); if (!commandName) return; const knownCommands = new Set([ "help", ...program.commands.flatMap((command) => [command.name(), ...command.aliases()]), ]); if (knownCommands.has(commandName)) return; program.error(`error: unknown command '${commandName}'`, { code: "commander.unknownCommand" }); } function hasTerminalGlobalFlag(args) { return args.some((arg) => arg === "--help" || arg === "-h" || arg === "--cli-version" || arg === "-V"); } function findFirstTopLevelOperand(args) { for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (!arg) continue; if (arg === "--") return args[index + 1]; if (arg.startsWith("--")) { if (arg === "--workdir" || arg === "--dir" || arg === "--site" || arg === "--registry") { index += 1; continue; } if (arg.startsWith("--workdir=") || arg.startsWith("--dir=") || arg.startsWith("--site=") || arg.startsWith("--registry=")) { continue; } if (arg === "--help" || arg === "--cli-version") return undefined; if (arg === "--no-input") continue; return undefined; } if (arg.startsWith("-")) { if (arg === "-h" || arg === "-V") return undefined; return undefined; } return arg; } return undefined; } async function resolveGlobalOpts() { const raw = program.opts(); const workdir = await resolveWorkdir(raw.workdir); const dir = resolve(workdir, raw.dir ?? "skills"); const site = raw.site ?? process.env.CLAWHUB_SITE ?? process.env.CLAWDHUB_SITE ?? DEFAULT_SITE; const registrySource = raw.registry ? "cli" : process.env.CLAWHUB_REGISTRY || process.env.CLAWDHUB_REGISTRY ? "env" : "default"; const registry = raw.registry ?? process.env.CLAWHUB_REGISTRY ?? process.env.CLAWDHUB_REGISTRY ?? DEFAULT_REGISTRY; return { workdir, dir, site, registry, registrySource }; } function isInputAllowed() { const globalFlags = program.opts(); return globalFlags.input !== false; } async function resolveWorkdir(explicit) { if (explicit?.trim()) return resolve(explicit.trim()); const envWorkdir = process.env.CLAWHUB_WORKDIR?.trim() ?? process.env.CLAWDHUB_WORKDIR?.trim(); if (envWorkdir) return resolve(envWorkdir); const cwd = resolve(process.cwd()); const hasMarker = await hasClawhubMarker(cwd); if (hasMarker) return cwd; const clawdbotWorkspace = await resolveClawdbotDefaultWorkspace(); return clawdbotWorkspace ? resolve(clawdbotWorkspace) : cwd; } async function hasClawhubMarker(workdir) { const lockfile = join(workdir, ".clawhub", "lock.json"); if (await pathExists(lockfile)) return true; const markerDir = join(workdir, ".clawhub"); if (await pathExists(markerDir)) return true; const legacyLockfile = join(workdir, ".clawdhub", "lock.json"); if (await pathExists(legacyLockfile)) return true; const legacyMarkerDir = join(workdir, ".clawdhub"); return pathExists(legacyMarkerDir); } async function pathExists(path) { try { await stat(path); return true; } catch { return false; } } registerCommand(program, ["login"]) .description("Log in with device flow or store a token") .option("--token ", "API token") .option("--label