1 line
3.0 MiB
Plaintext
1 line
3.0 MiB
Plaintext
{"repo": "atomgit-atomcode/atomcode", "files": {"crates/atomcode-config/src/i18n/en.rs": {"858ab6db7d1354e6e0e33168c8aacf28c313d913": {"t": 1785746256.9234304, "n": 84124, "h": "af478de61c0619e3", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Opened its model list; press Ctrl+A to add a model.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n Msg::VisionPreprocessorUnresolvable { model, provider } => format!(\n \"Current model \\\"{}\\\" does not support image input; the configured \\\n vision_preprocessor_provider \\\"{}\\\" does not resolve (check the name \\\n matches a provider/model in your config). Fix the name, or use \\\n /model to switch to a vision-capable model.\",\n model, provider\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to open Rewind)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "271ca9b321176e1f46008ed79d34f08c515e69af": {"t": 1785761152.930359, "n": 84124, "h": "af478de61c0619e3", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Opened its model list; press Ctrl+A to add a model.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n Msg::VisionPreprocessorUnresolvable { model, provider } => format!(\n \"Current model \\\"{}\\\" does not support image input; the configured \\\n vision_preprocessor_provider \\\"{}\\\" does not resolve (check the name \\\n matches a provider/model in your config). Fix the name, or use \\\n /model to switch to a vision-capable model.\",\n model, provider\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to open Rewind)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "a9f457f67562a45b063bb12f6c1192ddc0b7eaf1": {"t": 1785761160.4805698, "n": 83691, "h": "755084c80aba8293", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Opened its model list; press Ctrl+A to add a model.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to open Rewind)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "05143e0ccd2bd3de5715c06bb0c4dc53ad69a197": {"t": 1785761162.0418375, "n": 83684, "h": "17685c3be45375f8", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Add models to it on the Models tab (Ctrl+A).\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to open Rewind)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "25a89874e928dd8bfe6f1c92f67574cacfa85a2d": {"t": 1785761165.5384965, "n": 83687, "h": "8eada3fe1712f43e", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Add models to it on the Models tab (Ctrl+A).\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+A add model · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "cf56254e8d343aa07efae2f903eab81180c489fa": {"t": 1785761168.5642412, "n": 83668, "h": "d1cd9670a32cb7fd", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name } =>\n format!(\"Added account \\\"{name}\\\". Add models to it on the Models tab (Ctrl+A).\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "3c4f5d4d6d446c3643f930d7858c310ac3871f27": {"t": 1785761170.2132463, "n": 83664, "h": "f24a590e08f1fd0b", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Provider panel ──\n Msg::ProviderPanelTabAccounts => \"Accounts\".into(),\n Msg::ProviderPanelTabModels => \"Models\".into(),\n Msg::ProviderPanelEmptyAccounts =>\n \"(No provider accounts yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingAccounts => \"(No matching provider accounts)\".into(),\n Msg::ProviderPanelEmptyModels =>\n \"(No models yet — press Ctrl+A to add one)\".into(),\n Msg::ProviderPanelNoMatchingModels => \"(No matching models)\".into(),\n Msg::ProviderPanelLegacyBadge => \"legacy\".into(),\n Msg::ProviderPanelDefaultBadge => \"default\".into(),\n Msg::ProviderPanelModelCount { count } =>\n format!(\"{count} model{}\", if count == 1 { \"\" } else { \"s\" }).into(),\n Msg::ProviderPanelAccountsHint =>\n \"Filter · ↑↓ select · ↵ models · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelModelsHint =>\n \"Filter · ↑↓ select · ↵ default · Ctrl+A add · Ctrl+E edit · Ctrl+Dx2 delete · Tab switch · Esc close\".into(),\n Msg::ProviderPanelFilteredModelsHint { account } =>\n format!(\"[{account}] · ↑↓ select · ↵ default · Ctrl+E edit · Ctrl+Dx2 delete · Tab all · Esc close\").into(),\n Msg::ProviderPanelModelSaved { model } => format!(\"Saved model \\\"{model}\\\".\").into(),\n Msg::ProviderPanelAddTitle => \"[Add provider account]\".into(),\n Msg::ProviderPanelEditAccountTitle { account } =>\n format!(\"[Edit account {account}]\").into(),\n Msg::ProviderPanelAddModelTitle => \"[Add model]\".into(),\n Msg::ProviderPanelEditModelTitle => \"[Edit model]\".into(),\n Msg::ProviderPanelFieldVendor => \"Provider\".into(),\n Msg::ProviderPanelFieldAccount => \"Account\".into(),\n Msg::ProviderPanelFieldBaseUrl => \"Base URL\".into(),\n Msg::ProviderPanelFieldApiKey => \"API key\".into(),\n Msg::ProviderPanelFieldModel => \"Model\".into(),\n Msg::ProviderPanelFieldWindow => \"Context window\".into(),\n Msg::ProviderPanelFieldMakeDefault => \"Set as default\".into(),\n Msg::ProviderPanelSwitchHint => \"←→ to switch\".into(),\n Msg::ProviderPanelEnvHint { env } => format!(\"blank uses ${env}\").into(),\n Msg::ProviderPanelDefaultValue => \"default\".into(),\n Msg::ProviderPanelKeepOriginal => \"blank keeps current value\".into(),\n Msg::ProviderPanelProviderFormHint =>\n \"Tab Next ←→ Switch provider Space Toggle ↵ Save Esc Back\".into(),\n Msg::ProviderPanelAccountFormHint => \"Tab Switch ↵ Save Esc Back\".into(),\n Msg::ProviderPanelModelFormHint =>\n \"Tab Next ←→ Switch account Space Toggle ↵ Save Esc Back\".into(),\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "8e5f52b8509892e6dc911ade11bbe22c10bd2e3c": {"t": 1785761172.0840542, "n": 80903, "h": "90310fc2926bc9a2", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { accounts, models } =>\n format!(\n \" ✓ Added {} account{} · {} model{}:\\n\",\n accounts,\n if accounts == 1 { \"\" } else { \"s\" },\n models,\n if models == 1 { \"\" } else { \"s\" }\n )\n .into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} · {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "2f77d05520f187fbb7626e81f8c6c462b5b926fe": {"t": 1785761174.2203047, "n": 80708, "h": "aed7b6309cbf238e", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n F2 / Shift+F2 Next / previous model\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "cc80249971b5a5c03a51c50e6333eb4bfff65a70": {"t": 1785761177.0305781, "n": 80649, "h": "7225afd51fb3d3a9", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderUnsupportedBuild =>\n \"This build cannot access the AtomGit gateway. Install an official build or use /provider to switch providers.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "4c585071b3b0c4692897c38c7fbaf896f207d717": {"t": 1785761178.7797902, "n": 80473, "h": "2626a294e5bfcd79", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostTokenReport { prompt, completion, cached, cache_rate, total } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n\",\n prompt, completion, cached, cache_rate, total\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "d8d4d7b4cdd4b5edff232d66cfa025228d0a75ce": {"t": 1785761180.3725264, "n": 80227, "h": "703e4af77792a78a", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderStepPricing =>\n \"Pricing USD per 1M tokens? input,output,cached-input (blank = unknown/keep; `clear` removes; e.g. 2.5,10,0.25; free = 0,0,0)\".into(),\n Msg::ProviderStepPricingWithHint { current } =>\n format!(\"Pricing USD per 1M tokens? [{current}] (blank to keep; `clear` removes)\").into(),\n Msg::ProviderPricingInvalid =>\n \"Pricing must be three finite non-negative numbers: input,output,cached-input.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n Msg::CostFree => \"free\".into(),\n Msg::CostUnknown => \"unknown\".into(),\n Msg::CostUnattributed { tokens } =>\n format!(\"Unattributed legacy usage\\n Total tokens: {}\\n Estimated cost: unknown\", tokens).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "87c732c36ec4113e8d3c34684fb88ecfe147d8cd": {"t": 1785761181.961528, "n": 79496, "h": "88ab4f8925218d94", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::PendingMessagesTitle =>\n \"Messages to be submitted after next tool call (press esc to interrupt and send immediately)\".into(),\n Msg::PendingMessagesNotSent { count } =>\n format!(\"{count} pending message(s) were not sent because the runtime stopped\").into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "2ac9327aff2caaa490276809fc4cccba69c3e249": {"t": 1785761183.477338, "n": 79266, "h": "47ea8939c1fcf05d", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionScopeGlobal => \"User global\".into(),\n Msg::StatusInstructionScopeProject => \"Project shared\".into(),\n Msg::StatusInstructionScopeUser => \"User project override\".into(),\n Msg::StatusInstructionPresent { path, label, scope } =>\n format!(\" ✓ {scope} ({label}): {path}\\n\").into(),\n Msg::StatusInstructionMissing { path, label, scope } =>\n format!(\" × {scope} ({label}): {path} — not found\\n\").into(),\n Msg::StatusMemoryFilesHeader => \" Memory files:\\n\".into(),\n Msg::StatusMemoryScopeGlobal => \"User global\".into(),\n Msg::StatusMemoryScopeProject => \"Project memory\".into(),\n Msg::StatusMemoryPresent { path, scope } =>\n format!(\" ✓ {scope}: {path}\\n\").into(),\n Msg::StatusMemoryMissing { path, scope } =>\n format!(\" × {scope}: {path} — not found\\n\").into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "1d1eb66cabac724e40b5977a733a3ea8a6af77d9": {"t": 1785761184.9628448, "n": 78590, "h": "0a11729e301230e0", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "52a6a106b50728d704ebc53efa438f8e0b32dc1a": {"t": 1785761186.546126, "n": 78455, "h": "3eafc04b82ddfaaf", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n Msg::SkillsLoaded { names } =>\n format!(\" Loaded skills: {}\\n\", names).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "6e7c48585479799d52cccb4d82c9258b47a6bae3": {"t": 1785761191.0551636, "n": 78491, "h": "dce355dd87e52d08", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdCustomArgRequired { name } =>\n format!(\"/{name} requires an argument. Usage: /{name} <your-input>\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "d571afd422d9d4b9a535c17ccf302ad676c1588f": {"t": 1785761192.7458239, "n": 78356, "h": "331911bbd6200e73", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n Msg::SessionBusyForked { source_id, fork_id } =>\n format!(\n \"The latest session ({source_id}) is active in another window. \\\n Created an independent fork ({fork_id}) from its last committed state.\"\n ).into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "a202067522c7e493d27640760dc2371f5d3ae6c6": {"t": 1785761194.6161773, "n": 78086, "h": "70c61de6e84c55b0", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n \\ then Enter Insert newline (works in every terminal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "bc3a5f9f2367bcbf9e09b6c09c554a5380bc28bf": {"t": 1785761196.464072, "n": 78561, "h": "97bb5c2a99a89cf8", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "a28c215857b0e87dba221b4ebebe1d16d00b1fbe": {"t": 1785761198.1006916, "n": 78621, "h": "8d274ec64c744e7e", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model} for this session\\n\").into(),\n Msg::ModelSwitchedAndDefault { provider, model } =>\n format!(\" Switched to {provider} · {model}; set as default for new sessions\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"set the default model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting =>\n \" MCP reload requested. Old MCP tools are withdrawn before reconnecting in the background.\\n\".into(),\n Msg::McpClearedNoServers =>\n \" MCP reload requested. Old MCP tools are withdrawn; no servers are configured.\\n\".into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Reloading MCP capabilities.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel =>\n \"Set the default provider / model and switch this session\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Set default model /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to set the default model\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "036da2caca5bcb22601ca9efc7f0e69f554e4e06": {"t": 1785761199.5834646, "n": 79043, "h": "ac607bb8d015f3e1", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D×2 delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch this session's provider / model\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "90a92733211fea64fece290dd4e8a9a4f1e57bbb": {"t": 1785761201.1893806, "n": 79041, "h": "d190269de20e0f8c", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdSessionListLoading =>\n \" Loading sessions…\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch this session's provider / model\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "be7d1085abf676327b821392d02767c4199192fd": {"t": 1785761202.923231, "n": 78959, "h": "65c7f5e358c44bae", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch this session's provider / model\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory and start a new session\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "0b305583930f5d8af7bc87547a44aab9cb23c1c3": {"t": 1785761204.41125, "n": 78935, "h": "017eb6bfcd4a889b", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Manage providers: add, edit, delete, or set the global default. Press Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"Add\".into(),\n Msg::ProviderMenuAddDesc => \"Create a provider configuration\".into(),\n Msg::ProviderMenuEdit => \"Edit\".into(),\n Msg::ProviderMenuEditDesc => \"Modify an existing provider configuration\".into(),\n Msg::ProviderMenuDelete => \"Delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Delete an existing provider configuration\".into(),\n Msg::ProviderMenuSetDefault => \"Set global default\".into(),\n Msg::ProviderMenuSetDefaultDesc =>\n \"Set the default provider and switch this session\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Deleted \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch this session's provider / model\".into(),\n Msg::CmdDescProvider =>\n \"Manage providers (add / edit / delete / set global default)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "fb430c1aeabae6bddca8f0cb92c93b0984ef74f3": {"t": 1785761206.4992933, "n": 78781, "h": "46bfb17f390392b9", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdDiffTruncated =>\n \" … diff output truncated\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "1681d040ec14fd8bff9a71faba85b33fbdf82cc0": {"t": 1785761208.0822484, "n": 78698, "h": "0954f3e0a8d7727e", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionPickerTitleBare =>\n \"Resume session\".into(),\n Msg::SessionPickerEmptyProject =>\n \"(no sessions in this project yet)\".into(),\n Msg::SessionPickerEmptyFilter =>\n \"(no sessions match)\".into(),\n Msg::SessionPickerEmptyFilterQuery { query } =>\n format!(\"(no sessions match \\\"{query}\\\" — Backspace to clear)\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "859c41e0fdda32e26ea17efb8b732897028244cc": {"t": 1785761209.6694033, "n": 78301, "h": "b1f08701f2916e6c", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens, reason } => {\n let cause = reason.map(|r| format!(\": {r}\")).unwrap_or_default();\n format!(\"✗ Stopped{cause} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into()\n }\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "53d1791863008c1d4126dcf4f8f9a6d028b450a3": {"t": 1785761211.3544762, "n": 78197, "h": "6325fc113bd17380", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"↑↓ move · Enter open · Ctrl+D delete · Type to search · Esc cancel\".into(),\n Msg::SessionPickerTitle { n, total, project } =>\n format!(\"Resume session ({n}/{total} · {project})\").into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens } =>\n format!(\"✗ Stopped · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into(),\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "46b1a87c3120df050c9ae641ab064663c8a6bb3c": {"t": 1785761213.0000718, "n": 78040, "h": "f36f8d17edfbcf77", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"Enter open · F2 rename · Ctrl+D delete\".into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdSessionTransitionPending =>\n \" Runtime is reconfiguring; your input is preserved until it is ready.\\n\".into(),\n Msg::CmdSessionTransitionFailed { error } =>\n format!(\"Session switch failed; the previous session is still active: {error}\").into(),\n Msg::CmdCapabilityReloadFailed { error } =>\n format!(\"Runtime capability reload failed; the previous runtime is still active: {error}\").into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens } =>\n format!(\"✗ Stopped · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into(),\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}, "0a4a5bc434205add79f1d387be9dc255974f719e": {"t": 1785761214.60922, "n": 77585, "h": "93d14d39645e766d", "c": "use super::messages::Msg;\nuse std::borrow::Cow;\n\npub(super) fn en(msg: Msg<'_>) -> Cow<'static, str> {\n match msg {\n Msg::WelcomeBannerLine1 =>\n \"Welcome to AtomCode. Pick an option to get started:\".into(),\n Msg::WelcomeBannerLine2 =>\n \"(↑↓ to navigate, Enter to confirm, Esc to skip)\".into(),\n Msg::WelcomeOptionCodingPlan => \"Set up CodingPlan\".into(),\n Msg::WelcomeOptionCodingPlanHint => \"Free tokens · recommended\".into(),\n Msg::WelcomeOptionConfigureManually => \"Configure manually\".into(),\n Msg::WelcomeOptionConfigureManuallyHint => \"API key\".into(),\n Msg::WelcomeOptionSkip => \"Skip for now\".into(),\n Msg::WelcomeOptionSkipHint => \"explore first\".into(),\n\n // ── /login (full setup flow) ──\n Msg::CodingPlanSetupFailed { error } =>\n format!(\"/login setup failed: {error}\").into(),\n Msg::CpReauthAfter401 =>\n \" ⚠ Stored login expired — re-authenticating...\\n\".into(),\n Msg::ChatAuthExpired =>\n \"Authentication expired — please run /login to sign in again\".into(),\n Msg::NetworkConnectHint =>\n \"Network connect failed. If this works in a browser you may be behind a proxy/firewall: configure a proxy with /proxy or set HTTPS_PROXY, or open the login URL above in a browser to finish. Press Esc to skip and /login later.\".into(),\n Msg::CpSetupHeader =>\n \" AtomCode CodingPlan setup:\\n\\n\".into(),\n Msg::CpLoggedIn { who, username, email } =>\n format!(\" ✓ Logged in as {} ({}, {})\\n\", who, username, email).into(),\n Msg::CpStepSkipped { reason } =>\n format!(\" ✓ {}\\n\", reason).into(),\n Msg::CpLoginFailed { error } =>\n format!(\" × Login failed — {}\\n\", error).into(),\n Msg::CpClaimed { message, plan_type } =>\n format!(\" ✓ CodingPlan claimed — {} (CodingPlan {})\\n\", message, plan_type).into(),\n Msg::CpClaimSuccessFallback => \"success\".into(),\n Msg::CpAlreadyClaimed { reason } =>\n format!(\" ✓ CodingPlan already claimed — {}\\n\", reason).into(),\n Msg::CpClaimFailed { error } =>\n format!(\" × CodingPlan tier setup failed — {}\\n\", error).into(),\n Msg::CpClaimFailedBare =>\n \" × CodingPlan tier setup failed\\n\".into(),\n Msg::CpClaimTierSucceeded { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierAlreadyHeld { plan } =>\n format!(\" ✓ {} active\\n\", plan).into(),\n Msg::CpClaimTierFailed { tier, reason } =>\n format!(\" × CodingPlan {} tier setup failed — {}\\n\", tier, reason).into(),\n Msg::CpAddedProviders { count, plural_s } =>\n format!(\" ✓ Added {} provider{}:\\n\", count, plural_s).into(),\n Msg::CpLocked { name } =>\n // SGR 31 = standard red foreground, SGR 39 = reset to\n // default fg. Standard (not bright) so the terminal's\n // theme palette decides the exact shade — Solarized,\n // Dracula, light-mode, etc. all map this onto their\n // own \"red\" rather than a hard-coded RGB the user can't\n // tune. The `× … (requires Pro plan or higher)` text inside is\n // a redundant signal so retained-mode terminals (which\n // strip SGR via the strict sanitizer path) still get\n // the meaning, just without the colour.\n format!(\" \\x1b[31m× {} (requires Pro plan or higher)\\x1b[39m\\n\", name).into(),\n Msg::CpProviderRow { provider, model, default_suffix } =>\n format!(\" • {} → {}{}\\n\", provider, model, default_suffix).into(),\n Msg::CpDefaultSuffix => \" (default)\".into(),\n Msg::CpVisionAuto { kind } =>\n format!(\" ✓ Vision preprocessor → {} (auto-detected)\\n\", kind).into(),\n Msg::CpVisionUserSupplied { kind } =>\n format!(\" ✓ Vision preprocessor → {} (user setting kept)\\n\", kind).into(),\n Msg::CpVisionCleared =>\n \" ⚠ Vision preprocessor cleared — no VL/OCR model in current list\\n\".into(),\n Msg::CpModelsSkipped { reason } =>\n format!(\" ✓ Models step skipped — {}\\n\", reason).into(),\n Msg::CpModelsFailed { error } =>\n format!(\" × Models step failed — {}\\n\", error).into(),\n Msg::CpStatusHeader =>\n \" ✓ CodingPlan status:\\n\".into(),\n Msg::CpPlanPending { plan } =>\n format!(\" Plan: {} · pending activation\\n\", plan).into(),\n Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" Plan: {} · expires {} ({}d / {}d remaining)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::CpUsageLine { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::CpWindowQuotaExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::CpWindowQuotaHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::CpStatusFetchSkipped { reason } =>\n format!(\" ⚠ Status fetch skipped — {}\\n\", reason).into(),\n Msg::CpStatusFetchFailed { error } =>\n format!(\" ⚠ Status fetch failed (non-fatal) — {}\\n\", error).into(),\n Msg::CpOfficialBuildRequired => Cow::Borrowed(\n \"This feature requires the official AtomCode build. Download it from \\\n https://atomgit.com/atomgit_atomcode/atomcode/releases.\",\n ),\n Msg::CpAuthRequired => Cow::Borrowed(\n \"Not signed in to AtomCode CodingPlan. Run /login to sign in \\\n before sending a request.\",\n ),\n Msg::CpSignStaleClockSkew => Cow::Borrowed(\n \"Request rejected: signed timestamp outside the accepted window. \\\n Please check your system clock (NTP sync) and retry.\",\n ),\n Msg::CpSignReplayPersisted => Cow::Borrowed(\n \"Request was repeatedly flagged as a replay. Please try the command again.\",\n ),\n Msg::CpSignVersionTooOld => Cow::Borrowed(\n \"AtomCode is out of date and no longer compatible with CodingPlan. \\\n Please upgrade AtomCode to continue.\",\n ),\n Msg::CpUpgradeRequired => Cow::Borrowed(\n \"An upgrade is required to continue using CodingPlan. \\\n Please install the latest AtomCode from the official releases.\",\n ),\n\n Msg::ErrUnsupportedLocale { input } =>\n format!(\"unsupported locale: {input}\").into(),\n\n // ── Status bar ──\n Msg::StatusNoProvider =>\n \"no provider · /provider to configure\".into(),\n Msg::StatusRuntimeUnavailable =>\n \"runtime unavailable · restart or inspect the error above\".into(),\n Msg::StatusOfficialBuildRequired =>\n \"CodingPlan needs the official build\".into(),\n Msg::StatusUpgradeHint { version } =>\n format!(\"↑ {version} available · /upgrade\").into(),\n Msg::StatusUpgradeHintPm { version } =>\n format!(\"↑ {version} available · brew upgrade atomcode\").into(),\n Msg::StatusModelNotConfigured =>\n \"(not configured)\".into(),\n Msg::StatusClipboardImageHint =>\n \"Image in clipboard · ctrl+v / ctrl+alt+v to paste\".into(),\n Msg::StatusClipboardImageHintSlash =>\n \"Image in clipboard · /paste\".into(),\n Msg::StatusWebuiHint =>\n \"Tips: Use /webui to open AtomCode in your browser\".into(),\n\n // ── /status command body ──\n Msg::StatusBody { model, dir, config } =>\n format!(\n \" Model: {}\\n Dir: {}\\n Config: {}\\n\",\n model, dir, config,\n ).into(),\n Msg::StatusLoginLoggedIn { user } =>\n format!(\" Login: {}\\n\", user).into(),\n Msg::StatusLoginNotSignedIn =>\n \" Login: not signed in (run /login)\\n\".into(),\n Msg::StatusCpNotSignedIn =>\n \" CodingPlan: (not signed in — run /login to set up)\\n\".into(),\n Msg::StatusCpFetchFailed { error } =>\n format!(\" CodingPlan: (status fetch failed — {})\\n\", error).into(),\n Msg::StatusCpAuthExpired =>\n \" CodingPlan: (login expired — run /login to sign in again)\\n\".into(),\n Msg::StatusCpNoActive =>\n \" CodingPlan: (no active plan — run /login)\\n\".into(),\n Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>\n format!(\n \" CodingPlan: {} · expires {} ({}d/{}d)\\n\",\n plan, expires_at, remaining_days, total_days,\n ).into(),\n Msg::StatusCpUsage { usage, reset_at, duration } =>\n format!(\" Usage: {} · resets {} (in {})\\n\", usage, reset_at, duration).into(),\n Msg::StatusCpWindowExhausted =>\n \" ⚠ Current window quota exhausted\\n\".into(),\n Msg::StatusCpWindowHint { hint } =>\n format!(\" ⚠ {}\\n\", hint).into(),\n Msg::StatusInstructionFilesHeader =>\n \" Instruction files:\\n\".into(),\n Msg::StatusInstructionPresent { path, label } =>\n format!(\" ✓ {} ({})\\n\", path, label).into(),\n Msg::StatusInstructionMissing { label } =>\n format!(\" × {} — not found\\n\", label).into(),\n\n // ── Help ──\n Msg::HelpAvailableCommands =>\n \" Available commands:\\n\".into(),\n Msg::KeybindingsHelp => r#\" Keyboard shortcuts\n\n ── Input ──\n Enter Send message\n Ctrl+J Insert newline (universal)\n \\ then Enter Insert newline (atomcode fallback, universal)\n Alt+Enter Insert newline *\n Shift+Enter Insert newline **\n / Open slash command menu\n Tab Autocomplete\n Backspace / Ctrl+H Delete previous char\n Delete / Ctrl+? Delete next char\n Ctrl+W Delete word backward\n Ctrl+U Clear current line\n Ctrl+K Delete to end of line\n Ctrl+A / Home Jump to line start\n Ctrl+E / End Jump to line end\n Left / Right Move cursor\n\n ── History ──\n Up Previous input\n Down Next input\n\n ── Scrollback ──\n Use the host terminal's native scrollback (cmd+↑/↓, mouse wheel,\n tmux copy-mode — whatever your terminal already provides).\n Drag + Ctrl+C Copy text (atomcode does not capture the mouse)\n\n ── Session ──\n Ctrl+C Cancel current turn / dismiss modal\n Esc Esc Undo the previous turn\n Ctrl+D Exit AtomCode\n Ctrl+L Clear screen\n Ctrl+O Toggle tool real-time output\n Ctrl+V Paste (text + image)\n\n ── Slash menu / modal navigation ──\n Up / Down Move selection\n Enter Confirm\n Esc Cancel / close modal\n Tab Insert highlighted command\n\n * Alt+Enter works in most terminals; macOS Apple Terminal users\n must enable \"Use Option as Meta key\" under Settings → Profiles\n → Keyboard for the keystroke to register as a newline.\n ** Shift+Enter requires a terminal that disambiguates the modifier.\n Known-supported: Kitty / WezTerm / iTerm2 (with Report Modifiers\n enabled) / Windows Terminal / Ghostty / Warp. Other terminals\n (macOS Apple Terminal, default xterm, GNOME Terminal, VS Code's\n integrated terminal) collapse Shift+Enter into plain Enter —\n use Ctrl+J or \\ + Enter instead.\n\n Tip: run /help for the full slash command list.\n\"#.into(),\n\n // ── Provider wizard ──\n Msg::ProviderWizardHeader =>\n \" Provider management — Add / Edit / Delete / Set default. Esc to cancel.\\n\".into(),\n Msg::ProviderWizardCancelled =>\n \"(cancelled)\".into(),\n Msg::ProviderMenuAdd => \"add\".into(),\n Msg::ProviderMenuAddDesc => \"Add a new provider\".into(),\n Msg::ProviderMenuEdit => \"edit\".into(),\n Msg::ProviderMenuEditDesc => \"Edit an existing provider\".into(),\n Msg::ProviderMenuDelete => \"delete\".into(),\n Msg::ProviderMenuDeleteDesc => \"Remove a provider\".into(),\n Msg::ProviderMenuSetDefault => \"set-default\".into(),\n Msg::ProviderMenuSetDefaultDesc => \"Switch the default provider\".into(),\n Msg::ProviderImportPrompt =>\n \"Paste a template to auto-detect (curl / JSON / TOML), or Enter to fill manually:\".into(),\n Msg::ProviderImportParsed { base_url, type_name, model } =>\n format!(\"Detected: {base_url} · {type_name} · {model}\").into(),\n Msg::ProviderImportFailed =>\n \"Not recognized as a template. Paste curl / JSON / TOML, or Enter to fill manually.\".into(),\n Msg::ProviderNoProviders =>\n \"No providers configured yet.\".into(),\n Msg::ProviderDeleteConfirm { name } =>\n format!(\"Delete \\\"{name}\\\"? [y/N]\").into(),\n Msg::ProviderDeleted { name } =>\n format!(\"Removed \\\"{name}\\\".\").into(),\n Msg::ProviderDeleteKept => \"(kept)\".into(),\n Msg::ProviderDefaultSet { name } =>\n format!(\"Default set to {name}.\").into(),\n Msg::ProviderAdded { name, model } =>\n format!(\"Added provider \\\"{name}\\\" and switched to {name} · {model}.\").into(),\n Msg::ProviderUpdated { name } =>\n format!(\"Updated \\\"{name}\\\".\").into(),\n Msg::ProviderStepName => \"Provider name?\".into(),\n Msg::ProviderStepType => \"Type? (openai / claude / ollama)\".into(),\n Msg::ProviderStepTypeWithHint { current } =>\n format!(\"Type? [{current}] (openai / claude / ollama, blank to keep)\").into(),\n Msg::ProviderStepBaseUrl =>\n \"Base URL? (e.g. https://api.deepseek.com/v1)\".into(),\n Msg::ProviderStepBaseUrlWithHint { current } =>\n format!(\"Base URL? [{current}] (blank to keep)\").into(),\n Msg::ProviderDefaultHint => \"provider default\".into(),\n Msg::ProviderStepApiKey =>\n \"API key? (blank to leave unset)\".into(),\n Msg::ProviderStepApiKeyWithHint { hint } =>\n format!(\"API key? [{hint}]\").into(),\n Msg::ProviderStepApiKeySet => \"set — blank to keep\".into(),\n Msg::ProviderStepApiKeyUnset => \"unset\".into(),\n Msg::ProviderStepModel => \"Model?\".into(),\n Msg::ProviderStepModelWithHint { current } =>\n format!(\"Model? [{current}] (blank to keep)\").into(),\n Msg::ProviderStepContextWindow { default } =>\n format!(\"Context window? [{default}] tokens (blank to use default; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderStepContextWindowWithHint { current } =>\n format!(\"Context window? [{current}] tokens (blank to keep; e.g. 128000 / 256000 / 512000 / 1000000, or 128k / 1m)\").into(),\n Msg::ProviderContextWindowInvalid =>\n \"Context window must be a positive number of tokens, e.g. 128000 or 128k.\".into(),\n Msg::ProviderNameEmpty => \"Name cannot be empty.\".into(),\n Msg::ProviderBaseUrlEmpty => \"Base URL cannot be empty.\".into(),\n Msg::ProviderUnknownType =>\n \"Unknown type. Choose openai / claude / ollama.\".into(),\n Msg::ProviderUnknownTypeEdit =>\n \"Unknown type. Choose openai / claude / ollama or leave blank.\".into(),\n Msg::ProviderModelEmpty => \"Model cannot be empty.\".into(),\n Msg::ProviderEditKeep => \"(keep)\".into(),\n Msg::ProviderTypeInferred { type_name } =>\n format!(\"Detected type: {type_name}\").into(),\n Msg::ProviderStepNameDefault { default } =>\n format!(\"Provider name? [{default}] (blank to use this)\").into(),\n Msg::ProviderStepProgress { current, total } =>\n format!(\"({current}/{total})\").into(),\n\n // ── Model picker ──\n Msg::ModelSwitched { provider, model } =>\n format!(\" Switched to {provider} · {model}\\n\").into(),\n\n // ── Session picker ──\n Msg::SessionLoadFailed { error } =>\n format!(\"load session failed: {error}\").into(),\n Msg::SessionResumedLabel { name } =>\n format!(\"resumed: {name}\").into(),\n\n // ── Todo panel ──\n Msg::TodoPanelTitle => \"Todos\".into(),\n Msg::TodoPanelCompleted { n } => format!(\"{n} completed\").into(),\n Msg::TodoPanelMore { n } => format!(\"+{n} more…\").into(),\n\n // ── Approval panel ──\n Msg::ApprovalAllowOnce => \"Allow once\".into(),\n Msg::ApprovalAlwaysAllow { tool } => format!(\"Always allow {tool} (this session)\").into(),\n Msg::ApprovalAlwaysAllowFolder => {\n \"Always allow writes to this folder (this session)\".into()\n }\n Msg::ApprovalAlwaysAllowCommand => \"Always allow this command (this session)\".into(),\n Msg::ApprovalDeny => \"Deny\".into(),\n Msg::ApprovalHint => \"↑↓ select · Enter confirm · Esc cancel\".into(),\n Msg::ApprovalHeader { tool, detail } => {\n if detail.is_empty() {\n format!(\"Allow {tool}?\").into()\n } else {\n format!(\"Allow {tool}({detail})?\").into()\n }\n }\n Msg::ToolDenied => \"denied\".into(),\n\n Msg::CmdSwitchedAutoMode => \" Switched to auto mode (all tools auto-approved).\\n\".into(),\n Msg::CmdSwitchedAcceptEditsMode => {\n \" Switched to accept-edits mode (file edits auto-approved; bash still prompts).\\n\".into()\n }\n\n Msg::SessionTimeJustNow => \"just now\".into(),\n Msg::SessionTimeMinAgo { n } => format!(\"{n}m ago\").into(),\n Msg::SessionTimeHourAgo { n } => format!(\"{n}h ago\").into(),\n Msg::SessionTimeDayAgo { n } => format!(\"{n}d ago\").into(),\n Msg::SessionMsgCount { count } =>\n format!(\"{count} msgs\").into(),\n Msg::SessionNameEmpty =>\n \"Session name cannot be empty\".into(),\n Msg::SessionNameTooLong { max } =>\n format!(\"Session name too long (max {max} characters)\").into(),\n Msg::SessionNameControlChars =>\n \"Session name cannot contain control characters\".into(),\n Msg::SessionListFailed { error } =>\n format!(\"list sessions failed: {error}\").into(),\n Msg::SessionRenamed { old, new } =>\n format!(\" Renamed: '{old}' -> '{new}'\").into(),\n Msg::SessionSaveFailed { error } =>\n format!(\"Failed to save session: {error}. The name was not persisted.\").into(),\n Msg::SessionNoneSelected =>\n \"No session selected\".into(),\n Msg::SessionPickerHint =>\n \"Enter open · F2 rename · Ctrl+D delete\".into(),\n Msg::SessionDeleted { name } =>\n format!(\"\\\"{name}\\\" deleted\").into(),\n Msg::SessionDeleteConfirm { name } =>\n format!(\"Press Ctrl+D again to delete \\\"{name}\\\"\").into(),\n Msg::SessionDeleteFailed { error } =>\n format!(\"Failed to delete session: {error}\").into(),\n Msg::SessionRenameEditing { buffer } =>\n format!(\"> {buffer}_ [Enter: confirm, Esc: cancel]\").into(),\n\n // ── Dir picker ──\n Msg::DirCurrent => \"current\".into(),\n Msg::DirNotExists { path } =>\n format!(\"directory no longer exists: {path}\").into(),\n Msg::DirChanged { path } =>\n format!(\" Changed to: {path}\\n\").into(),\n Msg::DirNotADirectory { path } =>\n format!(\"Not a directory: {path}\").into(),\n\n // ── Language ──\n Msg::LanguageSwitched { label, locale } =>\n format!(\" ✓ Language switched to {label} ({locale}).\\n\").into(),\n\n // ── Idle / onboarding hints ──\n Msg::IdleHintPrefix =>\n \"type something, or press \".into(),\n Msg::IdleHintSlash => \"/\".into(),\n Msg::IdleHintSuffix =>\n \" to browse commands\".into(),\n Msg::IdleHintFull =>\n \"type something, or press / to browse commands\".into(),\n Msg::IdleHintProvider => \"/provider\".into(),\n Msg::IdleHintProviderSuffix =>\n \"to add a custom model\".into(),\n Msg::IdleHintProviderFull =>\n \"/provider to add a custom model\".into(),\n Msg::IdleHintCodingplan => \"/login\".into(),\n Msg::IdleHintCodingplanSuffix =>\n \"to claim a free token quota\".into(),\n Msg::IdleHintCodingplanFull =>\n \"/login to claim a free token quota\".into(),\n Msg::IdleHintWebui => \"/webui\".into(),\n Msg::IdleHintWebuiSuffix =>\n \"open a synced session in the browser\".into(),\n Msg::IdleHintWebuiFull =>\n \"/webui open a synced session in the browser\".into(),\n\n // ── Welcome screen tips ──\n Msg::WelcomeTipsHeading => \"Tips for getting started\".into(),\n Msg::WelcomeTipLogin => \"claim a free token quota\".into(),\n Msg::WelcomeTipProvider => \"add a custom model\".into(),\n Msg::WelcomeTipModel => \"switch the active model\".into(),\n Msg::WelcomeTipResume => \"resume your last session\".into(),\n Msg::WelcomeTipSetup => \"one-shot recommended setup\".into(),\n Msg::WelcomeTipSkills => \"browse available skills\".into(),\n Msg::WelcomeTipPlugin => \"install skill/command plugins\".into(),\n Msg::WelcomeTipWebui => \"open a synced session in the browser\".into(),\n Msg::WelcomeTipMcp => \"connect MCP tools\".into(),\n Msg::WelcomeTipPlan => \"read-only planning mode\".into(),\n Msg::WelcomeTipSession => \"manage & switch sessions\".into(),\n Msg::WelcomeTipLoop => \"run a prompt on a recurring loop\".into(),\n Msg::WelcomeTipGoal => \"set a goal for the session\".into(),\n Msg::WelcomeTipInit => \"scan the codebase into AGENTS.md\".into(),\n Msg::WelcomeTipLanguage => \"switch the UI language\".into(),\n Msg::WelcomeTipUsage => \"view token usage & quota\".into(),\n\n // ── Slash commands ──\n Msg::CmdSwitchedPlanMode =>\n \" Switched to Plan mode (read-only exploration).\\n\".into(),\n Msg::CmdSwitchedBuildMode =>\n \" Switched to Build mode (full execution).\\n\".into(),\n Msg::CmdNewSession =>\n \" New session started.\\n\".into(),\n Msg::CmdNoProviders =>\n \" No providers configured.\\n\".into(),\n Msg::CmdNoSessions =>\n \" No previous sessions found. Start a conversation first.\\n\".into(),\n Msg::CmdUnknownCommand { name } =>\n format!(\"Unknown command: /{name}\").into(),\n Msg::CmdLoginFailed { error } =>\n format!(\"login failed: {error}\").into(),\n Msg::CmdLogoutDone =>\n \" Signed out of AtomGit. Permissions refreshed.\\n\".into(),\n Msg::CmdLogoutFailed { error } =>\n format!(\"logout failed: {error}\").into(),\n Msg::CmdWhoamiNotSignedIn =>\n \" Not signed in. Use /login to authenticate.\\n\".into(),\n Msg::CmdReloadDone { provider, model } =>\n format!(\" Config reloaded. Active: {provider} · {model}\\n\").into(),\n Msg::CmdReloadFailed { error } =>\n format!(\"reload failed: {error} (kept previous config)\").into(),\n Msg::CmdUndoNotSupported =>\n \" Undo is not yet supported.\\n\".into(),\n Msg::CmdUndoDone { target, last } =>\n format!(\" ↩ Rolled back to before turn {target} (removed turns {target}–{last}). Your prompt is back in the input box.\\n\").into(),\n Msg::CmdUndoDiskWarning =>\n \" ⚠ Only conversation memory was rolled back — files on disk were NOT restored. Use /diff to review.\\n\".into(),\n Msg::CmdUndoNoTurns =>\n \" Nothing to undo (no prompts yet).\\n\".into(),\n Msg::CmdUndoOutOfRange { requested, available } =>\n format!(\" Invalid turn {requested} (conversation has {available} turn(s)).\\n\").into(),\n Msg::CmdUndoBusy =>\n \" Can't undo while the agent is working — press Esc to cancel first.\\n\".into(),\n Msg::CmdUndoBadArg =>\n \" Usage: /undo or /undo N (N = turn number).\\n\".into(),\n Msg::CmdNoChanges =>\n \" (no changes)\\n\".into(),\n Msg::CmdCheckingUpdate =>\n \" Checking for updates...\\n\".into(),\n Msg::CmdNoActiveProvider =>\n \"No active provider configured. Use /provider to add one.\".into(),\n Msg::CmdProviderUnavailable =>\n \"Provider is unavailable. Use /login to sign in or /provider to configure one.\".into(),\n Msg::CmdProviderReloading =>\n \"Provider/model is switching. Send after the switch completes.\".into(),\n Msg::SubmitHeldUntilProviderReady =>\n \" ↳ provider not ready yet — message queued, will send automatically once ready\\n\".into(),\n Msg::SubmitHeldUntilLogin =>\n \" ↳ not signed in — message queued; run /login and it will send automatically\\n\".into(),\n\n // ── Approval prompt ──\n Msg::ApprovalPromptAlt { tool, detail } =>\n format!(\"Allow {}({})? [Y]es=Enter / [N]o / [A]lways\", tool, detail).into(),\n Msg::ApprovalWaitingLabel =>\n \"▶ Waiting for approval: \".into(),\n Msg::ApprovalAllow => \" Allow \".into(),\n Msg::ApprovalAlways => \" Always \".into(),\n\n // ── Cancelled / Error prefix ──\n Msg::Cancelled => \"(cancelled)\".into(),\n Msg::ErrorPrefix { msg } =>\n format!(\"[Error: {msg}]\").into(),\n\n // ── Upgrade ──\n Msg::UpgradeSuccess { from, to } =>\n format!(\" ✓ Upgraded {} → {}\\n\", from, to).into(),\n Msg::UpgradeManifestFetched { version } =>\n format!(\" Latest version: {}\\n\", version).into(),\n Msg::UpgradeDownloading { pct, bytes, total } =>\n format!(\" Downloading {}% ({} / {} bytes)\\n\", pct, bytes, total).into(),\n Msg::UpgradeVerifying =>\n \" Verifying SHA256\\n\".into(),\n Msg::UpgradeReplacing =>\n \" Replacing binary\\n\".into(),\n Msg::UpgradeDone { version, backup } =>\n format!(\"\\n✓ Upgraded to {} (previous version kept at {})\\n Restarting new version...\\n\", version, backup).into(),\n Msg::UpgradeAlreadyLatest { current, latest } =>\n format!(\n \" ✓ Already on the latest version. already on {} (latest is {}). Pass --force to reinstall.\\n\",\n current, latest\n ).into(),\n Msg::UpgradeFailed { error } =>\n format!(\"Upgrade failed: {}\", error).into(),\n Msg::UpgradeRolledBack { exe, backup } =>\n format!(\"\\n✓ Rolled back. Current binary: {}; other version saved at {}\\n Restarting rolled-back version...\\n\", exe, backup).into(),\n\n // ── Terminal keyboard hints ──\n Msg::KbdHintMacos =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n Msg::KbdHintOther =>\n \" ⚠ Terminal does not support enhanced keyboard protocol.\\n Use Alt+Enter or Ctrl+Enter for newline (Shift+Enter won't work).\\n\\n\".into(),\n\n // ── /config ──\n Msg::ConfigProviderLabel { provider, path } =>\n format!(\" Provider: {}\\n Config: {}\\n\\n\", provider, path).into(),\n\n // ── /cost ──\n Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>\n format!(\n \" Prompt tokens: {}\\n Completion tokens: {}\\n Cached tokens: {} ({}% hit rate)\\n Total tokens: {}\\n Estimated cost: {}\\n\",\n prompt, completion, cached, cache_rate, total, cost\n ).into(),\n\n // ── /think ──\n Msg::ThinkStatus { status, budget, provider } =>\n format!(\n \" Extended thinking: {}\\n Budget: {} tokens\\n Provider: {}\\n\\n Usage: /think on | off | budget <N>\\n\",\n status, budget, provider\n ).into(),\n Msg::ThinkEnabled { budget } =>\n format!(\" Extended thinking enabled (budget: {} tokens).\\n\", budget).into(),\n Msg::ThinkDisabled =>\n \" Extended thinking disabled.\\n\".into(),\n Msg::ThinkBudgetSet { n } =>\n format!(\" Thinking budget set to {} tokens.\\n\", n).into(),\n Msg::ThinkBudgetTooSmall { n } =>\n format!(\"Budget must be >= 1024 (got {})\", n).into(),\n Msg::ThinkBudgetUsage =>\n \"Usage: /think budget <number>\".into(),\n Msg::ThinkUsage =>\n \" Usage: /think [on | off | budget <N>]\\n\".into(),\n\n // ── /remember, /forget ──\n Msg::RememberUsage =>\n \"Usage: /remember <fact to remember> (--global for global scope)\".into(),\n Msg::ForgetUsage =>\n \"Usage: /forget <keyword>\".into(),\n\n // ── /background ──\n Msg::BackgroundUsage =>\n \" Usage: /background <task description>\\n\".into(),\n\n // ── /init ──\n Msg::InitKickoff =>\n \" Analyzing the project and generating AGENTS.md…\\n\".into(),\n\n // ── /cd ──\n Msg::CdWorkingDir { cwd } =>\n format!(\" Working directory: {}\\n No recent projects. Use `/cd <path>` to switch.\\n\", cwd).into(),\n\n // ── /diff ──\n Msg::DiffFailed { error } =>\n format!(\"git diff failed: {}\", error).into(),\n\n // ── /upgrade ──\n Msg::UpgradePackageManaged =>\n \"This build is managed by HarmonyBrew. Run `brew upgrade atomcode` to upgrade.\".into(),\n Msg::UpgradeUnknownArg { arg } =>\n format!(\"unknown /upgrade argument: {}\\n usage: /upgrade [rollback|--force]\", arg).into(),\n\n // ── /skills ──\n Msg::SkillsNone =>\n \" No user-invocable skills loaded.\\n\".into(),\n Msg::SkillsAvailable =>\n \" Available skills:\\n\".into(),\n Msg::SkillUnknown { name } =>\n format!(\"Unknown skill: {} (try /skills to list)\", name).into(),\n\n // ── /mcp ──\n Msg::McpReloading { count } =>\n format!(\" Reloading MCP servers... ({} configured)\\n\", count).into(),\n Msg::McpConnecting =>\n \" Connecting:\\n\".into(),\n Msg::McpConnectingServer { name } =>\n format!(\" - {} connecting...\\n\", name).into(),\n Msg::McpNoServersConfigured =>\n \" No MCP servers configured.\\n\".into(),\n Msg::McpClearedReconnecting { removed } =>\n format!(\" ✓ Cleared {} MCP tools. Reconnecting in background...\\n\", removed).into(),\n Msg::McpClearedNoServers { removed } =>\n format!(\" ✓ Cleared {} MCP tools. No servers to connect.\\n\", removed).into(),\n Msg::McpToolsUsage =>\n \" Usage: /mcp tools <server>\\n Example: /mcp tools filesystem\\n\".into(),\n Msg::McpToolsListing { server } =>\n format!(\" Listing MCP tools for '{}'...\\n\", server).into(),\n Msg::McpNoRegistry =>\n \" No MCP registry loaded. Run /mcp reload first.\\n\".into(),\n Msg::McpServersHeader =>\n \" MCP Servers:\\n\".into(),\n Msg::McpReloadFailed { error } =>\n format!(\"mcp reload failed: failed to load .mcp.json / $ATOMCODE_HOME/mcp.json: {:#}\", error).into(),\n // /mcp login / logout\n Msg::McpOAuthLoginUsage =>\n \" Usage: /mcp login <server>\\n Example: /mcp login github\\n\".into(),\n Msg::McpOAuthLogoutUsage =>\n \" Usage: /mcp logout <server>\\n Example: /mcp logout github\\n\".into(),\n Msg::McpOAuthLoadConfigFailed { error } =>\n format!(\" MCP OAuth login failed to load config: {error}\\n\").into(),\n Msg::McpOAuthServerNotFound { server } =>\n format!(\" MCP OAuth login failed: server '{server}' not found in config.\\n\").into(),\n Msg::McpOAuthStarting { server } =>\n format!(\" Starting MCP OAuth for '{server}' in your browser...\\n\").into(),\n Msg::McpOAuthSaved { provider, server } =>\n format!(\" Saved {provider} OAuth token for MCP server '{server}'. Run /mcp reload to connect.\\n\").into(),\n Msg::McpOAuthFailed { error } =>\n format!(\" MCP OAuth failed: {error}\\n\").into(),\n Msg::McpOAuthTokenRemoved { server } =>\n format!(\" Removed saved OAuth token for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthNoToken { server } =>\n format!(\" No saved OAuth token found for MCP server '{server}'.\\n\").into(),\n Msg::McpOAuthLogoutFailed { error } =>\n format!(\" MCP OAuth logout failed: {error}\\n\").into(),\n // MCP / LSP server connect feedback\n Msg::McpServerConnected { name } =>\n format!(\"✓ MCP server '{name}' connected\").into(),\n Msg::McpServerFailed { name, error } =>\n format!(\"× MCP server '{name}' failed: {error}\").into(),\n Msg::McpProjectTrusted =>\n \" Project trusted — reloading MCP servers.\\n\".into(),\n Msg::McpProjectUntrusted =>\n \" Project trust revoked.\\n\".into(),\n Msg::McpProjectNotTrusted =>\n \" This project was not trusted.\\n\".into(),\n Msg::McpBlockedUntrusted =>\n \"⚠ Blocked MCP server(s) from an untrusted project. Run /mcp trust to allow, then /mcp reload.\\n\".into(),\n Msg::LspServerStarted { name, ext } =>\n format!(\"✓ LSP server '{name}' started for .{ext}\").into(),\n Msg::LspServerFailed { name, ext, error } =>\n format!(\"× LSP server '{name}' for .{ext} failed: {error}\").into(),\n\n // ── /worktree ──\n Msg::WorktreeUsage =>\n \" Usage:\\n /worktree create <branch> [base] Create worktree and switch\\n /worktree list List all worktrees\\n /worktree done Switch back to original directory\\n /worktree cleanup <branch> Clean up worktree\\n\".into(),\n Msg::WorktreeCreateUsage =>\n \" Usage: /worktree create <branch> [base]\\n Example: /worktree create fix-bug main\\n\".into(),\n Msg::WorktreeCreated { branch, base, path } =>\n format!(\" ✓ Worktree created\\n Branch: {} (based on {})\\n Path: {}\\n Working directory switched\\n\", branch, base, path).into(),\n Msg::WorktreeCreateFailed { error } =>\n format!(\"worktree create failed: {}\", error).into(),\n Msg::WorktreeNoActive =>\n \" No active worktrees.\\n\".into(),\n Msg::WorktreeListFailed { error } =>\n format!(\"worktree list failed: {}\", error).into(),\n Msg::WorktreeActiveHeader =>\n \" Active worktrees:\\n\".into(),\n Msg::WorktreeHasChanges => \"(has changes)\".into(),\n Msg::WorktreeClean => \"(clean)\".into(),\n Msg::WorktreeCurrent => \" ← current\".into(),\n Msg::WorktreeDoneBack { path } =>\n format!(\" ✓ Switched back to: {}\\n\", path).into(),\n Msg::WorktreeDoneMergeHint { branch } =>\n format!(\" Hint: use 'git merge {}' or create a PR to merge into main branch\\n\", branch).into(),\n Msg::WorktreeNoSession =>\n \" No active worktree session. Use /worktree create first.\\n\".into(),\n Msg::WorktreeCleanupUsage =>\n \" Usage: /worktree cleanup <branch> [--force]\\n\".into(),\n Msg::WorktreeCleaned { branch } =>\n format!(\" ✓ Worktree '{}' cleaned up\\n\", branch).into(),\n Msg::WorktreeCleanedSwitched { path } =>\n format!(\" Switched back to: {}\\n\", path).into(),\n Msg::WorktreeCleanupUncommitted { branch } =>\n format!(\" ⚠ Worktree '{}' has uncommitted changes.\\n Use /worktree cleanup {} --force to force cleanup\\n\", branch, branch).into(),\n Msg::WorktreeCleanupFailed { error } =>\n format!(\"worktree cleanup failed: {}\", error).into(),\n\n // ── /help commands (custom) ──\n Msg::HelpCustomCommandsHeader =>\n \" Custom commands:\\n\".into(),\n Msg::HelpCustomNone =>\n \" (none)\\n\\n\".into(),\n Msg::HelpCustomCreateHint =>\n \" Create: ~/.atomcode/commands/<name>.md or .atomcode/commands/<name>.md\\n\".into(),\n Msg::HelpSourceGlobal => \"global\".into(),\n Msg::HelpSourceProject => \"project\".into(),\n\n // ── /setup ──\n Msg::SetupHeader { installed, skipped, failed, duration_ms } =>\n format!(\"\\n✅ Setup complete — {} installed, {} skipped, {} failed · {}ms\\n\\n\", installed, skipped, failed, duration_ms).into(),\n Msg::SetupInstalledLabel =>\n \"Installed:\\n\".into(),\n Msg::SetupSkippedLabel =>\n \"\\nSkipped:\\n\".into(),\n Msg::SetupFailedLabel =>\n \"\\nFailed:\\n\".into(),\n Msg::SetupInstalledRow { kind, slug, path } =>\n format!(\" ✓ {}:{} → {}\\n\", kind, slug, path).into(),\n Msg::SetupSkippedRow { kind, slug, reason } =>\n format!(\" - {}:{} ({:?})\\n\", kind, slug, reason).into(),\n Msg::SetupFailedRow { kind, slug, error } =>\n format!(\" × {}:{} — {}\\n\", kind, slug, error).into(),\n Msg::CmdSetupTip =>\n // No leading emoji: U+1F4A1 has terminal/font-dependent display\n // width (1 vs 2 cells), which desynced this line's cell layout\n // on some terminals (garbled \"TTip:RRun…\" over SSH). ASCII-only\n // prefix keeps the width unambiguous.\n \"Tip: Run \\x1b[1;96m/setup\\x1b[0m to auto-configure hooks, skills, and MCP for this project.\".into(),\n Msg::CmdSetupRunning =>\n \"Running atomcode setup...\".into(),\n Msg::CmdSetupSkillsReloaded { count } =>\n format!(\" 🔄 Skills reloaded — {} available\", count).into(),\n Msg::CmdSetupError { error } =>\n format!(\"setup error: {error}\").into(),\n Msg::CmdSetupRunningSkill =>\n \" 🚀 Running setup skill — analyzing project and generating recommendations...\".into(),\n Msg::CmdSetupSkillMissing =>\n \"setup skill not found — try running /setup again to reinstall\".into(),\n\n // ── /plugin ──\n Msg::PluginUsage =>\n \"usage: /plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | reload | list]\".into(),\n Msg::PluginMarketplaceUsage =>\n \"usage: /plugin marketplace [add|remove|update|list] <args>\".into(),\n Msg::PluginInstallUsage =>\n \"usage: /plugin install <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginInstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` not found in any marketplace. Use /plugin marketplace list to see registered marketplaces.\").into(),\n Msg::PluginInstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` exists in multiple marketplaces, please specify one:\").into(),\n Msg::PluginUninstallUsage =>\n \"usage: /plugin uninstall <plugin> or <plugin>@<marketplace>\".into(),\n Msg::PluginUninstallNotFound { plugin } =>\n format!(\"plugin `{plugin}` is not installed. Use /plugin list to see installed plugins.\").into(),\n Msg::PluginUninstallAmbiguous { plugin } =>\n format!(\"plugin `{plugin}` is installed from multiple marketplaces, please specify:\\n\").into(),\n Msg::PluginNoMarketplaces =>\n \"no marketplaces registered\".into(),\n Msg::PluginMarketplacesHeader =>\n \"registered marketplaces:\".into(),\n Msg::PluginNoInstalled =>\n \"no installed plugins\".into(),\n Msg::PluginInstalledHeader =>\n \"installed plugins:\".into(),\n Msg::PluginMarketplaceCloning { url } =>\n format!(\"cloning marketplace from {url}…\").into(),\n Msg::PluginMarketplaceRemoved { name } =>\n format!(\"marketplace `{name}` removed\").into(),\n Msg::PluginMarketplaceRemoveFailed { error } =>\n format!(\"remove marketplace: {error}\").into(),\n Msg::PluginMarketplaceUpdating { name } =>\n format!(\"updating marketplace `{name}`…\").into(),\n Msg::PluginMarketplaceListFailed { error } =>\n format!(\"list marketplaces: {error}\").into(),\n Msg::PluginAutoUpdateSkipped { detail } =>\n format!(\"Marketplace sync skipped (chat unaffected): {detail}\").into(),\n Msg::OfflineModeActive =>\n \"Offline mode: web tools, telemetry, and auto-update are disabled.\".into(),\n Msg::PluginHooksUntrusted { count, names } => format!(\n \"{count} plugin(s) ship untrusted hooks ({names}) — they won't run. Trust: atomcode plugin trust <name>\"\n ).into(),\n Msg::PluginInstalling { plugin, marketplace } =>\n format!(\"installing `{plugin}@{marketplace}`…\").into(),\n Msg::PluginInstallingByName { plugin } =>\n format!(\"installing `{plugin}`…\").into(),\n Msg::PluginAlreadyInstalled { id } =>\n format!(\" plugin `{id}` is already installed.\\n PS: To reinstall, first run `/plugin uninstall {id}` then `/plugin install {id}`\\n\").into(),\n Msg::PluginMgrBrowse => \"Browse & install\".into(),\n Msg::PluginMgrAdd => \"Add marketplace…\".into(),\n Msg::PluginMgrRemove => \"Remove marketplace…\".into(),\n Msg::PluginMgrInstalled { count } => format!(\"Installed ({count})\").into(),\n Msg::PluginMgrInstalledMark => \"✓ installed\".into(),\n Msg::PluginMgrInstalledStatus => \"installed\".into(),\n Msg::PluginMgrInstallableStatus => \"can be installed\".into(),\n Msg::PluginMgrInstallingStatus => \"installing\".into(),\n Msg::PluginMgrUpdatingStatus => \"updating\".into(),\n Msg::PluginMgrHintNav => \"↑/↓ select · ⏎ open · esc back\".into(),\n Msg::PluginMgrHintToggle => \"⏎ install/uninstall · esc back\".into(),\n Msg::PluginMgrHintRemove => \"⏎ remove · esc back\".into(),\n Msg::PluginMgrHintUninstall => \"⏎ uninstall · esc back\".into(),\n Msg::PluginMgrHintUrl => \"Enter to add · Esc to cancel\".into(),\nMsg::PluginMgrHintPending => \"Installing, please wait… · esc back\".into(),\nMsg::PluginMgrHintUpdating => \"Updating, please wait… · esc back\".into(),\nMsg::PluginMgrInstallingLabel => \"Installing…\".into(),\n Msg::PluginMgrEmptyMarketplaces => \"No marketplaces. Pick “Add marketplace…” · esc back\".into(),\n Msg::PluginMgrEmptyPlugins => \"No plugins in this marketplace · esc back\".into(),\n Msg::PluginMgrEmptyInstalled => \"No plugins installed · esc back\".into(),\n Msg::PluginMgrCloning => \"Cloning marketplace…\".into(),\n Msg::PluginMgrInstalling { plugin } => format!(\"Installing {plugin}…\").into(),\n Msg::PluginMgrUpdating { plugin } => format!(\"Updating {plugin}…\").into(),\n Msg::PluginMgrEscToCancel => \"Esc to cancel\".into(),\n Msg::PluginMgrRemoveMarketplaceTitle => \" ◆ Remove Marketplace\".into(),\n Msg::PluginMgrRemoveMarketplacePrompt { name } => format!(\" \\x1b[33mAre you sure you want to remove marketplace '{name}'?\\x1b[39m\").into(),\n Msg::PluginMgrRemoveMarketplaceYes => \"Yes, remove\".into(),\n Msg::PluginMgrRemoveMarketplaceNo => \"No, keep\".into(),\n Msg::PluginMgrRemoveMarketplaceHint => \"↑/↓ select · ⏎ confirm · esc cancel\".into(),\n Msg::PluginScopeUser => \"Install for you (user scope)\".into(),\n Msg::PluginScopeUserDesc => \"~/.atomcode/plugins — all projects\".into(),\n Msg::PluginScopeProject => \"Install for all collaborators (project scope)\".into(),\n Msg::PluginScopeProjectDesc => \".atomcode/plugins — shared via git\".into(),\n Msg::PluginScopeLocal => \"Install for you, in this repo only (local scope)\".into(),\n Msg::PluginScopeLocalDesc => \".atomcode/plugins/local — not committed\".into(),\n Msg::PluginScopeHint => \"↑↓ Select scope · Enter confirm · Esc back\".into(),\n Msg::PluginScopeUserShort => \"user\".into(),\n Msg::PluginScopeProjectShort => \"project\".into(),\n Msg::PluginScopeLocalShort => \"local\".into(),\n Msg::PluginActionUninstall => \"Uninstall\".into(),\n Msg::PluginActionUninstallDesc => \"Uninstall all components and settings\".into(),\n Msg::PluginActionUpdate => \"Update\".into(),\n Msg::PluginActionUpdateDesc => \"Reinstall / Upgrade to latest version\".into(),\n Msg::PluginActionDisable => \"Disable\".into(),\n Msg::PluginActionDisableDesc => \"Temporarily disable this plugin\".into(),\n Msg::PluginActionBack => \"Back to parent\".into(),\n Msg::PluginActionBackDesc => \"Return to the installed list\".into(),\n Msg::PluginUninstalled { plugin, marketplace } =>\n format!(\"uninstalled `{plugin}@{marketplace}`\").into(),\n Msg::PluginUninstallFailed { error } =>\n format!(\"uninstall: {error}\").into(),\n Msg::PluginListFailed { error } =>\n format!(\"list plugins: {error}\").into(),\n Msg::PluginReloadDone { skills, warnings } =>\n format!(\"Plugins reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n Msg::PluginGitNotFound =>\n \"💡 git is not installed or not on PATH. Plugin marketplace auto-install and auto-update are disabled. Install git (e.g. `xcode-select --install` on macOS, `sudo apt install git` on Ubuntu) and restart AtomCode.\".into(),\n Msg::PluginMarketplaceAdded { name, commit, count, plugins } =>\n format!(\n \"✓ marketplace `{name}` added at {commit} ({count} plugins)\\n \\\n Plugins: {plugins} — run /plugin install <plugin>@{name} to install before using its commands\"\n ).into(),\n Msg::PluginMarketplaceUpdated { name, commit } =>\n format!(\"✓ marketplace `{name}` updated to {commit}\").into(),\n Msg::PluginInstallDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Installed {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::PluginUpdateDone { plugin, marketplace: _, loaded: _, skipped: _, show_details_hint: _ } => {\n format!(\" ⎿ ✓ Updated {plugin}. Run /reload-plugins to apply.\").into()\n }\n Msg::SetupAutoReloaded { skills, warnings } =>\n format!(\"✓ Setup complete, auto-reloaded: {skills} skill(s), {warnings} warning(s)\").into(),\n\n // ── Command descriptions ──\n Msg::CmdDescWebui => \"Launch the browser webui (subcommands: stop, lan, --host <addr>)\".into(),\nMsg::CmdDescSetup =>\n\"Scan project, install seeds, and run setup skill [hooks|mcp|skills|all]\".into(),\n Msg::CmdDescResume => \"Resume a previous session\".into(),\n Msg::CmdDescRename => \"Rename current session\".into(),\n Msg::CmdDescLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models\".into(),\n Msg::CmdDescLogout => \"Sign out of AtomGit\".into(),\n Msg::CmdDescWhoami => \"Show current logged-in user\".into(),\n Msg::CmdDescModel => \"Switch provider / model\".into(),\n Msg::CmdDescProvider => \"Manage providers (add / edit / delete)\".into(),\n Msg::CmdDescStatus => \"Show session status\".into(),\n Msg::CmdDescConfig => \"Show config path\".into(),\n Msg::CmdDescReload => \"Reload $ATOMCODE_HOME/config.toml from disk\".into(),\n Msg::CmdDescCd => \"Change working directory\".into(),\nMsg::CmdDescInit => \"Analyze the project and generate AGENTS.md\".into(),\nMsg::CmdDescBg => \"Background sessions: /bg, /bg list, /bg <N>, /bg drop <N>\".into(),\nMsg::CmdDescBackground => \"Run a one-shot task in an isolated background context (read-only-ish tool subset)\".into(),\n Msg::CmdDescDiff => \"Show git diff\".into(),\n Msg::CmdDescClear => \"Clear screen\".into(),\n Msg::CmdDescSession => \"Start a new session (clears conversation)\".into(),\n Msg::CmdDescCost => \"Show token cost\".into(),\n Msg::CmdDescUsage => \"Show CodingPlan usage (tabs: current / overview / models)\".into(),\n Msg::CmdDescContext => \"Show context budget breakdown\".into(),\n Msg::CmdDescCompact => \"Compact conversation history\".into(),\n Msg::CmdDescRemember => \"Save a fact to memory (/remember --global for global)\".into(),\n Msg::CmdDescForget => \"Remove matching memories\".into(),\n Msg::CmdDescMemory => \"Show all saved memories\".into(),\n Msg::CmdDescMcp => \"Show MCP server status (subcommand: reload)\".into(),\n Msg::CmdDescUndo => \"Undo: roll conversation memory back a turn (/undo or /undo N)\".into(),\n Msg::CmdDescWorktree => \"Git worktree isolation (create/list/done/cleanup)\".into(),\n Msg::CmdDescUpgrade => \"Upgrade atomcode to latest (subcommand: rollback)\".into(),\n Msg::CmdDescPlan => \"Switch to Plan mode (read-only exploration)\".into(),\n Msg::CmdDescBuild => \"Switch to Build mode (full execution)\".into(),\n Msg::CmdDescAuto => \"Switch to Auto mode (auto-approve all tools)\".into(),\n Msg::CmdDescThink => \"Extended thinking control (on/off/budget N)\".into(),\n Msg::CmdDescEffort => \"DeepSeek reasoning effort control (high / max / off)\".into(),\n Msg::CmdDescHelp => \"Show this help\".into(),\n Msg::CmdDescKeys => \"Show keyboard shortcuts\".into(),\n Msg::CmdDescLanguage => \"Switch display language\".into(),\n Msg::CmdDescQuit => \"Exit AtomCode\".into(),\n Msg::CmdDescSkills => \"Browse loaded skills\".into(),\n Msg::CmdDescPlugin => \"Plugin marketplace (subcommands: marketplace, install, uninstall, reload, list)\".into(),\n Msg::CmdDescPaste => \"Attach an image from the clipboard (Windows fallback for Ctrl+V)\".into(),\n Msg::CmdDescCopy => \"Copy a code block, or the full reply with /copy msg (/copy, /copy N, /copy all, /copy msg)\".into(),\n Msg::CopyOk { lines, chars } => format!(\"Copied code block to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyOkMsg { lines, chars } => format!(\"Copied reply to clipboard ({lines} lines, {chars} chars)\").into(),\n Msg::CopyNoCodeBlock => \"No code block in the last reply to copy\".into(),\n Msg::CopyMsgEmpty => \"The last reply is empty — nothing to copy\".into(),\n Msg::CopyBadIndex { count } => format!(\"No such code block — the last reply has {count} (use /copy N, 1..={count})\").into(),\n Msg::CopyFailed => \"Clipboard unavailable — could not copy\".into(),\n Msg::CmdDescSave => \"Save the current conversation to a markdown file (/save, /save [filename])\".into(),\n Msg::SaveOk { path } => format!(\"Conversation saved to {path}\").into(),\n Msg::SaveEmpty => \"No conversation to export yet\".into(),\n Msg::SaveIoError { error } => format!(\"Failed to save conversation: {error}\").into(),\n Msg::SaveInvalidPath { path } => format!(\"Invalid path — directory does not exist: {path}\").into(),\n Msg::SaveRefuseOverwrite { path } => format!(\"Target exists and isn't a markdown file — refused to overwrite it (avoids clobbering source/config): {path}. Use a .md filename or a new path.\").into(),\n Msg::CodeBlockCopied => \"📋 Copied code block to clipboard\".into(),\n Msg::CmdDescGuide => \"Ask atomcode-guide how to use\".into(),\n Msg::CmdDescView => \"View file content in an overlay modal\".into(),\n Msg::CmdDescApp => \"Expose this session to the mobile App via relay (QR pairing; /app stop to detach)\".into(),\n Msg::CmdDescSync => \"Attach to live webui session (/sync off to detach)\".into(),\n Msg::CmdDescReview => \"Code review the current changes (/review · /review staged · /review <base>)\".into(),\n Msg::CmdDescGoal => \"Set a completion goal (autonomous loop until met)\".into(),\n Msg::CmdDescProxy => \"Switch outbound proxy mode\".into(),\n Msg::CmdDescTodo => \"Show the current todo list; `/todo add <task>` appends one, `/todo clear` wipes it\".into(),\n Msg::CmdDescDesktop =>\n \"Open the AtomCode desktop app (launch it if installed, else show the download link)\".into(),\n Msg::DesktopOpening { name, path } =>\n format!(\"Opening {}…\\n {}\\n\", name, path).into(),\n Msg::DesktopNotInstalled { url } =>\n format!(\"AtomCode desktop app not found. Download & install:\\n {}\\n\", url).into(),\n Msg::DesktopLaunchFailed { path, err } =>\n format!(\"Found the app but couldn't launch it: {}\\n {}\\n\", err, path).into(),\n Msg::TodoNoList => \"No task list yet (the model hasn't created todos).\".into(),\n Msg::TodoListHeader => \"Current tasks:\".into(),\n Msg::TodoAddUsage => \"Usage: /todo add <task description>\".into(),\n Msg::ViewUsage => \"Usage: /view <filepath>\".into(),\n Msg::GuideMenuHeader => \"📖 AtomCode Guide — type /guide <question>\".into(),\n Msg::GuideMenuTopics => \"Common topics:\".into(),\n Msg::GuideMenuGettingStarted => \"Getting started First install, login, config\".into(),\n Msg::GuideMenuSwitchModel => \"Switch models /model /provider usage\".into(),\n Msg::GuideMenuMcp => \"Using MCP MCP server config & management\".into(),\n Msg::GuideMenuSkills => \"Skills and plugins /skills /plugin usage\".into(),\n Msg::GuideMenuMemory => \"Memory feature /remember /forget /memory\".into(),\n Msg::GuideMenuBackground => \"Background tasks /bg background execution\".into(),\n Msg::GuideMenuContext => \"Context management /compact /context /cost\".into(),\n Msg::GuideMenuKeybindings => \"Keyboard shortcuts Keyboard shortcut reference\".into(),\n Msg::GuideMenuConfig => \"Configuration config.toml reference\".into(),\n Msg::GuideMenuTip => \"\n Tip: type /guide <your question> for a specific answer.\n Example: /guide How to switch models\n\".into(),\n Msg::GuideMenuDocUrl => \" Full docs: https://atomcode.atomgit.com/docs/en/\".into(),\n Msg::CmdGuideInstalling => \"Installing ask skill, please wait...\".into(),\n Msg::CmdGuideAutoInstall => \"ask skill not installed — auto-installing atomcode@atomcode-skills...\".into(),\n Msg::CmdGuideAutoInvoke { topic } =>\n format!(\"ask skill installed, now answering: {}\", topic).into(),\n Msg::CmdGuideSkillNotFound =>\n \"Installation complete but ask skill not found — run /plugin reload and try again\".into(),\n Msg::CmdGuideInstallFailed { error } =>\n format!(\"ask skill install failed: {}. Run /plugin install atomcode@atomcode-skills manually\", error).into(),\n Msg::CmdPasteNoImage => \"No image in clipboard.\".into(),\n Msg::CmdPasteNoImageOhos => {\n \"HarmonyOS can't read images from the system clipboard yet. Save the image to a file, then paste/type its absolute path (e.g. /storage/.../pic.png) to attach it.\".into()\n }\n\n // ── reasoning effort ──\n Msg::ReasoningEffortNoEffect => \"reasoning_effort has no effect on the current model (only DeepSeek V4)\".into(),\n\n // ── config save failed ──\n Msg::ConfigSaveFailed { error } =>\n format!(\"config save failed: {}\", error).into(),\n\n // ── OnboardingWizard ──\n Msg::OnboardingStepHeaderWelcome => \"Step 1/3 · Welcome\".into(),\n Msg::OnboardingStepHeaderLanguage => \"Step 2/3 · Language\".into(),\n Msg::OnboardingStepHeaderSetup => \"Step 3/3 · Setup\".into(),\n Msg::OnboardingPanelTitle => \"AtomCode\".into(),\n Msg::OnboardingIntroVersionLine { v } =>\n format!(\"Version {v} · AI coding agent in your terminal\").into(),\n Msg::OnboardingIntroBullet1 =>\n \"• Multi-step agent loop · built-in code-graph tools\".into(),\n Msg::OnboardingIntroBullet2 =>\n \"• Connects to any OpenAI-compatible API\".into(),\n Msg::OnboardingIntroBullet3 =>\n \"• Free tokens via CodingPlan\".into(),\n Msg::OnboardingIntroPressEnter => \"Press Enter to continue.\".into(),\n Msg::OnboardingIntroCtrlC => \"Ctrl+C exits at any point.\".into(),\n Msg::OnboardingIntroCompactTagline =>\n \"AI coding agent that lives in your terminal.\".into(),\n Msg::OnboardingLanguageTitleBilingual =>\n \"Choose your language / 选择语言\".into(),\n Msg::OnboardingLanguagePrompt =>\n \"Pick the UI language. You can change it any time with `/language`.\".into(),\n Msg::OnboardingLanguageOptionAuto =>\n \"Auto-detect (LC_ALL / LANG)\".into(),\n Msg::OnboardingLanguageOptionEn => \"English\".into(),\n Msg::OnboardingLanguageOptionZhCn => \"简体中文 (Simplified Chinese)\".into(),\n Msg::OnboardingSetupTitle => \"How would you like to set up?\".into(),\n Msg::OnboardingNavHint =>\n \"1-3 select · Enter confirm · ← back · Esc skip\".into(),\n Msg::OnboardingConfirmClear =>\n \"/welcome will clear the screen. Continue? [y/N]\".into(),\n Msg::CmdWelcomeDescription => \"Re-run the onboarding wizard\".into(),\n Msg::VisionPreprocessSuccess { char_count } =>\n format!(\"✓ VL recognised image, returned {char_count} chars\").into(),\n Msg::VisionPreprocessFailed { reason } =>\n format!(\"VL preprocessing failed: {reason} · continuing text-only this turn; images restored, retry to re-run recognition\").into(),\n Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens, cached_pct } =>\n format!(\n \"✓ {done} · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens{}\",\n super::fmt_tokens(total_tokens),\n cached_pct.map(|p| format!(\" · {p}% cached\")).unwrap_or_default(),\n ).into(),\n Msg::TurnSummaryError { turn_count, tool_call_count, duration, total_tokens } =>\n format!(\"✗ Stopped · {turn_count} rounds · {tool_call_count} tools · {duration} · {} tokens\", super::fmt_tokens(total_tokens)).into(),\n Msg::LoginQrHeader =>\n \" Sign in to AtomGit — scan the QR code with your WeChat:\\n\\n\".into(),\n Msg::LoginUrlAfterQr =>\n \"\\n\\n OR open the URL below in a browser:\\n \".into(),\n Msg::LoginNoQrNoUrl =>\n \" Cannot render a QR code in this terminal,\\n \\\n and URL-based login is unavailable on this platform.\\n \\\n Try a Unicode-capable terminal to display the QR.\".into(),\n Msg::LoginUrlOnly =>\n \" Open this URL in any browser to sign in to AtomGit:\\n \".into(),\n Msg::LoginCancelHint => \"\\n\\n Press ESC to cancel\\n\".into(),\n Msg::CtxUsageHeader => \"Context Usage\".into(),\n Msg::CtxUsageNoTurns => \"(run at least one turn first — stats are captured per turn)\".into(),\n Msg::CtxUsageWaiting => \"(waiting for first complete turn — partial stats only)\".into(),\n Msg::CtxProvider => \"Provider\".into(),\n Msg::CtxCtxName => \"ctx\".into(),\n Msg::CtxLabelSystemPrompt => \"System prompt\".into(),\n Msg::CtxLabelToolDefs => \"Tool defs\".into(),\n Msg::CtxLabelColdZone => \"Cold zone\".into(),\n Msg::CtxLabelMessages => \"Messages\".into(),\n Msg::CtxLabelFree => \"Free\".into(),\n Msg::CtxMessagesInWindow { n } => format!(\"Messages in window: {n}\").into(),\n Msg::CtxSystemPromptHeader => \"=== SYSTEM PROMPT ===\".into(),\n Msg::CtxSystemPromptEmpty => \"(empty — wait for one complete turn to capture)\".into(),\n Msg::CtxTokensSuffix => \"tokens\".into(),\n Msg::CompactNothingShort => \"(nothing to compact — conversation is short)\\n\".into(),\n Msg::CompactStarting => \"(compacting with LLM summary...)\\n\".into(),\n Msg::CompactInterrupted =>\n \"(compaction interrupted — the coding runtime changed or stopped)\\n\".into(),\n Msg::CompactUnavailableDuringSync =>\n \"Cannot compact while live sync is active; run /sync off first\".into(),\n Msg::CompactUnavailableDuringResync =>\n \"Cannot compact until the local runtime has restored the latest synced conversation\".into(),\n Msg::LocalRuntimeRestorePending =>\n \"The local runtime is restoring the synced conversation; please wait\".into(),\n Msg::LocalRuntimeRestoreTimedOut =>\n \"The local runtime restore timed out; Live sync has been restored\".into(),\n Msg::CompactNothingNoSavings { before, after } =>\n format!(\"(nothing to compact — would not save tokens: {} → {})\\n\", before, after).into(),\n Msg::CompactDropped { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"(compacted — dropped {} message{}, {} → {} tokens)\\n\", messages, plural, before, after).into()\n }\n Msg::Compacting => \"Compacting…\".into(),\n Msg::CompactingSlow => \"Compacting… (slow)\".into(),\n Msg::CompactMarkDrain { messages, before, after } => {\n let plural = if messages == 1 { \"\" } else { \"s\" };\n format!(\"Compacted · {} message{} summarized · ~{}→~{} tok\", messages, plural, before, after).into()\n }\n Msg::CompactMarkStub { saved } =>\n format!(\"Tool output folded · saved ~{} tok\", saved).into(),\n Msg::GoalHelp =>\n \" /goal — autonomous multi-round work toward a stated condition.\\n \\\n Usage:\\n \\\n \\u{20}\\u{20}/goal <condition> set a new goal; agent loops until the evaluator says met\\n \\\n \\u{20}\\u{20}/goal show current goal status\\n \\\n \\u{20}\\u{20}/goal status same as above\\n \\\n \\u{20}\\u{20}/goal clear stop the active goal (aliases: stop, off, reset, none, cancel)\\n \\\n \\u{20}\\u{20}/goal help this help\\n \\\n Notes:\\n \\\n \\u{20}\\u{20}- A fast model evaluates each round; configure via [providers] +\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}evaluator_provider in ~/.atomcode/config.toml.\\n \\\n \\u{20}\\u{20}- No built-in round / time cap — express budgets in the condition\\n \\\n \\u{20}\\u{20}\\u{20}\\u{20}text itself (e.g. \\\"or stop after 20 turns\\\"). CC's /goal works the same way.\\n \\\n \\u{20}\\u{20}- Esc / Ctrl+C stops the goal at any time.\\n\".into(),\n Msg::GoalStatus { condition, round, mins, secs } =>\n format!(\" ◎ Goal: {}\\n Round: {}\\n Elapsed: {}m {}s\\n\", condition, round, mins, secs).into(),\n Msg::GoalNoActive =>\n \" No active goal.\\n Usage: /goal <condition> | /goal help\\n\".into(),\n Msg::GoalCleared => \" Goal cleared.\\n\".into(),\n\n // ── /loop ──\n Msg::LoopStatus { label, round, mins, secs } =>\n format!(\" ↻ loop: {} · round {} · {}m {}s\\n\", label, round, mins, secs).into(),\n Msg::LoopNoActive =>\n \" No active /loop.\\n Usage: /loop <interval> <cmd> or /loop <prompt>\\n\".into(),\n Msg::LoopCleared => \" /loop stopped.\\n\".into(),\n Msg::LoopRound { round, stats } =>\n format!(\"⚡ loop round {} · {}\", round, stats).into(),\n Msg::LoopStopped => \"⚠ loop stopped (limit reached)\\n\".into(),\n Msg::LoopEnded { reason } =>\n format!(\" ↻ Loop ended: {reason}\\n\").into(),\n Msg::LoopNoPersistHint =>\n \" (note: the loop won't survive a restart / resume)\".into(),\n Msg::CmdDescLoop =>\n \"Repeat a prompt/command on an interval, or let the model self-pace\".into(),\n Msg::ModelNoImageSupport { model } => format!(\n \"Current model \\\"{}\\\" does not support image input and no \\\n vision_preprocessor_provider is configured. Use /model to \\\n switch to a vision-capable model, or set \\\n vision_preprocessor_provider in config.\",\n model\n )\n .into(),\n // ── --dangerously-skip-permissions / -y ──\n Msg::BypassWarningBanner =>\n \"\\u{26a0} --dangerously-skip-permissions is active: all tool calls are auto-approved (no permission prompts)\\n\".into(),\n Msg::BypassWarningHeadless =>\n \"[headless] --dangerously-skip-permissions: all tool calls are auto-approved\".into(),\n\n Msg::AdminWarningBanner =>\n \"\\x1b[33m\\u{26a0} Warning: Running with Administrator privileges.\\n The model may have access to system files.\\n Consider running without elevation, inside a scoped working directory.\\x1b[39m\\n\".into(),\n Msg::AdminWarningHeadless =>\n \"[warning] Running with Administrator privileges — model may have access to system files.\".into(),\n\n Msg::CtrlCAgainToExit => \" (press Ctrl+C again to exit)\\n\".into(),\n Msg::EscAgainToUndo => \" (press Esc again to undo last turn)\\n\".into(),\n Msg::BashInputHint => \"Enter to run as a bash command\".into(),\n Msg::ShellModeHint => \"! for shell mode\".into(),\n Msg::SteerFoldedHint => \"will fold into current turn\".into(),\n Msg::HintMultiLineInput =>\n \" \\u{24d8} Multi-line input: end the line with `\\\\` then press Enter.\\n \\\n Works in every terminal. (Shift / Alt / Ctrl + Enter may also work\\n \\\n depending on the terminal's keyboard protocol — try them out.)\\n\\n\"\n .into(),\n\n // ── /bg (background sessions) ──\n Msg::BgHelp =>\n \" /bg Send current session to background and open a new foreground\\n /bg list List background sessions\\n /bg <N> Resume background slot N\\n /bg drop <N> Drop background slot N\\n /bg help Show this help\\n\".into(),\n Msg::BgListEmpty => \" No background sessions.\\n\".into(),\n Msg::BgListHeader => \" # ID State Created Summary\\n\".into(),\n Msg::BgListRow { slot, short_id, state, age, summary } =>\n format!(\" {:<3} {:<8} {:<9} {:<8} {}\\n\", slot, short_id, state, age, summary).into(),\n Msg::BgStateRunning => \"running\".into(),\n Msg::BgStateIdle => \"idle\".into(),\n Msg::BgStateDone => \"done\".into(),\n Msg::BgStateCancelled => \"cancelled\".into(),\n Msg::BgStateError => \"error\".into(),\n Msg::BgAgeNow => \"now\".into(),\n Msg::BgAgeMinutes { n } => format!(\"{n}m\").into(),\n Msg::BgAgeHours { n } => format!(\"{n}h\").into(),\n Msg::BgAgeDays { n } => format!(\"{n}d\").into(),\n Msg::BgSlotLimitReached { max } =>\n format!(\"background slot limit reached ({max})\").into(),\n Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>\n format!(\" New foreground session [{new_id}]\\n Background: [#{slot}] {old_id} (state: {state})\\n\").into(),\n Msg::BgInvalidSlot { slot, available } =>\n format!(\"invalid background slot {slot} (available: {available})\").into(),\n Msg::BgNoRuntimeClient => \"background slot has no runtime client\".into(),\n Msg::BgResumed { slot, short_id } =>\n format!(\" Resumed background [#{slot}] {short_id}\\n\").into(),\n Msg::BgPreviousForegroundMoved { slot } =>\n format!(\" Previous foreground moved to [#{slot}]\\n\").into(),\n Msg::BgDropped { slot, short_id } =>\n format!(\" Dropped background [#{slot}] {short_id}\\n\").into(),\n Msg::BgTaskStarted { slot, short_id } =>\n format!(\" Background: [#{slot}] {short_id} (state: running)\\n\").into(),\n Msg::BgTaskTimedOut { secs } =>\n format!(\"Background task timed out after {secs}s.\").into(),\n Msg::BgTaskError { error } =>\n format!(\"Error: {error}\").into(),\n Msg::BgTaskCancelled => \"Cancelled.\".into(),\n Msg::BgTaskNoSummary => \"Task completed (no summary text).\".into(),\n // -- CLI atomcode --help i18n --\n Msg::CliAbout => \"AI coding assistant in your terminal\".into(),\n Msg::CliAboutLogin => \"Sign in with AtomGit OAuth and claim CodingPlan models in one flow\".into(),\n Msg::CliAboutLogout => \"Logout from AtomCode\".into(),\n Msg::CliAboutStatus => \"Show current login status\".into(),\n Msg::CliAboutUpgrade => \"Upgrade atomcode in-place to the latest released version\".into(),\n Msg::CliAboutRollback => \"Roll back to the previous version (swap with .bak on disk)\".into(),\n Msg::CliAboutMcp => \"Manage MCP server entries in .mcp.json\".into(),\n Msg::CliAboutDaemon => \"Start the HTTP daemon for IDE integration\".into(),\n Msg::CliAboutWebui => \"Start the local browser webui\".into(),\n Msg::CliAboutTelemetry => \"Telemetry controls\".into(),\n Msg::CliAboutPlugin => \"Manage skill/command plugins\".into(),\n Msg::CliAboutUninstall => \"Uninstall AtomCode: remove the binary, PATH edit, and data\".into(),\n Msg::CliAboutSetup => \"Install seed files (skills/commands/hooks/MCP) to ~/.atomcode/\".into(),\n Msg::CliAboutHooks => \"Manage hooks (list, test, enable/disable)\".into(),\n Msg::CliAboutHooksList => \"List all loaded hooks with their status\".into(),\n Msg::CliAboutHooksTest => \"Test a specific hook by name\".into(),\n Msg::CliAboutHooksPaths => \"Show hook configuration paths\".into(),\n Msg::CliAboutPluginMarketplace => \"Marketplace registry operations\".into(),\n Msg::CliAboutPluginInstall => \"Install a plugin from a registered marketplace\".into(),\n Msg::CliAboutPluginUninstall => \"Uninstall a previously-installed plugin\".into(),\n Msg::CliAboutPluginList => \"List installed plugins\".into(),\n Msg::CliAboutMarketplaceAdd => \"Clone a marketplace git repo and register it locally\".into(),\n Msg::CliAboutMarketplaceRemove => \"Drop a registered marketplace\".into(),\n Msg::CliAboutMarketplaceUpdate => \"Re-pull a registered marketplace and refresh its plugin index\".into(),\n Msg::CliAboutMarketplaceList => \"List registered marketplaces\".into(),\n Msg::CliAboutMcpAdd => \"Add or replace a stdio MCP server\".into(),\n Msg::CliAboutMcpAddGithubOauth => \"Add GitHub remote MCP server using OAuth\".into(),\n Msg::CliAboutMcpLogin => \"Complete OAuth login for a remote MCP server\".into(),\n Msg::CliAboutMcpLogout => \"Remove saved OAuth credentials for a remote MCP server\".into(),\n Msg::CliAboutTelemetryStatus => \"Show current telemetry state and queue stats\".into(),\n Msg::CliAboutTelemetryEnable => \"Enable telemetry\".into(),\n Msg::CliAboutTelemetryDisable => \"Disable telemetry\".into(),\n Msg::CliAboutTelemetryDump => \"Print pending queued events\".into(),\n Msg::CliAboutTelemetryClear => \"Clear queued events\".into(),\n Msg::CliHelpContinue => \"Continue the previous session instead of starting a new one\".into(),\n Msg::CliHelpProvider => \"Provider to use (overrides config default)\".into(),\n Msg::CliHelpModel => \"Model to use (overrides config provider model)\".into(),\n Msg::CliHelpLang => \"Set interface language (e.g. en, zh-CN, zh)\".into(),\n Msg::CliHelpConfig => \"Path to config file\".into(),\n Msg::CliHelpDir => \"Working directory (defaults to current directory)\".into(),\n Msg::CliHelpPrompt => \"Prompt to run in headless (non-interactive) mode\".into(),\n Msg::CliHelpPromptFile => \"Read the prompt from a file\".into(),\n Msg::CliHelpVerbose => \"Show tool calls, token usage, and turn summary on stderr\".into(),\n Msg::CliHelpDev => \"Disable auto-update for this launch\".into(),\n Msg::CliHelpNoTelemetry => \"Disable telemetry for this invocation\".into(),\n Msg::CliHelpDangerouslySkipPermissions => \"Skip all permission prompts -- auto-approve every tool call\".into(),\n Msg::CliHelpForce => \"Reinstall even when already on the latest version\".into(),\n Msg::CliHelpPortDaemon => \"Port to listen on (default: 13456)\".into(),\n Msg::CliHelpClient => \"Client identifier for telemetry\".into(),\n Msg::CliHelpIdleTimeout => \"Idle-shutdown timeout in seconds; 0 disables\".into(),\n Msg::CliHelpPortWebui => \"Port (default: 13457)\".into(),\n Msg::CliHelpHost => \"Bind address (default: 127.0.0.1)\".into(),\n Msg::CliHelpUninstallYes => \"Skip prompts; use per-group default decisions\".into(),\n Msg::CliHelpUninstallPurge => \"Wipe ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallKeepData => \"Keep ~/.atomcode/ entirely\".into(),\n Msg::CliHelpUninstallDryRun => \"Print the plan; do nothing\".into(),\n Msg::CliHelpMcpGlobal => \"Write ~/.atomcode/mcp.json instead of <dir>/.mcp.json\".into(),\n Msg::CliHelpMcpDir => \"Directory for project .mcp.json\".into(),\n Msg::CliHelpMcpName => \"Server key\".into(),\n Msg::CliHelpHooksTestName => \"Hook name to test\".into(),\n Msg::CliHelpPluginSpec => \"e.g. plugin@marketplace\".into(),\n Msg::CliHelpMarketplaceUrl => \"Git URL of a marketplace repo\".into(),\n Msg::CliHelpMarketplaceName => \"Marketplace name\".into(),\n Msg::CliAboutHelp => \"Print this message or the help of the given subcommand(s)\".into(),\n Msg::CliHelpMcpCommand => \"Executable and arguments\".into(),\n\n // ── /usage modal ──\n Msg::UsageTabCurrent => \"Current\".into(),\n Msg::UsageTabOverview => \"Overview\".into(),\n Msg::UsageTabModels => \"Models\".into(),\n Msg::UsageCurrentTitle => \"Rate-limit window\".into(),\n Msg::UsageResetsIn { hms } => format!(\"Resets in {hms}\").into(),\n Msg::UsageWindowHours { hours } => format!(\"{hours}-hour rolling window\").into(),\n Msg::UsageWindowUnavailable => \"Window data unavailable\".into(),\n Msg::UsageStatFavorite => \"Favorite model\".into(),\n Msg::UsageStatTotal => \"Total tokens\".into(),\n Msg::UsageStatRequests => \"Requests\".into(),\n Msg::UsageStatActiveDays => \"Active days\".into(),\n Msg::UsageStatMostActive => \"Most active day\".into(),\n Msg::UsageStatLongestStreak => \"Longest streak\".into(),\n Msg::UsageStatCurrentStreak => \"Current streak\".into(),\n Msg::UsageHeatLess => \"Less\".into(),\n Msg::UsageHeatMore => \"More\".into(),\n Msg::UsageModelsTitle => \"Per-model usage\".into(),\n Msg::UsageNoData => \"No usage data available\".into(),\n Msg::UsageFooterHint => \"← / → or Tab switch · Ctrl+S copy · Esc close\".into(),\n Msg::UsageFetchFailed { error } => format!(\"Failed to load usage: {error}\").into(),\n Msg::UsagePlanTitle => \"Plan\".into(),\n Msg::UsagePlanActive => \"Active\".into(),\n Msg::UsagePlanExpired => \"Expired\".into(),\n Msg::UsagePlanClaimedExpires { claimed, expires } =>\n format!(\"Claimed {claimed} · Expires {expires}\").into(),\n Msg::UsagePlanRemaining { remaining, total } =>\n format!(\"Remaining {remaining}/{total} days\").into(),\n Msg::UsageCopied => \"Copied to clipboard\".into(),\n Msg::UsageCodingPlanOnly =>\n \"Usage is only available on CodingPlan — run /login.\".into(),\n\n // ── CodingRuntime provider init ──\n Msg::ProviderInitFailed { detail } =>\n format!(\"provider init failed: {detail}\").into(),\n Msg::ProviderInitNeedsLogin =>\n \"Not signed in — model unavailable. Run /login to continue.\".into(),\n Msg::ProviderInitSourceBuild =>\n \"This is a source build — the AtomGit free gateway isn't available. Use /provider to \\\n configure a model with your own api_key (e.g. DeepSeek / GLM / OpenAI), or switch to \\\n an official release build.\".into(),\n Msg::GatewayAuthUnavailable { base_url } =>\n format!(\n \"provider base_url '{base_url}' is an AtomGit gateway this build can't \\\n authenticate against. Use the official binary, or point the provider at a \\\n plain OpenAI-compatible endpoint with an api_key.\"\n ).into(),\n Msg::StreamStalled => \"esc to cancel\".into(),\n Msg::ConhostScrollHint =>\n \"Tip: the classic Windows console is limited — no scroll-back while a task runs, \\\n and glyphs/the mascot render degraded. \\x1b[1;96mWindows Terminal\\x1b[0m gives the full experience.\"\n .into(),\n }\n}\n\n#[cfg(test)]\nmod codingplan_crypto_tests {\n use super::*;\n use crate::i18n::Msg;\n\n #[test]\n fn en_official_build_required_mentions_releases() {\n let s = en(Msg::CpOfficialBuildRequired);\n assert!(s.contains(\"official\"));\n assert!(s.contains(\"releases\"));\n }\n\n #[test]\n fn en_stale_clock_mentions_system_time() {\n let s = en(Msg::CpSignStaleClockSkew);\n assert!(s.to_lowercase().contains(\"clock\") || s.to_lowercase().contains(\"time\"));\n }\n\n #[test]\n fn en_replay_persisted_is_non_empty() {\n let s = en(Msg::CpSignReplayPersisted);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_version_too_old_mentions_upgrade() {\n let s = en(Msg::CpSignVersionTooOld);\n assert!(s.to_lowercase().contains(\"upgrade\") || s.to_lowercase().contains(\"update\"));\n }\n\n #[test]\n fn en_upgrade_required_is_non_empty() {\n let s = en(Msg::CpUpgradeRequired);\n assert!(!s.is_empty());\n }\n\n #[test]\n fn en_conhost_scroll_hint_recommends_windows_terminal() {\n let s = en(Msg::ConhostScrollHint);\n assert!(s.contains(\"Windows Terminal\"));\n assert!(s.to_lowercase().contains(\"scroll\"));\n }\n}\n"}}, "site/docs/zh/configuration.html": {"858ab6db7d1354e6e0e33168c8aacf28c313d913": {"t": 1785746429.8179703, "n": 16081, "h": "31bf04ba5f815987", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n <div class=\"callout callout-info\">\n <strong>v5.0.3 的 Provider 管理</strong>\n <p>推荐直接运行 <code>/provider</code>:先添加供应商账号,再在该账号下添加一个或多个模型。供应商共用协议、Base URL 和 API Key;模型分别保存名称、上下文窗口等参数。旧版 <code>[providers.*]</code> 配置仍可继续使用,AtomCode 会自动兼容。</p>\n </div>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理供应商账号和账号下的模型;内置预设会自动填好协议与 Base URL,包括 AtomGit、TaoToken 等,也可以选择“自定义”。用 <code>/model</code> 切换当前会话,并把所选模型保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2>回合轮数</h2>\n <pre><code>[coding]\nmax_rounds = 200</code></pre>\n <p>达到上限时,TUI 会询问继续还是停止,不会直接丢失当前任务。设置为 <code>0</code> 表示不限制。</p>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式管理供应商账号及其模型,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "bacf7b9d53f04fdb9ead8a7c8d8fdf6b1aa0baa5": {"t": 1785761159.8064015, "n": 16081, "h": "31bf04ba5f815987", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n <div class=\"callout callout-info\">\n <strong>v5.0.3 的 Provider 管理</strong>\n <p>推荐直接运行 <code>/provider</code>:先添加供应商账号,再在该账号下添加一个或多个模型。供应商共用协议、Base URL 和 API Key;模型分别保存名称、上下文窗口等参数。旧版 <code>[providers.*]</code> 配置仍可继续使用,AtomCode 会自动兼容。</p>\n </div>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理供应商账号和账号下的模型;内置预设会自动填好协议与 Base URL,包括 AtomGit、TaoToken 等,也可以选择“自定义”。用 <code>/model</code> 切换当前会话,并把所选模型保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2>回合轮数</h2>\n <pre><code>[coding]\nmax_rounds = 200</code></pre>\n <p>达到上限时,TUI 会询问继续还是停止,不会直接丢失当前任务。设置为 <code>0</code> 表示不限制。</p>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式管理供应商账号及其模型,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "2a63b30571e1d2fe5ed3714f454a32b341572bc0": {"t": 1785761161.5752933, "n": 15616, "h": "69ea5ced1d1e5603", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "1737a748cfc706db9548e0859a43476728100d2e": {"t": 1785761163.6157043, "n": 15616, "h": "1abf5245bfb931d9", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "16c021d52b3be0b44bc9b49b6d647e41dc2c3f2a": {"t": 1785761165.4364398, "n": 15616, "h": "d880097116d3fdc7", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.1</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n <a class=\"dside-link\" href=\"./interactive-questions.html\" data-slug=\"interactive-questions\" data-i18n=\"side.interactive-questions\">交互提问</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./subagents.html\" data-slug=\"subagents\" data-i18n=\"side.subagents\">子代理</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>网络与代理</h2>\n <p>AtomCode 默认跟随系统代理配置,因此在公司代理后也能开箱即用。行为在 <code>[network.proxy]</code> 下配置:</p>\n <pre><code>[network.proxy]\nmode = \"follow_system\" # 默认 —— 采用环境变量 + 系统代理\n# mode = \"default_proxy\" # 使用下面显式的 http/https/all\n# mode = \"no_proxy\" # 强制直连\n# http = \"http://127.0.0.1:7890\"\n# https = \"http://127.0.0.1:7890\"\n# all = \"socks5://127.0.0.1:1080\"\n# no_proxy = \"localhost,127.0.0.1,.internal\"</code></pre>\n <table>\n <thead><tr><th>mode</th><th>行为</th></tr></thead>\n <tbody>\n <tr><td><code>follow_system</code>(默认)</td><td>采用 <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> / <code>ALL_PROXY</code> / <code>NO_PROXY</code>;未设置的部分回退到操作系统的系统代理(Windows 注册表 / macOS <code>scutil --proxy</code>)。</td></tr>\n <tr><td><code>default_proxy</code></td><td>使用本段里显式的 <code>http</code> / <code>https</code> / <code>all</code> / <code>no_proxy</code> 值。</td></tr>\n <tr><td><code>no_proxy</code></td><td>强制直连,忽略环境变量与系统代理。</td></tr>\n </tbody>\n </table>\n <p>IPv6 代理主机会自动加方括号(<code>::1</code> → <code>http://[::1]:8080</code>)。若登录时连接失败或超时,错误信息会指向你的代理 / 网络设置,便于区分是代理问题还是凭据错误。</p>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "de1cd59a8d61743707e4904a5375f4413543417c": {"t": 1785761166.6353228, "n": 14210, "h": "93bd270e3417b2a7", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.1</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "a28c215857b0e87dba221b4ebebe1d16d00b1fbe": {"t": 1785761168.838292, "n": 14210, "h": "29e29f01c26b3385", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中用 <code>/provider</code> 管理 provider 条目。用 <code>/model</code> 切换当前会话,并把所选条目保存为后续新会话的默认值;其他已经打开的会话不变。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "ad90366cba07ceab3cfc652f96bdd323e6c0dabc": {"t": 1785761170.2811244, "n": 14189, "h": "5c96f5ae1de67fd0", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。支持明文密钥、环境变量语法(如 <code>$MY_KEY</code> 或 <code>${MY_KEY:-default}</code>)、环境变量名(如 <code>MY_KEY</code>),或留空自动回退标准环境变量(如 <code>OPENAI_API_KEY</code> / <code>ANTHROPIC_API_KEY</code>)。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <h2>环境变量与 API Key 动态解析</h2>\n <p>为避免在配置文件中明文保存敏感密钥,并方便与其他开发工具共用环境变量,AtomCode 支持在 <code>config.toml</code> 中灵活使用环境变量:</p>\n <ul>\n <li><strong>环境变量语法展开 (<code>$VAR</code> / <code>${VAR}</code>)</strong>:\n <pre><code>[providers.my_openai]\ntype = \"openai\"\nmodel = \"gpt-4o\"\napi_key = \"$MY_OPENAI_KEY\" # 或 \"${MY_OPENAI_KEY}\"\nbase_url = \"https://api.openai.com/v1\"</code></pre>\n 支持带默认值的表达式(例:<code>api_key = \"${MY_KEY:-sk-fallback}\"</code>)。\n </li>\n <li><strong>直接填写环境变量名</strong>:\n <pre><code>[providers.my_claude]\ntype = \"claude\"\nmodel = \"claude-sonnet-4-6\"\napi_key = \"ANTHROPIC_API_KEY\" # 自动从环境变量 ANTHROPIC_API_KEY 中读取</code></pre>\n </li>\n <li><strong>留空自动回退标准环境变量</strong>:当未配置或留空 <code>api_key</code> 时,系统会自动寻找对应 <code>type</code> 的标准环境变量:\n <ul>\n <li><code>openai</code> / <code>openai-compat</code>:读取 <code>OPENAI_API_KEY</code></li>\n <li><code>claude</code> / <code>anthropic</code>:读取 <code>ANTHROPIC_API_KEY</code></li>\n <li><code>ollama</code>:读取 <code>OLLAMA_API_KEY</code></li>\n <li>通用后备:读取 <code>ATOMCODE_API_KEY</code></li>\n </ul>\n </li>\n </ul>\n <p>注:配置文件中的 <code>$VAR</code> 语法由 AtomCode 程序内部解析,在 Windows、macOS 和 Linux 上保持统一写法。</p>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "6a3a1f6933836f94dd7c26ea75c26645ee0d2489": {"t": 1785761171.5069304, "n": 12713, "h": "640e31487900d887", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v5.0.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "86bbaacbc30b3b5fa3105518b4c4052ffddb71df": {"t": 1785761173.1574996, "n": 12714, "h": "7098a7c445cbe180", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.26.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "17ebf59dcaad723bb94ed01721837618bd2da192": {"t": 1785761175.293464, "n": 12714, "h": "c5c1c3c639db3a40", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.9</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "0fd8208a0fe5d4aa56ed8bb12fe033682ba44847": {"t": 1785761176.662956, "n": 12714, "h": "63a350e67cb1c132", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.8</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "3269260453c27bd434d9d8f4770971f9eb339e5a": {"t": 1785761177.826284, "n": 12714, "h": "78dde1cdccfe41a9", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.7</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "431d0485045fe2e51660484e074ef0f42d9f2f39": {"t": 1785761179.2059317, "n": 12714, "h": "d53b7d9da964f695", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.6</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "6264e177874c3472ece603abac5df936bcccd7d8": {"t": 1785761180.721333, "n": 12714, "h": "d8128617f9f85a40", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.5</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "9bd888fc1ae56f7ebb84e3de26a5e96d3a8ba61e": {"t": 1785761182.2480638, "n": 12714, "h": "6d316d2e3471cd98", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.4</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "578ac062a237ed41e871d3ddfcfa18d3906e6790": {"t": 1785761183.5848343, "n": 12714, "h": "8ceeee4f9e424c27", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "a3ee1baf64fb11b73a84be262119f7090e25dbda": {"t": 1785761184.9288945, "n": 12714, "h": "1c9d27214b5e5d6f", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "fcb5525d2ca5b550483a3f35a3a4f8997a4a120d": {"t": 1785761186.1960986, "n": 12714, "h": "b023fe2802b5f882", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.1</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "de732c5f4d44b7bde84879f38d7cfefdfbae1a2e": {"t": 1785761187.5358357, "n": 12714, "h": "e67ee203e70d0d04", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.25.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "e987bb439507ad0913f8bee478321b140274b839": {"t": 1785761188.6966908, "n": 12714, "h": "a7d7597405541049", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./approvals.html\" data-slug=\"approvals\" data-i18n=\"side.approvals\">权限审批</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "9eccc8f68beda069fa2526fc8d695deac2547f46": {"t": 1785761190.1039116, "n": 12606, "h": "e1be6b066e9a41fa", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "241659424f17ba76815f4e987533ab3154458ee2": {"t": 1785761191.3511972, "n": 12606, "h": "8b5f102a1033577e", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.1</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "46232b8107bda45bc1f106ccc5f57af73ad20c72": {"t": 1785761192.5472076, "n": 12606, "h": "85e16b411e5ee9d7", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui.html\" data-slug=\"webui\" data-i18n=\"side.webui\">WebUI 界面</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "bfd45aac4384f4df06f25e2bcfe783cb61ca4bb6": {"t": 1785761193.7686107, "n": 12506, "h": "e1435dce7f7cf72f", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n <a class=\"dside-link\" href=\"./webui-remote-access.html\" data-slug=\"webui-remote-access\" data-i18n=\"side.webui-remote-access\">远程访问指南</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "26aa47211d50a2498cf54fa380edd0fce5486bab": {"t": 1785761195.1286917, "n": 12366, "h": "8caef59791dd5e22", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.24.0</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "13c051411f4bfad7d04897f6c150beee2d5e5592": {"t": 1785761196.4307868, "n": 12366, "h": "e9943643bfc5a330", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/login</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "27ee84b6604aad3ac1924f3f7326a95314b3032f": {"t": 1785761197.9718714, "n": 12371, "h": "3012d9a470236695", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.3</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/codingplan</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "350b15d383e1d610825e7bd445a6a162ff8f63f9": {"t": 1785761199.1567595, "n": 12371, "h": "12605d7c79f48806", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/codingplan</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "91dc4e2e916c55e18264a4e1998e78618e16a026": {"t": 1785761200.3606267, "n": 12721, "h": "9c4fb0037b7c8c0e", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>UI 偏好</h2>\n <p>TUI 显示偏好(滚动条开关等)单独存在一个文件,不和 <code>config.toml</code> 混在一起:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/ui-state.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\ui-state.toml</code></li>\n </ul>\n <p>由 <code>/scrollbar</code> 等命令自动维护(原子写,崩溃也不会破文件),通常不需要手动编辑。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/codingplan</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}, "3e115c22a6752ac1146d024739f6ef6b2bb06b3a": {"t": 1785761201.789139, "n": 12371, "h": "12605d7c79f48806", "c": "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>配置文件 · AtomCode 文档</title>\n<meta name=\"description\" content=\"配置文件 — AtomCode 文档。\">\n<link rel=\"icon\" type=\"image/png\" href=\"https://cdn-static.gitcode.host/static/images/logo-favicon.png\">\n<link rel=\"stylesheet\" href=\"../docs.css\">\n<script>(function(){try{var s=localStorage.getItem('atomcode_theme')||localStorage.getItem('atomcode-theme');if(s==='light'){document.documentElement.classList.add('light');document.documentElement.setAttribute('data-theme','light')}}catch(e){}})();</script>\n</head>\n<body data-page=\"configuration\">\n\n<header class=\"dhdr\" id=\"dhdr\">\n <a class=\"dhdr-logo\" href=\"../../index.html\">\n <img src=\"https://cdn-news.gitcode.com/news/atomcode-icon1.png\" alt=\"AtomCode\">\n <span>AtomCode</span>\n <span class=\"dhdr-badge\" data-i18n=\"badge.docs\">DOCS</span>\n <span class=\"dhdr-ver\">v4.23.2</span>\n </a>\n <div class=\"dhdr-right\">\n <button class=\"search-trigger\" data-open-search data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <span data-i18n=\"search.trigger.text\">搜索文档…</span>\n <span class=\"kbd\">⌘K</span>\n </button>\n <button class=\"icon-btn\" id=\"themeBtn\" data-i18n-aria=\"aria.theme\" aria-label=\"切换主题\"></button>\n <button class=\"icon-btn\" id=\"langBtn\" data-i18n-aria=\"aria.lang\" aria-label=\"切换语言\">中</button>\n <a class=\"dhdr-link\" href=\"https://atomgit.com/atomgit_atomcode/atomcode\" target=\"_blank\" rel=\"noopener\" data-i18n=\"hdr.repo\">仓库 →</a>\n <button class=\"icon-btn sb-toggle\" id=\"sbToggle\" data-i18n-aria=\"aria.sidebar\" aria-label=\"目录\">☰</button>\n </div>\n</header>\n\n<div class=\"dlayout\">\n <aside class=\"dside\" id=\"dside\">\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.overview\">概览</div>\n <a class=\"dside-link\" href=\"./index.html\" data-slug=\"index\" data-i18n=\"side.index\">文档首页</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.start\">开始</div>\n <a class=\"dside-link\" href=\"./getting-started.html\" data-slug=\"getting-started\" data-i18n=\"side.getting-started\">快速开始</a>\n <a class=\"dside-link\" href=\"./login.html\" data-slug=\"login\" data-i18n=\"side.login\">登录方式</a>\n <a class=\"dside-link\" href=\"./configuration.html\" data-slug=\"configuration\" data-i18n=\"side.configuration\">配置文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.usage\">使用</div>\n <a class=\"dside-link\" href=\"./basic-usage.html\" data-slug=\"basic-usage\" data-i18n=\"side.basic-usage\">基本使用</a>\n <a class=\"dside-link\" href=\"./slash-commands.html\" data-slug=\"slash-commands\" data-i18n=\"side.slash-commands\">斜杠命令</a>\n <a class=\"dside-link\" href=\"./keybindings.html\" data-slug=\"keybindings\" data-i18n=\"side.keybindings\">快捷键</a>\n <a class=\"dside-link\" href=\"./sessions.html\" data-slug=\"sessions\" data-i18n=\"side.sessions\">会话与撤销</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.advanced\">进阶</div>\n <a class=\"dside-link\" href=\"./tools.html\" data-slug=\"tools\" data-i18n=\"side.tools\">内置工具</a>\n <a class=\"dside-link\" href=\"./skills.html\" data-slug=\"skills\" data-i18n=\"side.skills\">Skills 扩展</a>\n <a class=\"dside-link\" href=\"./mcp.html\" data-slug=\"mcp\" data-i18n=\"side.mcp\">MCP 集成</a>\n <a class=\"dside-link\" href=\"./plugins.html\" data-slug=\"plugins\" data-i18n=\"side.plugins\">Plugin 系统</a>\n <a class=\"dside-link\" href=\"./memory.html\" data-slug=\"memory\" data-i18n=\"side.memory\">永久记忆</a>\n <a class=\"dside-link\" href=\"./project-instructions.html\" data-slug=\"project-instructions\" data-i18n=\"side.project-instructions\">项目指令文件</a>\n </div>\n\n <div class=\"dside-group\">\n <div class=\"dside-group-t\" data-i18n=\"side.g.ops\">问题</div>\n <a class=\"dside-link\" href=\"./faq.html\" data-slug=\"faq\" data-i18n=\"side.faq\">常见问题</a>\n </div>\n </aside>\n\n <main class=\"dmain prose-docs\">\n<h1>配置文件</h1>\n <p class=\"lede\">AtomCode 的配置采用 TOML 格式,支持多个 provider 并行存在、随时切换。本页讲清每一个字段的含义和常见 provider 的示例配置。</p>\n\n <h2>配置文件位置</h2>\n <p>默认路径:</p>\n <ul>\n <li>macOS / Linux / HarmonyOS PC:<code>~/.atomcode/config.toml</code></li>\n <li>Windows:<code>%USERPROFILE%\\.atomcode\\config.toml</code></li>\n </ul>\n <p>也可以通过 <code>--config /path/to/config.toml</code> 指定任意路径。首次运行时,若该文件不存在,3 步启动向导会引导你完成初始化。</p>\n\n <h2>最小示例</h2>\n <pre><code>default_provider = \"deepseek\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-xxxxxxxxxxxxxxxx\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n <p>这个配置已经可以启动 atomcode 并与 DeepSeek 对话。</p>\n\n <h2>顶层字段</h2>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>default_provider</code></td><td>string</td><td>启动时默认使用的 provider 名(必须是 <code>[providers.*]</code> 中定义过的 key)</td></tr>\n <tr><td><code>default_workdir</code></td><td>string?</td><td>默认工作目录。用 <code>/cd</code> 切换目录时自动写回这里,下次启动恢复</td></tr>\n <tr><td><code>providers</code></td><td>table</td><td>provider 名到 <a href=\"#provider-config\">ProviderConfig</a> 的映射</td></tr>\n <tr><td><code>vision_preprocessor_provider</code></td><td>string?</td><td>当主 provider 不支持图片但用户 attach 了图时,把图片转交给这里指定的 provider 做 OCR / 描述,结果以文本形式 splice 给主模型。详见 <a href=\"#vision-preprocessor\">视觉预处理器</a></td></tr>\n </tbody>\n </table>\n\n <h2 id=\"provider-config\">ProviderConfig 字段</h2>\n <p>每一个 <code>[providers.xxx]</code> 表下可用的字段:</p>\n <table>\n <thead>\n <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>\n </thead>\n <tbody>\n <tr><td><code>type</code></td><td>string</td><td>是</td><td>provider 协议类型,目前支持 <code>openai</code>、<code>claude</code>、<code>ollama</code></td></tr>\n <tr><td><code>api_key</code></td><td>string?</td><td>视情况</td><td>API Key。<code>ollama</code> 可不填。OAuth 登录的 provider 不含此字段,token 从 <code>~/.atomcode/auth.toml</code> 自动读取</td></tr>\n <tr><td><code>model</code></td><td>string</td><td>是</td><td>模型名,比如 <code>deepseek-chat</code>、<code>gpt-4o</code>、<code>claude-sonnet-4-6</code></td></tr>\n <tr><td><code>base_url</code></td><td>string</td><td>是</td><td>API 基地址,需指向实际接口。例:<code>https://api.deepseek.com/v1</code>、<code>http://localhost:11434</code></td></tr>\n <tr><td><code>context_window</code></td><td>integer</td><td>否</td><td>模型上下文窗口(tokens),默认 64000。<code>ollama</code> 默认 8000</td></tr>\n <tr><td><code>max_tokens</code></td><td>integer?</td><td>否</td><td>单次响应的最大输出 token 数。留空则使用 <code>context_window / 4</code></td></tr>\n <tr><td><code>system_prompt</code></td><td>string?</td><td>否</td><td>覆盖默认系统提示。大多数场景不需要</td></tr>\n <tr><td><code>user_agent</code></td><td>string?</td><td>否</td><td>覆盖 HTTP 请求的 User-Agent,当上游接口屏蔽通用 UA 时使用</td></tr>\n </tbody>\n </table>\n\n <div class=\"callout callout-info\">\n <strong>为什么默认 64K 而不是 128K?</strong>\n <p>即便模型官方声称支持 128K+,其有效注意力窗口通常要小得多,过大上下文反而会触发\"迷失在中段\"的问题,并且阻止 AtomCode 的上下文压缩策略生效。64K 是经验上最稳的默认值,若你确定模型表现良好,可自行上调。</p>\n </div>\n\n <h2>常见 provider 配置示例</h2>\n\n <h3>Claude (Anthropic)</h3>\n <pre><code>[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\ncontext_window = 128000</code></pre>\n\n <h3>OpenAI</h3>\n <pre><code>[providers.openai]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"gpt-4o\"\ncontext_window = 128000</code></pre>\n\n <h3>DeepSeek</h3>\n <pre><code>[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\ncontext_window = 64000</code></pre>\n\n <h3>智谱 GLM</h3>\n <pre><code>[providers.glm]\ntype = \"openai\"\napi_key = \"...\"\nmodel = \"glm-4-plus\"\nbase_url = \"https://open.bigmodel.cn/api/paas/v4\"\ncontext_window = 128000</code></pre>\n\n <h3>通义千问 Qwen</h3>\n <pre><code>[providers.qwen]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"qwen-plus\"\nbase_url = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\ncontext_window = 128000</code></pre>\n\n <h3>SiliconFlow</h3>\n <pre><code>[providers.siliconflow]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen2.5-72B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n\n <h3>Ollama(本地)</h3>\n <pre><code>[providers.ollama]\ntype = \"ollama\"\nmodel = \"llama3.2\"\nbase_url = \"http://localhost:11434\"\ncontext_window = 8000</code></pre>\n <div class=\"callout callout-warn\">\n <strong>注意</strong>\n <p>Ollama 模型 function calling 的兼容性参差不齐,弱一些的本地模型可能无法稳定触发工具调用。建议优先尝试 Qwen2.5 / Llama3.2 的 Instruct 版本。</p>\n </div>\n\n <h2>多 provider 与快速切换</h2>\n <p>配置文件允许同时存在任意数量的 provider:</p>\n <pre><code>default_provider = \"claude\"\n\n[providers.claude]\ntype = \"claude\"\napi_key = \"sk-ant-...\"\nmodel = \"claude-sonnet-4-6\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.local]\ntype = \"ollama\"\nmodel = \"qwen2.5:14b\"\nbase_url = \"http://localhost:11434\"</code></pre>\n <p>在 TUI 中可以用 <code>/provider</code> 在这些条目之间切换,用 <code>/model</code> 只切换当前 provider 下的模型。命令行启动时也支持一次性覆盖:</p>\n <pre><code>atomcode --provider deepseek --model deepseek-reasoner</code></pre>\n\n <h2 id=\"vision-preprocessor\">视觉预处理器</h2>\n <p>当当前 active provider 的模型不支持图片输入(纯文本模型,比如 DeepSeek-V3 / Kimi),用户却 <a href=\"./basic-usage.html#image-attach\">attach 了图片</a>时,AtomCode 不会直接拒绝,而是会把图片转给一个独立的\"视觉预处理 provider\"做 OCR + 描述,把结果以文本形式 splice 进用户消息里再发给主模型。</p>\n <p>启用方式:在配置里指定一个支持图片的 provider key 作为 <code>vision_preprocessor_provider</code>:</p>\n <pre><code>default_provider = \"deepseek\"\nvision_preprocessor_provider = \"qwen-vl\"\n\n[providers.deepseek]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"deepseek-chat\"\nbase_url = \"https://api.deepseek.com/v1\"\n\n[providers.qwen-vl]\ntype = \"openai\"\napi_key = \"sk-...\"\nmodel = \"Qwen/Qwen3-VL-32B-Instruct\"\nbase_url = \"https://api.siliconflow.cn/v1\"</code></pre>\n <ul>\n <li>这里的 <code>qwen-vl</code> 必须是 <code>[providers.*]</code> 里已经定义过的 key,且对应的模型支持视觉输入(常见的有 <code>Qwen3-VL-*</code> / <code>GLM-4V-*</code> / <code>claude-3+</code> / <code>gpt-4o</code> / <code>gemini-*</code> 等)。</li>\n <li>当前 active provider 自身就支持图片时,<strong>不会</strong>走预处理器,直接发原始图片字节。</li>\n <li><code>/codingplan</code> 流程会从下发的模型列表中自动挑选一个 VL/OCR 模型并写入这个字段(可在事后手动覆盖)。</li>\n <li>VL 调用使用<strong>无进展超时</strong>(idle timeout):只要 stream 持续吐 chunk 不限总时长;30 秒没动静才超时。失败时主模型仍会收到带 \"图片识别失败\" 标记的消息,而不是静默吞掉。</li>\n </ul>\n <p>用法层面的细节参见 <a href=\"./basic-usage.html#image-attach\">基本使用 · 图片附件 / 截图</a>。</p>\n\n <h2>编辑配置的三种方式</h2>\n <ul>\n <li><strong>手动编辑</strong> —— 直接用你喜欢的编辑器打开 <code>~/.atomcode/config.toml</code>。</li>\n <li><strong>TUI 内 <code>/config</code></strong> —— 在 atomcode 内部直接调用默认编辑器打开配置文件。</li>\n <li><strong>TUI 内 <code>/provider</code></strong> —— 交互式地添加、编辑、删除 provider,改动会自动落盘。</li>\n </ul>\n\n <h2>下一步</h2>\n <ul>\n <li><a href=\"./login.html\">登录方式</a> —— 如果不想手动管理 API Key,用 AtomGit OAuth 一步到位。</li>\n <li><a href=\"./basic-usage.html\">基本使用</a> —— 看看 CLI 参数如何一次性覆盖配置。</li>\n </ul>\n\n <footer class=\"dftr\">\n <span data-i18n=\"ftr.copy\">© 2026 AtomCode · MIT</span>\n <a href=\"https://atomgit.com/atomgit_atomcode/atomcode/issues\" target=\"_blank\" rel=\"noopener\" data-i18n=\"ftr.issue\">报告问题</a>\n </footer>\n </main>\n</div>\n\n<div class=\"search-modal\" id=\"searchModal\" role=\"dialog\" data-i18n-aria=\"aria.search\" aria-label=\"搜索文档\">\n <div class=\"search-modal-bg\"></div>\n <div class=\"search-modal-box\">\n <div class=\"search-input-wrap\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>\n <input id=\"searchInput\" type=\"search\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"搜索文档…\" autocomplete=\"off\">\n <span class=\"search-esc\">ESC</span>\n </div>\n <div class=\"search-results\" id=\"searchResults\"></div>\n </div>\n</div>\n\n<script src=\"../docs.js\"></script>\n</body>\n</html>\n"}}}} |