mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
465c95ae53 | ||
|
|
42100d6268 | ||
|
|
ca29e4e8f7 | ||
|
|
cd8136f4b1 | ||
|
|
71c89e9de4 | ||
|
|
632f3e199e | ||
|
|
4b97e6638e |
@@ -34,7 +34,7 @@
|
|||||||
| GrowthBook | 企业级特性开关 | [文档](https://ccb.agent-aura.top/docs/internals/growthbook-adapter) |
|
| GrowthBook | 企业级特性开关 | [文档](https://ccb.agent-aura.top/docs/internals/growthbook-adapter) |
|
||||||
| /dream 记忆整理 | 自动整理和优化记忆文件 | [文档](https://ccb.agent-aura.top/docs/features/auto-dream) |
|
| /dream 记忆整理 | 自动整理和优化记忆文件 | [文档](https://ccb.agent-aura.top/docs/features/auto-dream) |
|
||||||
|
|
||||||
- 🚀 [想要启动项目](#快速开始源码版)
|
- 🚀 [想要启动项目](#-快速开始源码版)
|
||||||
- 🐛 [想要调试项目](#vs-code-调试)
|
- 🐛 [想要调试项目](#vs-code-调试)
|
||||||
- 📖 [想要学习项目](#teach-me-学习项目)
|
- 📖 [想要学习项目](#teach-me-学习项目)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-code-best",
|
"name": "claude-code-best",
|
||||||
"version": "1.11.0",
|
"version": "1.11.1",
|
||||||
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
|
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "claude-code-best <claude-code-best@proton.me>",
|
"author": "claude-code-best <claude-code-best@proton.me>",
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ export { TerminalWriteProvider, useTerminalNotification, type TerminalNotificati
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
export {
|
export {
|
||||||
ThemeProvider,
|
ThemeProvider,
|
||||||
|
setThemeConfigCallbacks,
|
||||||
usePreviewTheme,
|
usePreviewTheme,
|
||||||
useTheme,
|
useTheme,
|
||||||
useThemeSetting,
|
useThemeSetting,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const DEFAULT_BUILD_FEATURES = [
|
|||||||
'HISTORY_SNIP', // 历史消息裁剪,压缩上下文窗口
|
'HISTORY_SNIP', // 历史消息裁剪,压缩上下文窗口
|
||||||
'CONTEXT_COLLAPSE', // 上下文折叠,自动压缩旧消息
|
'CONTEXT_COLLAPSE', // 上下文折叠,自动压缩旧消息
|
||||||
'MONITOR_TOOL', // Monitor 工具,流式监控后台进程输出
|
'MONITOR_TOOL', // Monitor 工具,流式监控后台进程输出
|
||||||
'FORK_SUBAGENT', // Fork 子代理,在隔离上下文中并行执行任务
|
// 'FORK_SUBAGENT', // 已禁用:启用后 prompt 引导模型用 fork(继承父模型)替代 Explore(haiku),导致探索任务使用同等级模型
|
||||||
// 'UDS_INBOX', // inbox 数组只增不减(非 GB 级主因)
|
// 'UDS_INBOX', // inbox 数组只增不减(非 GB 级主因)
|
||||||
'KAIROS', // Kairos 定时任务系统核心
|
'KAIROS', // Kairos 定时任务系统核心
|
||||||
// 'COORDINATOR_MODE', // 已禁用:AgentSummary 30s fork 循环,GB 级泄露主因
|
// 'COORDINATOR_MODE', // 已禁用:AgentSummary 30s fork 循环,GB 级泄露主因
|
||||||
@@ -74,8 +74,8 @@ export const DEFAULT_BUILD_FEATURES = [
|
|||||||
// this branch (see docs/agent/sur-skill-overflow-bugs.md) close the
|
// this branch (see docs/agent/sur-skill-overflow-bugs.md) close the
|
||||||
// overflow risk, but Haiku-on-first-Chinese-query and disk-side
|
// overflow risk, but Haiku-on-first-Chinese-query and disk-side
|
||||||
// observation accumulation remain operator-discretion concerns.
|
// observation accumulation remain operator-discretion concerns.
|
||||||
'EXPERIMENTAL_SKILL_SEARCH',
|
// 'EXPERIMENTAL_SKILL_SEARCH',
|
||||||
'SKILL_LEARNING',
|
// 'SKILL_LEARNING',
|
||||||
// P3: poor mode
|
// P3: poor mode
|
||||||
'POOR', // 穷鬼模式,跳过 extract_memories/prompt_suggestion 减少消耗
|
'POOR', // 穷鬼模式,跳过 extract_memories/prompt_suggestion 减少消耗
|
||||||
// Team Memory
|
// Team Memory
|
||||||
|
|||||||
@@ -1,38 +1,36 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { FpsMetricsProvider } from '../context/fpsMetrics.js'
|
import { FpsMetricsProvider } from '../context/fpsMetrics.js';
|
||||||
import { StatsProvider, type StatsStore } from '../context/stats.js'
|
import { StatsProvider, type StatsStore } from '../context/stats.js';
|
||||||
import { type AppState, AppStateProvider } from '../state/AppState.js'
|
import { type AppState, AppStateProvider } from '../state/AppState.js';
|
||||||
import { onChangeAppState } from '../state/onChangeAppState.js'
|
import { onChangeAppState } from '../state/onChangeAppState.js';
|
||||||
import type { FpsMetrics } from '../utils/fpsTracker.js'
|
import type { FpsMetrics } from '../utils/fpsTracker.js';
|
||||||
import { ThemeProvider } from '@anthropic/ink'
|
import { ThemeProvider } from '@anthropic/ink';
|
||||||
|
import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
getFpsMetrics: () => FpsMetrics | undefined
|
getFpsMetrics: () => FpsMetrics | undefined;
|
||||||
stats?: StatsStore
|
stats?: StatsStore;
|
||||||
initialState: AppState
|
initialState: AppState;
|
||||||
children: React.ReactNode
|
children: React.ReactNode;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top-level wrapper for interactive sessions.
|
* Top-level wrapper for interactive sessions.
|
||||||
* Provides FPS metrics, stats context, and app state to the component tree.
|
* Provides FPS metrics, stats context, and app state to the component tree.
|
||||||
*/
|
*/
|
||||||
export function App({
|
export function App({ getFpsMetrics, stats, initialState, children }: Props): React.ReactNode {
|
||||||
getFpsMetrics,
|
|
||||||
stats,
|
|
||||||
initialState,
|
|
||||||
children,
|
|
||||||
}: Props): React.ReactNode {
|
|
||||||
return (
|
return (
|
||||||
<FpsMetricsProvider getFpsMetrics={getFpsMetrics}>
|
<FpsMetricsProvider getFpsMetrics={getFpsMetrics}>
|
||||||
<StatsProvider store={stats}>
|
<StatsProvider store={stats}>
|
||||||
<AppStateProvider
|
<AppStateProvider initialState={initialState} onChangeAppState={onChangeAppState}>
|
||||||
initialState={initialState}
|
<ThemeProvider
|
||||||
onChangeAppState={onChangeAppState}
|
initialState={getGlobalConfig().theme}
|
||||||
>
|
onThemeSave={setting => saveGlobalConfig(current => ({ ...current, theme: setting }))}
|
||||||
{children}
|
>
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
</AppStateProvider>
|
</AppStateProvider>
|
||||||
</StatsProvider>
|
</StatsProvider>
|
||||||
</FpsMetricsProvider>
|
</FpsMetricsProvider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { buildMergePrompt, SnapshotUpdateDialog } from '../SnapshotUpdateDialog.
|
|||||||
import { Select } from '../../CustomSelect/index.js';
|
import { Select } from '../../CustomSelect/index.js';
|
||||||
|
|
||||||
function getSnapshotDialogFromRenderedTree(rendered: React.ReactElement) {
|
function getSnapshotDialogFromRenderedTree(rendered: React.ReactElement) {
|
||||||
const appStateProvider = rendered as React.ReactElement<{
|
const themeProvider = rendered as React.ReactElement<{
|
||||||
|
children: React.ReactElement;
|
||||||
|
}>;
|
||||||
|
const appStateProvider = themeProvider.props.children as React.ReactElement<{
|
||||||
children: React.ReactElement;
|
children: React.ReactElement;
|
||||||
}>;
|
}>;
|
||||||
const keybindingSetup = appStateProvider.props.children as React.ReactElement<{
|
const keybindingSetup = appStateProvider.props.children as React.ReactElement<{
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ import {
|
|||||||
import { preconnectAnthropicApi } from '../utils/apiPreconnect.js'
|
import { preconnectAnthropicApi } from '../utils/apiPreconnect.js'
|
||||||
import { applyExtraCACertsFromConfig } from '../utils/caCertsConfig.js'
|
import { applyExtraCACertsFromConfig } from '../utils/caCertsConfig.js'
|
||||||
import { registerCleanup } from '../utils/cleanupRegistry.js'
|
import { registerCleanup } from '../utils/cleanupRegistry.js'
|
||||||
import { enableConfigs, recordFirstStartTime } from '../utils/config.js'
|
import {
|
||||||
|
enableConfigs,
|
||||||
|
getGlobalConfig,
|
||||||
|
recordFirstStartTime,
|
||||||
|
saveGlobalConfig,
|
||||||
|
} from '../utils/config.js'
|
||||||
import { logForDebugging } from '../utils/debug.js'
|
import { logForDebugging } from '../utils/debug.js'
|
||||||
import { detectCurrentRepository } from '../utils/detectRepository.js'
|
import { detectCurrentRepository } from '../utils/detectRepository.js'
|
||||||
import { logForDiagnosticsNoPII } from '../utils/diagLogs.js'
|
import { logForDiagnosticsNoPII } from '../utils/diagLogs.js'
|
||||||
@@ -51,6 +56,7 @@ import { setShellIfWindows } from '../utils/windowsPaths.js'
|
|||||||
import { initSentry } from '../utils/sentry.js'
|
import { initSentry } from '../utils/sentry.js'
|
||||||
import { initUser } from '../utils/user.js'
|
import { initUser } from '../utils/user.js'
|
||||||
import { initLangfuse, shutdownLangfuse } from '../services/langfuse/index.js'
|
import { initLangfuse, shutdownLangfuse } from '../services/langfuse/index.js'
|
||||||
|
import { setThemeConfigCallbacks } from '@anthropic/ink'
|
||||||
|
|
||||||
// initialize1PEventLogging is dynamically imported to defer OpenTelemetry sdk-logs/resources
|
// initialize1PEventLogging is dynamically imported to defer OpenTelemetry sdk-logs/resources
|
||||||
|
|
||||||
@@ -66,6 +72,11 @@ export const init = memoize(async (): Promise<void> => {
|
|||||||
try {
|
try {
|
||||||
const configsStart = Date.now()
|
const configsStart = Date.now()
|
||||||
enableConfigs()
|
enableConfigs()
|
||||||
|
setThemeConfigCallbacks({
|
||||||
|
loadTheme: () => getGlobalConfig().theme,
|
||||||
|
saveTheme: setting =>
|
||||||
|
saveGlobalConfig(current => ({ ...current, theme: setting })),
|
||||||
|
})
|
||||||
logForDiagnosticsNoPII('info', 'init_configs_enabled', {
|
logForDiagnosticsNoPII('info', 'init_configs_enabled', {
|
||||||
duration_ms: Date.now() - configsStart,
|
duration_ms: Date.now() - configsStart,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { feature } from 'bun:bundle'
|
import { feature } from 'bun:bundle';
|
||||||
import { appendFileSync } from 'fs'
|
import { appendFileSync } from 'fs';
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { logEvent } from 'src/services/analytics/index.js'
|
import { logEvent } from 'src/services/analytics/index.js';
|
||||||
import {
|
import { gracefulShutdown, gracefulShutdownSync } from 'src/utils/gracefulShutdown.js';
|
||||||
gracefulShutdown,
|
|
||||||
gracefulShutdownSync,
|
|
||||||
} from 'src/utils/gracefulShutdown.js'
|
|
||||||
import {
|
import {
|
||||||
type ChannelEntry,
|
type ChannelEntry,
|
||||||
getAllowedChannels,
|
getAllowedChannels,
|
||||||
@@ -13,63 +10,59 @@ import {
|
|||||||
setHasDevChannels,
|
setHasDevChannels,
|
||||||
setSessionTrustAccepted,
|
setSessionTrustAccepted,
|
||||||
setStatsStore,
|
setStatsStore,
|
||||||
} from './bootstrap/state.js'
|
} from './bootstrap/state.js';
|
||||||
import type { Command } from './commands.js'
|
import type { Command } from './commands.js';
|
||||||
import { createStatsStore, type StatsStore } from './context/stats.js'
|
import { createStatsStore, type StatsStore } from './context/stats.js';
|
||||||
import { getSystemContext } from './context.js'
|
import { getSystemContext } from './context.js';
|
||||||
import { initializeTelemetryAfterTrust } from './entrypoints/init.js'
|
import { initializeTelemetryAfterTrust } from './entrypoints/init.js';
|
||||||
import { isSynchronizedOutputSupported } from '@anthropic/ink'
|
import { isSynchronizedOutputSupported } from '@anthropic/ink';
|
||||||
import type { RenderOptions, Root, TextProps } from '@anthropic/ink'
|
import type { RenderOptions, Root, TextProps } from '@anthropic/ink';
|
||||||
import { KeybindingSetup } from './keybindings/KeybindingProviderSetup.js'
|
import { KeybindingSetup } from './keybindings/KeybindingProviderSetup.js';
|
||||||
import { startDeferredPrefetches } from './main.js'
|
import { startDeferredPrefetches } from './main.js';
|
||||||
import {
|
import {
|
||||||
checkGate_CACHED_OR_BLOCKING,
|
checkGate_CACHED_OR_BLOCKING,
|
||||||
initializeGrowthBook,
|
initializeGrowthBook,
|
||||||
resetGrowthBook,
|
resetGrowthBook,
|
||||||
} from './services/analytics/growthbook.js'
|
} from './services/analytics/growthbook.js';
|
||||||
import { isQualifiedForGrove } from './services/api/grove.js'
|
import { isQualifiedForGrove } from './services/api/grove.js';
|
||||||
import { handleMcpjsonServerApprovals } from './services/mcpServerApproval.js'
|
import { handleMcpjsonServerApprovals } from './services/mcpServerApproval.js';
|
||||||
import { AppStateProvider } from './state/AppState.js'
|
import { AppStateProvider } from './state/AppState.js';
|
||||||
import { onChangeAppState } from './state/onChangeAppState.js'
|
import { onChangeAppState } from './state/onChangeAppState.js';
|
||||||
import { normalizeApiKeyForConfig } from './utils/authPortable.js'
|
import { ThemeProvider } from '@anthropic/ink';
|
||||||
|
import { normalizeApiKeyForConfig } from './utils/authPortable.js';
|
||||||
import {
|
import {
|
||||||
getExternalClaudeMdIncludes,
|
getExternalClaudeMdIncludes,
|
||||||
getMemoryFiles,
|
getMemoryFiles,
|
||||||
shouldShowClaudeMdExternalIncludesWarning,
|
shouldShowClaudeMdExternalIncludesWarning,
|
||||||
} from './utils/claudemd.js'
|
} from './utils/claudemd.js';
|
||||||
import {
|
import {
|
||||||
checkHasTrustDialogAccepted,
|
checkHasTrustDialogAccepted,
|
||||||
getCustomApiKeyStatus,
|
getCustomApiKeyStatus,
|
||||||
getGlobalConfig,
|
getGlobalConfig,
|
||||||
saveGlobalConfig,
|
saveGlobalConfig,
|
||||||
} from './utils/config.js'
|
} from './utils/config.js';
|
||||||
import { updateDeepLinkTerminalPreference } from './utils/deepLink/terminalPreference.js'
|
import { updateDeepLinkTerminalPreference } from './utils/deepLink/terminalPreference.js';
|
||||||
import { isEnvTruthy, isRunningOnHomespace } from './utils/envUtils.js'
|
import { isEnvTruthy, isRunningOnHomespace } from './utils/envUtils.js';
|
||||||
import { type FpsMetrics, FpsTracker } from './utils/fpsTracker.js'
|
import { type FpsMetrics, FpsTracker } from './utils/fpsTracker.js';
|
||||||
import { updateGithubRepoPathMapping } from './utils/githubRepoPathMapping.js'
|
import { updateGithubRepoPathMapping } from './utils/githubRepoPathMapping.js';
|
||||||
import { applyConfigEnvironmentVariables } from './utils/managedEnv.js'
|
import { applyConfigEnvironmentVariables } from './utils/managedEnv.js';
|
||||||
import type { PermissionMode } from './utils/permissions/PermissionMode.js'
|
import type { PermissionMode } from './utils/permissions/PermissionMode.js';
|
||||||
import { getBaseRenderOptions } from './utils/renderOptions.js'
|
import { getBaseRenderOptions } from './utils/renderOptions.js';
|
||||||
import { getSettingsWithAllErrors } from './utils/settings/allErrors.js'
|
import { getSettingsWithAllErrors } from './utils/settings/allErrors.js';
|
||||||
import {
|
import { hasSkipDangerousModePermissionPrompt } from './utils/settings/settings.js';
|
||||||
hasSkipDangerousModePermissionPrompt,
|
|
||||||
} from './utils/settings/settings.js'
|
|
||||||
|
|
||||||
export function completeOnboarding(): void {
|
export function completeOnboarding(): void {
|
||||||
saveGlobalConfig(current => ({
|
saveGlobalConfig(current => ({
|
||||||
...current,
|
...current,
|
||||||
hasCompletedOnboarding: true,
|
hasCompletedOnboarding: true,
|
||||||
lastOnboardingVersion: MACRO.VERSION,
|
lastOnboardingVersion: MACRO.VERSION,
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
export function showDialog<T = void>(
|
export function showDialog<T = void>(root: Root, renderer: (done: (result: T) => void) => React.ReactNode): Promise<T> {
|
||||||
root: Root,
|
|
||||||
renderer: (done: (result: T) => void) => React.ReactNode,
|
|
||||||
): Promise<T> {
|
|
||||||
return new Promise<T>(resolve => {
|
return new Promise<T>(resolve => {
|
||||||
const done = (result: T): void => void resolve(result)
|
const done = (result: T): void => void resolve(result);
|
||||||
root.render(renderer(done))
|
root.render(renderer(done));
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,12 +71,8 @@ export function showDialog<T = void>(
|
|||||||
* console.error is swallowed by Ink's patchConsole, so we render
|
* console.error is swallowed by Ink's patchConsole, so we render
|
||||||
* through the React tree instead.
|
* through the React tree instead.
|
||||||
*/
|
*/
|
||||||
export async function exitWithError(
|
export async function exitWithError(root: Root, message: string, beforeExit?: () => Promise<void>): Promise<never> {
|
||||||
root: Root,
|
return exitWithMessage(root, message, { color: 'error', beforeExit });
|
||||||
message: string,
|
|
||||||
beforeExit?: () => Promise<void>,
|
|
||||||
): Promise<never> {
|
|
||||||
return exitWithMessage(root, message, { color: 'error', beforeExit })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,21 +85,19 @@ export async function exitWithMessage(
|
|||||||
root: Root,
|
root: Root,
|
||||||
message: string,
|
message: string,
|
||||||
options?: {
|
options?: {
|
||||||
color?: TextProps['color']
|
color?: TextProps['color'];
|
||||||
exitCode?: number
|
exitCode?: number;
|
||||||
beforeExit?: () => Promise<void>
|
beforeExit?: () => Promise<void>;
|
||||||
},
|
},
|
||||||
): Promise<never> {
|
): Promise<never> {
|
||||||
const { Text } = await import('@anthropic/ink')
|
const { Text } = await import('@anthropic/ink');
|
||||||
const color = options?.color
|
const color = options?.color;
|
||||||
const exitCode = options?.exitCode ?? 1
|
const exitCode = options?.exitCode ?? 1;
|
||||||
root.render(
|
root.render(color ? <Text color={color}>{message}</Text> : <Text>{message}</Text>);
|
||||||
color ? <Text color={color}>{message}</Text> : <Text>{message}</Text>,
|
root.unmount();
|
||||||
)
|
await options?.beforeExit?.();
|
||||||
root.unmount()
|
|
||||||
await options?.beforeExit?.()
|
|
||||||
// eslint-disable-next-line custom-rules/no-process-exit -- exit after Ink unmount
|
// eslint-disable-next-line custom-rules/no-process-exit -- exit after Ink unmount
|
||||||
process.exit(exitCode)
|
process.exit(exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,24 +110,26 @@ export function showSetupDialog<T = void>(
|
|||||||
options?: { onChangeAppState?: typeof onChangeAppState },
|
options?: { onChangeAppState?: typeof onChangeAppState },
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return showDialog<T>(root, done => (
|
return showDialog<T>(root, done => (
|
||||||
<AppStateProvider onChangeAppState={options?.onChangeAppState}>
|
<ThemeProvider
|
||||||
<KeybindingSetup>{renderer(done)}</KeybindingSetup>
|
initialState={getGlobalConfig().theme}
|
||||||
</AppStateProvider>
|
onThemeSave={setting => saveGlobalConfig(current => ({ ...current, theme: setting }))}
|
||||||
))
|
>
|
||||||
|
<AppStateProvider onChangeAppState={options?.onChangeAppState}>
|
||||||
|
<KeybindingSetup>{renderer(done)}</KeybindingSetup>
|
||||||
|
</AppStateProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the main UI into the root and wait for it to exit.
|
* Render the main UI into the root and wait for it to exit.
|
||||||
* Handles the common epilogue: start deferred prefetches, wait for exit, graceful shutdown.
|
* Handles the common epilogue: start deferred prefetches, wait for exit, graceful shutdown.
|
||||||
*/
|
*/
|
||||||
export async function renderAndRun(
|
export async function renderAndRun(root: Root, element: React.ReactNode): Promise<void> {
|
||||||
root: Root,
|
root.render(element);
|
||||||
element: React.ReactNode,
|
startDeferredPrefetches();
|
||||||
): Promise<void> {
|
await root.waitUntilExit();
|
||||||
root.render(element)
|
await gracefulShutdown(0);
|
||||||
startDeferredPrefetches()
|
|
||||||
await root.waitUntilExit()
|
|
||||||
await gracefulShutdown(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showSetupScreens(
|
export async function showSetupScreens(
|
||||||
@@ -156,29 +145,29 @@ export async function showSetupScreens(
|
|||||||
isEnvTruthy(false) ||
|
isEnvTruthy(false) ||
|
||||||
process.env.IS_DEMO // Skip onboarding in demo mode
|
process.env.IS_DEMO // Skip onboarding in demo mode
|
||||||
) {
|
) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = getGlobalConfig()
|
const config = getGlobalConfig();
|
||||||
let onboardingShown = false
|
let onboardingShown = false;
|
||||||
if (
|
if (
|
||||||
!config.theme ||
|
!config.theme ||
|
||||||
!config.hasCompletedOnboarding // always show onboarding at least once
|
!config.hasCompletedOnboarding // always show onboarding at least once
|
||||||
) {
|
) {
|
||||||
onboardingShown = true
|
onboardingShown = true;
|
||||||
const { Onboarding } = await import('./components/Onboarding.js')
|
const { Onboarding } = await import('./components/Onboarding.js');
|
||||||
await showSetupDialog(
|
await showSetupDialog(
|
||||||
root,
|
root,
|
||||||
done => (
|
done => (
|
||||||
<Onboarding
|
<Onboarding
|
||||||
onDone={() => {
|
onDone={() => {
|
||||||
completeOnboarding()
|
completeOnboarding();
|
||||||
void done()
|
void done();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
{ onChangeAppState },
|
{ onChangeAppState },
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always show the trust dialog in interactive sessions, regardless of permission mode.
|
// Always show the trust dialog in interactive sessions, regardless of permission mode.
|
||||||
@@ -192,83 +181,71 @@ export async function showSetupScreens(
|
|||||||
// If it returns true, the TrustDialog would auto-resolve regardless of
|
// If it returns true, the TrustDialog would auto-resolve regardless of
|
||||||
// security features, so we can skip the dynamic import and render cycle.
|
// security features, so we can skip the dynamic import and render cycle.
|
||||||
if (!checkHasTrustDialogAccepted()) {
|
if (!checkHasTrustDialogAccepted()) {
|
||||||
const { TrustDialog } = await import(
|
const { TrustDialog } = await import('./components/TrustDialog/TrustDialog.js');
|
||||||
'./components/TrustDialog/TrustDialog.js'
|
await showSetupDialog(root, done => <TrustDialog commands={commands} onDone={done} />);
|
||||||
)
|
|
||||||
await showSetupDialog(root, done => (
|
|
||||||
<TrustDialog commands={commands} onDone={done} />
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal that trust has been verified for this session.
|
// Signal that trust has been verified for this session.
|
||||||
// GrowthBook checks this to decide whether to include auth headers.
|
// GrowthBook checks this to decide whether to include auth headers.
|
||||||
setSessionTrustAccepted(true)
|
setSessionTrustAccepted(true);
|
||||||
|
|
||||||
// Reset and reinitialize GrowthBook after trust is established.
|
// Reset and reinitialize GrowthBook after trust is established.
|
||||||
// Defense for login/logout: clears any prior client so the next init
|
// Defense for login/logout: clears any prior client so the next init
|
||||||
// picks up fresh auth headers.
|
// picks up fresh auth headers.
|
||||||
resetGrowthBook()
|
resetGrowthBook();
|
||||||
void initializeGrowthBook()
|
void initializeGrowthBook();
|
||||||
|
|
||||||
// Now that trust is established, prefetch system context if it wasn't already
|
// Now that trust is established, prefetch system context if it wasn't already
|
||||||
void getSystemContext()
|
void getSystemContext();
|
||||||
|
|
||||||
// If settings are valid, check for any mcp.json servers that need approval
|
// If settings are valid, check for any mcp.json servers that need approval
|
||||||
const { errors: allErrors } = getSettingsWithAllErrors()
|
const { errors: allErrors } = getSettingsWithAllErrors();
|
||||||
if (allErrors.length === 0) {
|
if (allErrors.length === 0) {
|
||||||
await handleMcpjsonServerApprovals(root)
|
await handleMcpjsonServerApprovals(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for claude.md includes that need approval
|
// Check for claude.md includes that need approval
|
||||||
if (await shouldShowClaudeMdExternalIncludesWarning()) {
|
if (await shouldShowClaudeMdExternalIncludesWarning()) {
|
||||||
const externalIncludes = getExternalClaudeMdIncludes(
|
const externalIncludes = getExternalClaudeMdIncludes(await getMemoryFiles(true));
|
||||||
await getMemoryFiles(true),
|
const { ClaudeMdExternalIncludesDialog } = await import('./components/ClaudeMdExternalIncludesDialog.js');
|
||||||
)
|
|
||||||
const { ClaudeMdExternalIncludesDialog } = await import(
|
|
||||||
'./components/ClaudeMdExternalIncludesDialog.js'
|
|
||||||
)
|
|
||||||
await showSetupDialog(root, done => (
|
await showSetupDialog(root, done => (
|
||||||
<ClaudeMdExternalIncludesDialog
|
<ClaudeMdExternalIncludesDialog onDone={done} isStandaloneDialog externalIncludes={externalIncludes} />
|
||||||
onDone={done}
|
));
|
||||||
isStandaloneDialog
|
|
||||||
externalIncludes={externalIncludes}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track current repo path for teleport directory switching (fire-and-forget)
|
// Track current repo path for teleport directory switching (fire-and-forget)
|
||||||
// This must happen AFTER trust to prevent untrusted directories from poisoning the mapping
|
// This must happen AFTER trust to prevent untrusted directories from poisoning the mapping
|
||||||
void updateGithubRepoPathMapping()
|
void updateGithubRepoPathMapping();
|
||||||
if (feature('LODESTONE')) {
|
if (feature('LODESTONE')) {
|
||||||
updateDeepLinkTerminalPreference()
|
updateDeepLinkTerminalPreference();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply full environment variables after trust dialog is accepted OR in bypass mode
|
// Apply full environment variables after trust dialog is accepted OR in bypass mode
|
||||||
// In bypass mode (CI/CD, automation), we trust the environment so apply all variables
|
// In bypass mode (CI/CD, automation), we trust the environment so apply all variables
|
||||||
// In normal mode, this happens after the trust dialog is accepted
|
// In normal mode, this happens after the trust dialog is accepted
|
||||||
// This includes potentially dangerous environment variables from untrusted sources
|
// This includes potentially dangerous environment variables from untrusted sources
|
||||||
applyConfigEnvironmentVariables()
|
applyConfigEnvironmentVariables();
|
||||||
|
|
||||||
// Initialize telemetry after env vars are applied so OTEL endpoint env vars and
|
// Initialize telemetry after env vars are applied so OTEL endpoint env vars and
|
||||||
// otelHeadersHelper (which requires trust to execute) are available.
|
// otelHeadersHelper (which requires trust to execute) are available.
|
||||||
// Defer to next tick so the OTel dynamic import resolves after first render
|
// Defer to next tick so the OTel dynamic import resolves after first render
|
||||||
// instead of during the pre-render microtask queue.
|
// instead of during the pre-render microtask queue.
|
||||||
setImmediate(() => initializeTelemetryAfterTrust())
|
setImmediate(() => initializeTelemetryAfterTrust());
|
||||||
|
|
||||||
if (await isQualifiedForGrove()) {
|
if (await isQualifiedForGrove()) {
|
||||||
const { GroveDialog } = await import('src/components/grove/Grove.js')
|
const { GroveDialog } = await import('src/components/grove/Grove.js');
|
||||||
const decision = await showSetupDialog<string>(root, done => (
|
const decision = await showSetupDialog<string>(root, done => (
|
||||||
<GroveDialog
|
<GroveDialog
|
||||||
showIfAlreadyViewed={false}
|
showIfAlreadyViewed={false}
|
||||||
location={onboardingShown ? 'onboarding' : 'policy_update_modal'}
|
location={onboardingShown ? 'onboarding' : 'policy_update_modal'}
|
||||||
onDone={done}
|
onDone={done}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
if (decision === 'escape') {
|
if (decision === 'escape') {
|
||||||
logEvent('tengu_grove_policy_exited', {})
|
logEvent('tengu_grove_policy_exited', {});
|
||||||
gracefulShutdownSync(0)
|
gracefulShutdownSync(0);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,36 +253,24 @@ export async function showSetupScreens(
|
|||||||
// On homespace, ANTHROPIC_API_KEY is preserved in process.env for child
|
// On homespace, ANTHROPIC_API_KEY is preserved in process.env for child
|
||||||
// processes but ignored by Claude Code itself (see auth.ts).
|
// processes but ignored by Claude Code itself (see auth.ts).
|
||||||
if (process.env.ANTHROPIC_API_KEY && !isRunningOnHomespace()) {
|
if (process.env.ANTHROPIC_API_KEY && !isRunningOnHomespace()) {
|
||||||
const customApiKeyTruncated = normalizeApiKeyForConfig(
|
const customApiKeyTruncated = normalizeApiKeyForConfig(process.env.ANTHROPIC_API_KEY);
|
||||||
process.env.ANTHROPIC_API_KEY,
|
const keyStatus = getCustomApiKeyStatus(customApiKeyTruncated);
|
||||||
)
|
|
||||||
const keyStatus = getCustomApiKeyStatus(customApiKeyTruncated)
|
|
||||||
if (keyStatus === 'new') {
|
if (keyStatus === 'new') {
|
||||||
const { ApproveApiKey } = await import('./components/ApproveApiKey.js')
|
const { ApproveApiKey } = await import('./components/ApproveApiKey.js');
|
||||||
await showSetupDialog<boolean>(
|
await showSetupDialog<boolean>(
|
||||||
root,
|
root,
|
||||||
done => (
|
done => <ApproveApiKey customApiKeyTruncated={customApiKeyTruncated} onDone={done} />,
|
||||||
<ApproveApiKey
|
|
||||||
customApiKeyTruncated={customApiKeyTruncated}
|
|
||||||
onDone={done}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
{ onChangeAppState },
|
{ onChangeAppState },
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(permissionMode === 'bypassPermissions' ||
|
(permissionMode === 'bypassPermissions' || allowDangerouslySkipPermissions) &&
|
||||||
allowDangerouslySkipPermissions) &&
|
|
||||||
!hasSkipDangerousModePermissionPrompt()
|
!hasSkipDangerousModePermissionPrompt()
|
||||||
) {
|
) {
|
||||||
const { BypassPermissionsModeDialog } = await import(
|
const { BypassPermissionsModeDialog } = await import('./components/BypassPermissionsModeDialog.js');
|
||||||
'./components/BypassPermissionsModeDialog.js'
|
await showSetupDialog(root, done => <BypassPermissionsModeDialog onAccept={done} />);
|
||||||
)
|
|
||||||
await showSetupDialog(root, done => (
|
|
||||||
<BypassPermissionsModeDialog onAccept={done} />
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --dangerously-load-development-channels confirmation. On accept, append
|
// --dangerously-load-development-channels confirmation. On accept, append
|
||||||
@@ -313,72 +278,60 @@ export async function showSetupScreens(
|
|||||||
// is NOT bypassed — gateChannelServer() still runs; this flag only exists
|
// is NOT bypassed — gateChannelServer() still runs; this flag only exists
|
||||||
// to sidestep the --channels approved-server allowlist.
|
// to sidestep the --channels approved-server allowlist.
|
||||||
if (devChannels && devChannels.length > 0) {
|
if (devChannels && devChannels.length > 0) {
|
||||||
const { DevChannelsDialog } = await import(
|
const { DevChannelsDialog } = await import('./components/DevChannelsDialog.js');
|
||||||
'./components/DevChannelsDialog.js'
|
|
||||||
)
|
|
||||||
await showSetupDialog(root, done => (
|
await showSetupDialog(root, done => (
|
||||||
<DevChannelsDialog
|
<DevChannelsDialog
|
||||||
channels={devChannels}
|
channels={devChannels}
|
||||||
onAccept={() => {
|
onAccept={() => {
|
||||||
// Mark dev entries per-entry so the allowlist bypass doesn't leak
|
// Mark dev entries per-entry so the allowlist bypass doesn't leak
|
||||||
// to --channels entries when both flags are passed.
|
// to --channels entries when both flags are passed.
|
||||||
setAllowedChannels([
|
setAllowedChannels([...getAllowedChannels(), ...devChannels.map(c => ({ ...c, dev: true }))]);
|
||||||
...getAllowedChannels(),
|
setHasDevChannels(true);
|
||||||
...devChannels.map(c => ({ ...c, dev: true })),
|
void done();
|
||||||
])
|
|
||||||
setHasDevChannels(true)
|
|
||||||
void done()
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show Chrome onboarding for first-time Claude in Chrome users
|
// Show Chrome onboarding for first-time Claude in Chrome users
|
||||||
if (
|
if (claudeInChrome && !getGlobalConfig().hasCompletedClaudeInChromeOnboarding) {
|
||||||
claudeInChrome &&
|
const { ClaudeInChromeOnboarding } = await import('./components/ClaudeInChromeOnboarding.js');
|
||||||
!getGlobalConfig().hasCompletedClaudeInChromeOnboarding
|
await showSetupDialog(root, done => <ClaudeInChromeOnboarding onDone={done} />);
|
||||||
) {
|
|
||||||
const { ClaudeInChromeOnboarding } = await import(
|
|
||||||
'./components/ClaudeInChromeOnboarding.js'
|
|
||||||
)
|
|
||||||
await showSetupDialog(root, done => (
|
|
||||||
<ClaudeInChromeOnboarding onDone={done} />
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return onboardingShown
|
return onboardingShown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRenderContext(exitOnCtrlC: boolean): {
|
export function getRenderContext(exitOnCtrlC: boolean): {
|
||||||
renderOptions: RenderOptions
|
renderOptions: RenderOptions;
|
||||||
getFpsMetrics: () => FpsMetrics | undefined
|
getFpsMetrics: () => FpsMetrics | undefined;
|
||||||
stats: StatsStore
|
stats: StatsStore;
|
||||||
} {
|
} {
|
||||||
let lastFlickerTime = 0
|
let lastFlickerTime = 0;
|
||||||
const baseOptions = getBaseRenderOptions(exitOnCtrlC)
|
const baseOptions = getBaseRenderOptions(exitOnCtrlC);
|
||||||
|
|
||||||
// Log analytics event when stdin override is active
|
// Log analytics event when stdin override is active
|
||||||
if (baseOptions.stdin) {
|
if (baseOptions.stdin) {
|
||||||
logEvent('tengu_stdin_interactive', {})
|
logEvent('tengu_stdin_interactive', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fpsTracker = new FpsTracker()
|
const fpsTracker = new FpsTracker();
|
||||||
const stats = createStatsStore()
|
const stats = createStatsStore();
|
||||||
setStatsStore(stats)
|
setStatsStore(stats);
|
||||||
|
|
||||||
// Bench mode: when set, append per-frame phase timings as JSONL for
|
// Bench mode: when set, append per-frame phase timings as JSONL for
|
||||||
// offline analysis by bench/repl-scroll.ts. Captures the full TUI
|
// offline analysis by bench/repl-scroll.ts. Captures the full TUI
|
||||||
// render pipeline (yoga → screen buffer → diff → optimize → stdout)
|
// render pipeline (yoga → screen buffer → diff → optimize → stdout)
|
||||||
// so perf work on any phase can be validated against real user flows.
|
// so perf work on any phase can be validated against real user flows.
|
||||||
const frameTimingLogPath = process.env.CLAUDE_CODE_FRAME_TIMING_LOG
|
const frameTimingLogPath = process.env.CLAUDE_CODE_FRAME_TIMING_LOG;
|
||||||
return {
|
return {
|
||||||
getFpsMetrics: () => fpsTracker.getMetrics(),
|
getFpsMetrics: () => fpsTracker.getMetrics(),
|
||||||
stats,
|
stats,
|
||||||
renderOptions: {
|
renderOptions: {
|
||||||
...baseOptions,
|
...baseOptions,
|
||||||
onFrame: event => {
|
onFrame: event => {
|
||||||
fpsTracker.record(event.durationMs)
|
fpsTracker.record(event.durationMs);
|
||||||
stats.observe('frame_duration_ms', event.durationMs)
|
stats.observe('frame_duration_ms', event.durationMs);
|
||||||
if (frameTimingLogPath && event.phases) {
|
if (frameTimingLogPath && event.phases) {
|
||||||
// Bench-only env-var-gated path: sync write so no frames dropped
|
// Bench-only env-var-gated path: sync write so no frames dropped
|
||||||
// on abrupt exit. ~100 bytes at ≤60fps is negligible. rss/cpu are
|
// on abrupt exit. ~100 bytes at ≤60fps is negligible. rss/cpu are
|
||||||
@@ -390,30 +343,30 @@ export function getRenderContext(exitOnCtrlC: boolean): {
|
|||||||
...event.phases,
|
...event.phases,
|
||||||
rss: process.memoryUsage.rss(),
|
rss: process.memoryUsage.rss(),
|
||||||
cpu: process.cpuUsage(),
|
cpu: process.cpuUsage(),
|
||||||
}) + '\n'
|
}) + '\n';
|
||||||
// eslint-disable-next-line custom-rules/no-sync-fs -- bench-only, sync so no frames dropped on exit
|
// eslint-disable-next-line custom-rules/no-sync-fs -- bench-only, sync so no frames dropped on exit
|
||||||
appendFileSync(frameTimingLogPath, line)
|
appendFileSync(frameTimingLogPath, line);
|
||||||
}
|
}
|
||||||
// Skip flicker reporting for terminals with synchronized output —
|
// Skip flicker reporting for terminals with synchronized output —
|
||||||
// DEC 2026 buffers between BSU/ESU so clear+redraw is atomic.
|
// DEC 2026 buffers between BSU/ESU so clear+redraw is atomic.
|
||||||
if (isSynchronizedOutputSupported()) {
|
if (isSynchronizedOutputSupported()) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
for (const flicker of event.flickers) {
|
for (const flicker of event.flickers) {
|
||||||
if (flicker.reason === 'resize') {
|
if (flicker.reason === 'resize') {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
const now = Date.now()
|
const now = Date.now();
|
||||||
if (now - lastFlickerTime < 1000) {
|
if (now - lastFlickerTime < 1000) {
|
||||||
logEvent('tengu_flicker', {
|
logEvent('tengu_flicker', {
|
||||||
desiredHeight: flicker.desiredHeight,
|
desiredHeight: flicker.desiredHeight,
|
||||||
actualHeight: flicker.availableHeight,
|
actualHeight: flicker.availableHeight,
|
||||||
reason: flicker.reason,
|
reason: flicker.reason,
|
||||||
} as unknown as Record<string, boolean | number | undefined>)
|
} as unknown as Record<string, boolean | number | undefined>);
|
||||||
}
|
}
|
||||||
lastFlickerTime = now
|
lastFlickerTime = now;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user