feat: rebrand as Open Claude and harden OpenAI REPL

This commit is contained in:
Kevin Codex
2026-04-01 13:31:18 +08:00
parent 55098bf9b7
commit 2d7aa9c841
14 changed files with 109 additions and 53 deletions

View File

@@ -52,8 +52,11 @@ const result = await Bun.build({
naming: 'cli.mjs', naming: 'cli.mjs',
define: { define: {
// MACRO.* build-time constants // MACRO.* build-time constants
// Set version high enough to pass minimum version checks // Keep the internal compatibility version high enough to pass
// first-party minimum-version guards, but expose the real package
// version separately in Open Claude branding.
'MACRO.VERSION': JSON.stringify('99.0.0'), 'MACRO.VERSION': JSON.stringify('99.0.0'),
'MACRO.DISPLAY_VERSION': JSON.stringify(version),
'MACRO.BUILD_TIME': JSON.stringify(new Date().toISOString()), 'MACRO.BUILD_TIME': JSON.stringify(new Date().toISOString()),
'MACRO.ISSUES_EXPLAINER': 'MACRO.ISSUES_EXPLAINER':
JSON.stringify('report the issue at https://github.com/anthropics/claude-code/issues'), JSON.stringify('report the issue at https://github.com/anthropics/claude-code/issues'),

View File

@@ -33,42 +33,42 @@ type Segments = {
}; };
const POSES: Record<ClawdPose, Segments> = { const POSES: Record<ClawdPose, Segments> = {
default: { default: {
r1L: ' ', r1L: ' ',
r1E: '▛███▜', r1E: '○ ○ ',
r1R: '', r1R: '',
r2L: '▝▜', r2L: '',
r2R: '▛▘' r2R: ''
}, },
'look-left': { 'look-left': {
r1L: ' ', r1L: ' ',
r1E: '▟███▟', r1E: '◔ ○ ',
r1R: '', r1R: '',
r2L: '▝▜', r2L: '',
r2R: '▛▘' r2R: ''
}, },
'look-right': { 'look-right': {
r1L: ' ', r1L: ' ',
r1E: '▙███▙', r1E: '○ ◔ ',
r1R: '', r1R: '',
r2L: '▝▜', r2L: '',
r2R: '▛▘' r2R: ''
}, },
'arms-up': { 'arms-up': {
r1L: '▗▟', r1L: '\\╭',
r1E: '▛███▜', r1E: '○ ○ ',
r1R: '▙▖', r1R: '╮/',
r2L: ' ', r2L: ' ',
r2R: ' ' r2R: ' '
} }
}; };
// Apple Terminal uses a bg-fill trick (see below), so only eye poses make // Apple Terminal uses a bg-fill trick (see below), so only eye poses make
// sense. Arm poses fall back to default. // sense. Arm poses fall back to default.
const APPLE_EYES: Record<ClawdPose, string> = { const APPLE_EYES: Record<ClawdPose, string> = {
default: ' ▗ ▖ ', default: ' ○ ○ ',
'look-left': ' ▘ ▘ ', 'look-left': ' ◔ ○ ',
'look-right': ' ▝ ▝ ', 'look-right': ' ○ ◔ ',
'arms-up': ' ▗ ▖ ' 'arms-up': ' ○ ○ '
}; };
export function Clawd(t0) { export function Clawd(t0) {
const $ = _c(26); const $ = _c(26);
@@ -140,7 +140,7 @@ export function Clawd(t0) {
} }
let t8; let t8;
if ($[16] === Symbol.for("react.memo_cache_sentinel")) { if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
t8 = <Text color="clawd_body" backgroundColor="clawd_background"></Text>; t8 = <Text color="clawd_body" backgroundColor="clawd_background">OPEN </Text>;
$[16] = t8; $[16] = t8;
} else { } else {
t8 = $[16]; t8 = $[16];
@@ -164,7 +164,7 @@ export function Clawd(t0) {
} }
let t11; let t11;
if ($[22] === Symbol.for("react.memo_cache_sentinel")) { if ($[22] === Symbol.for("react.memo_cache_sentinel")) {
t11 = <Text color="clawd_body">{" "} {" "}</Text>; t11 = <Text color="clawd_body">{" "}{" "}</Text>;
$[22] = t11; $[22] = t11;
} else { } else {
t11 = $[22]; t11 = $[22];

View File

@@ -88,7 +88,7 @@ export function CondensedLogo() {
} }
let t5; let t5;
if ($[8] === Symbol.for("react.memo_cache_sentinel")) { if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
t5 = <Text bold={true}>Claude Code</Text>; t5 = <Text bold={true}>Open Claude</Text>;
$[8] = t5; $[8] = t5;
} else { } else {
t5 = $[8]; t5 = $[8];

View File

@@ -28,7 +28,7 @@ export function WelcomeV2() {
let t7; let t7;
let t8; let t8;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) { if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <Text><Text color="claude">{"Welcome to Claude Code"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>; t0 = <Text><Text color="claude">{"Welcome to Open Claude"} </Text><Text dimColor={true}>v{MACRO.DISPLAY_VERSION ?? MACRO.VERSION} </Text></Text>;
t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>; t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
t2 = <Text>{" "}</Text>; t2 = <Text>{" "}</Text>;
t3 = <Text>{" "}</Text>; t3 = <Text>{" "}</Text>;
@@ -113,7 +113,7 @@ export function WelcomeV2() {
let t5; let t5;
let t6; let t6;
if ($[18] === Symbol.for("react.memo_cache_sentinel")) { if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <Text><Text color="claude">{"Welcome to Claude Code"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>; t0 = <Text><Text color="claude">{"Welcome to Open Claude"} </Text><Text dimColor={true}>v{MACRO.DISPLAY_VERSION ?? MACRO.VERSION} </Text></Text>;
t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>; t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
t2 = <Text>{" "}</Text>; t2 = <Text>{" "}</Text>;
t3 = <Text>{" * \u2588\u2588\u2588\u2588\u2588\u2593\u2593\u2591 "}</Text>; t3 = <Text>{" * \u2588\u2588\u2588\u2588\u2588\u2593\u2593\u2591 "}</Text>;
@@ -218,7 +218,7 @@ function AppleTerminalWelcomeV2(t0) {
} }
let t2; let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) { if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = <Text dimColor={true}>v{MACRO.VERSION} </Text>; t2 = <Text dimColor={true}>v{MACRO.DISPLAY_VERSION ?? MACRO.VERSION} </Text>;
$[2] = t2; $[2] = t2;
} else { } else {
t2 = $[2]; t2 = $[2];
@@ -329,7 +329,7 @@ function AppleTerminalWelcomeV2(t0) {
} }
let t2; let t2;
if ($[24] === Symbol.for("react.memo_cache_sentinel")) { if ($[24] === Symbol.for("react.memo_cache_sentinel")) {
t2 = <Text dimColor={true}>v{MACRO.VERSION} </Text>; t2 = <Text dimColor={true}>v{MACRO.DISPLAY_VERSION ?? MACRO.VERSION} </Text>;
$[24] = t2; $[24] = t2;
} else { } else {
t2 = $[24]; t2 = $[24];

View File

@@ -72,7 +72,7 @@ async function main(): Promise<void> {
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) { if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) {
// MACRO.VERSION is inlined at build time // MACRO.VERSION is inlined at build time
// biome-ignore lint/suspicious/noConsole:: intentional console output // biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${MACRO.VERSION} (Claude Code)`); console.log(`${MACRO.DISPLAY_VERSION ?? MACRO.VERSION} (Open Claude)`);
return; return;
} }
@@ -322,10 +322,12 @@ async function main(): Promise<void> {
} }
// No special flags detected, load and run the full CLI // No special flags detected, load and run the full CLI
if (process.env.OPENCLAUDE_ENABLE_EARLY_INPUT === '1') {
const { const {
startCapturingEarlyInput startCapturingEarlyInput
} = await import('../utils/earlyInput.js'); } = await import('../utils/earlyInput.js');
startCapturingEarlyInput(); startCapturingEarlyInput();
}
profileCheckpoint('cli_before_main_import'); profileCheckpoint('cli_before_main_import');
const { const {
main: cliMain main: cliMain

View File

@@ -115,6 +115,7 @@ export default class App extends PureComponent<Props, State> {
keyParseState = INITIAL_STATE; keyParseState = INITIAL_STATE;
// Timer for flushing incomplete escape sequences // Timer for flushing incomplete escape sequences
incompleteEscapeTimer: NodeJS.Timeout | null = null; incompleteEscapeTimer: NodeJS.Timeout | null = null;
stdinMode: 'readable' | 'data' = process.env.OPENCLAUDE_USE_READABLE_STDIN === '1' ? 'readable' : 'data';
// Timeout durations for incomplete sequences (ms) // Timeout durations for incomplete sequences (ms)
readonly NORMAL_TIMEOUT = 50; // Short timeout for regular esc sequences readonly NORMAL_TIMEOUT = 50; // Short timeout for regular esc sequences
readonly PASTE_TIMEOUT = 500; // Longer timeout for paste operations readonly PASTE_TIMEOUT = 500; // Longer timeout for paste operations
@@ -228,7 +229,12 @@ export default class App extends PureComponent<Props, State> {
stopCapturingEarlyInput(); stopCapturingEarlyInput();
stdin.ref(); stdin.ref();
stdin.setRawMode(true); stdin.setRawMode(true);
stdin.resume();
if (this.stdinMode === 'data') {
stdin.addListener('data', this.handleDataChunk);
} else {
stdin.addListener('readable', this.handleReadable); stdin.addListener('readable', this.handleReadable);
}
// Enable bracketed paste mode // Enable bracketed paste mode
this.props.stdout.write(EBP); this.props.stdout.write(EBP);
// Enable terminal focus reporting (DECSET 1004) // Enable terminal focus reporting (DECSET 1004)
@@ -275,6 +281,8 @@ export default class App extends PureComponent<Props, State> {
this.props.stdout.write(DBP); this.props.stdout.write(DBP);
stdin.setRawMode(false); stdin.setRawMode(false);
stdin.removeListener('readable', this.handleReadable); stdin.removeListener('readable', this.handleReadable);
stdin.removeListener('data', this.handleDataChunk);
stdin.pause();
stdin.unref(); stdin.unref();
} }
}; };
@@ -366,6 +374,27 @@ export default class App extends PureComponent<Props, State> {
} }
} }
}; };
handleDataChunk = (chunk: string | Buffer): void => {
const now = Date.now();
if (now - this.lastStdinTime > STDIN_RESUME_GAP_MS) {
this.props.onStdinResume?.();
}
this.lastStdinTime = now;
try {
this.processInput(chunk);
} catch (error) {
logError(error);
const {
stdin
} = this.props;
if (this.rawModeEnabledCount > 0 && !stdin.listeners('data').includes(this.handleDataChunk)) {
logForDebugging('handleDataChunk: re-attaching stdin data listener after error recovery', {
level: 'warn'
});
stdin.addListener('data', this.handleDataChunk);
}
}
};
handleInput = (input: string | undefined): void => { handleInput = (input: string | undefined): void => {
// Exit on Ctrl+C // Exit on Ctrl+C
if (input === '\x03' && this.props.exitOnCtrlC) { if (input === '\x03' && this.props.exitOnCtrlC) {

View File

@@ -165,6 +165,12 @@ const EXTENDED_KEYS_TERMINALS = [
/** True if this terminal correctly handles extended key reporting /** True if this terminal correctly handles extended key reporting
* (Kitty keyboard protocol + xterm modifyOtherKeys). */ * (Kitty keyboard protocol + xterm modifyOtherKeys). */
export function supportsExtendedKeys(): boolean { export function supportsExtendedKeys(): boolean {
// Open Claude defaults this off because some real terminals render the UI
// but stop delivering normal typing once kitty/modifyOtherKeys negotiation
// is enabled. Power users can opt back in explicitly.
if (process.env.OPENCLAUDE_ENABLE_EXTENDED_KEYS !== '1') {
return false
}
return EXTENDED_KEYS_TERMINALS.includes(env.terminal ?? '') return EXTENDED_KEYS_TERMINALS.includes(env.terminal ?? '')
} }

View File

@@ -3782,7 +3782,7 @@ async function run(): Promise<CommanderCommand> {
pendingHookMessages pendingHookMessages
}, renderAndRun); }, renderAndRun);
} }
}).version(`${MACRO.VERSION} (Claude Code)`, '-v, --version', 'Output the version number'); }).version(`${MACRO.DISPLAY_VERSION ?? MACRO.VERSION} (Open Claude)`, '-v, --version', 'Output the version number');
// Worktree flags // Worktree flags
program.option('-w, --worktree [name]', 'Create a new git worktree for this session (optionally specify a name)'); program.option('-w, --worktree [name]', 'Create a new git worktree for this session (optionally specify a name)');

View File

@@ -217,6 +217,7 @@ import { IdeOnboardingDialog } from '../components/IdeOnboardingDialog.js';
import { EffortCallout, shouldShowEffortCallout } from '../components/EffortCallout.js'; import { EffortCallout, shouldShowEffortCallout } from '../components/EffortCallout.js';
import type { EffortValue } from '../utils/effort.js'; import type { EffortValue } from '../utils/effort.js';
import { RemoteCallout } from '../components/RemoteCallout.js'; import { RemoteCallout } from '../components/RemoteCallout.js';
import { getAPIProvider } from '../utils/model/providers.js';
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ /* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
const AntModelSwitchCallout = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null; const AntModelSwitchCallout = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null;
const shouldShowAntModelSwitch = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : (): boolean => false; const shouldShowAntModelSwitch = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : (): boolean => false;
@@ -2675,7 +2676,7 @@ export function REPL({
// useDeferredHookMessages) and attachment messages (appended by // useDeferredHookMessages) and attachment messages (appended by
// processTextPrompt) — both pushed length past 1 on turn one, so the // processTextPrompt) — both pushed length past 1 on turn one, so the
// title silently fell through to the "Claude Code" default. // title silently fell through to the "Claude Code" default.
if (!titleDisabled && !sessionTitle && !agentTitle && !haikuTitleAttemptedRef.current) { if (getAPIProvider() === 'firstParty' && !titleDisabled && !sessionTitle && !agentTitle && !haikuTitleAttemptedRef.current) {
const firstUserMessage = newMessages.find(m => m.type === 'user' && !m.isMeta); const firstUserMessage = newMessages.find(m => m.type === 'user' && !m.isMeta);
const text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content) : null; const text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content) : null;
// Skip synthetic breadcrumbs — slash-command output, prompt-skill // Skip synthetic breadcrumbs — slash-command output, prompt-skill

View File

@@ -7,6 +7,7 @@ import { getModelBetas } from '../utils/betas.js'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js' import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js'
import { logError } from '../utils/log.js' import { logError } from '../utils/log.js'
import { getSmallFastModel } from '../utils/model/model.js' import { getSmallFastModel } from '../utils/model/model.js'
import { getAPIProvider } from '../utils/model/providers.js'
import { isEssentialTrafficOnly } from '../utils/privacyLevel.js' import { isEssentialTrafficOnly } from '../utils/privacyLevel.js'
import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from './analytics/index.js' import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from './analytics/index.js'
import { logEvent } from './analytics/index.js' import { logEvent } from './analytics/index.js'
@@ -223,6 +224,10 @@ export async function checkQuotaStatus(): Promise<void> {
return return
} }
if (getAPIProvider() !== 'firstParty') {
return
}
// Check if we should process rate limits (real subscriber or mock testing) // Check if we should process rate limits (real subscriber or mock testing)
if (!shouldProcessRateLimits(isClaudeAISubscriber())) { if (!shouldProcessRateLimits(isClaudeAISubscriber())) {
return return

View File

@@ -9,6 +9,7 @@ import { getClaudeAIOAuthTokens } from 'src/utils/auth.js'
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js' import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js'
import { logForDebugging } from 'src/utils/debug.js' import { logForDebugging } from 'src/utils/debug.js'
import { isEnvDefinedFalsy } from 'src/utils/envUtils.js' import { isEnvDefinedFalsy } from 'src/utils/envUtils.js'
import { getAPIProvider } from 'src/utils/model/providers.js'
import { clearMcpAuthCache } from './client.js' import { clearMcpAuthCache } from './client.js'
import { normalizeNameForMCP } from './normalization.js' import { normalizeNameForMCP } from './normalization.js'
import type { ScopedMcpServerConfig } from './types.js' import type { ScopedMcpServerConfig } from './types.js'
@@ -39,6 +40,15 @@ const MCP_SERVERS_BETA_HEADER = 'mcp-servers-2025-12-04'
export const fetchClaudeAIMcpConfigsIfEligible = memoize( export const fetchClaudeAIMcpConfigsIfEligible = memoize(
async (): Promise<Record<string, ScopedMcpServerConfig>> => { async (): Promise<Record<string, ScopedMcpServerConfig>> => {
try { try {
if (getAPIProvider() !== 'firstParty') {
logForDebugging('[claudeai-mcp] Skipped: non-first-party provider')
logEvent('tengu_claudeai_mcp_eligibility', {
state:
'non_first_party_provider' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
return {}
}
if (isEnvDefinedFalsy(process.env.ENABLE_CLAUDEAI_MCP_SERVERS)) { if (isEnvDefinedFalsy(process.env.ENABLE_CLAUDEAI_MCP_SERVERS)) {
logForDebugging('[claudeai-mcp] Disabled via env var') logForDebugging('[claudeai-mcp] Disabled via env var')
logEvent('tengu_claudeai_mcp_eligibility', { logEvent('tengu_claudeai_mcp_eligibility', {

View File

@@ -109,7 +109,7 @@ export function execFileNoThrowWithCwd(
// Use execa for cross-platform .bat/.cmd compatibility on Windows // Use execa for cross-platform .bat/.cmd compatibility on Windows
execa(file, args, { execa(file, args, {
maxBuffer, maxBuffer,
signal: abortSignal, cancelSignal: abortSignal,
timeout: finalTimeout, timeout: finalTimeout,
cwd: finalCwd, cwd: finalCwd,
env: finalEnv, env: finalEnv,

View File

@@ -36,6 +36,9 @@ import {
import { createSignal } from './signal.js' import { createSignal } from './signal.js'
export function isFastModeEnabled(): boolean { export function isFastModeEnabled(): boolean {
if (getAPIProvider() !== 'firstParty') {
return false
}
return !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_FAST_MODE) return !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_FAST_MODE)
} }
@@ -70,6 +73,10 @@ function getDisabledReasonMessage(
} }
export function getFastModeUnavailableReason(): string | null { export function getFastModeUnavailableReason(): string | null {
if (getAPIProvider() !== 'firstParty') {
return 'Fast mode is not available on third-party providers'
}
if (!isFastModeEnabled()) { if (!isFastModeEnabled()) {
return 'Fast mode is not available' return 'Fast mode is not available'
} }
@@ -109,13 +116,6 @@ export function getFastModeUnavailableReason(): string | null {
} }
} }
// Only available for 1P (not Bedrock/Vertex/Foundry)
if (getAPIProvider() !== 'firstParty') {
const reason = 'Fast mode is not available on Bedrock, Vertex, or Foundry'
logForDebugging(`Fast mode unavailable: ${reason}`)
return reason
}
if (orgStatus.status === 'disabled') { if (orgStatus.status === 'disabled') {
if ( if (
orgStatus.reason === 'network_error' || orgStatus.reason === 'network_error' ||

View File

@@ -245,7 +245,7 @@ export function getLogoDisplayData(): {
billingType: string billingType: string
agentName: string | undefined agentName: string | undefined
} { } {
const version = process.env.DEMO_VERSION ?? MACRO.VERSION const version = process.env.DEMO_VERSION ?? MACRO.DISPLAY_VERSION ?? MACRO.VERSION
const serverUrl = getDirectConnectServerUrl() const serverUrl = getDirectConnectServerUrl()
const displayPath = process.env.DEMO_VERSION const displayPath = process.env.DEMO_VERSION
? '/code/claude' ? '/code/claude'