#!/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