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'),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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'