diff --git a/packages/kilo-docs/lib/nav/code-with-ai.ts b/packages/kilo-docs/lib/nav/code-with-ai.ts index 59eac752024..8ccc3de9631 100644 --- a/packages/kilo-docs/lib/nav/code-with-ai.ts +++ b/packages/kilo-docs/lib/nav/code-with-ai.ts @@ -10,7 +10,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..8f7f288822a --- /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 web` | start kilo server and open web interface | +| `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 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..cb32eb5b17e --- /dev/null +++ b/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md @@ -0,0 +1,597 @@ +--- +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: opencode.local) [string] [default: "opencode.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 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 attach + +``` +attach to a running kilo server + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +## 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] + --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] + --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 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 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 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 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] +``` + +### 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: opencode.local) [string] [default: "opencode.local"] + --cors additional domains to allow for CORS [array] [default: []] +``` + +## kilo web + +``` +start kilo server and open web interface + +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: opencode.local) [string] [default: "opencode.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 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 6adbb9d608b..5a93bf504ef 100644 --- a/packages/kilo-docs/pages/code-with-ai/platforms/cli.md +++ b/packages/kilo-docs/pages/code-with-ai/platforms/cli.md @@ -60,27 +60,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/cli/commands.ts b/packages/opencode/src/cli/commands.ts new file mode 100644 index 00000000000..e7a42a94d74 --- /dev/null +++ b/packages/opencode/src/cli/commands.ts @@ -0,0 +1,46 @@ +// kilocode_change - new file +import { AcpCommand } from "./cmd/acp" +import { McpCommand } from "./cmd/mcp" +import { TuiThreadCommand } from "./cmd/tui/thread" +import { AttachCommand } from "./cmd/tui/attach" +import { RunCommand } from "./cmd/run" +import { GenerateCommand } from "./cmd/generate" +import { DebugCommand } from "./cmd/debug" +import { AuthCommand } from "./cmd/auth" +import { AgentCommand } from "./cmd/agent" +import { UpgradeCommand } from "./cmd/upgrade" +import { UninstallCommand } from "./cmd/uninstall" +import { ServeCommand } from "./cmd/serve" +import { WebCommand } from "./cmd/web" +import { ModelsCommand } from "./cmd/models" +import { StatsCommand } from "./cmd/stats" +import { ExportCommand } from "./cmd/export" +import { ImportCommand } from "./cmd/import" +import { PrCommand } from "./cmd/pr" +import { SessionCommand } from "./cmd/session" +import { DbCommand } from "./cmd/db" +import { HelpCommand } from "../kilocode/help-command" // kilocode_change + +export const commands = [ + AcpCommand, + McpCommand, + TuiThreadCommand, + AttachCommand, + RunCommand, + GenerateCommand, + DebugCommand, + AuthCommand, + AgentCommand, + UpgradeCommand, + UninstallCommand, + ServeCommand, + WebCommand, + ModelsCommand, + StatsCommand, + ExportCommand, + ImportCommand, + PrCommand, + SessionCommand, + DbCommand, + HelpCommand, // kilocode_change +] diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 825cecfd140..9141de12902 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -1,33 +1,15 @@ import yargs from "yargs" import { hideBin } from "yargs/helpers" -import { RunCommand } from "./cli/cmd/run" -import { GenerateCommand } from "./cli/cmd/generate" +import { commands } from "./cli/commands" // kilocode_change import { Log } from "./util/log" -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 { ModelsCommand } from "./cli/cmd/models" import { UI } from "./cli/ui" import { Installation } from "./installation" import { NamedError } from "@opencode-ai/util/error" import { FormatError } from "./cli/error" -import { ServeCommand } from "./cli/cmd/serve" import { WorkspaceServeCommand } from "./cli/cmd/workspace-serve" import { Filesystem } from "./util/filesystem" -import { DebugCommand } from "./cli/cmd/debug" -import { StatsCommand } from "./cli/cmd/stats" -import { McpCommand } from "./cli/cmd/mcp" // import { GithubCommand } from "./cli/cmd/github" // kilocode_change -import { ExportCommand } from "./cli/cmd/export" -import { ImportCommand } from "./cli/cmd/import" -import { AttachCommand } from "./cli/cmd/tui/attach" -import { TuiThreadCommand } from "./cli/cmd/tui/thread" -import { AcpCommand } from "./cli/cmd/acp" import { EOL } from "os" -import { WebCommand } from "./cli/cmd/web" -import { PrCommand } from "./cli/cmd/pr" -import { SessionCommand } from "./cli/cmd/session" // kilocode_change start - Import telemetry, instance disposal, and legacy migration import { Telemetry } from "@kilocode/kilo-telemetry" import { Instance } from "./project/instance" // kilocode_change @@ -41,12 +23,11 @@ if (!process.env[ENV_FEATURE]) { const isServe = process.argv.includes("serve") process.env[ENV_FEATURE] = isServe ? "unknown" : "cli" } +import { Global } from "./global" import { Config } from "./config/config" import { Auth } from "./auth" // kilocode_change end -import { DbCommand } from "./cli/cmd/db" import path from "path" -import { Global } from "./global" import { JsonMigration } from "./storage/json-migration" import { Database } from "./storage/db" @@ -161,33 +142,19 @@ let cli = yargs(hideBin(process.argv)) }) .usage("\n" + UI.logo()) .completion("completion", "generate shell completion script") - .command(AcpCommand) - .command(McpCommand) - .command(TuiThreadCommand) - .command(AttachCommand) - .command(RunCommand) - .command(GenerateCommand) - .command(DebugCommand) - .command(AuthCommand) - .command(AgentCommand) - .command(UpgradeCommand) - .command(UninstallCommand) - .command(ServeCommand) - .command(WebCommand) - .command(ModelsCommand) - .command(StatsCommand) - .command(ExportCommand) - .command(ImportCommand) - // .command(GithubCommand) // kilocode_change (Disabled until backend is ready) - .command(PrCommand) - .command(SessionCommand) - .command(DbCommand) + +// kilocode_change start - use commands barrel +for (const command of commands) { + cli.command(command as any) +} +// kilocode_change end +// .command(GithubCommand) // kilocode_change (Disabled until backend is ready) if (Installation.isLocal()) { - cli = cli.command(WorkspaceServeCommand) + cli = cli.command(WorkspaceServeCommand as any) } -cli = cli +cli .fail((msg, err) => { if ( msg?.startsWith("Unknown argument") || 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..c33caa54039 --- /dev/null +++ b/packages/opencode/src/kilocode/help-command.ts @@ -0,0 +1,37 @@ +import { cmd } from "../cli/cmd/cmd" +import { generateHelp } from "./help" + +export const HelpCommand = 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) { + process.stdout.write("Usage: kilo help --all Show full CLI reference\n") + process.stdout.write(" kilo help Show help for a specific command\n") + return + } + const output = await generateHelp({ + command: args.command, + all: args.all, + format: args.format as "md" | "text", + }) + process.stdout.write(output + "\n") + }, +}) diff --git a/packages/opencode/src/kilocode/help.ts b/packages/opencode/src/kilocode/help.ts new file mode 100644 index 00000000000..e058293b966 --- /dev/null +++ b/packages/opencode/src/kilocode/help.ts @@ -0,0 +1,226 @@ +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 undefined + return raw.split(/[\s[<]/)[0] +} + +async function getHelpText(name: string, cmd: Cmd): Promise { + const inst = yargs([]).scriptName(`kilo ${name}`).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, cmd: Cmd): Promise> { + if (!cmd.builder || typeof cmd.builder !== "function") return [] + + const inst = yargs([]).scriptName(`kilo ${name}`).wrap(null) + ;(cmd.builder as any)(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, + }) + } + } 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(`## kilo ${section.name}`) + 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 label = section.hidden ? `kilo ${section.name} [internal]` : `kilo ${section.name}` + 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("../cli/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, cmd) + + 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 : "", + }) + } + + rows.push({ + display: "kilo completion", + description: "generate shell completion script", + }) + + 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..da4da50d08d --- /dev/null +++ b/packages/opencode/test/kilocode/help.test.ts @@ -0,0 +1,157 @@ +import { describe, test, expect } from "bun:test" +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") + }) +}) 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` diff --git a/specs/cli-docs-generation.md b/specs/cli-docs-generation.md new file mode 100644 index 00000000000..e3508ec9964 --- /dev/null +++ b/specs/cli-docs-generation.md @@ -0,0 +1,254 @@ +# Auto-Generated CLI Reference Docs + +**Goal:** Use `generateHelp` from `src/kilocode/help.ts` to auto-generate CLI reference documentation, eliminating manual maintenance of the command table in `cli.md` and adding a full detailed reference page. The CLI code becomes the single source of truth for command names, descriptions, and options. + +**Depends on:** `kilo help --all` feature (PR #571 / issue #560). + +--- + +## Problem + +`packages/kilo-docs/pages/code-with-ai/platforms/cli.md` lines 62-83 contain a hand-written command table. It is already stale — lists `kilo github` (disabled), missing `acp` and `help`. Every command add/remove/rename requires a manual docs update that is easy to forget. + +--- + +## Design + +Two generated artifacts, one source of truth: + +1. **Quick reference table** — a Markdoc partial (`cli-commands-table.md`) included in the existing CLI docs page via `{% partial %}`. Replaces the hand-written table. +2. **Full CLI reference page** — a standalone page (`cli-reference.md`) with detailed help for every command and subcommand, generated by `generateHelp({ all: true, format: "md" })`. + +Both are generated by `script/generate.ts` and auto-committed by the existing `generate.yml` workflow on push to `dev`. + +--- + +## Codebase Orientation + +- `script/generate.ts` — root-level generation script. Currently generates SDK only. Runs via `.github/workflows/generate.yml` on push to `dev`, auto-commits changes. +- `script/format.ts` — runs `prettier --write .` after generation. +- `packages/opencode/src/kilocode/help.ts` — existing `generateHelp()` function (from PR #571). Will gain a new `generateCommandTable()` export. +- `packages/kilo-docs/markdoc/partials/` — existing partials directory (contains `install-cli.md`, etc.). +- `packages/kilo-docs/pages/code-with-ai/platforms/cli.md` — CLI docs page with hand-written command table at lines 62-83. Already uses `{% partial file="install-cli.md" /%}`. +- `packages/kilo-docs/lib/nav/code-with-ai.ts` — nav config. Currently has `{ href: "/code-with-ai/platforms/cli", children: "CLI" }`. + +--- + +## Testing Plan + +Add tests to `packages/opencode/test/kilocode/help.test.ts`. + +**Tests to write:** + +1. `generateCommandTable()` returns a string containing a markdown table header (`| Command | Description |`). +2. Table contains rows for known commands (`run`, `auth`, `debug`, `mcp`). +3. Table does NOT contain `$0` — the default command appears as `kilo [project]`. +4. Table contains no ANSI escape sequences. +5. Table skips commands with no `describe` (like `GenerateCommand`). + +--- + +## Task 1: Write failing tests for `generateCommandTable` + +**Files:** + +- Modify: `packages/opencode/test/kilocode/help.test.ts` + +Add a new `describe("generateCommandTable", ...)` block importing `generateCommandTable` from `../../src/kilocode/help`. Tests will fail because the function doesn't exist yet. + +**Verify failure:** + +```bash +bun test test/kilocode/help.test.ts +``` + +--- + +## Task 2: Implement `generateCommandTable` in `src/kilocode/help.ts` + +**Files:** + +- Modify: `packages/opencode/src/kilocode/help.ts` + +**Implementation:** + +Export a new function: + +```ts +export async function generateCommandTable(): Promise +``` + +Logic: + +1. Load commands from the barrel (same as `generateHelp`). +2. For each command, extract the command string and describe string. +3. For `$0 [project]` (TuiThreadCommand), output display name as `kilo [project]` with its describe. +4. Skip commands with no `describe` (e.g. `GenerateCommand`). +5. Add a `kilo completion` row manually at the end (registered via `.completion()`, not `.command()`, so it won't appear in the barrel). +6. Format command strings as inline code: `` `kilo run [message..]` ``. +7. Output a markdown table: + +```markdown +| Command | Description | +| ---------------------- | ----------------------- | +| `kilo [project]` | Start kilo tui | +| `kilo run [message..]` | Run kilo with a message | +| `kilo auth` | Manage credentials | + +... +| `kilo completion` | Generate shell completion script | +``` + +No yargs instance needed — reads `CommandModule.command` and `CommandModule.describe` directly. + +**Run tests:** + +```bash +bun test test/kilocode/help.test.ts +``` + +**Typecheck:** + +```bash +bun run typecheck +``` + +--- + +## Task 3: Create the generation script for docs + +**Files:** + +- Create: `script/generate-cli-docs.ts` +- Modify: `script/generate.ts` + +**Step 1: Create `script/generate-cli-docs.ts`** + +A standalone bun script (`#!/usr/bin/env bun`) that: + +1. Imports `generateHelp` and `generateCommandTable` from `packages/opencode/src/kilocode/help.ts`. +2. Generates the command table and writes it to `packages/kilo-docs/markdoc/partials/cli-commands-table.md`. +3. Generates the full reference with frontmatter and writes it to `packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md`. + +The full reference page should have this structure: + +```markdown +--- +title: "CLI Command Reference" +description: "Complete reference for all Kilo CLI commands and subcommands" +--- + +# CLI Command Reference + + + +{generated content from generateHelp({ all: true, format: "md" })} +``` + +The partial file: + +```markdown + + +{generated table from generateCommandTable()} +``` + +**Step 2: Add to `script/generate.ts`** + +Add one line before the format step: + +```ts +await $`./script/generate-cli-docs.ts` +``` + +Generation order becomes: + +1. SDK build +2. OpenAPI spec generation +3. CLI docs generation (new) +4. Prettier format (existing — formats the generated markdown) + +**Step 3: Verify** + +```bash +./script/generate-cli-docs.ts +cat packages/kilo-docs/markdoc/partials/cli-commands-table.md +head -30 packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md +``` + +--- + +## Task 4: Update the docs pages + +**Files:** + +- Modify: `packages/kilo-docs/pages/code-with-ai/platforms/cli.md` +- Modify: `packages/kilo-docs/lib/nav/code-with-ai.ts` + +**Step 1: Replace the hand-written table in `cli.md`** + +Replace the "### Top-Level CLI Commands" section and its table (lines 59-83) with: + +```markdown +### Top-Level CLI Commands + +{% partial file="cli-commands-table.md" /%} + +For detailed help on every command and subcommand, see the [CLI Command Reference](/code-with-ai/platforms/cli-reference). +``` + +**Step 2: Add nav entry for the reference page** + +In `packages/kilo-docs/lib/nav/code-with-ai.ts`, update the CLI entry: + +```ts +{ + href: "/code-with-ai/platforms/cli", + children: "CLI", + subLinks: [ + { href: "/code-with-ai/platforms/cli-reference", children: "Command Reference" }, + ], +}, +``` + +--- + +## Task 5: Final checks + +**Step 1: Run full generation** + +```bash +./script/generate.ts +``` + +Verify both files are generated and formatted. + +**Step 2: Run tests** + +```bash +bun test test/kilocode/help.test.ts +``` + +**Step 3: Typecheck** + +```bash +bun turbo typecheck +``` + +**Step 4: Verify docs build** + +```bash +bun run --filter @kilocode/kilo-docs build +``` + +--- + +## Implementation Details + +- `generateCommandTable()` does NOT need a yargs instance — reads `CommandModule` fields directly, fast and side-effect-free. +- `generateHelp()` already handles the full reference. No changes to its core logic. +- The `generate.yml` workflow auto-commits — no CI changes needed. Adding/removing a command and merging to `dev` automatically updates the docs. +- `script/format.ts` runs prettier on the whole repo after generation, so generated markdown will be consistently formatted. +- Generated files have `` comments. +- The `help` command itself should appear in the full reference — it's a real user-facing command. +- `kilo completion` is manually added to the table since it's registered via `.completion()` not `.command()`. diff --git a/specs/help-all-command.md b/specs/help-all-command.md new file mode 100644 index 00000000000..d5ee909f743 --- /dev/null +++ b/specs/help-all-command.md @@ -0,0 +1,381 @@ +# `kilo help --all` Implementation Plan + +**Goal:** Add a `kilo help` command that outputs the full CLI reference (all commands and subcommands, including hidden ones) as Markdown or plain text; optionally scoped to a single subsystem with `kilo help `. + +**Architecture:** A new `HelpCommand` yargs `CommandModule` registered in `src/index.ts`. It accepts an optional positional `[command]`, an `--all` flag, and a `--format` flag (`md` | `text`). It programmatically builds child yargs instances for each command by reusing the existing `CommandModule` definitions, calls `getHelp()` on each, strips ANSI codes, and formats as Markdown sections or plain text. Output goes to stdout so it is pipeable (`kilo help --all > REFERENCE.md`). + +**Tech Stack:** yargs 18, TypeScript, Bun + +--- + +## Codebase Orientation + +The repo is a Turborepo + Bun monorepo. All work happens in `packages/opencode/`. + +- Entry point: `packages/opencode/src/index.ts` — builds the yargs `cli` instance and registers all commands. +- Commands live in `packages/opencode/src/cli/cmd/`. Each file exports a yargs `CommandModule` (e.g. `AuthCommand`, `RunCommand`). +- Group commands (those with subcommands) expose their subcommands via a `builder` function that calls `.command()` on the passed yargs instance. Example: `src/cli/cmd/auth.ts` registers `AuthLoginCommand`, `AuthLogoutCommand`, `AuthListCommand` inside its `builder`. +- The `cmd()` helper in `src/cli/cmd/cmd.ts` is just a thin type wrapper — ignore it for this feature. +- Run the CLI locally: `bun run --cwd packages/opencode --conditions=browser src/index.ts ` +- Run tests: `bun test` from `packages/opencode/` (NOT from repo root). +- Run a single test file: `bun test test/kilocode/help.test.ts` from `packages/opencode/`. +- Typecheck: `bun run typecheck` from `packages/opencode/` (uses `tsgo`, not `tsc`). +- This is a fork of opencode. Kilo-specific files in `src/kilocode/` do NOT need `// kilocode_change` markers. Files outside that directory that you modify DO need `// kilocode_change` markers on changed lines. + +--- + +## Testing Plan + +Create `packages/opencode/test/kilocode/help.test.ts`. + +The tests import the formatter logic directly (not via subprocess) and assert on the rendered string output. They exercise the real command tree — no mocks. + +**Tests to write:** + +1. `--all` output contains a Markdown `##` heading for each known top-level command (`run`, `auth`, `debug`, `mcp`, `session`, `agent`). +2. `--all` output contains Markdown `##` headings for known nested subcommands (`kilo auth login`, `kilo auth logout`, `kilo debug config`). +3. `kilo help auth` output contains auth subcommand headings but does NOT contain `run` or `debug` headings. +4. Output contains no ANSI escape sequences (test with `/\x1b\[/.test(output)` === false). +5. Hidden commands are present in `--all` output and their section contains the word `internal` (case-insensitive). +6. `--format text` output does NOT contain Markdown `##` headings or triple-backtick fences. +7. `--format text` output for `--all` still contains each command name. +8. `kilo help nonexistent` throws or prints an error message containing "unknown command". + +NOTE: I will write all tests before I add any implementation behavior. + +--- + +## Task 1: Write the failing tests + +**Files:** + +- Create: `packages/opencode/test/kilocode/help.test.ts` + +**Step 1: Write all tests described in the Testing Plan above.** + +The test file should import a `generateHelp` function that will be created in Task 3. Since it does not exist yet, all tests will fail with an import error. That is expected. + +Use `bun:test` (`import { describe, test, expect } from "bun:test"`). + +Structure: + +``` +import { generateHelp } from "../../src/kilocode/help" + +describe("kilo help --all (markdown)", () => { ... }) +describe("kilo help --all (text)", () => { ... }) +describe("kilo help ", () => { ... }) +describe("edge cases", () => { ... }) +``` + +`generateHelp` signature (design it for testability): + +```ts +generateHelp(options: { + command?: string // undefined = all top-level commands + all?: boolean // if false and no command, callers should use yargs' built-in --help + format?: "md" | "text" // default "md" +}): Promise +``` + +**Step 2: Run to confirm failure** + +```bash +bun test test/kilocode/help.test.ts +``` + +Expected: FAIL — `../../src/kilocode/help` does not exist. + +--- + +## Task 2: Extract command list into a shared barrel + +The `generateHelp` function needs access to all registered commands. Currently they are inlined in `src/index.ts`. Extract them. + +**Files:** + +- Create: `packages/opencode/src/cli/commands.ts` +- Modify: `packages/opencode/src/index.ts` + +**Step 1: Create `packages/opencode/src/cli/commands.ts`** + +Export a `commands` array containing all `CommandModule` objects currently passed to `.command()` in `src/index.ts`. Import each command at the top of the file exactly as `src/index.ts` does today. + +Do NOT include `TuiThreadCommand` if it is truly internal-only and not relevant to user-facing help. Check `src/cli/cmd/tui/thread.ts` — if it has `hidden: true`, still include it (the help formatter will handle hidden commands explicitly). + +Do NOT include `HelpCommand` yet (it will be added in Task 4). + +Mark the file with `// kilocode_change - new file` at the top since it is outside `src/kilocode/`. + +**Step 2: Update `src/index.ts`** + +Replace the `.command(X).command(Y)...` chain with imports from the barrel: + +```ts +import { commands } from "./cli/commands" // kilocode_change +// ... +commands.forEach((c) => cli.command(c)) // kilocode_change +``` + +Keep the `// kilocode_change` markers on any changed lines. + +**Step 3: Verify the CLI still works** + +```bash +bun run --cwd packages/opencode --conditions=browser src/index.ts --help 2>&1 | grep "Commands:" +``` + +Expected: `Commands:` header present, all commands listed as before. + +**Step 4: Typecheck** + +```bash +bun run typecheck +``` + +Expected: no errors. + +--- + +## Task 3: Implement `generateHelp` in `src/kilocode/help.ts` + +**Files:** + +- Create: `packages/opencode/src/kilocode/help.ts` + +**Step 1: Implement `generateHelp`** + +Key implementation points: + +1. **Import the commands barrel** from `../cli/commands`. + +2. **ANSI stripping:** Use `output.replace(/\x1b\[[0-9;]*m/g, "")`. The logo and UI helpers emit ANSI codes aggressively; every `getHelp()` result must be stripped. + +3. **`wrap(null)`:** When constructing child yargs instances, always call `.wrap(null)`. Without this, yargs wraps lines at terminal width (80 chars), which breaks Markdown code blocks and makes text output ugly. + +4. **Do not use the top-level `cli` yargs instance.** Build fresh child yargs instances inside `generateHelp` to avoid side effects. + +5. **Walking the command tree:** + - For each `CommandModule` in the commands list (or just the one matching `command` if scoped): + - Build a fresh yargs instance: `yargs([]).scriptName("kilo").wrap(null)`. + - If the `CommandModule` has a `builder` function, call `builder(instance)` to register subcommands. + - Call `await instance.getHelp()` to get the help string. + - Strip ANSI. + - Record the command name and whether it is hidden (`CommandModule.hidden === true`). + - Recurse: inspect the builder-returned yargs instance to find sub-`CommandModule`s. The cleanest approach is to keep a parallel list of subcommands: for group commands (auth, debug, mcp, session, agent), their `builder` files already import and register named subcommand objects — extract those by reading the source or by calling `instance.getCommandInstance?.()` (yargs internal). **Simpler approach:** For each group command, also call `builder` on a fresh yargs instance and call `.getHelp()` on the result to get the grouped help which lists subcommands; then iterate the known subcommand `CommandModule` objects directly (since they are already imported in the `*Command` files). + + The simplest correct approach is a **two-level walk**: + - Level 1: all top-level commands from the barrel. + - Level 2: for commands that have a `builder`, call `builder(fresh yargs)` and then call `.getHelp()` — this gives subcommand listings. But to get per-subcommand help, you need to build a yargs instance scoped to just that subcommand. + + **Recommended approach:** Create a small helper `getSubcommands(cmd: CommandModule): CommandModule[]`. For the known group commands, this is already available because their source files export the subcommand objects. Add a `subcommands?: CommandModule[]` property to each group command export (or co-locate a `subcommands` export in each group command file). This is cleaner than introspecting yargs internals. + + Actually — the simplest approach that avoids modifying every command file: build a yargs instance, call `builder` to register subcommands, then access `instance.getInternalMethods().getCommandInstance().getCommandHandlers()`. This is yargs internals but works in yargs 18. Verify it works before relying on it: + + ```ts + const inst = yargs([]).scriptName("kilo").wrap(null) + AuthCommand.builder(inst) + const handlers = inst.getInternalMethods().getCommandInstance().getCommandHandlers() + // handlers is a record of command name -> handler descriptor + ``` + + If this works, use it. If not, fall back to co-locating `subcommands` arrays in each group command file. + +6. **Formatting:** + + _Markdown (`--format md`, default):_ + + ``` + ## kilo auth + + ``` + + {stripped help text} + + ``` + + ### kilo auth login + + > **Internal command** — not intended for direct use. + + ``` + + {stripped help text} + + ``` + + ``` + + Top-level commands get `##`, their subcommands get `###`. Hidden commands get the blockquote callout inserted before the code fence. + + _Text (`--format text`):_ + + ``` + ================================================================================ + kilo auth + ================================================================================ + + {stripped help text} + + --- kilo auth login [internal] --- + + {stripped help text} + + ``` + + No Markdown syntax. Hidden commands are noted with `[internal]` in the separator line. + +7. **Scoped help (`command` option set):** Filter the top-level commands list to the one matching `command`. Error with a thrown `Error("unknown command: ")` if not found. Then walk that command's full subtree. + +8. **Suppress the logo:** The top-level yargs instance registers `.usage("\n" + UI.logo())`. Child instances built inside `generateHelp` must NOT register this usage string. Since you are building fresh instances, this is automatic — just don't call `.usage(UI.logo())`. + +**Step 2: Run tests** + +```bash +bun test test/kilocode/help.test.ts +``` + +Expected: most tests PASS. Fix any failures before continuing. + +**Step 3: Typecheck** + +```bash +bun run typecheck +``` + +--- + +## Task 4: Implement `HelpCommand` and register it + +**Files:** + +- Create: `packages/opencode/src/kilocode/help-command.ts` +- Modify: `packages/opencode/src/cli/commands.ts` +- Modify: `packages/opencode/src/index.ts` (only if not using the barrel approach from Task 2) + +**Step 1: Implement `HelpCommand`** + +```ts +// packages/opencode/src/kilocode/help-command.ts +// kilocode_change - new file (in kilocode dir, no marker needed actually — it's in kilocode/) +``` + +The command shape: + +``` +command: "help [command]" +describe: "show help (--all for full reference, --format for output format)" +builder: + positional "command": optional string, describe "command to show help for" + option "all": boolean, default false, describe "show help for all commands" + option "format": string, choices ["md", "text"], default "md", describe "output format" +handler(args): + if not args.all and not args.command: + // no-op: let yargs handle it, or print a usage hint + // simplest: call process.stdout.write(await generateHelp({ all: true, format: args.format })) + // Actually: print a short message directing to --all or + cli.showHelp() // but we don't have cli here — just print to stdout via yargs help + return + const output = await generateHelp({ + command: args.command, + all: args.all, + format: args.format as "md" | "text", + }) + process.stdout.write(output + "\n") +``` + +Note: `kilo help` with no arguments should print the standard top-level help (same as `kilo --help`). The cleanest way: if neither `--all` nor a positional is present, print a short usage message and exit 0. Do not attempt to call yargs internals for this case. + +**Step 2: Add to commands barrel** + +In `packages/opencode/src/cli/commands.ts`, import `HelpCommand` from `../../src/kilocode/help-command` and add it to the `commands` array. + +**Step 3: Smoke test** + +```bash +# Full reference, markdown +bun run --cwd packages/opencode --conditions=browser src/index.ts help --all 2>/dev/null | head -60 + +# Scoped to auth +bun run --cwd packages/opencode --conditions=browser src/index.ts help auth 2>/dev/null + +# Scoped to auth, plain text +bun run --cwd packages/opencode --conditions=browser src/index.ts help auth --format text 2>/dev/null + +# Pipe to file and check line count +bun run --cwd packages/opencode --conditions=browser src/index.ts help --all 2>/dev/null > /tmp/kilo-reference.md && wc -l /tmp/kilo-reference.md + +# Unknown command error +bun run --cwd packages/opencode --conditions=browser src/index.ts help nonexistent 2>/dev/null; echo "exit: $?" +``` + +Expected for unknown command: error message printed, non-zero exit. + +**Step 4: Run all tests** + +```bash +bun test test/kilocode/help.test.ts +``` + +Expected: all PASS. + +**Step 5: Full typecheck** + +```bash +bun run typecheck +``` + +Expected: no errors. + +--- + +## Task 5: Final checks and commit + +**Step 1: Run full test suite** + +```bash +bun test +``` + +Fix any regressions. + +**Step 2: Typecheck** + +```bash +bun turbo typecheck +``` + +**Step 3: Commit** + +```bash +git add packages/opencode/src/kilocode/help.ts \ + packages/opencode/src/kilocode/help-command.ts \ + packages/opencode/src/cli/commands.ts \ + packages/opencode/src/index.ts \ + packages/opencode/test/kilocode/help.test.ts +git commit -m "feat: add kilo help --all command for full CLI reference in markdown or text" +``` + +--- + +**Testing Details:** Tests call `generateHelp()` directly with real command definitions (no mocks) and assert on the rendered string. They verify structural correctness (headings present/absent by scope), ANSI-free output, format differences between `md` and `text`, hidden command annotation, and error handling for unknown commands. This tests actual behavior — not yargs internals or data structures. + +**Implementation Details:** + +- `wrap(null)` on all child yargs instances is mandatory — without it yargs wraps at terminal width. +- `getHelp()` is async (`Promise`); always `await` it. +- ANSI stripping regex `/\x1b\[[0-9;]*m/g` covers all SGR codes emitted by the logo and UI helpers. +- The logo (registered via `.usage("\n" + UI.logo())`) will NOT appear in child instances since you build fresh yargs instances — do not register usage there. +- Yargs 18 internal API `instance.getInternalMethods().getCommandInstance().getCommandHandlers()` can be used to discover registered subcommands at runtime. Verify it works before relying on it; fall back to co-locating `subcommands` arrays in group command files if needed. +- Hidden commands (`hidden: true` on a `CommandModule`) must appear in `--all` output with an explicit `[internal]` / `> **Internal command**` callout. +- `kilo help` (no args, no `--all`) should behave gracefully — print a short usage hint or delegate to yargs' built-in help. Do not error. +- Keep `HelpCommand` out of its own `--all` output, or accept that it appears (it is a valid command). Either is fine — just be consistent. +- The `commands.ts` barrel is a new file in a shared path; mark it `// kilocode_change - new file` at the top. + +**Questions:** + +- Should `kilo help --all --format text` use `---` separators or the `===` rule style? (Plan uses `===`; adjust to taste.) +- Should the `help` command itself appear in its own `--all` output? Probably yes — it's a real command users can discover. +- If `yargs.getInternalMethods().getCommandInstance().getCommandHandlers()` is not reliable, the fallback is adding `subcommands?: CommandModule[]` to each group command export. Confirm which approach works before finalizing Task 3. + +---