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

View File

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

View File

@@ -28,7 +28,7 @@ export function WelcomeV2() {
let t7;
let t8;
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>;
t2 = <Text>{" "}</Text>;
t3 = <Text>{" "}</Text>;
@@ -113,7 +113,7 @@ export function WelcomeV2() {
let t5;
let t6;
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>;
t2 = <Text>{" "}</Text>;
t3 = <Text>{" * \u2588\u2588\u2588\u2588\u2588\u2593\u2593\u2591 "}</Text>;
@@ -218,7 +218,7 @@ function AppleTerminalWelcomeV2(t0) {
}
let t2;
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;
} else {
t2 = $[2];
@@ -329,7 +329,7 @@ function AppleTerminalWelcomeV2(t0) {
}
let t2;
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;
} else {
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')) {
// MACRO.VERSION is inlined at build time
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${MACRO.VERSION} (Claude Code)`);
console.log(`${MACRO.DISPLAY_VERSION ?? MACRO.VERSION} (Open Claude)`);
return;
}
@@ -322,10 +322,12 @@ async function main(): Promise<void> {
}
// No special flags detected, load and run the full CLI
const {
startCapturingEarlyInput
} = await import('../utils/earlyInput.js');
startCapturingEarlyInput();
if (process.env.OPENCLAUDE_ENABLE_EARLY_INPUT === '1') {
const {
startCapturingEarlyInput
} = await import('../utils/earlyInput.js');
startCapturingEarlyInput();
}
profileCheckpoint('cli_before_main_import');
const {
main: cliMain

View File

@@ -115,6 +115,7 @@ export default class App extends PureComponent<Props, State> {
keyParseState = INITIAL_STATE;
// Timer for flushing incomplete escape sequences
incompleteEscapeTimer: NodeJS.Timeout | null = null;
stdinMode: 'readable' | 'data' = process.env.OPENCLAUDE_USE_READABLE_STDIN === '1' ? 'readable' : 'data';
// Timeout durations for incomplete sequences (ms)
readonly NORMAL_TIMEOUT = 50; // Short timeout for regular esc sequences
readonly PASTE_TIMEOUT = 500; // Longer timeout for paste operations
@@ -228,7 +229,12 @@ export default class App extends PureComponent<Props, State> {
stopCapturingEarlyInput();
stdin.ref();
stdin.setRawMode(true);
stdin.addListener('readable', this.handleReadable);
stdin.resume();
if (this.stdinMode === 'data') {
stdin.addListener('data', this.handleDataChunk);
} else {
stdin.addListener('readable', this.handleReadable);
}
// Enable bracketed paste mode
this.props.stdout.write(EBP);
// Enable terminal focus reporting (DECSET 1004)
@@ -275,6 +281,8 @@ export default class App extends PureComponent<Props, State> {
this.props.stdout.write(DBP);
stdin.setRawMode(false);
stdin.removeListener('readable', this.handleReadable);
stdin.removeListener('data', this.handleDataChunk);
stdin.pause();
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 => {
// Exit on Ctrl+C
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
* (Kitty keyboard protocol + xterm modifyOtherKeys). */
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 ?? '')
}

View File

@@ -3782,7 +3782,7 @@ async function run(): Promise<CommanderCommand> {
pendingHookMessages
}, 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
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 type { EffortValue } from '../utils/effort.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 */
const AntModelSwitchCallout = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null;
const shouldShowAntModelSwitch = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : (): boolean => false;
@@ -2675,7 +2676,7 @@ export function REPL({
// useDeferredHookMessages) and attachment messages (appended by
// processTextPrompt) — both pushed length past 1 on turn one, so the
// 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 text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content) : null;
// 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 { logError } from '../utils/log.js'
import { getSmallFastModel } from '../utils/model/model.js'
import { getAPIProvider } from '../utils/model/providers.js'
import { isEssentialTrafficOnly } from '../utils/privacyLevel.js'
import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from './analytics/index.js'
import { logEvent } from './analytics/index.js'
@@ -223,6 +224,10 @@ export async function checkQuotaStatus(): Promise<void> {
return
}
if (getAPIProvider() !== 'firstParty') {
return
}
// Check if we should process rate limits (real subscriber or mock testing)
if (!shouldProcessRateLimits(isClaudeAISubscriber())) {
return

View File

@@ -9,6 +9,7 @@ import { getClaudeAIOAuthTokens } from 'src/utils/auth.js'
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js'
import { logForDebugging } from 'src/utils/debug.js'
import { isEnvDefinedFalsy } from 'src/utils/envUtils.js'
import { getAPIProvider } from 'src/utils/model/providers.js'
import { clearMcpAuthCache } from './client.js'
import { normalizeNameForMCP } from './normalization.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(
async (): Promise<Record<string, ScopedMcpServerConfig>> => {
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)) {
logForDebugging('[claudeai-mcp] Disabled via env var')
logEvent('tengu_claudeai_mcp_eligibility', {

View File

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

View File

@@ -36,6 +36,9 @@ import {
import { createSignal } from './signal.js'
export function isFastModeEnabled(): boolean {
if (getAPIProvider() !== 'firstParty') {
return false
}
return !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_FAST_MODE)
}
@@ -70,6 +73,10 @@ function getDisabledReasonMessage(
}
export function getFastModeUnavailableReason(): string | null {
if (getAPIProvider() !== 'firstParty') {
return 'Fast mode is not available on third-party providers'
}
if (!isFastModeEnabled()) {
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.reason === 'network_error' ||

View File

@@ -245,7 +245,7 @@ export function getLogoDisplayData(): {
billingType: string
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 displayPath = process.env.DEMO_VERSION
? '/code/claude'