| import type { Command } from "commander"; |
| import { resolveCommitHash } from "../../infra/git-commit.js"; |
| import { formatDocsLink } from "../../terminal/links.js"; |
| import { isRich, theme } from "../../terminal/theme.js"; |
| import { escapeRegExp } from "../../utils.js"; |
| import { hasFlag, hasRootVersionAlias } from "../argv.js"; |
| import { formatCliBannerLine, hasEmittedCliBanner } from "../banner.js"; |
| import { replaceCliName, resolveCliDisplayName, resolveCliName } from "../cli-name.js"; |
| import { CLI_LOG_LEVEL_VALUES, parseCliLogLevelOption } from "../log-level-option.js"; |
| import { getCoreCliCommandsWithSubcommands } from "./command-registry.js"; |
| import type { ProgramContext } from "./context.js"; |
| import { getSubCliCommandsWithSubcommands } from "./register.subclis.js"; |
|
|
| const ROOT_COMMANDS_WITH_SUBCOMMANDS = new Set([ |
| ...getCoreCliCommandsWithSubcommands(), |
| ...getSubCliCommandsWithSubcommands(), |
| ]); |
| const ROOT_COMMANDS_HINT = |
| "Hint: commands suffixed with * have subcommands. Run <command> --help for details."; |
|
|
| const EXAMPLES = [ |
| ["openskynet models --help", "Show detailed help for the models command."], |
| [ |
| "openskynet channels login --verbose", |
| "Link personal WhatsApp Web and show QR + connection logs.", |
| ], |
| [ |
| 'openskynet message send --target +15555550123 --message "Hi" --json', |
| "Send via your web session and print JSON result.", |
| ], |
| ["openskynet gateway --port 18789", "Run the WebSocket Gateway locally."], |
| [ |
| "openskynet --dev gateway", |
| "Run a dev Gateway (isolated state/config) on ws://127.0.0.1:19001.", |
| ], |
| ["openskynet gateway --force", "Kill anything bound to the default gateway port, then start it."], |
| ["openskynet gateway ...", "Gateway control via WebSocket."], |
| [ |
| 'openskynet agent --to +15555550123 --message "Run summary" --deliver', |
| "Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.", |
| ], |
| [ |
| 'openskynet message send --channel telegram --target @mychat --message "Hi"', |
| "Send via your Telegram bot.", |
| ], |
| ] as const; |
|
|
| export function configureProgramHelp(program: Command, ctx: ProgramContext) { |
| const cliName = resolveCliName(); |
| const cliDisplayName = resolveCliDisplayName(); |
| const cliNamePattern = escapeRegExp(cliName); |
|
|
| program |
| .name(cliName) |
| .description("") |
| .version(ctx.programVersion) |
| .option( |
| "--dev", |
| "Dev profile: isolate state/config, default gateway port 19001, and shift derived ports (browser/canvas)", |
| ) |
| .option( |
| "--profile <name>", |
| "Use a named profile (isolates state/config under a profile-specific directory)", |
| ) |
| .option( |
| "--log-level <level>", |
| `Global log level override for file + console (${CLI_LOG_LEVEL_VALUES})`, |
| parseCliLogLevelOption, |
| ); |
|
|
| program.option("--no-color", "Disable ANSI colors", false); |
| program.helpOption("-h, --help", "Display help for command"); |
| program.helpCommand("help [command]", "Display help for command"); |
|
|
| program.configureHelp({ |
| |
| sortSubcommands: true, |
| sortOptions: true, |
| optionTerm: (option) => theme.option(option.flags), |
| subcommandTerm: (cmd) => { |
| const isRootCommand = cmd.parent === program; |
| const hasSubcommands = isRootCommand && ROOT_COMMANDS_WITH_SUBCOMMANDS.has(cmd.name()); |
| return theme.command(hasSubcommands ? `${cmd.name()} *` : cmd.name()); |
| }, |
| }); |
|
|
| const formatHelpOutput = (str: string) => { |
| let output = str; |
| const isRootHelp = new RegExp( |
| `^Usage:\\s+${cliNamePattern}\\s+\\[options\\]\\s+\\[command\\]\\s*$`, |
| "m", |
| ).test(output); |
| if (isRootHelp && /^Commands:/m.test(output)) { |
| output = output.replace(/^Commands:/m, `Commands:\n ${theme.muted(ROOT_COMMANDS_HINT)}`); |
| } |
|
|
| return output |
| .replace(/^Usage:/gm, theme.heading("Usage:")) |
| .replace(/^Options:/gm, theme.heading("Options:")) |
| .replace(/^Commands:/gm, theme.heading("Commands:")); |
| }; |
|
|
| program.configureOutput({ |
| writeOut: (str) => { |
| process.stdout.write(formatHelpOutput(str)); |
| }, |
| writeErr: (str) => { |
| process.stderr.write(formatHelpOutput(str)); |
| }, |
| outputError: (str, write) => write(theme.error(str)), |
| }); |
|
|
| if ( |
| hasFlag(process.argv, "-V") || |
| hasFlag(process.argv, "--version") || |
| hasRootVersionAlias(process.argv) |
| ) { |
| const commit = resolveCommitHash({ moduleUrl: import.meta.url }); |
| console.log( |
| commit |
| ? `${cliDisplayName} ${ctx.programVersion} (${commit})` |
| : `${cliDisplayName} ${ctx.programVersion}`, |
| ); |
| process.exit(0); |
| } |
|
|
| program.addHelpText("beforeAll", () => { |
| if (hasEmittedCliBanner()) { |
| return ""; |
| } |
| const rich = isRich(); |
| const line = formatCliBannerLine(ctx.programVersion, { richTty: rich }); |
| return `\n${line}\n`; |
| }); |
|
|
| const fmtExamples = EXAMPLES.map( |
| ([cmd, desc]) => ` ${theme.command(replaceCliName(cmd, cliName))}\n ${theme.muted(desc)}`, |
| ).join("\n"); |
|
|
| program.addHelpText("afterAll", ({ command }) => { |
| if (command !== program) { |
| return ""; |
| } |
| const docs = formatDocsLink("/cli", "docs.openclaw.ai/cli"); |
| return `\n${theme.heading("Examples:")}\n${fmtExamples}\n\n${theme.muted("Docs:")} ${docs}\n`; |
| }); |
| } |
|
|