Files
Kilo-Org_kilocode/packages/opencode/test/kilocode/help.test.ts
Imanol Maiztegui 9c182afcd0 fix(providers): enforce provider ID matching to isolate custom configurations
Provider plugins (llmgateway, nvidia, openrouter, vercel, zenmux) now
verify the provider ID before applying attribution headers, preventing
custom-defined providers sharing the same endpoint URL from being
mutated by built-in plugin logic.

Additional changes:
- Add auth-v2.json migration path for multi-account store continuity
- Remove obsolete @ai-sdk/xai patch and ConsoleCommand registration
- Add native LLM session recordings for anthropic, openai-oauth, and zen
- Update CLI help snapshots to reflect Kilo branding
- Expand i18n with usage-exceeded dialog strings (it, nl, uk)
2026-06-17 19:11:26 +02:00

251 lines
9.6 KiB
TypeScript

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 { ProvidersCommand } from "../../src/cli/cmd/providers" // kilocode_change — upstream renamed auth → providers
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 { RemoteCommand } from "../../src/cli/cmd/remote"
import { ConfigCommand as ConfigCLICommand } from "../../src/cli/cmd/config"
import { PluginCommand } from "../../src/cli/cmd/plug"
import { DbCommand } from "../../src/cli/cmd/db"
import { HelpCommand } from "../../src/kilocode/help-command"
import { ProfileCommand } from "../../src/kilocode/cli/cmd/profile"
// 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 <url>",
describe: "attach to a running kilo server",
handler() {},
}
// Synthetic entry for the yargs built-in .completion() command
const CompletionStub = {
command: "completion",
describe: "generate shell completion script",
handler() {},
}
const commands = [
AcpCommand,
McpCommand,
TuiStub,
AttachStub,
RunCommand,
GenerateCommand,
DebugCommand,
ProvidersCommand,
AgentCommand,
UpgradeCommand,
UninstallCommand,
ServeCommand,
WebCommand,
ModelsCommand,
StatsCommand,
ExportCommand,
ImportCommand,
PrCommand,
SessionCommand,
RemoteCommand,
DbCommand,
ConfigCLICommand,
PluginCommand,
ProfileCommand,
HelpCommand,
CompletionStub,
] 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", "profile"]) {
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", "profile"]) {
expect(output).toContain(`kilo ${cmd}`)
}
})
})
describe("kilo help <command>", () => {
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("Kilo CLI customizations are wired into index.ts", () => {
const file = (rel: string) => Bun.file(path.resolve(import.meta.dir, rel)).text()
const INDEX = "../../src/index.ts"
const SETUP = "../../src/kilocode/cli/setup.ts"
const BARREL = "../../src/kilocode/commands.ts"
test("CLI is branded `kilo`, not `opencode`", async () => {
const index = await file(INDEX)
expect(index).toContain('.scriptName("kilo")')
expect(index).not.toContain('.scriptName("opencode")')
})
test("index.ts invokes the KiloCli integration points", async () => {
// These thin call-sites are the only wiring between upstream index.ts and the Kilo
// customizations in setup.ts. If a future upstream merge drops them, every Kilo command
// and the telemetry/lifecycle hooks silently disappear, exactly the regression this guards.
const index = await file(INDEX)
expect(index).toContain("KiloCli.register(")
expect(index).toContain("KiloCli.bootstrap(")
expect(index).toContain("KiloCli.shutdown(")
})
test("registers the local Kilo Console instead of the upstream account console", async () => {
const index = await file(INDEX)
const setup = await file(SETUP)
const barrel = await file(BARREL)
expect(setup).toContain("KiloConsoleCommand")
expect(index).not.toContain(".command(ConsoleCommand)")
expect(barrel).not.toContain('from "../cli/cmd/account"')
})
test("every .command() in index.ts has an entry in the commands array", async () => {
const index = await file(INDEX)
const barrel = await file(BARREL)
// 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([])
})
test("every barrel command is registered in index.ts or setup.ts", async () => {
// Reverse direction of the test above: every source-of-truth command must actually be
// runnable. The merge dropped `daemon`/`profile`/`remote`/`config` from index.ts while the
// barrel still listed them, this catches that.
const index = await file(INDEX)
const setup = await file(SETUP)
const barrel = await file(BARREL)
const registered = new Set(
[...index.matchAll(/\.command\((\w+)\)/g), ...setup.matchAll(/\.command\((\w+)\)/g)].map((m) => m[1]!),
)
const arrayMatch = barrel.match(/export const commands\s*=\s*\[([\s\S]*?)\]/)
expect(arrayMatch).toBeTruthy()
// Strip comments first, the array body contains a comment mentioning `AuthCommand`.
const body = arrayMatch![1]!.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "")
const entries = [...body.matchAll(/\b(\w+Command)\b/g)].map((m) => m[1]!)
// Not registered as a bare `.command(Ident)`:
// CompletionCommand - provided by yargs `.completion(...)`
// HelpCommand - registered via createHelpCommand(() => cli)
// (DevSetup/DevAlias enter the array via `...dev`, so they aren't scraped here)
const except = new Set(["CompletionCommand", "HelpCommand"])
const missing = entries.filter((name) => !except.has(name) && !registered.has(name))
expect(missing).toEqual([])
})
})