diff --git a/.gitattributes b/.gitattributes index d351e0fc83e..30441642037 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,3 +18,7 @@ packages/kilo-vscode/tests/**/*.png filter=lfs diff=lfs merge=lfs -text **/i18n/parity.test.ts linguist-generated=false packages/kilo-i18n/src/*.ts linguist-generated=true packages/kilo-i18n/src/en.ts linguist-generated=false + +# Auto-generated CLI reference docs +packages/kilo-docs/markdoc/partials/cli-commands-table.md linguist-generated=true +packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md linguist-generated=true diff --git a/packages/kilo-docs/lib/nav/code-with-ai.ts b/packages/kilo-docs/lib/nav/code-with-ai.ts index 69a90dc7786..06795fcbe6a 100644 --- a/packages/kilo-docs/lib/nav/code-with-ai.ts +++ b/packages/kilo-docs/lib/nav/code-with-ai.ts @@ -14,7 +14,11 @@ export const CodeWithAiNav: NavSection[] = [ href: "/code-with-ai/platforms/jetbrains", children: "JetBrains Extension", }, - { href: "/code-with-ai/platforms/cli", children: "CLI" }, + { + href: "/code-with-ai/platforms/cli", + children: "CLI", + subLinks: [{ href: "/code-with-ai/platforms/cli-reference", children: "Command Reference" }], + }, { href: "/code-with-ai/platforms/cloud-agent", children: "Cloud Agent" }, { href: "/code-with-ai/platforms/mobile", children: "Mobile Apps" }, { href: "/code-with-ai/platforms/slack", children: "Slack" }, diff --git a/packages/kilo-docs/markdoc/partials/cli-commands-table.md b/packages/kilo-docs/markdoc/partials/cli-commands-table.md new file mode 100644 index 00000000000..d6437cac497 --- /dev/null +++ b/packages/kilo-docs/markdoc/partials/cli-commands-table.md @@ -0,0 +1,25 @@ + + +| Command | Description | +| --- | --- | +| `kilo acp` | start ACP (Agent Client Protocol) server | +| `kilo mcp` | manage MCP (Model Context Protocol) servers | +| `kilo [project]` | start kilo tui | +| `kilo attach ` | attach to a running kilo server | +| `kilo run [message..]` | run kilo with a message | +| `kilo debug` | debugging and troubleshooting tools | +| `kilo auth` | manage credentials | +| `kilo agent` | manage agents | +| `kilo upgrade [target]` | upgrade kilo to the latest or a specific version | +| `kilo uninstall` | uninstall kilo and remove all related files | +| `kilo serve` | starts a headless kilo server | +| `kilo models [provider]` | list all available models | +| `kilo stats` | show token usage and cost statistics | +| `kilo export [sessionID]` | export session data as JSON | +| `kilo import ` | import session data from JSON file or URL | +| `kilo pr ` | fetch and checkout a GitHub PR branch, then run kilo | +| `kilo session` | manage sessions | +| `kilo remote` | enable remote connection for real-time session relay | +| `kilo db` | database tools | +| `kilo help [command]` | show full CLI reference | +| `kilo completion` | generate shell completion script | diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md b/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md new file mode 100644 index 00000000000..ed944d4407a --- /dev/null +++ b/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md @@ -0,0 +1,816 @@ +--- +title: "CLI Command Reference" +description: "Complete reference for all Kilo CLI commands and subcommands" +--- + +# CLI Command Reference + + + +## kilo acp + +``` +start ACP (Agent Client Protocol) server + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --port port to listen on [number] [default: 0] + --hostname hostname to listen on [string] [default: "127.0.0.1"] + --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false] + --mdns-domain custom domain name for mDNS service (default: kilo.local) [string] [default: "kilo.local"] + --cors additional domains to allow for CORS [array] [default: []] + --cwd working directory [string] [default: "."] +``` + +## kilo mcp + +``` +manage MCP (Model Context Protocol) servers + +Commands: + kilo mcp add add an MCP server + kilo mcp list list MCP servers and their status [aliases: ls] + kilo mcp auth [name] authenticate with an OAuth-enabled MCP server + kilo mcp logout [name] remove OAuth credentials for an MCP server + kilo mcp debug debug OAuth connection for an MCP server + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo mcp add + +``` +add an MCP server + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo mcp list + +``` +list MCP servers and their status + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo mcp auth + +``` +authenticate with an OAuth-enabled MCP server + +Commands: + kilo mcp auth list list OAuth-capable MCP servers and their auth status [aliases: ls] + +Positionals: + name name of the MCP server [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo mcp auth list + +``` +list OAuth-capable MCP servers and their auth status + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo mcp logout + +``` +remove OAuth credentials for an MCP server + +Positionals: + name name of the MCP server [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo mcp debug + +``` +debug OAuth connection for an MCP server + +Positionals: + name name of the MCP server [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo [project] + +``` +start kilo tui + +Positionals: + project path to start kilo in [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --port port to listen on [number] [default: 0] + --hostname hostname to listen on [string] [default: "127.0.0.1"] + --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false] + --mdns-domain custom domain name for mDNS service (default: kilo.local) [string] [default: "kilo.local"] + --cors additional domains to allow for CORS [array] [default: []] + -m, --model model to use in the format of provider/model [string] + -c, --continue continue the last session [boolean] + -s, --session session id to continue [string] + --fork fork the session when continuing (use with --continue or --session) [boolean] + --cloud-fork fetch session from cloud and continue locally (use with --session) [boolean] + --prompt prompt to use [string] + --agent agent to use [string] +``` + +## kilo attach + +``` +attach to a running kilo server + +Positionals: + url http://localhost:4096 [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --dir directory to run in [string] + -c, --continue continue the last session [boolean] + -s, --session session id to continue [string] + --fork fork the session when continuing (use with --continue or --session) [boolean] + --cloud-fork fetch session from cloud and continue locally (use with --session) [boolean] + -p, --password basic auth password (defaults to KILO_SERVER_PASSWORD) [string] +``` + +## kilo run + +``` +run kilo with a message + +Positionals: + message message to send [string] [default: []] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --command the command to run, use message for args [string] + -c, --continue continue the last session [boolean] + -s, --session session id to continue [string] + --fork fork the session before continuing (requires --continue or --session) [boolean] + --cloud-fork fetch session from cloud and continue locally (requires --session) [boolean] + --share share the session [boolean] + -m, --model model to use in the format of provider/model [string] + --agent agent to use [string] + --format format: default (formatted) or json (raw JSON events) [string] [choices: "default", "json"] [default: "default"] + -f, --file file(s) to attach to message [array] + --title title for the session (uses truncated prompt if no value provided) [string] + --attach attach to a running opencode server (e.g., http://localhost:4096) [string] + -p, --password basic auth password (defaults to KILO_SERVER_PASSWORD) [string] + --dir directory to run in, path on remote server if attaching [string] + --port port for the local server (defaults to random port if no value provided) [number] + --variant model variant (provider-specific reasoning effort, e.g., high, max, minimal) [string] + --thinking show thinking blocks [boolean] [default: false] + --auto auto-approve all permissions (for autonomous/pipeline usage) [boolean] [default: false] +``` + +## kilo debug + +``` +debugging and troubleshooting tools + +Commands: + kilo debug config show resolved configuration + kilo debug lsp LSP debugging utilities + kilo debug rg ripgrep debugging utilities + kilo debug file file system debugging utilities + kilo debug scrap list all known projects + kilo debug skill list all available skills + kilo debug snapshot snapshot debugging utilities + kilo debug agent show agent configuration details + kilo debug paths show global paths (data, config, cache, state) + kilo debug wait wait indefinitely (for debugging) + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug config + +``` +show resolved configuration + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug lsp + +``` +LSP debugging utilities + +Commands: + kilo debug lsp diagnostics get diagnostics for a file + kilo debug lsp symbols search workspace symbols + kilo debug lsp document-symbols get symbols from a document + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug lsp diagnostics + +``` +get diagnostics for a file + +Positionals: + file [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug lsp symbols + +``` +search workspace symbols + +Positionals: + query [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug lsp document-symbols + +``` +get symbols from a document + +Positionals: + uri [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug rg + +``` +ripgrep debugging utilities + +Commands: + kilo debug rg tree show file tree using ripgrep + kilo debug rg files list files using ripgrep + kilo debug rg search search file contents using ripgrep + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug rg tree + +``` +show file tree using ripgrep + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --limit [number] +``` + +### kilo debug rg files + +``` +list files using ripgrep + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --query Filter files by query [string] + --glob Glob pattern to match files [string] + --limit Limit number of results [number] +``` + +### kilo debug rg search + +``` +search file contents using ripgrep + +Positionals: + pattern Search pattern [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --glob File glob patterns [array] + --limit Limit number of results [number] +``` + +### kilo debug file + +``` +file system debugging utilities + +Commands: + kilo debug file read read file contents as JSON + kilo debug file status show file status information + kilo debug file list list files in a directory + kilo debug file search search files by query + kilo debug file tree [dir] show directory tree + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug file read + +``` +read file contents as JSON + +Positionals: + path File path to read [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug file status + +``` +show file status information + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug file list + +``` +list files in a directory + +Positionals: + path File path to list [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug file search + +``` +search files by query + +Positionals: + query Search query [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug file tree + +``` +show directory tree + +Positionals: + dir Directory to tree [string] [default: "."] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug scrap + +``` +list all known projects + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug skill + +``` +list all available skills + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug snapshot + +``` +snapshot debugging utilities + +Commands: + kilo debug snapshot track track current snapshot state + kilo debug snapshot patch show patch for a snapshot hash + kilo debug snapshot diff show diff for a snapshot hash + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug snapshot track + +``` +track current snapshot state + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug snapshot patch + +``` +show patch for a snapshot hash + +Positionals: + hash hash [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug snapshot diff + +``` +show diff for a snapshot hash + +Positionals: + hash hash [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug agent + +``` +show agent configuration details + +Positionals: + name Agent name [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --tool Tool id to execute [string] + --params Tool params as JSON or a JS object literal [string] +``` + +### kilo debug paths + +``` +show global paths (data, config, cache, state) + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo debug wait + +``` +wait indefinitely (for debugging) + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo auth + +``` +manage credentials + +Commands: + kilo auth login [url] log in to a provider + kilo auth logout log out from a configured provider + kilo auth list list providers [aliases: ls] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo auth login + +``` +log in to a provider + +Positionals: + url kilo auth provider [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + -p, --provider provider id or name to log in to (skips provider selection) [string] + -m, --method login method label (skips method selection) [string] +``` + +### kilo auth logout + +``` +log out from a configured provider + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo auth list + +``` +list providers + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo agent + +``` +manage agents + +Commands: + kilo agent create create a new agent + kilo agent list list all available agents + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo agent create + +``` +create a new agent + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --path directory path to generate the agent file [string] + --description what the agent should do [string] + --mode agent mode [string] [choices: "all", "primary", "subagent"] + --tools comma-separated list of tools to enable (default: all). Available: "bash, read, write, edit, list, glob, grep, webfetch, task, todowrite, todoread" [string] + -m, --model model to use in the format of provider/model [string] +``` + +### kilo agent list + +``` +list all available agents + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo upgrade + +``` +upgrade kilo to the latest or a specific version + +Positionals: + target version to upgrade to, for ex '0.1.48' or 'v0.1.48' [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + -m, --method installation method to use [string] [choices: "curl", "npm", "pnpm", "bun", "brew", "choco", "scoop"] +``` + +## kilo uninstall + +``` +uninstall kilo and remove all related files + +Options: + --help Show help [boolean] + --version Show version number [boolean] + -c, --keep-config keep configuration files [boolean] [default: false] + -d, --keep-data keep session data and snapshots [boolean] [default: false] + --dry-run show what would be removed without removing [boolean] [default: false] + -f, --force skip confirmation prompts [boolean] [default: false] +``` + +## kilo serve + +``` +starts a headless kilo server + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --port port to listen on [number] [default: 0] + --hostname hostname to listen on [string] [default: "127.0.0.1"] + --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false] + --mdns-domain custom domain name for mDNS service (default: kilo.local) [string] [default: "kilo.local"] + --cors additional domains to allow for CORS [array] [default: []] +``` + +## kilo models + +``` +list all available models + +Positionals: + provider provider ID to filter models by [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --verbose use more verbose model output (includes metadata like costs) [boolean] + --refresh refresh the models cache from models.dev [boolean] +``` + +## kilo stats + +``` +show token usage and cost statistics + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --days show stats for the last N days (default: all time) [number] + --tools number of tools to show (default: all) [number] + --models show model statistics (default: hidden). Pass a number to show top N, otherwise shows all + --project filter by project (default: all projects, empty string: current project) [string] +``` + +## kilo export + +``` +export session data as JSON + +Positionals: + sessionID session id to export [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo import + +``` +import session data from JSON file or URL + +Positionals: + file path to JSON file or share URL [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo pr + +``` +fetch and checkout a GitHub PR branch, then run kilo + +Positionals: + number PR number to checkout [number] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo session + +``` +manage sessions + +Commands: + kilo session list list sessions + kilo session delete delete a session + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo session list + +``` +list sessions + +Options: + --help Show help [boolean] + --version Show version number [boolean] + -n, --max-count limit to N most recent sessions [number] + --format output format [string] [choices: "table", "json"] [default: "table"] +``` + +### kilo session delete + +``` +delete a session + +Positionals: + sessionID session ID to delete [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo remote + +``` +enable remote connection for real-time session relay + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo db + +``` +database tools + +Commands: + kilo db [query] open an interactive sqlite3 shell or run a query [default] + kilo db path print the database path + kilo db migrate migrate JSON data to SQLite (merges with existing data) + +Positionals: + query SQL query to execute [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --format Output format [string] [choices: "json", "tsv"] [default: "tsv"] +``` + +### kilo db path + +``` +print the database path + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +### kilo db migrate + +``` +migrate JSON data to SQLite (merges with existing data) + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## kilo help + +``` +show full CLI reference + +Positionals: + command command to show help for [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --all show help for all commands [boolean] [default: false] + --format output format [string] [choices: "md", "text"] [default: "md"] +``` diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/cli.md b/packages/kilo-docs/pages/code-with-ai/platforms/cli.md index 1ee5cbfe61d..0c206fffb56 100644 --- a/packages/kilo-docs/pages/code-with-ai/platforms/cli.md +++ b/packages/kilo-docs/pages/code-with-ai/platforms/cli.md @@ -61,27 +61,9 @@ Or use npm: ### Top-Level CLI Commands -| Command | Description | -| ------------------------- | ------------------------------------------ | -| `kilo [project]` | Start the TUI (Terminal User Interface) | -| `kilo run [message..]` | Run with a message (non-interactive mode) | -| `kilo attach ` | Attach to a running kilo server | -| `kilo serve` | Start a headless server | -| `kilo web` | Start server and open web interface | -| `kilo auth` | Manage credentials (login, logout, list) | -| `kilo agent` | Manage agents (create, list) | -| `kilo mcp` | Manage MCP servers (list, add, auth) | -| `kilo models [provider]` | List available models | -| `kilo stats` | Show token usage and cost statistics | -| `kilo session` | Manage sessions (list) | -| `kilo export [sessionID]` | Export session data as JSON | -| `kilo import ` | Import session data from JSON file or URL | -| `kilo upgrade [target]` | Upgrade kilo to latest or specific version | -| `kilo uninstall` | Uninstall kilo and remove related files | -| `kilo pr ` | Fetch and checkout a GitHub PR branch | -| `kilo github` | Manage GitHub agent (install, run) | -| `kilo debug` | Debugging and troubleshooting tools | -| `kilo completion` | Generate shell completion script | +{% partial file="cli-commands-table.md" /%} + +For detailed help on every command and subcommand, see the [CLI Command Reference](/docs/code-with-ai/platforms/cli-reference). ### Global Options diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 0b5bc16bc36..a81aacd9d8e 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -54,6 +54,7 @@ import { Auth } from "./auth" import { DbCommand } from "./cli/cmd/db" import path from "path" import { Global } from "./global" +import { createHelpCommand } from "./kilocode/help-command" // kilocode_change import { JsonMigration } from "./storage/json-migration" import { Database } from "./storage/db" @@ -198,6 +199,10 @@ let cli = yargs(hideBin(process.argv)) .command(DbCommand) .command(ConfigCLICommand) // kilocode_change +// kilocode_change start - registered after initial chain to avoid self-referential type error +cli = cli.command(createHelpCommand(() => cli)) +// kilocode_change end + if (Installation.isLocal()) { cli = cli.command(WorkspaceServeCommand) } diff --git a/packages/opencode/src/kilocode/commands.ts b/packages/opencode/src/kilocode/commands.ts new file mode 100644 index 00000000000..a882e8f1595 --- /dev/null +++ b/packages/opencode/src/kilocode/commands.ts @@ -0,0 +1,59 @@ +// All CommandModules in one place so help.ts and generate-cli-docs.ts can +// introspect them without importing index.ts (which has startup side effects). +// When upstream adds a new command to index.ts, add it here too. +import { AcpCommand } from "../cli/cmd/acp" +import { McpCommand } from "../cli/cmd/mcp" +import { TuiThreadCommand } from "../cli/cmd/tui/thread" +import { AttachCommand } from "../cli/cmd/tui/attach" +import { RunCommand } from "../cli/cmd/run" +import { GenerateCommand } from "../cli/cmd/generate" +import { DebugCommand } from "../cli/cmd/debug" +import { AuthCommand } from "../cli/cmd/auth" +import { AgentCommand } from "../cli/cmd/agent" +import { UpgradeCommand } from "../cli/cmd/upgrade" +import { UninstallCommand } from "../cli/cmd/uninstall" +import { ServeCommand } from "../cli/cmd/serve" +import { ModelsCommand } from "../cli/cmd/models" +import { StatsCommand } from "../cli/cmd/stats" +import { ExportCommand } from "../cli/cmd/export" +import { ImportCommand } from "../cli/cmd/import" +import { PrCommand } from "../cli/cmd/pr" +import { SessionCommand } from "../cli/cmd/session" +import { RemoteCommand } from "../cli/cmd/remote" +import { DbCommand } from "../cli/cmd/db" +import { ConfigCommand as ConfigCLICommand } from "../cli/cmd/config" +import { HelpCommand } from "./help-command" + +// Synthetic entry for the yargs built-in .completion() command so that +// generateHelp --all and cli-reference.md include it automatically. +const CompletionCommand = { + command: "completion", + describe: "generate shell completion script", + handler: () => {}, +} + +export const commands = [ + AcpCommand, + McpCommand, + TuiThreadCommand, + AttachCommand, + RunCommand, + GenerateCommand, + DebugCommand, + AuthCommand, + AgentCommand, + UpgradeCommand, + UninstallCommand, + ServeCommand, + ModelsCommand, + StatsCommand, + ExportCommand, + ImportCommand, + PrCommand, + SessionCommand, + RemoteCommand, + DbCommand, + ConfigCLICommand, + HelpCommand, + CompletionCommand, +] diff --git a/packages/opencode/src/kilocode/generate-cli-docs.ts b/packages/opencode/src/kilocode/generate-cli-docs.ts new file mode 100644 index 00000000000..fb60badcdd6 --- /dev/null +++ b/packages/opencode/src/kilocode/generate-cli-docs.ts @@ -0,0 +1,28 @@ +import { generateHelp, generateCommandTable } from "./help" +import path from "path" + +const root = path.resolve(import.meta.dir, "../../../..") + "/" + +const TABLE_PATH = root + "packages/kilo-docs/markdoc/partials/cli-commands-table.md" +const REFERENCE_PATH = root + "packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md" + +const table = await generateCommandTable() +await Bun.write(TABLE_PATH, `\n\n${table}`) +console.log(`wrote ${TABLE_PATH}`) + +const cwd = process.cwd() +const reference = (await generateHelp({ all: true, format: "md" })).replaceAll(cwd, ".") +await Bun.write( + REFERENCE_PATH, + `--- +title: "CLI Command Reference" +description: "Complete reference for all Kilo CLI commands and subcommands" +--- + +# CLI Command Reference + + + +${reference}`, +) +console.log(`wrote ${REFERENCE_PATH}`) diff --git a/packages/opencode/src/kilocode/help-command.ts b/packages/opencode/src/kilocode/help-command.ts new file mode 100644 index 00000000000..ecb5f90f14d --- /dev/null +++ b/packages/opencode/src/kilocode/help-command.ts @@ -0,0 +1,45 @@ +import { cmd } from "../cli/cmd/cmd" +import { generateHelp } from "./help" +import type { Argv } from "yargs" + +export function createHelpCommand(root?: () => Argv) { + return cmd({ + command: "help [command]", + describe: "show full CLI reference", + builder: (yargs) => + yargs + .positional("command", { + describe: "command to show help for", + type: "string", + }) + .option("all", { + describe: "show help for all commands", + type: "boolean", + default: false, + }) + .option("format", { + describe: "output format", + type: "string", + choices: ["md", "text"] as const, + default: "md" as const, + }), + async handler(args) { + if (!args.command && !args.all) { + if (root) { + const help = await root().getHelp() + process.stdout.write(help + "\n") + } + return + } + const output = await generateHelp({ + command: args.command, + all: args.all, + format: args.format as "md" | "text", + }) + process.stdout.write(output + "\n") + }, + }) +} + +// Static instance for introspection by commands.ts / help.ts (handler not invoked) +export const HelpCommand = createHelpCommand() diff --git a/packages/opencode/src/kilocode/help.ts b/packages/opencode/src/kilocode/help.ts new file mode 100644 index 00000000000..206ca744bbb --- /dev/null +++ b/packages/opencode/src/kilocode/help.ts @@ -0,0 +1,237 @@ +import yargs from "yargs" +import type { CommandModule } from "yargs" +import { Log } from "../util/log" + +type Cmd = CommandModule + +const ANSI_REGEX = /\x1b\[[0-9;]*m/g + +function strip(text: string): string { + return text.replace(ANSI_REGEX, "") +} + +function extractCommandName(cmd: Cmd): string | undefined { + const raw = typeof cmd.command === "string" ? cmd.command : cmd.command?.[0] + if (!raw) return undefined + if (raw.startsWith("$0")) return raw.slice(2).trim() || "" + return raw.split(/[\s[<]/)[0] +} + +async function getHelpText(name: string, cmd: Cmd): Promise { + const inst = yargs([]) + .scriptName(name ? `kilo ${name}` : "kilo") + .wrap(null) + if (cmd.builder) { + if (typeof cmd.builder === "function") { + ;(cmd.builder as any)(inst) + } else { + inst.options(cmd.builder as any) + } + } + if (cmd.describe) { + inst.usage(typeof cmd.describe === "string" ? cmd.describe : "") + } + const help = await inst.getHelp() + return strip(help) +} + +async function getSubcommands( + name: string, + builder: ((y: any) => any) | undefined, + depth = 0, +): Promise> { + if (!builder || typeof builder !== "function") return [] + if (depth > 4) return [] // guard against infinite recursion + + const inst = yargs([]).scriptName(`kilo ${name}`).wrap(null) + builder(inst) + + const result: Array<{ name: string; hidden: boolean; help: string }> = [] + + try { + // yargs 18 internal API — verified against yargs@18.0.0 + // If these internals change, the catch block below will log a warning + // and subcommand help will be omitted (top-level help still works) + const internal = (inst as any).getInternalMethods() + const cmdInstance = internal.getCommandInstance() + const handlers = cmdInstance.getCommandHandlers() + + for (const [sub, handler] of Object.entries(handlers as Record)) { + if (sub === "$0") continue + + const full = `${name} ${sub}` + const subInst = yargs([]).scriptName(`kilo ${full}`).wrap(null) + + if (handler.builder && typeof handler.builder === "function") { + handler.builder(subInst) + } else if (handler.builder && typeof handler.builder === "object") { + subInst.options(handler.builder) + } + + if (handler.description) { + subInst.usage(handler.description) + } + + const help = strip(await subInst.getHelp()) + result.push({ + name: full, + hidden: handler.description === false, + help, + }) + + // recurse into sub-subcommands + const deeper = await getSubcommands( + full, + typeof handler.builder === "function" ? handler.builder : undefined, + depth + 1, + ) + result.push(...deeper) + } + } catch (err) { + Log.Default.warn("failed to extract subcommands via yargs internals", { err }) + } + + return result +} + +function formatMarkdown( + sections: Array<{ + name: string + hidden: boolean + help: string + subs: Array<{ name: string; hidden: boolean; help: string }> + }>, +): string { + const parts: string[] = [] + + for (const section of sections) { + parts.push(`## ${section.name ? `kilo ${section.name}` : "kilo"}`) + parts.push("") + if (section.hidden) { + parts.push("> **Internal command** — not intended for direct use.") + parts.push("") + } + parts.push("```") + parts.push(section.help) + parts.push("```") + parts.push("") + + for (const sub of section.subs) { + parts.push(`### kilo ${sub.name}`) + parts.push("") + if (sub.hidden) { + parts.push("> **Internal command** — not intended for direct use.") + parts.push("") + } + parts.push("```") + parts.push(sub.help) + parts.push("```") + parts.push("") + } + } + + return parts.join("\n") +} + +function formatText( + sections: Array<{ + name: string + hidden: boolean + help: string + subs: Array<{ name: string; hidden: boolean; help: string }> + }>, +): string { + const parts: string[] = [] + const rule = "=".repeat(80) + + for (const section of sections) { + parts.push(rule) + const display = section.name ? `kilo ${section.name}` : "kilo" + const label = section.hidden ? `${display} [internal]` : display + parts.push(label) + parts.push(rule) + parts.push("") + parts.push(section.help) + parts.push("") + + for (const sub of section.subs) { + const sublabel = sub.hidden ? `--- kilo ${sub.name} [internal] ---` : `--- kilo ${sub.name} ---` + parts.push(sublabel) + parts.push("") + parts.push(sub.help) + parts.push("") + } + } + + return parts.join("\n") +} + +async function loadCommands(): Promise { + const { commands } = await import("./commands") + return commands as Cmd[] +} + +export async function generateHelp(options: { + command?: string + all?: boolean + format?: "md" | "text" + commands?: Cmd[] +}): Promise { + const format = options.format ?? "md" + + const cmds = options.commands ?? (await loadCommands()) + const relevant = (() => { + if (options.command) return cmds.filter((c) => extractCommandName(c) === options.command) + if (options.all) return cmds.filter((c) => extractCommandName(c) !== undefined && c.describe) + return [] + })() + + if (options.command && relevant.length === 0) { + throw new Error(`unknown command: ${options.command}`) + } + + const sections: Array<{ + name: string + hidden: boolean + help: string + subs: Array<{ name: string; hidden: boolean; help: string }> + }> = [] + + for (const cmd of relevant) { + const name = extractCommandName(cmd)! + const help = await getHelpText(name, cmd) + const hidden = (cmd as any).hidden === true + const subs = await getSubcommands(name, typeof cmd.builder === "function" ? cmd.builder : undefined) + + sections.push({ name, hidden, help, subs }) + } + + return format === "md" ? formatMarkdown(sections) : formatText(sections) +} + +export async function generateCommandTable(options?: { commands?: Cmd[] }) { + const cmds = options?.commands ?? (await loadCommands()) + + const rows: Array<{ display: string; description: string }> = [] + + for (const cmd of cmds) { + const raw = typeof cmd.command === "string" ? cmd.command : cmd.command?.[0] + if (!raw) continue + if (!cmd.describe) continue + + const display = raw.startsWith("$0") ? "kilo" + raw.slice(2) : "kilo " + raw + + rows.push({ + display: display.trim(), + description: typeof cmd.describe === "string" ? cmd.describe : "", + }) + } + + const lines = ["| Command | Description |", "| --- | --- |"] + + for (const row of rows) { + lines.push(`| \`${row.display}\` | ${row.description} |`) + } + + return lines.join("\n") + "\n" +} diff --git a/packages/opencode/test/kilocode/help.test.ts b/packages/opencode/test/kilocode/help.test.ts new file mode 100644 index 00000000000..744ff1771ab --- /dev/null +++ b/packages/opencode/test/kilocode/help.test.ts @@ -0,0 +1,177 @@ +import { describe, test, expect } from "bun:test" +import path from "path" +import { generateHelp, generateCommandTable } from "../../src/kilocode/help" +import { AcpCommand } from "../../src/cli/cmd/acp" +import { McpCommand } from "../../src/cli/cmd/mcp" +import { RunCommand } from "../../src/cli/cmd/run" +import { GenerateCommand } from "../../src/cli/cmd/generate" +import { DebugCommand } from "../../src/cli/cmd/debug" +import { AuthCommand } from "../../src/cli/cmd/auth" +import { AgentCommand } from "../../src/cli/cmd/agent" +import { UpgradeCommand } from "../../src/cli/cmd/upgrade" +import { UninstallCommand } from "../../src/cli/cmd/uninstall" +import { ServeCommand } from "../../src/cli/cmd/serve" +import { WebCommand } from "../../src/cli/cmd/web" +import { ModelsCommand } from "../../src/cli/cmd/models" +import { StatsCommand } from "../../src/cli/cmd/stats" +import { ExportCommand } from "../../src/cli/cmd/export" +import { ImportCommand } from "../../src/cli/cmd/import" +import { PrCommand } from "../../src/cli/cmd/pr" +import { SessionCommand } from "../../src/cli/cmd/session" +import { DbCommand } from "../../src/cli/cmd/db" +import { HelpCommand } from "../../src/kilocode/help-command" + +// Stand-in for TuiThreadCommand — the real one imports @opentui/solid which +// doesn't resolve in the test environment. Only command/describe matter here. +const TuiStub = { + command: "$0 [project]", + describe: "start kilo tui", + handler() {}, +} + +// Stand-in for AttachCommand — same reason as TuiStub above. +const AttachStub = { + command: "attach ", + describe: "attach to a running kilo server", + handler() {}, +} + +const commands = [ + AcpCommand, + McpCommand, + TuiStub, + AttachStub, + RunCommand, + GenerateCommand, + DebugCommand, + AuthCommand, + AgentCommand, + UpgradeCommand, + UninstallCommand, + ServeCommand, + WebCommand, + ModelsCommand, + StatsCommand, + ExportCommand, + ImportCommand, + PrCommand, + SessionCommand, + DbCommand, + HelpCommand, +] as any[] + +describe("kilo help --all (markdown)", () => { + test("contains ## heading for each known top-level command", async () => { + const output = await generateHelp({ all: true, format: "md", commands }) + for (const cmd of ["run", "auth", "debug", "mcp", "session", "agent"]) { + expect(output).toContain(`## kilo ${cmd}`) + } + }) + + test("contains headings for nested subcommands", async () => { + const output = await generateHelp({ all: true, format: "md", commands }) + expect(output).toContain("kilo auth login") + expect(output).toContain("kilo auth logout") + expect(output).toContain("kilo debug config") + }) +}) + +describe("kilo help --all (text)", () => { + test("does NOT contain Markdown ## headings or triple-backtick fences", async () => { + const output = await generateHelp({ all: true, format: "text", commands }) + expect(output).not.toMatch(/^##\s/m) + expect(output).not.toContain("```") + }) + + test("still contains each command name", async () => { + const output = await generateHelp({ all: true, format: "text", commands }) + for (const cmd of ["run", "auth", "debug", "mcp", "session", "agent"]) { + expect(output).toContain(`kilo ${cmd}`) + } + }) +}) + +describe("kilo help ", () => { + test("kilo help auth contains auth subcommand headings", async () => { + const output = await generateHelp({ command: "auth", format: "md", commands }) + expect(output).toContain("kilo auth login") + expect(output).toContain("kilo auth logout") + expect(output).toContain("kilo auth list") + }) + + test("kilo help auth does NOT contain run or debug headings", async () => { + const output = await generateHelp({ command: "auth", format: "md", commands }) + expect(output).not.toContain("## kilo run") + expect(output).not.toContain("## kilo debug") + }) +}) + +describe("edge cases", () => { + test("output contains no ANSI escape sequences", async () => { + const output = await generateHelp({ all: true, format: "md", commands }) + expect(/\x1b\[/.test(output)).toBe(false) + }) + + test("kilo help nonexistent throws unknown command error", async () => { + await expect(generateHelp({ command: "nonexistent", commands })).rejects.toThrow("unknown command") + }) +}) + +describe("generateCommandTable", () => { + test("returns a string containing a markdown table header", async () => { + const output = await generateCommandTable({ commands }) + expect(output).toContain("| Command | Description |") + }) + + test("contains rows for known commands", async () => { + const output = await generateCommandTable({ commands }) + for (const name of ["run", "auth", "debug", "mcp"]) { + expect(output).toContain(`kilo ${name}`) + } + }) + + test("default command appears as kilo [project], not $0", async () => { + const output = await generateCommandTable({ commands }) + expect(output).toContain("`kilo [project]`") + expect(output).not.toContain("$0") + }) + + test("contains no ANSI escape sequences", async () => { + const output = await generateCommandTable({ commands }) + expect(/\x1b\[/.test(output)).toBe(false) + }) + + test("skips commands with no describe", async () => { + const output = await generateCommandTable({ commands }) + expect(output).not.toContain("`kilo generate`") + }) + + test("contains kilo completion row", async () => { + const output = await generateCommandTable({ commands }) + expect(output).toContain("`kilo completion`") + }) + + test("contains kilo help row", async () => { + const output = await generateCommandTable({ commands }) + expect(output).toContain("`kilo help") + }) +}) + +describe("commands.ts stays in sync with index.ts", () => { + test("every .command() in index.ts has an entry in the commands array", async () => { + const index = await Bun.file(path.resolve(import.meta.dir, "../../src/index.ts")).text() + const barrel = await Bun.file(path.resolve(import.meta.dir, "../../src/kilocode/commands.ts")).text() + + // Match uncommented .command(XxxCommand) calls in index.ts + const registered = [...index.matchAll(/^\s*\.command\((\w+)\)/gm)].map((m) => m[1]!) + expect(registered.length).toBeGreaterThan(0) + + // Extract identifiers inside the exported commands = [...] array, not just anywhere in the file + const arrayMatch = barrel.match(/export const commands\s*=\s*\[([\s\S]*?)\]/) + expect(arrayMatch).toBeTruthy() + const entries = [...arrayMatch![1]!.matchAll(/\b(\w+Command)\b/g)].map((m) => m[1]!) + + const missing = registered.filter((name) => !entries.includes(name)) + expect(missing).toEqual([]) + }) +}) diff --git a/script/generate-cli-docs.ts b/script/generate-cli-docs.ts new file mode 100755 index 00000000000..bd5370abe3c --- /dev/null +++ b/script/generate-cli-docs.ts @@ -0,0 +1,5 @@ +#!/usr/bin/env bun + +import { $ } from "bun" + +await $`bun run --conditions=browser ./src/kilocode/generate-cli-docs.ts`.cwd("packages/opencode") diff --git a/script/generate.ts b/script/generate.ts index 8fc251d89d4..5b62c3714fe 100755 --- a/script/generate.ts +++ b/script/generate.ts @@ -6,4 +6,6 @@ await $`bun ./packages/sdk/js/script/build.ts` await $`bun dev generate > ../sdk/openapi.json`.cwd("packages/opencode") +await $`bun ./script/generate-cli-docs.ts` + await $`./script/format.ts`