diff --git a/.changeset/tui-about-dialog.md b/.changeset/tui-about-dialog.md new file mode 100644 index 000000000000..7fca9a691136 --- /dev/null +++ b/.changeset/tui-about-dialog.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": minor +--- + +Add an About dialog in the TUI accessible from the command palette (under "Kilo") or via the `/about` slash command. Shows version, channel, runtime/platform, config path, project root, connected providers, and default model, with helpful links (docs, GitHub, issues, Discord) and a `c` shortcut to copy diagnostics to the clipboard. diff --git a/packages/kilo-docs/source-links.md b/packages/kilo-docs/source-links.md index 62a00fb5f42a..4b8efaca49e6 100644 --- a/packages/kilo-docs/source-links.md +++ b/packages/kilo-docs/source-links.md @@ -86,6 +86,9 @@ - + +- + - - @@ -120,9 +123,11 @@ - + - + - - diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx new file mode 100644 index 000000000000..ea3409766910 --- /dev/null +++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-about.tsx @@ -0,0 +1,180 @@ +// kilocode_change - new file +import { TextAttributes } from "@opentui/core" +import { createMemo, For } from "solid-js" +import { InstallationVersion, InstallationChannel } from "@opencode-ai/core/installation/version" +import { Global } from "@opencode-ai/core/global" +import { useDialog, type DialogContext } from "@tui/ui/dialog" +import { useTheme } from "@tui/context/theme" +import { useSync } from "@tui/context/sync" +import { useLocal } from "@tui/context/local" +import { useProject } from "@tui/context/project" +import { useClipboard } from "@tui/context/clipboard" +import { useToast } from "@tui/ui/toast" +import { useTuiPaths } from "@tui/context/runtime" +import { abbreviateHome } from "@tui/runtime" +import * as Model from "@tui/util/model" +import { useBindings } from "@tui/keymap" +import { REDACTED_PATH } from "../../../../pii" + +function runtime() { + const rt = (globalThis as { Bun?: { version?: string } }).Bun + return rt?.version ? `Bun ${rt.version}` : `Node ${process.versions.node}` +} + +function truncate(text: string, max: number) { + return text.length > max ? text.slice(0, max - 1) + "\u2026" : text +} + +function pathLabel(raw: string, privacy: boolean, home: string): string { + if (privacy) return REDACTED_PATH + return truncate(abbreviateHome(raw, home), 60) +} + +export function showAboutDialog(dialog: DialogContext) { + dialog.replace(() => ) +} + +function Row(props: { label: string; value: string; theme: ReturnType["theme"]; muted?: boolean }) { + return ( + + + {props.label} + + + {props.value} + + + ) +} + +export function DialogAbout() { + const dialog = useDialog() + const { theme } = useTheme() + const sync = useSync() + const local = useLocal() + const project = useProject() + const clipboard = useClipboard() + const toast = useToast() + const paths = useTuiPaths() + + const privacy = () => + sync.data.config.privacy_mode === true || sync.data.globalConfig.privacy_mode === true + + const providers = () => { + const connected = sync.data.provider_next.connected + return connected.length ? connected.join(", ") : "(none connected)" + } + const defaultModel = createMemo(() => { + const m = local.model.current() + if (m) return `${m.providerID}/${Model.name(sync.data.provider, m.providerID, m.modelID)}` + const cfg = sync.data.config.model + if (!cfg) return "" + const { providerID, modelID } = Model.parse(cfg) + return `${providerID}/${Model.name(sync.data.provider, providerID, modelID)}` + }) + const projectRoot = () => project.data.project.mainDir ?? project.instance.path().directory ?? process.cwd() + + const links = [ + { label: "Docs", url: "https://kilo.ai/docs" }, + { label: "GitHub", url: "https://github.com/Kilo-Org/kilocode" }, + { label: "Issues", url: "https://github.com/Kilo-Org/kilocode/issues" }, + { label: "Discord", url: "https://kilo.ai/discord" }, + ] + + const copy = () => { + if (!clipboard.write) { + toast.show({ variant: "error", message: "Clipboard not available" }) + return + } + const lines = [ + `Kilo CLI ${InstallationVersion} (${InstallationChannel})`, + `Runtime: ${runtime()} (${process.platform}/${process.arch})`, + `Config: ${pathLabel(Global.Path.config, privacy(), paths.home)}`, + `Project: ${pathLabel(projectRoot(), privacy(), paths.home)}`, + `Providers: ${providers()}`, + `Default model: ${defaultModel() || "(none)"}`, + ] + if (privacy()) lines.push("(paths hidden — privacy_mode is on)") + const text = lines.join("\n") + void clipboard.write(text).then( + () => toast.show({ variant: "success", message: "Diagnostics copied" }), + (err: unknown) => + toast.show({ + variant: "error", + message: `Copy failed: ${err instanceof Error ? err.message : String(err)}`, + }), + ) + } + + useBindings(() => ({ + bindings: [ + { key: "escape", desc: "Close about", group: "Dialog", cmd: () => dialog.clear() }, + { key: "return", desc: "Close about", group: "Dialog", cmd: () => dialog.clear() }, + { key: "c", desc: "Copy diagnostics", group: "Dialog", cmd: copy }, + ], + })) + + return ( + + + + About Kilo CLI + + dialog.clear()}> + esc + + + + + + Open source AI coding agent. Access hundreds of models through Kilo Gateway. + + + + + Identity + + + + + + + + Runtime + + + + + + + + + Provider + + + + + + + + Links + + + {(link) => ( + + {" "} + {link.label}: {link.url} + + )} + + + + + + + c copy diagnostics + + + + ) +} diff --git a/packages/opencode/src/kilocode/kilo-commands.tsx b/packages/opencode/src/kilocode/kilo-commands.tsx index 9d0963bc09c2..2558b6fb65d5 100644 --- a/packages/opencode/src/kilocode/kilo-commands.tsx +++ b/packages/opencode/src/kilocode/kilo-commands.tsx @@ -23,6 +23,7 @@ import { DialogIndexing } from "./components/dialog-indexing.js" import { DialogProviderUsage } from "./components/dialog-provider-usage.js" import { indexingEnabled } from "./indexing-feature" import { refreshBalance } from "./balance-refresh" +import { showAboutDialog } from "./cli/cmd/tui/component/dialog-about.js" // These types are OpenCode-internal and imported at runtime type UseSDK = any @@ -340,6 +341,18 @@ export function registerKiloCommands(useSDK: () => UseSDK) { } }, }, + + // /about command + { + name: "kilo.about", + title: "About", + desc: "Show version, environment, and diagnostic info", + category: "Kilo", + slashName: "about", + run: () => { + showAboutDialog(dialog) + }, + }, ].map((command) => ({ namespace: "palette", ...command, diff --git a/packages/opencode/src/kilocode/pii.ts b/packages/opencode/src/kilocode/pii.ts index f3d900215633..dff2941317cf 100644 --- a/packages/opencode/src/kilocode/pii.ts +++ b/packages/opencode/src/kilocode/pii.ts @@ -1 +1,2 @@ export const REDACTED_BALANCE = "•••" +export const REDACTED_PATH = "•••"