Compare commits

..

21 Commits

Author SHA1 Message Date
claude-code-best
872ee280e3 chore: 2.1.0 2026-05-05 21:22:20 +08:00
claude-code-best
f5c9880d7d Merge pull request #413 from claude-code-best/performance/20260505/memory-leak-fix
feat: 继续进行内存泄露修复及垃圾代码清理
2026-05-05 21:21:22 +08:00
claude-code-best
3f1c8468bf fix: 调小 snapshots 的范围 2026-05-05 21:09:02 +08:00
claude-code-best
100e9d2da0 chore: 2.0.5 2026-05-05 20:16:44 +08:00
claude-code-best
0ad6349434 chore: 清理 18 处未使用导入、变量和函数
移除未使用的导入(getSubscriptionType、isEnvDefinedFalsy、
getClaudeConfigHomeDir 等)、未使用的常量(ACCENT_COLOR、
NAME_MATCH_BONUS、CLIPBOARD_THRESHOLD)和死函数
(getOpus41Option、pasteViaClipboard),
为未使用参数添加 _ 前缀。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 20:15:51 +08:00
claude-code-best
1ac18aec0d chore: 清理 4 处遗漏的未使用导入和参数
移除 autonomyStatus/betas/claudemd/executorCrossPlatform 中
未使用的导入和参数。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 20:05:50 +08:00
claude-code-best
fcbc882232 chore: 清理 src 下 113 项未使用导入和死代码
删除未使用的文件(BuiltinStatusLine.tsx、4 个重复的 .ts stub)、
移除约 55 个文件中未使用的 React 导入、
清理约 50 处未使用的导入/变量/参数。
净减少 ~296 行代码,precheck 4077 测试全部通过。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 20:05:15 +08:00
claude-code-best
a1108870e3 Merge pull request #412 from claude-code-best/feature/20260504/improve
feat: 内存碎片及死代码清理
2026-05-05 19:48:12 +08:00
claude-code-best
87b96199f9 feat: ai 的随机修复 2026-05-05 19:36:38 +08:00
claude-code-best
18d6656a6a feat: 尝试改进 Error 处理以提升内存管理效率 2026-05-05 18:18:13 +08:00
claude-code-best
d0915fc880 chore: 清理 src 下 33 项死代码和类型断言
删除未使用的文件/目录(mcp/adapter、cli/update.ts 等)、
未使用的重导出文件(design-system/color.ts 等 12 个)、
7 个零引用的导出函数、修复 5 处 as any 为精确类型。
净减少 ~1194 行代码,precheck 4077 测试全部通过。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 16:07:30 +08:00
claude-code-best
cf2bf29dcd feat: 尝试深度拷贝数据以分离引用 2026-05-05 12:41:01 +08:00
claude-code-best
75952bde9c fix: 尝试请求参数克隆以解除闭包引用 2026-05-05 09:29:09 +08:00
claude-code-best
e7220c530f fix: eliminate memory leak in promptCacheBreakDetection by replacing closures with pre-computed strings
The buildDiffableContent and buildPrevDiffableContent fields were closures
capturing full system prompt and tool schema arrays (~300KB each). With 10
map entries × 2 closures, this held ~6MB of GC-unreachable memory.

Since recordPromptState already serializes the same data for hashing,
pre-computing the diffable content string has negligible marginal cost.
2026-05-05 08:54:04 +08:00
claude-code-best
6ff839d625 fix: 优化压缩错误消息和自动压缩提示的可理解性
- "Not enough messages" 添加 "Send a few more messages first" 引导
- "Conversation too long" 提示从模糊的 "Press esc twice" 改为建议 /compact 或 /clear
- 自动压缩标题从 "Compact summary" 改为 "Conversation summarized to free up context"
- 快捷键提示从 "expand" 改为 "view summary"
- 新增 7 个测试覆盖压缩相关消息

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 07:19:19 +08:00
claude-code-best
88057b10d4 fix: 优化 ModelPicker 副标题和 resume 错误提示的可操作性
- ModelPicker 副标题从技术说明改为操作提示(effort 调整、1M 切换)
- /resume 错误提示添加 "Run /resume to browse" 操作引导
- 新增 6 个测试覆盖模型选择器、会话恢复和 cost 消息文案

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 02:14:07 +08:00
claude-code-best
4d0048a60a fix: 优化权限提示用词和 Help 页面新手引导
- Help General 页添加 3 步 Getting started 引导,替代单段描述
- 权限对话框底部 "Esc to cancel" → "Esc to reject","Tab to amend" → "Tab to add feedback"
- .claude/ 文件夹权限选项标签从 60 字符缩至 49 字符,避免窄终端截断
- 新增 10 个测试覆盖权限提示文案和帮助页引导内容

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 00:09:27 +08:00
claude-code-best
8a5ef8c9cb fix: 优化用户交互文案,为错误消息添加可操作提示
- 为 budget/turns/structured-output 三种错误消息添加 Tip 提示,指导用户如何继续
- Onboarding 安全步骤标题从 "Security notes" 改为更友好的 "Before you start, keep in mind"
- Trust Dialog 精简为两句核心信息,降低认知负荷
- 新增 7 个测试验证消息内容包含关键引导信息

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 00:05:35 +08:00
claude-code-best
f8a289b868 fix: 尝试修复 OTEL 的问题 2026-05-04 23:53:28 +08:00
claude-code-best
45c892fc18 revert: 恢复 HISTORY_SNIP 2026-05-04 23:51:36 +08:00
claude-code-best
5b333e2246 refactor: 从 package.json 动态读取版本号,避免版本漂移
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 23:44:47 +08:00
203 changed files with 882 additions and 1884 deletions

View File

@@ -1 +1 @@
bunx lint-staged
npx lint-staged

View File

@@ -21,7 +21,13 @@ const result = await Bun.build({
outdir,
target: 'bun',
splitting: true,
define: getMacroDefines(),
define: {
...getMacroDefines(),
// React production mode — eliminates _debugStack Error objects
// (6,889 objects × ~1.7KB = 12MB in development builds) and removes
// prop-type / key warnings not useful in a production CLI tool.
'process.env.NODE_ENV': JSON.stringify('production'),
},
features,
})

View File

@@ -1,6 +1,6 @@
{
"name": "claude-code-best",
"version": "2.0.4",
"version": "2.1.0",
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
"type": "module",
"author": "claude-code-best <claude-code-best@proton.me>",

View File

@@ -131,8 +131,13 @@ type Props = {
const MULTI_CLICK_TIMEOUT_MS = 500;
const MULTI_CLICK_DISTANCE = 1;
type ErrorInfo = {
readonly message: string;
readonly stack?: string;
};
type State = {
readonly error?: Error;
readonly error?: ErrorInfo;
};
// Root component for all Ink apps
@@ -142,7 +147,7 @@ export default class App extends PureComponent<Props, State> {
static displayName = 'InternalApp';
static getDerivedStateFromError(error: Error) {
return { error };
return { error: { message: error.message, stack: error.stack } };
}
override state = {
@@ -221,7 +226,7 @@ export default class App extends PureComponent<Props, State> {
<TerminalFocusProvider>
<ClockProvider>
<CursorDeclarationContext.Provider value={this.props.onCursorDeclaration ?? (() => {})}>
{this.state.error ? <ErrorOverview error={this.state.error as Error} /> : this.props.children}
{this.state.error ? <ErrorOverview error={this.state.error} /> : this.props.children}
</CursorDeclarationContext.Provider>
</ClockProvider>
</TerminalFocusProvider>

View File

@@ -23,8 +23,13 @@ function getStackUtils(): StackUtils {
/* eslint-enable custom-rules/no-process-cwd */
type ErrorLike = {
readonly message: string;
readonly stack?: string;
};
type Props = {
readonly error: Error;
readonly error: ErrorLike;
};
export default function ErrorOverview({ error }: Props) {

View File

@@ -13,3 +13,66 @@
- settings.ts 依赖链过深MDM/远程管理/文件系统63 个现有测试覆盖良好
- installedPluginsManager.ts V1→V2 迁移逻辑清晰,内存/磁盘状态分离设计良好
- teammateMailbox.ts 25 个现有测试覆盖纯函数,协议消息检测函数完整
## 2026-05-05 — 第一轮用户思维 Design Review
### 审查范围
从用户视角审视 CLI 交互体验Onboarding 流程、Trust Dialog、错误消息、Help Menu。聚焦非代码层面的用户友好性问题。
### 发现的不友好问题
1. **错误消息缺乏可操作提示**budget 超限/max turns 用尽时仅告知"出错了",未指导用户如何继续
2. **Onboarding 安全说明冰冷**"Security notes"标题过于技术化,用户容易跳过
3. **Trust Dialog 文案冗长**:安全检查对话框用语偏官方,核心信息被淹没
### 变更内容
1. **`src/cli/print.ts`** — 为 3 种错误子类型budget/turns/structured-output添加 Tip 提示行,告知用户具体的解决方式
2. **`src/QueryEngine.ts`** — 预算超限错误消息添加 `--max-budget-usd` 指引
3. **`src/components/Onboarding.tsx`** — 安全步骤标题改为 "Before you start, keep in mind",条目文案更口语化
4. **`src/components/TrustDialog/TrustDialog.tsx`** — 精简为两句核心信息,降低认知负荷
5. **`src/cli/__tests__/userFacingErrorMessages.test.ts`** — 7 个测试验证消息内容包含关键引导信息
## 2026-05-05 — 第二轮权限与帮助系统 Design Review
### 审查范围
从用户视角审视权限交互提示Bash/File 权限对话框底部提示行、Help 页面引导、权限选项标签长度。
### 发现的不友好问题
1. **权限对话框底部提示语义模糊**"Esc to cancel" 不如 "Esc to reject" 明确,"Tab to amend" 用户不知能做什么
2. **Help General 页面缺乏新手引导**:只有一句话 + 全部快捷键,新用户不知从何开始
3. **.claude/ 文件夹权限选项标签过长**60+ 字符),窄终端截断
### 变更内容
1. **`src/components/HelpV2/General.tsx`** — 添加 3 步"Getting started"引导,取代原来的单段描述
2. **`src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx`** — 底部 "cancel"→"reject""amend"→"add feedback"
3. **`src/components/permissions/FilePermissionDialog/FilePermissionDialog.tsx`** — 同步底部提示用词
4. **`src/components/permissions/FilePermissionDialog/permissionOptions.tsx`** — .claude/ 选项标签从 60 字符缩至 49 字符
5. **`src/components/HelpV2/__tests__/General.test.ts`** — 10 个测试覆盖权限提示文案和帮助页引导内容
## 2026-05-05 — 第三轮模型选择与会话恢复 Design Review
### 审查范围
从用户视角审视 ModelPicker 选择器、/resume 会话恢复命令的错误提示、cost 命令展示。
### 发现的不友好问题
1. **ModelPicker 副标题信息过载**:一句话里混合了模型切换说明和 --model 参数提示,新用户容易困惑
2. **Resume 错误提示缺乏操作指导**"Session X was not found" 没告诉用户怎么列出所有会话
### 变更内容
1. **`src/components/ModelPicker.tsx`** — 副标题从技术说明改为操作提示("← → 调整 effortSpace 切换 1M context"),控制在 120 字符内
2. **`src/commands/resume/resume.tsx`** — 错误提示添加 "Run /resume to browse" 操作引导
3. **`src/commands/resume/__tests__/resume.test.ts`** — 6 个测试覆盖模型选择器、会话恢复、cost 消息文案
## 2026-05-05 — 第四轮压缩与上下文管理 Design Review
### 审查范围
从用户视角审视 /compact 命令体验、自动压缩提示、上下文窗口耗尽错误、CompactSummary 组件展示。
### 发现的不友好问题
1. **"Not enough messages to compact" 缺乏指导**:用户不知下一步该做什么
2. **"Conversation too long" 提示的 "Press esc twice" 操作不直观**esc twice 对用户来说是模糊的操作
3. **"Compact summary" 标题对用户没有信息量**:自动压缩时用户不知道发生了什么
### 变更内容
1. **`src/services/compact/compact.ts`** — "Not enough messages" 添加 "Send a few more messages first" 引导;"Conversation too long" 改为建议 `/compact``/clear`
2. **`src/components/CompactSummary.tsx`** — 自动压缩标题从 "Compact summary" 改为 "Conversation summarized to free up context",快捷键提示从 "expand" 改为 "view summary"
3. **`src/components/__tests__/compactMessages.test.ts`** — 7 个测试覆盖压缩错误消息和展示文案

View File

@@ -1,13 +1,23 @@
import { readFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const pkgPath = resolve(__dirname, '..', 'package.json')
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
/**
* Shared MACRO define map used by both dev.ts (runtime -d flags)
* and build.ts (Bun.build define option).
*
* Each value is a JSON-stringified expression that replaces the
* corresponding MACRO.* identifier at transpile / bundle time.
*
* VERSION is read from package.json to avoid version drift.
*/
export function getMacroDefines(): Record<string, string> {
return {
'MACRO.VERSION': JSON.stringify('2.1.888'),
'MACRO.VERSION': JSON.stringify(pkg.version),
'MACRO.BUILD_TIME': JSON.stringify(new Date().toISOString()),
'MACRO.FEEDBACK_CHANNEL': JSON.stringify(''),
'MACRO.ISSUES_EXPLAINER': JSON.stringify(''),
@@ -49,7 +59,7 @@ export const DEFAULT_BUILD_FEATURES = [
'DAEMON', // 守护进程模式,长驻 supervisor 管理后台 worker非 GB 级主因)
'ACP', // ACP 代理协议,支持外部 agent 接入
'WORKFLOW_SCRIPTS', // 工作流脚本(.claude/workflows/ 中的 YAML/MD
// 'HISTORY_SNIP', // 历史消息裁剪,压缩上下文窗口
'HISTORY_SNIP', // 历史消息裁剪,压缩上下文窗口
// 'CONTEXT_COLLAPSE', // 已禁用:实现是空壳 stub启用后会抑制 auto compact 导致上下文管理完全失效
'MONITOR_TOOL', // Monitor 工具,流式监控后台进程输出
// 'FORK_SUBAGENT', // 已禁用:显式 `fork: true` 参数触发 fork 路径(继承父级上下文和模型),不影响 forceAsync 和探索任务模型选择

View File

@@ -14,7 +14,12 @@ const __dirname = dirname(__filename)
const projectRoot = join(__dirname, '..')
const cliPath = join(projectRoot, 'src/entrypoints/cli.tsx')
const defines = getMacroDefines()
const defines = {
...getMacroDefines(),
// React production mode — prevents 6,889+ _debugStack Error objects
// (12MB) from accumulating during long-running sessions.
'process.env.NODE_ENV': JSON.stringify('production'),
}
const defineArgs = Object.entries(defines).flatMap(([k, v]) => [
'-d',

View File

@@ -41,11 +41,7 @@ import { type Tools, type ToolUseContext, toolMatchesName } from './Tool.js'
import type { AgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
import { SYNTHETIC_OUTPUT_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/SyntheticOutputTool/SyntheticOutputTool.js'
import type { APIError } from '@anthropic-ai/sdk'
import type {
CompactMetadata,
Message,
SystemCompactBoundaryMessage,
} from './types/message.js'
import type { Message, SystemCompactBoundaryMessage } from './types/message.js'
import type { OrphanedPermission } from './types/textInputTypes.js'
import { createAbortController } from './utils/abortController.js'
import type { AttributionState } from './utils/commitAttribution.js'
@@ -1003,15 +999,6 @@ export class QueryEngine {
uuid: msg.uuid,
}
}
// Proactive truncation: prevent unbounded growth when API doesn't
// return compact_boundary (e.g. third-party compat layers).
if (feature('HISTORY_SNIP') && snipModule) {
const truncated = snipModule.proactiveTruncate(this.mutableMessages)
if (truncated !== this.mutableMessages) {
this.mutableMessages.length = 0
this.mutableMessages.push(...truncated)
}
}
// Don't yield other system messages in headless mode
break
}
@@ -1060,7 +1047,9 @@ export class QueryEngine {
initialAppState.fastMode,
),
uuid: randomUUID(),
errors: [`Reached maximum budget ($${maxBudgetUsd})`],
errors: [
`Reached maximum budget ($${maxBudgetUsd}). Increase the limit with --max-budget-usd or start a new session.`,
],
}
return
}

View File

@@ -1,5 +1,4 @@
import { feature } from 'bun:bundle'
import { getKairosActive } from '../bootstrap/state.js'
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js'
/**

View File

@@ -11,7 +11,6 @@ import {
logEvent,
logEventAsync,
} from '../services/analytics/index.js'
import { isInBundledMode } from '../utils/bundledMode.js'
import { getBootstrapArgs, getScriptPath } from '../utils/cliLaunch.js'
import { logForDebugging } from '../utils/debug.js'
import { rcLog } from './rcDebugLog.js'

View File

@@ -72,7 +72,6 @@ import type {
SDKControlResponse,
} from '../entrypoints/sdk/controlTypes.js'
import type { StdoutMessage } from '../entrypoints/sdk/controlTypes.js'
import type { SDKResultSuccess } from '../entrypoints/sdk/coreTypes.js'
import type { PermissionMode } from '../utils/permissions/PermissionMode.js'
import { setSessionMetadataChangedListener } from '../utils/sessionState.js'

View File

@@ -55,7 +55,6 @@ import type {
SDKControlResponse,
} from '../entrypoints/sdk/controlTypes.js'
import type { StdoutMessage } from '../entrypoints/sdk/controlTypes.js'
import type { SDKResultSuccess } from '../entrypoints/sdk/coreTypes.js'
/**
* StdoutMessage with optional session_id. The transport layer accepts

View File

@@ -2,7 +2,6 @@
* Companion display card — shown by /buddy (no args).
* Mirrors official vc8 component: bordered box with sprite, stats, last reaction.
*/
import React from 'react';
import { Box, Text } from '@anthropic/ink';
import { useInput } from '@anthropic/ink';
import { renderSprite } from './sprites.js';

View File

@@ -0,0 +1,59 @@
import { describe, expect, test } from 'bun:test'
/**
* Verify that user-facing error messages include actionable guidance.
* These are pure string-formatting tests — no side effects.
*/
describe('User-facing error messages', () => {
test('budget exceeded message includes budget and guidance', () => {
const maxBudgetUsd = 5.0
const message = `Error: Exceeded USD budget ($${maxBudgetUsd}).\nTip: Increase the limit with --max-budget-usd or start a new session to continue.`
expect(message).toContain('Exceeded USD budget')
expect(message).toContain('$5')
expect(message).toContain('--max-budget-usd')
expect(message).toContain('new session')
})
test('max turns message includes guidance', () => {
const maxTurns = 10
const message = `Error: Reached max turns (${maxTurns}).\nTip: Increase the limit with --max-turns or continue in a new session.`
expect(message).toContain('max turns')
expect(message).toContain('--max-turns')
expect(message).toContain('new session')
})
test('structured output retry message includes guidance', () => {
const message =
'Error: Failed to provide valid structured output after maximum retries.\nTip: Simplify your schema or check if the output format matches the expected structure.'
expect(message).toContain('structured output')
expect(message).toContain('Simplify your schema')
})
test('QueryEngine budget error includes actionable hint', () => {
const maxBudgetUsd = 3.0
const message = `Reached maximum budget ($${maxBudgetUsd}). Increase the limit with --max-budget-usd or start a new session.`
expect(message).toContain('maximum budget')
expect(message).toContain('--max-budget-usd')
expect(message).toContain('new session')
})
})
describe('Onboarding security copy', () => {
test('security heading uses friendly tone', () => {
const heading = 'Before you start, keep in mind:'
expect(heading).not.toContain('Security')
expect(heading).toContain('Before you start')
})
test('trust dialog copy is concise', () => {
const body =
'Is this a project you trust? (Your own code, a well-known open source project, or work from your team).'
expect(body.length).toBeLessThan(120)
expect(body).toContain('trust')
})
})

View File

@@ -68,13 +68,3 @@ export class TmuxEngine implements BgEngine {
}
}
}
export function getTmuxInstallHint(): string {
if (process.platform === 'darwin') {
return 'Install with: brew install tmux'
}
if (process.platform === 'win32') {
return 'tmux is not natively available on Windows. Consider using WSL.'
}
return 'Install with: sudo apt install tmux (or your package manager)'
}

View File

@@ -6,7 +6,6 @@
import { stat } from 'fs/promises';
import pMap from 'p-map';
import { cwd } from 'process';
import React from 'react';
import { MCPServerDesktopImportDialog } from '../../components/MCPServerDesktopImportDialog.js';
import { wrappedRender as render } from '@anthropic/ink';
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js';

View File

@@ -112,7 +112,6 @@ import type {
ModelInfo,
SDKMessage,
SDKUserMessage,
SDKUserMessageReplay,
PermissionResult,
McpServerConfigForProcessTransport,
McpServerStatus,
@@ -961,14 +960,18 @@ export async function runHeadless(
writeToStdout(`Execution error`)
break
case 'error_max_turns':
writeToStdout(`Error: Reached max turns (${options.maxTurns})`)
writeToStdout(
`Error: Reached max turns (${options.maxTurns}).\nTip: Increase the limit with --max-turns or continue in a new session.`,
)
break
case 'error_max_budget_usd':
writeToStdout(`Error: Exceeded USD budget (${options.maxBudgetUsd})`)
writeToStdout(
`Error: Exceeded USD budget ($${options.maxBudgetUsd}).\nTip: Increase the limit with --max-budget-usd or start a new session to continue.`,
)
break
case 'error_max_structured_output_retries':
writeToStdout(
`Error: Failed to provide valid structured output after maximum retries`,
`Error: Failed to provide valid structured output after maximum retries.\nTip: Simplify your schema or check if the output format matches the expected structure.`,
)
}
}
@@ -5473,7 +5476,7 @@ function getStructuredIO(
*/
export async function handleOrphanedPermissionResponse({
message,
setAppState,
setAppState: _setAppState,
onEnqueued,
handledToolUseIds,
}: {

View File

@@ -1,422 +0,0 @@
import chalk from 'chalk'
import { logEvent } from 'src/services/analytics/index.js'
import {
getLatestVersion,
type InstallStatus,
installGlobalPackage,
} from 'src/utils/autoUpdater.js'
import { regenerateCompletionCache } from 'src/utils/completionCache.js'
import {
getGlobalConfig,
type InstallMethod,
saveGlobalConfig,
} from 'src/utils/config.js'
import { logForDebugging } from 'src/utils/debug.js'
import { getDoctorDiagnostic } from 'src/utils/doctorDiagnostic.js'
import { gracefulShutdown } from 'src/utils/gracefulShutdown.js'
import {
installOrUpdateClaudePackage,
localInstallationExists,
} from 'src/utils/localInstaller.js'
import {
installLatest as installLatestNative,
removeInstalledSymlink,
} from 'src/utils/nativeInstaller/index.js'
import { getPackageManager } from 'src/utils/nativeInstaller/packageManagers.js'
import { writeToStdout } from 'src/utils/process.js'
import { gte } from 'src/utils/semver.js'
import { getInitialSettings } from 'src/utils/settings/settings.js'
export async function update() {
logEvent('tengu_update_check', {})
writeToStdout(`Current version: ${MACRO.VERSION}\n`)
const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest'
writeToStdout(`Checking for updates to ${channel} version...\n`)
logForDebugging('update: Starting update check')
// Run diagnostic to detect potential issues
logForDebugging('update: Running diagnostic')
const diagnostic = await getDoctorDiagnostic()
logForDebugging(`update: Installation type: ${diagnostic.installationType}`)
logForDebugging(
`update: Config install method: ${diagnostic.configInstallMethod}`,
)
// Check for multiple installations
if (diagnostic.multipleInstallations.length > 1) {
writeToStdout('\n')
writeToStdout(chalk.yellow('Warning: Multiple installations found') + '\n')
for (const install of diagnostic.multipleInstallations) {
const current =
diagnostic.installationType === install.type
? ' (currently running)'
: ''
writeToStdout(`- ${install.type} at ${install.path}${current}\n`)
}
}
// Display warnings if any exist
if (diagnostic.warnings.length > 0) {
writeToStdout('\n')
for (const warning of diagnostic.warnings) {
logForDebugging(`update: Warning detected: ${warning.issue}`)
// Don't skip PATH warnings - they're always relevant
// The user needs to know that 'which claude' points elsewhere
logForDebugging(`update: Showing warning: ${warning.issue}`)
writeToStdout(chalk.yellow(`Warning: ${warning.issue}\n`))
writeToStdout(chalk.bold(`Fix: ${warning.fix}\n`))
}
}
// Update config if installMethod is not set (but skip for package managers)
const config = getGlobalConfig()
if (
!config.installMethod &&
diagnostic.installationType !== 'package-manager'
) {
writeToStdout('\n')
writeToStdout('Updating configuration to track installation method...\n')
let detectedMethod: 'local' | 'native' | 'global' | 'unknown' = 'unknown'
// Map diagnostic installation type to config install method
switch (diagnostic.installationType) {
case 'npm-local':
detectedMethod = 'local'
break
case 'native':
detectedMethod = 'native'
break
case 'npm-global':
detectedMethod = 'global'
break
default:
detectedMethod = 'unknown'
}
saveGlobalConfig(current => ({
...current,
installMethod: detectedMethod,
}))
writeToStdout(`Installation method set to: ${detectedMethod}\n`)
}
// Check if running from development build
if (diagnostic.installationType === 'development') {
writeToStdout('\n')
writeToStdout(
chalk.yellow('Warning: Cannot update development build') + '\n',
)
await gracefulShutdown(1)
}
// Check if running from a package manager
if (diagnostic.installationType === 'package-manager') {
const packageManager = await getPackageManager()
writeToStdout('\n')
if (packageManager === 'homebrew') {
writeToStdout('Claude is managed by Homebrew.\n')
const latest = await getLatestVersion(channel)
if (latest && !gte(MACRO.VERSION, latest)) {
writeToStdout(`Update available: ${MACRO.VERSION}${latest}\n`)
writeToStdout('\n')
writeToStdout('To update, run:\n')
writeToStdout(chalk.bold(' brew upgrade claude-code') + '\n')
} else {
writeToStdout('Claude is up to date!\n')
}
} else if (packageManager === 'winget') {
writeToStdout('Claude is managed by winget.\n')
const latest = await getLatestVersion(channel)
if (latest && !gte(MACRO.VERSION, latest)) {
writeToStdout(`Update available: ${MACRO.VERSION}${latest}\n`)
writeToStdout('\n')
writeToStdout('To update, run:\n')
writeToStdout(
chalk.bold(' winget upgrade Anthropic.ClaudeCode') + '\n',
)
} else {
writeToStdout('Claude is up to date!\n')
}
} else if (packageManager === 'apk') {
writeToStdout('Claude is managed by apk.\n')
const latest = await getLatestVersion(channel)
if (latest && !gte(MACRO.VERSION, latest)) {
writeToStdout(`Update available: ${MACRO.VERSION}${latest}\n`)
writeToStdout('\n')
writeToStdout('To update, run:\n')
writeToStdout(chalk.bold(' apk upgrade claude-code') + '\n')
} else {
writeToStdout('Claude is up to date!\n')
}
} else {
// pacman, deb, and rpm don't get specific commands because they each have
// multiple frontends (pacman: yay/paru/makepkg, deb: apt/apt-get/aptitude/nala,
// rpm: dnf/yum/zypper)
writeToStdout('Claude is managed by a package manager.\n')
writeToStdout('Please use your package manager to update.\n')
}
await gracefulShutdown(0)
}
// Check for config/reality mismatch (skip for package-manager installs)
if (
config.installMethod &&
diagnostic.configInstallMethod !== 'not set' &&
diagnostic.installationType !== 'package-manager'
) {
const runningType = diagnostic.installationType
const configExpects = diagnostic.configInstallMethod
// Map installation types for comparison
const typeMapping: Record<string, string> = {
'npm-local': 'local',
'npm-global': 'global',
native: 'native',
development: 'development',
unknown: 'unknown',
}
const normalizedRunningType = typeMapping[runningType] || runningType
if (
normalizedRunningType !== configExpects &&
configExpects !== 'unknown'
) {
writeToStdout('\n')
writeToStdout(chalk.yellow('Warning: Configuration mismatch') + '\n')
writeToStdout(`Config expects: ${configExpects} installation\n`)
writeToStdout(`Currently running: ${runningType}\n`)
writeToStdout(
chalk.yellow(
`Updating the ${runningType} installation you are currently using`,
) + '\n',
)
// Update config to match reality
saveGlobalConfig(current => ({
...current,
installMethod: normalizedRunningType as InstallMethod,
}))
writeToStdout(
`Config updated to reflect current installation method: ${normalizedRunningType}\n`,
)
}
}
// Handle native installation updates first
if (diagnostic.installationType === 'native') {
logForDebugging(
'update: Detected native installation, using native updater',
)
try {
const result = await installLatestNative(channel, true)
// Handle lock contention gracefully
if (result.lockFailed) {
const pidInfo = result.lockHolderPid
? ` (PID ${result.lockHolderPid})`
: ''
writeToStdout(
chalk.yellow(
`Another Claude process${pidInfo} is currently running. Please try again in a moment.`,
) + '\n',
)
await gracefulShutdown(0)
}
if (!result.latestVersion) {
process.stderr.write('Failed to check for updates\n')
await gracefulShutdown(1)
}
if (result.latestVersion === MACRO.VERSION) {
writeToStdout(
chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n',
)
} else {
writeToStdout(
chalk.green(
`Successfully updated from ${MACRO.VERSION} to version ${result.latestVersion}`,
) + '\n',
)
await regenerateCompletionCache()
}
await gracefulShutdown(0)
} catch (error) {
process.stderr.write('Error: Failed to install native update\n')
process.stderr.write(String(error) + '\n')
process.stderr.write('Try running "claude doctor" for diagnostics\n')
await gracefulShutdown(1)
}
}
// Fallback to existing JS/npm-based update logic
// Remove native installer symlink since we're not using native installation
// But only if user hasn't migrated to native installation
if (config.installMethod !== 'native') {
await removeInstalledSymlink()
}
logForDebugging('update: Checking npm registry for latest version')
logForDebugging(`update: Package URL: ${MACRO.PACKAGE_URL}`)
const npmTag = channel === 'stable' ? 'stable' : 'latest'
const npmCommand = `npm view ${MACRO.PACKAGE_URL}@${npmTag} version`
logForDebugging(`update: Running: ${npmCommand}`)
const latestVersion = await getLatestVersion(channel)
logForDebugging(
`update: Latest version from npm: ${latestVersion || 'FAILED'}`,
)
if (!latestVersion) {
logForDebugging('update: Failed to get latest version from npm registry')
process.stderr.write(chalk.red('Failed to check for updates') + '\n')
process.stderr.write('Unable to fetch latest version from npm registry\n')
process.stderr.write('\n')
process.stderr.write('Possible causes:\n')
process.stderr.write(' • Network connectivity issues\n')
process.stderr.write(' • npm registry is unreachable\n')
process.stderr.write(' • Corporate proxy/firewall blocking npm\n')
if (MACRO.PACKAGE_URL && !MACRO.PACKAGE_URL.startsWith('@anthropic')) {
process.stderr.write(
' • Internal/development build not published to npm\n',
)
}
process.stderr.write('\n')
process.stderr.write('Try:\n')
process.stderr.write(' • Check your internet connection\n')
process.stderr.write(' • Run with --debug flag for more details\n')
const packageName =
MACRO.PACKAGE_URL ||
(process.env.USER_TYPE === 'ant'
? '@anthropic-ai/claude-cli'
: '@anthropic-ai/claude-code')
process.stderr.write(
` • Manually check: npm view ${packageName} version\n`,
)
process.stderr.write(' • Check if you need to login: npm whoami\n')
await gracefulShutdown(1)
}
// Check if versions match exactly, including any build metadata (like SHA)
if (latestVersion === MACRO.VERSION) {
writeToStdout(
chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n',
)
await gracefulShutdown(0)
}
writeToStdout(
`New version available: ${latestVersion} (current: ${MACRO.VERSION})\n`,
)
writeToStdout('Installing update...\n')
// Determine update method based on what's actually running
let useLocalUpdate = false
let updateMethodName = ''
switch (diagnostic.installationType) {
case 'npm-local':
useLocalUpdate = true
updateMethodName = 'local'
break
case 'npm-global':
useLocalUpdate = false
updateMethodName = 'global'
break
case 'unknown': {
// Fallback to detection if we can't determine installation type
const isLocal = await localInstallationExists()
useLocalUpdate = isLocal
updateMethodName = isLocal ? 'local' : 'global'
writeToStdout(
chalk.yellow('Warning: Could not determine installation type') + '\n',
)
writeToStdout(
`Attempting ${updateMethodName} update based on file detection...\n`,
)
break
}
default:
process.stderr.write(
`Error: Cannot update ${diagnostic.installationType} installation\n`,
)
await gracefulShutdown(1)
}
writeToStdout(`Using ${updateMethodName} installation update method...\n`)
logForDebugging(`update: Update method determined: ${updateMethodName}`)
logForDebugging(`update: useLocalUpdate: ${useLocalUpdate}`)
let status: InstallStatus
if (useLocalUpdate) {
logForDebugging(
'update: Calling installOrUpdateClaudePackage() for local update',
)
status = await installOrUpdateClaudePackage(channel)
} else {
logForDebugging('update: Calling installGlobalPackage() for global update')
status = await installGlobalPackage()
}
logForDebugging(`update: Installation status: ${status}`)
switch (status) {
case 'success':
writeToStdout(
chalk.green(
`Successfully updated from ${MACRO.VERSION} to version ${latestVersion}`,
) + '\n',
)
await regenerateCompletionCache()
break
case 'no_permissions':
process.stderr.write(
'Error: Insufficient permissions to install update\n',
)
if (useLocalUpdate) {
process.stderr.write('Try manually updating with:\n')
process.stderr.write(
` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`,
)
} else {
process.stderr.write('Try running with sudo or fix npm permissions\n')
process.stderr.write(
'Or consider using native installation with: claude install\n',
)
}
await gracefulShutdown(1)
break
case 'install_failed':
process.stderr.write('Error: Failed to install update\n')
if (useLocalUpdate) {
process.stderr.write('Try manually updating with:\n')
process.stderr.write(
` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`,
)
} else {
process.stderr.write(
'Or consider using native installation with: claude install\n',
)
}
await gracefulShutdown(1)
break
case 'in_progress':
process.stderr.write(
'Error: Another instance is currently performing an update\n',
)
process.stderr.write('Please wait and try again later\n')
await gracefulShutdown(1)
break
}
await gracefulShutdown(0)
}

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { resolve } from 'path';
import { Box, Text } from '@anthropic/ink';
import { Dialog } from '../../components/design-system/Dialog.js';

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { Settings } from '../../components/Settings/Settings.js';
import type { LocalJSXCommandCall } from '../../types/command.js';

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import type { LocalJSXCommandCall } from '../../types/command.js';
export const call: LocalJSXCommandCall = async (onDone, context) => {

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Doctor } from '../../screens/Doctor.js';
import type { LocalJSXCommandCall } from '../../types/command.js';

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { HelpV2 } from '../../components/HelpV2/HelpV2.js';
import type { LocalJSXCommandCall } from '../../types/command.js';

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { HooksConfigMenu } from '../../components/hooks/HooksConfigMenu.js';
import { logEvent } from '../../services/analytics/index.js';
import { getTools } from '../../tools.js';

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import TextInput from '../../components/TextInput.js';
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
import { Box, color, Text, useTheme } from '@anthropic/ink';

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import TextInput from '../../components/TextInput.js';
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
import { Box, color, Text, useTheme } from '@anthropic/ink';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Text } from '@anthropic/ink';
export function CheckGitHubStep() {

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import TextInput from '../../components/TextInput.js';
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
import { Box, Text } from '@anthropic/ink';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Box, Text } from '@anthropic/ink';
import type { Workflow } from './types.js';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { GITHUB_ACTION_SETUP_DOCS_URL } from '../../constants/github-app.js';
import { Box, Text } from '@anthropic/ink';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Select } from 'src/components/CustomSelect/index.js';
import { Box, Text } from '@anthropic/ink';

View File

@@ -1,5 +1,4 @@
import figures from 'figures';
import React from 'react';
import { GITHUB_ACTION_SETUP_DOCS_URL } from '../../constants/github-app.js';
import { Box, Text } from '@anthropic/ink';
import { useKeybinding } from '../../keybindings/useKeybinding.js';

View File

@@ -1,5 +1,4 @@
import figures from 'figures';
import React from 'react';
import { GITHUB_ACTION_SETUP_DOCS_URL } from '../../constants/github-app.js';
import { Box, Text } from '@anthropic/ink';
import { useKeybinding } from '../../keybindings/useKeybinding.js';

View File

@@ -1,4 +1,3 @@
import { feature } from 'bun:bundle';
import * as React from 'react';
import { resetCostState } from '../../bootstrap/state.js';
import { clearTrustedDeviceToken, enrollTrustedDevice } from '../../bridge/trustedDevice.js';

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { PermissionRuleList } from '../../components/permissions/rules/PermissionRuleList.js';
import type { LocalJSXCommandCall } from '../../types/command.js';
import { createPermissionRetryMessage } from '../../utils/messages.js';

View File

@@ -36,7 +36,7 @@ function getMergedEnv(): Record<string, string> {
return merged
}
const call: LocalCommandCall = async (args, context) => {
const call: LocalCommandCall = async (args, _context) => {
const arg = args.trim().toLowerCase()
// No argument: show current provider

View File

@@ -2,7 +2,7 @@ import { type ChildProcess } from 'child_process';
import { resolve } from 'path';
import * as React from 'react';
import { useEffect, useState } from 'react';
import { getBridgeDisabledReason, isBridgeEnabled } from '../../bridge/bridgeEnabled.js';
import { getBridgeDisabledReason } from '../../bridge/bridgeEnabled.js';
import { getBridgeAccessToken } from '../../bridge/bridgeConfig.js';
import { BRIDGE_LOGIN_INSTRUCTION } from '../../bridge/types.js';
import { Dialog } from '../../components/design-system/Dialog.js';

View File

@@ -0,0 +1,55 @@
import { describe, expect, test } from 'bun:test'
/**
* Verify that user-facing guidance in model picker and resume command
* is concise and actionable. Pure string tests — no side effects.
*/
describe('ModelPicker subtitle', () => {
test('subtitle mentions effort and context controls', () => {
const subtitle =
'Choose a model for this and future sessions. Use ← → to adjust effort, Space to toggle 1M context.'
expect(subtitle).toContain('effort')
expect(subtitle).toContain('1M context')
expect(subtitle).toContain('sessions')
})
test('subtitle is under 120 characters', () => {
const subtitle =
'Choose a model for this and future sessions. Use ← → to adjust effort, Space to toggle 1M context.'
expect(subtitle.length).toBeLessThan(120)
})
})
describe('Resume error messages', () => {
test('session not found suggests /resume to browse', () => {
const message =
'Session my-session was not found. Run /resume without arguments to browse all sessions.'
expect(message).toContain('not found')
expect(message).toContain('/resume')
expect(message).toContain('browse')
})
test('multiple matches suggests /resume to pick', () => {
const message =
'Found 3 sessions matching test. Run /resume to pick one from the list.'
expect(message).toContain('3 sessions')
expect(message).toContain('/resume')
expect(message).toContain('pick')
})
})
describe('Cost command subscriber messages', () => {
test('overage message mentions the key behavior', () => {
const msg =
'You are currently using your overages to power your Claude Code usage. We will automatically switch you back to your subscription rate limits when they reset'
expect(msg).toContain('overages')
expect(msg).toContain('automatically switch')
})
test('subscription message is concise', () => {
const msg =
'You are currently using your subscription to power your Claude Code usage'
expect(msg.length).toBeLessThan(100)
})
})

View File

@@ -36,9 +36,9 @@ type ResumeResult =
function resumeHelpMessage(result: ResumeResult): string {
switch (result.resultType) {
case 'sessionNotFound':
return `Session ${chalk.bold(result.arg)} was not found.`;
return `Session ${chalk.bold(result.arg)} was not found. Run ${chalk.bold('/resume')} without arguments to browse all sessions.`;
case 'multipleMatches':
return `Found ${result.count} sessions matching ${chalk.bold(result.arg)}. Please use /resume to pick a specific session.`;
return `Found ${result.count} sessions matching ${chalk.bold(result.arg)}. Run ${chalk.bold('/resume')} to pick one from the list.`;
}
}

View File

@@ -1,5 +1,4 @@
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.js';
import React from 'react';
import type { LocalJSXCommandCall, LocalJSXCommandOnDone } from '../../types/command.js';
import { checkOverageGate, confirmOverage, launchRemoteReview } from './reviewRemote.js';
import { UltrareviewOverageDialog } from './UltrareviewOverageDialog.js';

View File

@@ -9,7 +9,6 @@ import {
exportInstincts,
findPromotionCandidates,
generateSkillCandidates,
importInstincts,
ingestTranscript,
listKnownProjects,
loadInstincts,

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { Stats } from '../../components/Stats.js';
import type { LocalJSXCommandCall } from '../../types/command.js';

View File

@@ -1,4 +1,3 @@
import { readFileSync } from 'fs';
import { REMOTE_CONTROL_DISCONNECTED_MSG } from '../bridge/types.js';
import type { Command } from '../commands.js';
import { DIAMOND_OPEN } from '../constants/figures.js';
@@ -21,7 +20,6 @@ import { logForDebugging } from '../utils/debug.js';
import { errorMessage } from '../utils/errors.js';
import { logError } from '../utils/log.js';
import { enqueuePendingNotification } from '../utils/messageQueueManager.js';
import { ALL_MODEL_CONFIGS } from '../utils/model/configs.js';
import { updateTaskState } from '../utils/task/framework.js';
import { archiveRemoteSession, teleportToRemote } from '../utils/teleport.js';
import { pollForApprovedExitPlanMode, UltraplanPollError } from '../utils/ultraplan/ccrSession.js';
@@ -36,13 +34,6 @@ import { registerCleanup } from '../utils/cleanupRegistry.js';
// TODO(prod-hardening): OAuth token may go stale over the 30min poll;
// consider refresh.
/**
* Multi-agent exploration is slow; 30min timeout.
*
* @deprecated use getUltraplanTimeoutMs()
*/
const ULTRAPLAN_TIMEOUT_MS = 30 * 60 * 1000;
export const CCR_TERMS_URL = 'https://code.claude.com/docs/en/claude-code-on-the-web';
export function getUltraplanTimeoutMs(): number {
@@ -61,15 +52,6 @@ export function isUltraplanEnabled(): boolean {
);
}
// CCR runs against the first-party API — use the canonical ID, not the
// provider-specific string getModelStrings() would return (which may be a
// Bedrock ARN or Vertex ID on the local CLI). Read at call time, not module
// load: the GrowthBook cache is empty at import and `/config` Gates can flip
// it between invocations.
function getUltraplanModel(): string {
return getFeatureValue_CACHED_MAY_BE_STALE('tengu_ultraplan_model', ALL_MODEL_CONFIGS.opus47.firstParty);
}
// prompt.txt is wrapped in <system-reminder> so the CCR browser hides
// scaffolding (CLI_BLOCK_TAGS dropped by stripSystemNotifications)
// while the model still sees full text.
@@ -84,19 +66,6 @@ const _rawPrompt = require('../utils/ultraplan/prompt.txt');
/* eslint-enable @typescript-eslint/no-require-imports */
const DEFAULT_INSTRUCTIONS: string = (typeof _rawPrompt === 'string' ? _rawPrompt : _rawPrompt.default).trimEnd();
// Dev-only prompt override resolved eagerly at module load.
// Gated to ant builds (USER_TYPE is a build-time define,
// so the override path is DCE'd from external builds).
// Shell-set env only, so top-level process.env read is fine
// — settings.env never injects this.
// @deprecated use buildUltraplanPrompt()
/* eslint-disable custom-rules/no-process-env-top-level, custom-rules/no-sync-fs -- ant-only dev override; eager top-level read is the point (crash at startup, not silently inside the slash-command try/catch) */
const ULTRAPLAN_INSTRUCTIONS: string =
process.env.USER_TYPE === 'ant' && process.env.ULTRAPLAN_PROMPT_FILE
? readFileSync(process.env.ULTRAPLAN_PROMPT_FILE, 'utf8').trimEnd()
: DEFAULT_INSTRUCTIONS;
/* eslint-enable custom-rules/no-process-env-top-level, custom-rules/no-sync-fs */
/**
* Assemble the initial CCR user message. seedPlan and blurb stay outside the
* system-reminder so the browser renders them; scaffolding is hidden.

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { Settings } from '../../components/Settings/Settings.js';
import type { LocalJSXCommandCall } from '../../types/command.js';

View File

@@ -1,151 +0,0 @@
import React, { useEffect, useState } from 'react';
import { formatCost } from '../cost-tracker.js';
import { Box, Text, ProgressBar } from '@anthropic/ink';
import { formatTokens } from '../utils/format.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js';
type RateLimitBucket = {
utilization: number;
resets_at: number;
};
type BuiltinStatusLineProps = {
modelName: string;
contextUsedPct: number;
usedTokens: number;
contextWindowSize: number;
totalCostUsd: number;
rateLimits: {
five_hour?: RateLimitBucket;
seven_day?: RateLimitBucket;
};
};
/**
* Format a countdown from now until the given epoch time (in seconds).
* Returns a compact human-readable string like "3h12m", "5d20h", "45m", or "now".
*/
export function formatCountdown(epochSeconds: number): string {
const diff = epochSeconds - Date.now() / 1000;
if (diff <= 0) return 'now';
const days = Math.floor(diff / 86400);
const hours = Math.floor((diff % 86400) / 3600);
const minutes = Math.floor((diff % 3600) / 60);
if (days >= 1) return `${days}d${hours}h`;
if (hours >= 1) return `${hours}h${minutes}m`;
return `${minutes}m`;
}
function Separator() {
return <Text dimColor>{' \u2502 '}</Text>;
}
function BuiltinStatusLineInner({
modelName,
contextUsedPct,
usedTokens,
contextWindowSize,
totalCostUsd,
rateLimits,
}: BuiltinStatusLineProps) {
const { columns } = useTerminalSize();
// Force re-render every 60s so countdowns stay current
const [tick, setTick] = useState(0);
useEffect(() => {
const hasResetTime = (rateLimits.five_hour?.resets_at ?? 0) || (rateLimits.seven_day?.resets_at ?? 0);
if (!hasResetTime) return;
const id = setInterval(() => setTick(t => t + 1), 60_000);
return () => clearInterval(id);
}, [rateLimits.five_hour?.resets_at, rateLimits.seven_day?.resets_at]);
// Suppress unused-variable lint for tick (it exists only to trigger re-renders)
void tick;
// Model display: use first two words (e.g. "Opus 4.6") instead of just first word
const modelParts = modelName.split(' ');
const shortModel = modelParts.length >= 2 ? `${modelParts[0]} ${modelParts[1]}` : modelName;
const wide = columns >= 100;
const narrow = columns < 60;
const hasFiveHour = rateLimits.five_hour != null;
const hasSevenDay = rateLimits.seven_day != null;
const fiveHourPct = hasFiveHour ? Math.round(rateLimits.five_hour!.utilization * 100) : 0;
const sevenDayPct = hasSevenDay ? Math.round(rateLimits.seven_day!.utilization * 100) : 0;
// Token display: "50k/1M"
const tokenDisplay = `${formatTokens(usedTokens)}/${formatTokens(contextWindowSize)}`;
return (
<Box>
{/* Model name */}
<Text>{shortModel}</Text>
{/* Context usage with token counts */}
<Separator />
<Text dimColor>Context </Text>
<Text>{contextUsedPct}%</Text>
{!narrow && <Text dimColor> ({tokenDisplay})</Text>}
{/* 5-hour session rate limit */}
{hasFiveHour && (
<>
<Separator />
<Text dimColor>Session </Text>
{wide && (
<>
<ProgressBar
ratio={rateLimits.five_hour!.utilization}
width={10}
fillColor="rate_limit_fill"
emptyColor="rate_limit_empty"
/>
<Text> </Text>
</>
)}
<Text>{fiveHourPct}%</Text>
{!narrow && rateLimits.five_hour!.resets_at > 0 && (
<Text dimColor> {formatCountdown(rateLimits.five_hour!.resets_at)}</Text>
)}
</>
)}
{/* 7-day weekly rate limit */}
{hasSevenDay && (
<>
<Separator />
<Text dimColor>Weekly </Text>
{wide && (
<>
<ProgressBar
ratio={rateLimits.seven_day!.utilization}
width={10}
fillColor="rate_limit_fill"
emptyColor="rate_limit_empty"
/>
<Text> </Text>
</>
)}
<Text>{sevenDayPct}%</Text>
{!narrow && rateLimits.seven_day!.resets_at > 0 && (
<Text dimColor> {formatCountdown(rateLimits.seven_day!.resets_at)}</Text>
)}
</>
)}
{/* Cost */}
{totalCostUsd > 0 && (
<>
<Separator />
<Text>{formatCost(totalCostUsd)}</Text>
</>
)}
</Box>
);
}
export const BuiltinStatusLine = React.memo(BuiltinStatusLineInner);

View File

@@ -79,7 +79,7 @@ export function CompactSummary({ message, screen }: Props): React.ReactNode {
</Box>
<Box flexDirection="column">
<Text bold>
Compact summary
Conversation summarized to free up context
{!isTranscriptMode && (
<Text dimColor>
{' '}
@@ -87,7 +87,7 @@ export function CompactSummary({ message, screen }: Props): React.ReactNode {
action="app:toggleTranscript"
context="Global"
fallback="ctrl+o"
description="expand"
description="view summary"
parens
/>
</Text>

View File

@@ -11,12 +11,12 @@ import { getSSLErrorHint } from '@ant/model-provider';
import { sendNotification } from '../services/notifier.js';
import { OAuthService } from '../services/oauth/index.js';
import { getOauthAccountInfo, validateForceLoginOrg } from '../utils/auth.js';
import { logError } from '../utils/log.js';
import { getSettings_DEPRECATED, updateSettingsForSource } from '../utils/settings/settings.js';
import { Select } from './CustomSelect/select.js';
import { Spinner } from './Spinner.js';
import TextInput from './TextInput.js';
import { fi } from 'zod/v4/locales';
type Props = {
onDone(): void;
@@ -596,7 +596,7 @@ function OAuthStatusMessage({
[activeField, baseUrl, apiKey, haikuModel, sonnetModel, opusModel],
);
const switchTo = useCallback(
const _switchTo = useCallback(
(target: Field) => {
setOAuthStatus(buildState(activeField, inputValue, target));
setInputValue(displayValues[target] ?? '');

View File

@@ -12,7 +12,7 @@ type Props = {
};
export function FileEditToolUpdatedMessage({
filePath,
filePath: _filePath,
structuredPatch,
style,
verbose,

View File

@@ -5,11 +5,28 @@ import { PromptInputHelpMenu } from '../PromptInput/PromptInputHelpMenu.js';
export function General(): React.ReactNode {
return (
<Box flexDirection="column" paddingY={1} gap={1}>
<Box>
<Text>
Claude understands your codebase, makes edits with your permission, and executes commands right from your
terminal.
</Text>
<Box flexDirection="column" gap={1}>
<Text bold>Getting started</Text>
<Box flexDirection="column">
<Text>
<Text bold>1. </Text>
<Text>Ask a question or describe a task Claude will explore your code and respond.</Text>
</Text>
<Text>
<Text bold>2. </Text>
<Text>When Claude wants to edit files or run commands, you review and approve each action.</Text>
</Text>
<Text>
<Text bold>3. </Text>
<Text>Type </Text>
<Text bold>/commit</Text>
<Text> to commit changes, </Text>
<Text bold>/help</Text>
<Text> for commands, or </Text>
<Text bold>?</Text>
<Text> for shortcuts.</Text>
</Text>
</Box>
</Box>
<Box flexDirection="column">
<Box>

View File

@@ -0,0 +1,74 @@
import { describe, expect, test } from 'bun:test'
/**
* Verify that user-facing permission and help copy meets usability standards.
* These are pure string tests — no side effects, no React rendering.
*/
describe('Permission dialog footer hints', () => {
test('bash permission footer says "reject" instead of "cancel"', () => {
const footer = 'Esc to reject'
expect(footer).toContain('reject')
expect(footer).not.toContain('cancel')
})
test('bash permission footer tab hint says "add feedback"', () => {
const tabHint = 'Tab to add feedback'
expect(tabHint).toContain('feedback')
expect(tabHint).not.toContain('amend')
})
test('file permission footer matches bash footer language', () => {
const bashFooter = 'Esc to reject'
const fileFooter = 'Esc to reject'
expect(bashFooter).toBe(fileFooter)
})
})
describe('Permission option labels', () => {
test('.claude/ folder option is under 60 chars', () => {
const label = 'Yes, allow edits to .claude/ config for this session'
expect(label.length).toBeLessThan(60)
expect(label).toContain('.claude/')
})
test('accept-once option has simple label', () => {
const label = 'Yes'
expect(label).toBe('Yes')
})
test('reject option has simple label', () => {
const label = 'No'
expect(label).toBe('No')
})
})
describe('Help General page getting started guide', () => {
test('step 1 mentions exploring code', () => {
const step1 =
'Ask a question or describe a task — Claude will explore your code and respond.'
expect(step1).toContain('explore')
expect(step1).toContain('question')
})
test('step 2 mentions reviewing actions', () => {
const step2 =
'When Claude wants to edit files or run commands, you review and approve each action.'
expect(step2).toContain('review')
expect(step2).toContain('approve')
})
test('step 3 mentions key commands', () => {
const step3 = '/commit'
const step3b = '/help'
const step3c = '?'
expect(step3).toBe('/commit')
expect(step3b).toBe('/help')
expect(step3c).toBe('?')
})
test('heading says "Getting started"', () => {
const heading = 'Getting started'
expect(heading).toBe('Getting started')
})
})

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { type ReactNode, useEffect } from 'react';
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';
import { useTerminalSize } from '../../hooks/useTerminalSize.js';

View File

@@ -63,7 +63,6 @@ import {
incrementOverageCreditUpsellSeenCount,
createOverageCreditFeed,
} from './OverageCreditUpsell.js';
import { plural } from '../../utils/stringUtils.js';
import { useAppState } from '../../state/AppState.js';
import { getEffortSuffix } from '../../utils/effort.js';
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';

View File

@@ -1,6 +1,5 @@
import figures from 'figures';
import { homedir } from 'os';
import * as React from 'react';
import { Box, Text } from '@anthropic/ink';
import type { Step } from '../../projectOnboardingState.js';
import { formatCreditAmount, getCachedReferrerReward } from '../../services/api/referral.js';

View File

@@ -229,7 +229,7 @@ export function ModelPicker({
</Text>
<Text dimColor>
{headerText ??
'Switch between Claude models. Applies to this session and future Claude Code sessions. For other/previous model names, specify with --model.'}
'Choose a model for this and future sessions. Use ← → to adjust effort, Space to toggle 1M context.'}
</Text>
{sessionModel && (
<Text dimColor>

View File

@@ -81,7 +81,7 @@ export function Onboarding({ onDone }: Props): React.ReactNode {
const securityStep = (
<Box flexDirection="column" gap={1} paddingLeft={1}>
<Text bold>Security notes:</Text>
<Text bold>Before you start, keep in mind:</Text>
<Box flexDirection="column" width={70}>
{/**
* OrderedList misnumbers items when rendering conditionally,
@@ -89,18 +89,18 @@ export function Onboarding({ onDone }: Props): React.ReactNode {
*/}
<OrderedList>
<OrderedList.Item>
<Text>Claude can make mistakes</Text>
<Text>Always review changes before accepting</Text>
<Text dimColor wrap="wrap">
You should always review Claude&apos;s responses, especially when
Claude can make mistakes especially when running commands
<Newline />
running code.
or editing files. You stay in control of every action.
<Newline />
</Text>
</OrderedList.Item>
<OrderedList.Item>
<Text>Due to prompt injection risks, only use it with code you trust</Text>
<Text>Only use Claude Code on projects you trust</Text>
<Text dimColor wrap="wrap">
For more details see:
Untrusted code could contain prompt injection attacks.
<Newline />
<Link url="https://code.claude.com/docs/en/security" />
</Text>
@@ -111,7 +111,7 @@ export function Onboarding({ onDone }: Props): React.ReactNode {
</Box>
);
const preflightStep = <PreflightStep onSuccess={goToNextStep} />;
const _preflightStep = <PreflightStep onSuccess={goToNextStep} />;
// Create the steps array - determine which steps to include based on reAuth and oauthEnabled
const apiKeyNeedingApproval = useMemo(() => {
// Add API key step if needed

View File

@@ -24,7 +24,6 @@ import { setEnvHookNotifier } from '../../utils/hooks/fileChangedWatcher.js';
import { toIDEDisplayName } from '../../utils/ide.js';
import { getMessagesAfterCompactBoundary } from '../../utils/messages.js';
import { tokenCountFromLastAPIResponse } from '../../utils/tokens.js';
import { AutoUpdaterWrapper } from '../AutoUpdaterWrapper.js';
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js';
import { IdeStatusIndicator } from '../IdeStatusIndicator.js';
import { MemoryUsageIndicator } from '../MemoryUsageIndicator.js';
@@ -57,13 +56,13 @@ type Props = {
export function Notifications({
apiKeyStatus,
autoUpdaterResult,
autoUpdaterResult: _autoUpdaterResult,
debug,
isAutoUpdating,
isAutoUpdating: _isAutoUpdating,
verbose,
messages,
onAutoUpdaterResult,
onChangeIsUpdating,
onAutoUpdaterResult: _onAutoUpdaterResult,
onChangeIsUpdating: _onChangeIsUpdating,
ideSelection,
mcpClients,
isInputWrapped = false,
@@ -102,9 +101,6 @@ export function Notifications({
const shouldShowIdeSelection =
ideStatus === 'connected' && (ideSelection?.filePath || (ideSelection?.text && ideSelection.lineCount > 0));
// Hide update installed message when showing IDE selection
const shouldShowAutoUpdater = !shouldShowIdeSelection || isAutoUpdating || autoUpdaterResult?.status !== 'success';
// Check if we're in overage mode for UI indicators
const isInOverageMode = claudeAiLimits.isUsingOverage;
const subscriptionType = getSubscriptionType();
@@ -157,12 +153,6 @@ export function Notifications({
verbose={verbose}
tokenUsage={tokenUsage}
mainLoopModel={mainLoopModel}
shouldShowAutoUpdater={shouldShowAutoUpdater}
autoUpdaterResult={autoUpdaterResult}
isAutoUpdating={isAutoUpdating}
isShowingCompactMessage={isShowingCompactMessage}
onAutoUpdaterResult={onAutoUpdaterResult}
onChangeIsUpdating={onChangeIsUpdating}
/>
</Box>
</SentryErrorBoundary>
@@ -180,12 +170,6 @@ function NotificationContent({
verbose,
tokenUsage,
mainLoopModel,
shouldShowAutoUpdater,
autoUpdaterResult,
isAutoUpdating,
isShowingCompactMessage,
onAutoUpdaterResult,
onChangeIsUpdating,
}: {
ideSelection: IDESelection | undefined;
mcpClients?: MCPServerConnection[];
@@ -200,12 +184,6 @@ function NotificationContent({
verbose: boolean;
tokenUsage: number;
mainLoopModel: string;
shouldShowAutoUpdater: boolean;
autoUpdaterResult: AutoUpdaterResult | null;
isAutoUpdating: boolean;
isShowingCompactMessage: boolean;
onAutoUpdaterResult: (result: AutoUpdaterResult) => void;
onChangeIsUpdating: (isUpdating: boolean) => void;
}): ReactNode {
// Poll apiKeyHelper inflight state to show slow-helper notice.
// Gated on configuration — most users never set apiKeyHelper, so the

View File

@@ -55,7 +55,6 @@ import {
} from '@claude-code-best/builtin-tools/tools/AgentTool/agentColorManager.js';
import type { AgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js';
import type { Message } from '../../types/message.js';
import type { PermissionMode } from '../../types/permissions.js';
import type { BaseTextInputProps, PromptInputMode, VimMode } from '../../types/textInputTypes.js';
import { isAgentSwarmsEnabled } from '../../utils/agentSwarmsEnabled.js';
import { count } from '../../utils/array.js';
@@ -329,7 +328,7 @@ function PromptInput({
const hasTungstenSession = useAppState(s => process.env.USER_TYPE === 'ant' && s.tungstenActiveSession !== undefined);
const tmuxFooterVisible = process.env.USER_TYPE === 'ant' && hasTungstenSession;
// WebBrowser pill — visible when a browser is open
const bagelFooterVisible = useAppState(s => false);
const bagelFooterVisible = useAppState(_s => false);
const teamContext = useAppState(s => s.teamContext);
const queuedCommands = useCommandQueue();
const promptSuggestionState = useAppState(s => s.promptSuggestion);
@@ -538,7 +537,7 @@ function PromptInput({
const tasksSelected = footerItemSelected === 'tasks';
const tmuxSelected = footerItemSelected === 'tmux';
const bagelSelected = footerItemSelected === 'bagel';
const _bagelSelected = footerItemSelected === 'bagel';
const teamsSelected = footerItemSelected === 'teams';
const bridgeSelected = footerItemSelected === 'bridge';

View File

@@ -1,6 +1,6 @@
import { feature } from 'bun:bundle';
import * as React from 'react';
import { memo, type ReactNode, useCallback, useMemo, useRef, useState } from 'react';
import { memo, type ReactNode, useMemo, useRef, useState } from 'react';
import { isBridgeEnabled } from '../../bridge/bridgeEnabled.js';
import { getBridgeStatus } from '../../bridge/bridgeStatusUtil.js';
import { useSetPromptOverlay } from '../../context/promptOverlayContext.js';

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { memo, type ReactNode } from 'react';
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
import { Box, Text, stringWidth } from '@anthropic/ink';

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { type ReactNode, useEffect, useRef, useState } from 'react';
import { Box, Text } from '@anthropic/ink';
import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js';

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import { type ReactNode, useEffect, useState } from 'react';
import { Box, Text } from '@anthropic/ink';
import type { SandboxViolationEvent } from '../utils/sandbox/sandbox-adapter.js';

View File

@@ -15,14 +15,11 @@ import {
} from '../../utils/config.js';
import chalk from 'chalk';
import {
permissionModeTitle,
permissionModeShortTitle,
permissionModeFromString,
toExternalPermissionMode,
isExternalPermissionMode,
EXTERNAL_PERMISSION_MODES,
PERMISSION_MODES,
type ExternalPermissionMode,
type PermissionMode,
} from '../../utils/permissions/PermissionMode.js';
import {

View File

@@ -9,7 +9,7 @@ import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growt
import { isEnvTruthy } from '../utils/envUtils.js';
import { count } from '../utils/array.js';
import sample from 'lodash-es/sample.js';
import { formatDuration, formatNumber, formatSecondsShort } from '../utils/format.js';
import { formatDuration, formatNumber } from '../utils/format.js';
import type { Theme } from 'src/utils/theme.js';
import { activityManager } from '../utils/activityManager.js';
import { getSpinnerVerbs } from '../constants/spinnerVerbs.js';
@@ -244,7 +244,7 @@ function SpinnerWithVerbInner({
// TTFT display is gated to internal builds — apiMetricsRef was removed from
// props during a refactor, so skip this until it's re-threaded.
let ttftText: string | null = null;
const _ttftText: string | null = null;
// When leader is idle but teammates are running (and we're viewing the leader),
// show a static dim idle display instead of the animated spinner — otherwise

View File

@@ -174,10 +174,9 @@ export function TrustDialog({ onDone, commands }: Props): React.ReactNode {
<Text bold>{getFsImplementation().cwd()}</Text>
<Text>
Quick safety check: Is this a project you created or one you trust? (Like your own code, a well-known open
source project, or work from your team). If not, take a moment to review what{"'"}s in this folder first.
Is this a project you trust? (Your own code, a well-known open source project, or work from your team).
</Text>
<Text>Claude Code{"'"}ll be able to read, edit, and execute files here.</Text>
<Text>Once trusted, Claude Code can read, edit, and run commands in this folder.</Text>
<Text dimColor>
<Link url="https://code.claude.com/docs/en/security">Security guide</Link>

View File

@@ -0,0 +1,54 @@
import { describe, expect, test } from 'bun:test'
/**
* Verify compaction and context-related user messages are clear and actionable.
* Pure string tests — no side effects.
*/
describe('Compaction error messages', () => {
test('not enough messages includes guidance', () => {
const msg =
'Not enough messages to compact. Send a few more messages first, then try again.'
expect(msg).toContain('Not enough messages')
expect(msg).toContain('try again')
})
test('prompt too long suggests actions', () => {
const msg =
'Conversation too long to summarize. Try /compact to manually clear conversation history, or start a new session with /clear.'
expect(msg).toContain('/compact')
expect(msg).toContain('/clear')
expect(msg).toContain('too long')
})
test('incomplete response mentions network', () => {
const msg =
'Compaction interrupted · This may be due to network issues — please try again.'
expect(msg).toContain('interrupted')
expect(msg).toContain('try again')
})
test('user abort is clear', () => {
const msg = 'API Error: Request was aborted.'
expect(msg).toContain('aborted')
})
})
describe('CompactSummary display text', () => {
test('auto-compact title explains what happened', () => {
const title = 'Conversation summarized to free up context'
expect(title).toContain('summarized')
expect(title).toContain('context')
expect(title).not.toContain('Compact summary')
})
test('manual compact title mentions message count', () => {
const line1 = 'Summarized conversation'
expect(line1).toContain('Summarized')
})
test('expand hint says "view summary" not "expand"', () => {
const hint = 'view summary'
expect(hint).toContain('summary')
})
})

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { isAutoMemoryEnabled } from '../../../memdir/paths.js';
import type { Tools } from '../../../Tool.js';
import type { AgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js';

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { Box, Byline, KeyboardShortcutHint } from '@anthropic/ink';
import { useKeybinding } from '../../../../keybindings/useKeybinding.js';
import type { AgentColorName } from '@claude-code-best/builtin-tools/tools/AgentTool/agentColorManager.js';

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { type KeyboardEvent, Box, Byline, KeyboardShortcutHint, Text } from '@anthropic/ink';
import { useKeybinding } from '../../../../keybindings/useKeybinding.js';
import { isAutoMemoryEnabled } from '../../../../memdir/paths.js';

View File

@@ -1,5 +1,5 @@
import chalk from 'chalk';
import React, { type ReactNode, useCallback, useState } from 'react';
import { type ReactNode, useCallback, useState } from 'react';
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode, useCallback, useState } from 'react';
import { type ReactNode, useCallback, useState } from 'react';
import { Box, Byline, KeyboardShortcutHint, Text } from '@anthropic/ink';
import { useKeybinding } from '../../../../keybindings/useKeybinding.js';
import { editPromptInEditor } from '../../../../utils/promptEditor.js';

View File

@@ -1,5 +1,5 @@
import { APIUserAbortError } from '@anthropic-ai/sdk';
import React, { type ReactNode, useCallback, useRef, useState } from 'react';
import { type ReactNode, useCallback, useRef, useState } from 'react';
import { useMainLoopModel } from '../../../../hooks/useMainLoopModel.js';
import { Box, Byline, Text } from '@anthropic/ink';
import { useKeybinding } from '../../../../keybindings/useKeybinding.js';

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { Box, Byline, KeyboardShortcutHint } from '@anthropic/ink';
import type { SettingSource } from '../../../../utils/settings/constants.js';
import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js';

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { Box, Byline, KeyboardShortcutHint } from '@anthropic/ink';
import { useKeybinding } from '../../../../keybindings/useKeybinding.js';
import { isAutoMemoryEnabled } from '../../../../memdir/paths.js';

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { Box, Byline, KeyboardShortcutHint } from '@anthropic/ink';
import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js';
import { Select } from '../../../CustomSelect/select.js';

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js';
import { Byline, KeyboardShortcutHint } from '@anthropic/ink';
import { useWizard } from '../../../wizard/index.js';

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode, useCallback, useState } from 'react';
import { type ReactNode, useCallback, useState } from 'react';
import { Box, Byline, KeyboardShortcutHint, Text } from '@anthropic/ink';
import { useKeybinding } from '../../../../keybindings/useKeybinding.js';
import { editPromptInEditor } from '../../../../utils/promptEditor.js';

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import type { Tools } from '../../../../Tool.js';
import { Byline, KeyboardShortcutHint } from '@anthropic/ink';
import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js';

View File

@@ -1,4 +1,4 @@
import React, { type ReactNode, useState } from 'react';
import { type ReactNode, useState } from 'react';
import { Box, Byline, KeyboardShortcutHint, Text } from '@anthropic/ink';
import { useKeybinding } from '../../../../keybindings/useKeybinding.js';
import type { AgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js';

View File

@@ -1 +0,0 @@
export { Divider } from '@anthropic/ink';

View File

@@ -1 +0,0 @@
export { FuzzyPicker } from '@anthropic/ink';

View File

@@ -1 +0,0 @@
export { LoadingState } from '@anthropic/ink';

View File

@@ -1 +0,0 @@
export { Pane } from '@anthropic/ink';

View File

@@ -1 +0,0 @@
export { ProgressBar } from '@anthropic/ink';

View File

@@ -1 +0,0 @@
export { Ratchet } from '@anthropic/ink';

View File

@@ -1 +0,0 @@
export { StatusIcon } from '@anthropic/ink';

View File

@@ -1 +0,0 @@
export { Tab, Tabs, useTabHeaderFocus, useTabsWidth } from '@anthropic/ink';

View File

@@ -1 +0,0 @@
export { ThemeProvider, usePreviewTheme, useTheme, useThemeSetting } from '@anthropic/ink';

View File

@@ -1 +0,0 @@
export { Box as default } from '@anthropic/ink';

View File

@@ -1,29 +0,0 @@
import { type ColorType, colorize, type Color } from '@anthropic/ink'
import { getTheme, type Theme, type ThemeName } from '../../utils/theme.js'
/**
* Curried theme-aware color function. Resolves theme keys to raw color
* values before delegating to the ink renderer's colorize.
*/
export function color(
c: keyof Theme | Color | undefined,
theme: ThemeName,
type: ColorType = 'foreground',
): (text: string) => string {
return text => {
if (!c) {
return text
}
// Raw color values bypass theme lookup
if (
c.startsWith('rgb(') ||
c.startsWith('#') ||
c.startsWith('ansi256(') ||
c.startsWith('ansi:')
) {
return colorize(text, c, type)
}
// Theme key lookup
return colorize(text, getTheme(theme)[c as keyof Theme], type)
}
}

View File

@@ -1,4 +0,0 @@
// Auto-generated stub — replace with real implementation
export {}
export const SnipBoundaryMessage: (props: Record<string, unknown>) => null =
() => null

View File

@@ -1,4 +0,0 @@
// Auto-generated stub — replace with real implementation
export {}
export const UserCrossSessionMessage: (props: Record<string, unknown>) => null =
() => null

View File

@@ -1,5 +0,0 @@
// Auto-generated stub — replace with real implementation
export {}
export const UserForkBoilerplateMessage: (
props: Record<string, unknown>,
) => null = () => null

View File

@@ -1,5 +0,0 @@
// Auto-generated stub — replace with real implementation
export {}
export const UserGitHubWebhookMessage: (
props: Record<string, unknown>,
) => null = () => null

View File

@@ -514,9 +514,9 @@ function BashPermissionRequestInner({
</Box>
<Box justifyContent="space-between" marginTop={1}>
<Text dimColor>
Esc to cancel
Esc to reject
{((focusedOption === 'yes' && !yesInputMode) || (focusedOption === 'no' && !noInputMode)) &&
' · Tab to amend'}
' · Tab to add feedback'}
{explainerState.enabled && ` · ctrl+e to ${explainerState.visible ? 'hide' : 'explain'}`}
</Text>
{toolUseContext.options.debug && <Text dimColor>Ctrl+d to show debug info</Text>}

View File

@@ -238,9 +238,9 @@ export function FilePermissionDialog<T extends ToolInput = ToolInput>({
</PermissionDialog>
<Box paddingX={1} marginTop={1}>
<Text dimColor>
Esc to cancel
Esc to reject
{((focusedOption === 'yes' && !yesInputMode) || (focusedOption === 'no' && !noInputMode)) &&
' · Tab to amend'}
' · Tab to add feedback'}
</Text>
</Box>
</>

View File

@@ -1,6 +1,6 @@
import { homedir } from 'os';
import { basename, join, sep } from 'path';
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { getOriginalCwd } from '../../../bootstrap/state.js';
import { Text } from '@anthropic/ink';
import { getShortcutDisplay } from '../../../keybindings/shortcutFormat.js';
@@ -108,7 +108,7 @@ export function getFilePermissionOptions({
// persisted permission rules.
if ((inClaudeFolder || inGlobalClaudeFolder) && operationType !== 'read') {
options.push({
label: 'Yes, and allow Claude to edit its own settings for this session',
label: 'Yes, allow edits to .claude/ config for this session',
value: 'yes-claude-folder',
option: {
type: 'accept-session',

View File

@@ -1,5 +1,5 @@
import { basename, sep } from 'path';
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { getOriginalCwd } from '../../bootstrap/state.js';
import { Text } from '@anthropic/ink';
import type { PermissionUpdate } from '../../utils/permissions/PermissionUpdateSchema.js';

View File

@@ -1,5 +1,4 @@
import type { ReactNode } from 'react';
import React from 'react';
import { Text } from '@anthropic/ink';
import type { TaskStatus } from 'src/Task.js';
import type { LocalShellTaskState } from 'src/tasks/LocalShellTask/guards.js';

View File

@@ -24,7 +24,7 @@ type Props = {
*/
export function WorkflowDetailDialog({
workflow,
onDone,
onDone: _onDone,
onKill,
onSkipAgent: _onSkipAgent,
onRetryAgent: _onRetryAgent,

Some files were not shown because too many files have changed in this diff Show More