Merge pull request #5 from y574444354/feat/basic-config

feat: 品牌化首页为 CoStrict 风格
This commit is contained in:
geroge 2026-04-09 14:34:48 +08:00 committed by GitHub
commit 523db59b4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 312 additions and 596 deletions

View File

@ -7,7 +7,7 @@
*/ */
export function getMacroDefines(): Record<string, string> { export function getMacroDefines(): Record<string, string> {
return { return {
"MACRO.VERSION": JSON.stringify("2.1.888"), "MACRO.VERSION": JSON.stringify("4.0.1"),
"MACRO.BUILD_TIME": JSON.stringify(new Date().toISOString()), "MACRO.BUILD_TIME": JSON.stringify(new Date().toISOString()),
"MACRO.FEEDBACK_CHANNEL": JSON.stringify(""), "MACRO.FEEDBACK_CHANNEL": JSON.stringify(""),
"MACRO.ISSUES_EXPLAINER": JSON.stringify(""), "MACRO.ISSUES_EXPLAINER": JSON.stringify(""),

View File

@ -1,98 +1,21 @@
import * as React from 'react' import * as React from 'react';
import { Box, Text } from '@anthropic/ink' import { Box, Text } from '@anthropic/ink';
import { env } from '../../utils/env.js'
export type ClawdPose = export type ClawdPose = 'default' | 'arms-up' | 'look-left' | 'look-right';
| 'default'
| 'arms-up' // both arms raised (used during jump)
| 'look-left' // both pupils shifted left
| 'look-right' // both pupils shifted right
type Props = { type Props = {
pose?: ClawdPose pose?: ClawdPose;
} };
// Standard-terminal pose fragments. Each row is split into segments so we can // CoStrict ASCII art 大字标志,两行块字符风格:
// vary only the parts that change (eyes, arms) while keeping the body/bg spans // █▀▀ █▀█ █▀ ▀█▀ █▀█ █ █▀▀ ▀█▀
// stable. All poses end up 9 cols wide. // █▄▄ █▄█ ▄█ █ █▀▄ █ █▄▄ █
//
// arms-up: the row-2 arm shapes (▝▜ / ▛▘) move to row 1 as their
// bottom-heavy mirrors (▗▟ / ▙▖) — same silhouette, one row higher.
//
// look-* use top-quadrant eye chars (▙/▟) so both eyes change from the
// default (▛/▜, bottom pupils) — otherwise only one eye would appear to move.
type Segments = {
/** row 1 left (no bg): optional raised arm + side */
r1L: string
/** row 1 eyes (with bg): left-eye, forehead, right-eye */
r1E: string
/** row 1 right (no bg): side + optional raised arm */
r1R: string
/** row 2 left (no bg): arm + body curve */
r2L: string
/** row 2 right (no bg): body curve + arm */
r2R: string
}
const POSES: Record<ClawdPose, Segments> = {
default: { r1L: ' ▐', r1E: '▛███▜', r1R: '▌', r2L: '▝▜', r2R: '▛▘' },
'look-left': { r1L: ' ▐', r1E: '▟███▟', r1R: '▌', r2L: '▝▜', r2R: '▛▘' },
'look-right': { r1L: ' ▐', r1E: '▙███▙', r1R: '▌', r2L: '▝▜', r2R: '▛▘' },
'arms-up': { 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': ' ▗ ▖ ',
}
export function Clawd({ pose = 'default' }: Props = {}): React.ReactNode { export function Clawd({ pose = 'default' }: Props = {}): React.ReactNode {
if (env.terminal === 'Apple_Terminal') {
return <AppleTerminalClawd pose={pose} />
}
const p = POSES[pose]
return (
<Box flexDirection="column">
<Text>
<Text color="clawd_body">{p.r1L}</Text>
<Text color="clawd_body" backgroundColor="clawd_background">
{p.r1E}
</Text>
<Text color="clawd_body">{p.r1R}</Text>
</Text>
<Text>
<Text color="clawd_body">{p.r2L}</Text>
<Text color="clawd_body" backgroundColor="clawd_background">
</Text>
<Text color="clawd_body">{p.r2R}</Text>
</Text>
<Text color="clawd_body">
{' '} {' '}
</Text>
</Box>
)
}
function AppleTerminalClawd({ pose }: { pose: ClawdPose }): React.ReactNode {
// Apple's Terminal renders vertical space between chars by default.
// It does NOT render vertical space between background colors
// so we use background color to draw the main shape.
return ( return (
<Box flexDirection="column" alignItems="center"> <Box flexDirection="column" alignItems="center">
<Text> <Text color="claudeBlue_FOR_SYSTEM_SPINNER">{'█▀▀ █▀█ █▀ ▀█▀ █▀█ █ █▀▀ ▀█▀'}</Text>
<Text color="clawd_body"></Text> <Text color="claudeBlue_FOR_SYSTEM_SPINNER">{'█▄▄ █▄█ ▄█ █ █▀▄ █ █▄▄ █ '}</Text>
<Text color="clawd_background" backgroundColor="clawd_body">
{APPLE_EYES[pose]}
</Text>
<Text color="clawd_body"></Text>
</Text>
<Text backgroundColor="clawd_body">{' '.repeat(7)}</Text>
<Text color="clawd_body"> </Text>
</Box> </Box>
) );
} }

View File

@ -1,83 +1,71 @@
import * as React from 'react' import * as React from 'react';
import { type ReactNode, useEffect } from 'react' import { type ReactNode, useEffect } from 'react';
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';
import { useTerminalSize } from '../../hooks/useTerminalSize.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js';
import { Box, Text, stringWidth } from '@anthropic/ink' import { Box, Text, stringWidth } from '@anthropic/ink';
import { useAppState } from '../../state/AppState.js' import { useAppState } from '../../state/AppState.js';
import { getEffortSuffix } from '../../utils/effort.js' import { getEffortSuffix } from '../../utils/effort.js';
import { truncate } from '../../utils/format.js' import { truncate } from '../../utils/format.js';
import { isFullscreenEnvEnabled } from '../../utils/fullscreen.js' import { isFullscreenEnvEnabled } from '../../utils/fullscreen.js';
import { import { formatModelAndBilling, getLogoDisplayData, truncatePath } from '../../utils/logoV2Utils.js';
formatModelAndBilling, import { renderModelSetting } from '../../utils/model/model.js';
getLogoDisplayData, import { OffscreenFreeze } from '../OffscreenFreeze.js';
truncatePath, import { AnimatedClawd } from './AnimatedClawd.js';
} from '../../utils/logoV2Utils.js' import { Clawd } from './Clawd.js';
import { renderModelSetting } from '../../utils/model/model.js' import { GuestPassesUpsell, incrementGuestPassesSeenCount, useShowGuestPassesUpsell } from './GuestPassesUpsell.js';
import { OffscreenFreeze } from '../OffscreenFreeze.js'
import { AnimatedClawd } from './AnimatedClawd.js'
import { Clawd } from './Clawd.js'
import {
GuestPassesUpsell,
incrementGuestPassesSeenCount,
useShowGuestPassesUpsell,
} from './GuestPassesUpsell.js'
import { import {
incrementOverageCreditUpsellSeenCount, incrementOverageCreditUpsellSeenCount,
OverageCreditUpsell, OverageCreditUpsell,
useShowOverageCreditUpsell, useShowOverageCreditUpsell,
} from './OverageCreditUpsell.js' } from './OverageCreditUpsell.js';
export function CondensedLogo(): ReactNode { export function CondensedLogo(): ReactNode {
const { columns } = useTerminalSize() const { columns } = useTerminalSize();
const agent = useAppState(s => s.agent) const agent = useAppState(s => s.agent);
const effortValue = useAppState(s => s.effortValue) const effortValue = useAppState(s => s.effortValue);
const model = useMainLoopModel() const model = useMainLoopModel();
const modelDisplayName = renderModelSetting(model) const modelDisplayName = renderModelSetting(model);
const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData() const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData();
// Prefer AppState.agent (set from --agent CLI flag) over settings // Prefer AppState.agent (set from --agent CLI flag) over settings
const agentName = agent ?? agentNameFromSettings const agentName = agent ?? agentNameFromSettings;
const showGuestPassesUpsell = useShowGuestPassesUpsell() const showGuestPassesUpsell = useShowGuestPassesUpsell();
const showOverageCreditUpsell = useShowOverageCreditUpsell() const showOverageCreditUpsell = useShowOverageCreditUpsell();
useEffect(() => { useEffect(() => {
if (showGuestPassesUpsell) { if (showGuestPassesUpsell) {
incrementGuestPassesSeenCount() incrementGuestPassesSeenCount();
} }
}, [showGuestPassesUpsell]) }, [showGuestPassesUpsell]);
useEffect(() => { useEffect(() => {
if (showOverageCreditUpsell && !showGuestPassesUpsell) { if (showOverageCreditUpsell && !showGuestPassesUpsell) {
incrementOverageCreditUpsellSeenCount() incrementOverageCreditUpsellSeenCount();
} }
}, [showOverageCreditUpsell, showGuestPassesUpsell]) }, [showOverageCreditUpsell, showGuestPassesUpsell]);
// Calculate available width for text content // Calculate available width for text content
// Account for: condensed clawd width (11 chars) + gap (2) + padding (2) = 15 chars // Account for: condensed clawd width (11 chars) + gap (2) + padding (2) = 15 chars
const textWidth = Math.max(columns - 15, 20) const textWidth = Math.max(columns - 15, 20);
// Truncate version to fit within available width, accounting for "Claude Code v" prefix // Truncate version to fit within available width, accounting for "CoStrict v" prefix
const versionPrefix = 'Claude Code v' const versionPrefix = 'CoStrict v';
const truncatedVersion = truncate( const truncatedVersion = truncate(version, Math.max(textWidth - versionPrefix.length, 6));
version,
Math.max(textWidth - versionPrefix.length, 6),
)
const effortSuffix = getEffortSuffix(model, effortValue) const effortSuffix = getEffortSuffix(model, effortValue);
const { shouldSplit, truncatedModel, truncatedBilling } = const { shouldSplit, truncatedModel, truncatedBilling } = formatModelAndBilling(
formatModelAndBilling( modelDisplayName + effortSuffix,
modelDisplayName + effortSuffix, billingType,
billingType, textWidth,
textWidth, );
)
// Truncate path, accounting for agent name if present // Truncate path, accounting for agent name if present
const separator = ' · ' const separator = ' · ';
const atPrefix = '@' const atPrefix = '@';
const cwdAvailableWidth = agentName const cwdAvailableWidth = agentName
? textWidth - atPrefix.length - stringWidth(agentName) - separator.length ? textWidth - atPrefix.length - stringWidth(agentName) - separator.length
: textWidth : textWidth;
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10)) const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10));
// OffscreenFreeze: the logo sits at the top of the message list and is the // OffscreenFreeze: the logo sits at the top of the message list and is the
// first thing to enter scrollback. useMainLoopModel() subscribes to model // first thing to enter scrollback. useMainLoopModel() subscribes to model
@ -85,34 +73,40 @@ export function CondensedLogo(): ReactNode {
// of which changing while in scrollback would force a full terminal reset. // of which changing while in scrollback would force a full terminal reset.
return ( return (
<OffscreenFreeze> <OffscreenFreeze>
<Box flexDirection="row" gap={2} alignItems="center"> <Box
{isFullscreenEnvEnabled() ? <AnimatedClawd /> : <Clawd />} flexDirection="row"
gap={2}
alignItems="center"
borderStyle="round"
borderColor="claudeBlue_FOR_SYSTEM_SPINNER"
paddingX={1}
paddingY={1}
>
{isFullscreenEnvEnabled() ? <AnimatedClawd /> : <Clawd />}
{/* Info */} {/* Info */}
<Box flexDirection="column"> <Box flexDirection="column">
<Text> <Text>
<Text bold>Claude Code</Text>{' '} <Text bold color="claudeBlue_FOR_SYSTEM_SPINNER">
<Text dimColor>v{truncatedVersion}</Text> CoStrict
</Text> </Text>{' '}
{shouldSplit ? ( <Text dimColor>v{truncatedVersion}</Text>
<>
<Text dimColor>{truncatedModel}</Text>
<Text dimColor>{truncatedBilling}</Text>
</>
) : (
<Text dimColor>
{truncatedModel} · {truncatedBilling}
</Text> </Text>
)} {shouldSplit ? (
<Text dimColor> <>
{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd} <Text dimColor>{truncatedModel}</Text>
</Text> <Text dimColor>{truncatedBilling}</Text>
{showGuestPassesUpsell && <GuestPassesUpsell />} </>
{!showGuestPassesUpsell && showOverageCreditUpsell && ( ) : (
<OverageCreditUpsell maxWidth={textWidth} twoLine /> <Text dimColor>
)} {truncatedModel} · {truncatedBilling}
</Box> </Text>
)}
<Text dimColor>{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}</Text>
{showGuestPassesUpsell && <GuestPassesUpsell />}
{!showGuestPassesUpsell && showOverageCreditUpsell && <OverageCreditUpsell maxWidth={textWidth} twoLine />}
</Box>
</Box> </Box>
</OffscreenFreeze> </OffscreenFreeze>
) );
} }

View File

@ -1,69 +1,61 @@
import * as React from 'react' import * as React from 'react';
import { Box, Text, stringWidth } from '@anthropic/ink' import { Box, Text, stringWidth } from '@anthropic/ink';
import { truncate } from '../../utils/format.js' import { truncate } from '../../utils/format.js';
export type FeedLine = { export type FeedLine = {
text: string text: string;
timestamp?: string timestamp?: string;
} };
export type FeedConfig = { export type FeedConfig = {
title: string title: string;
lines: FeedLine[] lines: FeedLine[];
footer?: string footer?: string;
emptyMessage?: string emptyMessage?: string;
customContent?: { content: React.ReactNode; width: number } customContent?: { content: React.ReactNode; width: number };
} };
type FeedProps = { type FeedProps = {
config: FeedConfig config: FeedConfig;
actualWidth: number actualWidth: number;
} };
export function calculateFeedWidth(config: FeedConfig): number { export function calculateFeedWidth(config: FeedConfig): number {
const { title, lines, footer, emptyMessage, customContent } = config const { title, lines, footer, emptyMessage, customContent } = config;
let maxWidth = stringWidth(title) let maxWidth = stringWidth(title);
if (customContent !== undefined) { if (customContent !== undefined) {
maxWidth = Math.max(maxWidth, customContent.width) maxWidth = Math.max(maxWidth, customContent.width);
} else if (lines.length === 0 && emptyMessage) { } else if (lines.length === 0 && emptyMessage) {
maxWidth = Math.max(maxWidth, stringWidth(emptyMessage)) maxWidth = Math.max(maxWidth, stringWidth(emptyMessage));
} else { } else {
const gap = ' ' const gap = ' ';
const maxTimestampWidth = Math.max( const maxTimestampWidth = Math.max(0, ...lines.map(line => (line.timestamp ? stringWidth(line.timestamp) : 0)));
0,
...lines.map(line => (line.timestamp ? stringWidth(line.timestamp) : 0)),
)
for (const line of lines) { for (const line of lines) {
const timestampWidth = maxTimestampWidth > 0 ? maxTimestampWidth : 0 const timestampWidth = maxTimestampWidth > 0 ? maxTimestampWidth : 0;
const lineWidth = const lineWidth = stringWidth(line.text) + (timestampWidth > 0 ? timestampWidth + gap.length : 0);
stringWidth(line.text) + maxWidth = Math.max(maxWidth, lineWidth);
(timestampWidth > 0 ? timestampWidth + gap.length : 0)
maxWidth = Math.max(maxWidth, lineWidth)
} }
} }
if (footer) { if (footer) {
maxWidth = Math.max(maxWidth, stringWidth(footer)) maxWidth = Math.max(maxWidth, stringWidth(footer));
} }
return maxWidth return maxWidth;
} }
export function Feed({ config, actualWidth }: FeedProps): React.ReactNode { export function Feed({ config, actualWidth }: FeedProps): React.ReactNode {
const { title, lines, footer, emptyMessage, customContent } = config const { title, lines, footer, emptyMessage, customContent } = config;
const gap = ' ' const gap = ' ';
const maxTimestampWidth = Math.max( const maxTimestampWidth = Math.max(0, ...lines.map(line => (line.timestamp ? stringWidth(line.timestamp) : 0)));
0,
...lines.map(line => (line.timestamp ? stringWidth(line.timestamp) : 0)),
)
return ( return (
<Box flexDirection="column" width={actualWidth}> <Box flexDirection="column" width={actualWidth}>
<Text bold color="claude"> <Text bold color="claudeBlue_FOR_SYSTEM_SPINNER">
{title} {title}
</Text> </Text>
{customContent ? ( {customContent ? (
@ -80,25 +72,19 @@ export function Feed({ config, actualWidth }: FeedProps): React.ReactNode {
) : ( ) : (
<> <>
{lines.map((line, index) => { {lines.map((line, index) => {
const textWidth = Math.max( const textWidth = Math.max(10, actualWidth - (maxTimestampWidth > 0 ? maxTimestampWidth + gap.length : 0));
10,
actualWidth -
(maxTimestampWidth > 0 ? maxTimestampWidth + gap.length : 0),
)
return ( return (
<Text key={index}> <Text key={index}>
{maxTimestampWidth > 0 && ( {maxTimestampWidth > 0 && (
<> <>
<Text dimColor> <Text dimColor>{(line.timestamp || '').padEnd(maxTimestampWidth)}</Text>
{(line.timestamp || '').padEnd(maxTimestampWidth)}
</Text>
{gap} {gap}
</> </>
)} )}
<Text>{truncate(line.text, textWidth)}</Text> <Text>{truncate(line.text, textWidth)}</Text>
</Text> </Text>
) );
})} })}
{footer && ( {footer && (
<Text dimColor italic> <Text dimColor italic>
@ -108,5 +94,5 @@ export function Feed({ config, actualWidth }: FeedProps): React.ReactNode {
</> </>
)} )}
</Box> </Box>
) );
} }

View File

@ -1,32 +1,27 @@
import * as React from 'react' import * as React from 'react';
import { Box } from '@anthropic/ink' import { Box } from '@anthropic/ink';
import { Divider } from '@anthropic/ink' import { Divider } from '@anthropic/ink';
import type { FeedConfig } from './Feed.js' import type { FeedConfig } from './Feed.js';
import { calculateFeedWidth, Feed } from './Feed.js' import { calculateFeedWidth, Feed } from './Feed.js';
type FeedColumnProps = { type FeedColumnProps = {
feeds: FeedConfig[] feeds: FeedConfig[];
maxWidth: number maxWidth: number;
} };
export function FeedColumn({ export function FeedColumn({ feeds, maxWidth }: FeedColumnProps): React.ReactNode {
feeds, const feedWidths = feeds.map(feed => calculateFeedWidth(feed));
maxWidth, const maxOfAllFeeds = Math.max(...feedWidths);
}: FeedColumnProps): React.ReactNode { const actualWidth = Math.min(maxOfAllFeeds, maxWidth);
const feedWidths = feeds.map(feed => calculateFeedWidth(feed))
const maxOfAllFeeds = Math.max(...feedWidths)
const actualWidth = Math.min(maxOfAllFeeds, maxWidth)
return ( return (
<Box flexDirection="column"> <Box flexDirection="column">
{feeds.map((feed, index) => ( {feeds.map((feed, index) => (
<React.Fragment key={index}> <React.Fragment key={index}>
<Feed config={feed} actualWidth={actualWidth} /> <Feed config={feed} actualWidth={actualWidth} />
{index < feeds.length - 1 && ( {index < feeds.length - 1 && <Divider color="claudeBlue_FOR_SYSTEM_SPINNER" width={actualWidth} />}
<Divider color="claude" width={actualWidth} />
)}
</React.Fragment> </React.Fragment>
))} ))}
</Box> </Box>
) );
} }

View File

@ -1,7 +1,7 @@
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
import * as React from 'react' import * as React from 'react';
import { Box, Text, color, stringWidth } from '@anthropic/ink' import { Box, Text, color, stringWidth } from '@anthropic/ink';
import { useTerminalSize } from '../../hooks/useTerminalSize.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js';
import { import {
getLayoutMode, getLayoutMode,
calculateLayoutDimensions, calculateLayoutDimensions,
@ -11,46 +11,39 @@ import {
getRecentActivitySync, getRecentActivitySync,
getRecentReleaseNotesSync, getRecentReleaseNotesSync,
getLogoDisplayData, getLogoDisplayData,
} from '../../utils/logoV2Utils.js' } from '../../utils/logoV2Utils.js';
import { truncate } from '../../utils/format.js' import { truncate } from '../../utils/format.js';
import { getDisplayPath } from '../../utils/file.js' import { getDisplayPath } from '../../utils/file.js';
import { Clawd } from './Clawd.js' import { Clawd } from './Clawd.js';
import { FeedColumn } from './FeedColumn.js' import { FeedColumn } from './FeedColumn.js';
import { import {
createRecentActivityFeed, createRecentActivityFeed,
createWhatsNewFeed, createWhatsNewFeed,
createProjectOnboardingFeed, createProjectOnboardingFeed,
createGuestPassesFeed, createGuestPassesFeed,
} from './feedConfigs.js' } from './feedConfigs.js';
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js' import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js';
import { resolveThemeSetting } from 'src/utils/systemTheme.js' import { resolveThemeSetting } from 'src/utils/systemTheme.js';
import { getInitialSettings } from 'src/utils/settings/settings.js' import { getInitialSettings } from 'src/utils/settings/settings.js';
import { import { isDebugMode, isDebugToStdErr, getDebugLogPath } from 'src/utils/debug.js';
isDebugMode, import { useEffect, useState } from 'react';
isDebugToStdErr,
getDebugLogPath,
} from 'src/utils/debug.js'
import { useEffect, useState } from 'react'
import { import {
getSteps, getSteps,
shouldShowProjectOnboarding, shouldShowProjectOnboarding,
incrementProjectOnboardingSeenCount, incrementProjectOnboardingSeenCount,
} from '../../projectOnboardingState.js' } from '../../projectOnboardingState.js';
import { CondensedLogo } from './CondensedLogo.js' import { CondensedLogo } from './CondensedLogo.js';
import { OffscreenFreeze } from '../OffscreenFreeze.js' import { OffscreenFreeze } from '../OffscreenFreeze.js';
import { checkForReleaseNotesSync } from '../../utils/releaseNotes.js' import { checkForReleaseNotesSync } from '../../utils/releaseNotes.js';
import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js' import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js';
import { isEnvTruthy } from 'src/utils/envUtils.js' import { isEnvTruthy } from 'src/utils/envUtils.js';
import { import { getStartupPerfLogPath, isDetailedProfilingEnabled } from 'src/utils/startupProfiler.js';
getStartupPerfLogPath, import { EmergencyTip } from './EmergencyTip.js';
isDetailedProfilingEnabled, import { VoiceModeNotice } from './VoiceModeNotice.js';
} from 'src/utils/startupProfiler.js' import { Opus1mMergeNotice } from './Opus1mMergeNotice.js';
import { EmergencyTip } from './EmergencyTip.js' import { GateOverridesWarning } from './GateOverridesWarning.js';
import { VoiceModeNotice } from './VoiceModeNotice.js' import { ExperimentEnrollmentNotice } from './ExperimentEnrollmentNotice.js';
import { Opus1mMergeNotice } from './Opus1mMergeNotice.js' import { feature } from 'bun:bundle';
import { GateOverridesWarning } from './GateOverridesWarning.js'
import { ExperimentEnrollmentNotice } from './ExperimentEnrollmentNotice.js'
import { feature } from 'bun:bundle'
// Conditional require so ChannelsNotice.tsx tree-shakes when both flags are // Conditional require so ChannelsNotice.tsx tree-shakes when both flags are
// false. A module-scope helper component inside a feature() ternary does NOT // false. A module-scope helper component inside a feature() ternary does NOT
@ -61,128 +54,98 @@ import { feature } from 'bun:bundle'
const ChannelsNoticeModule = const ChannelsNoticeModule =
feature('KAIROS') || feature('KAIROS_CHANNELS') feature('KAIROS') || feature('KAIROS_CHANNELS')
? (require('./ChannelsNotice.js') as typeof import('./ChannelsNotice.js')) ? (require('./ChannelsNotice.js') as typeof import('./ChannelsNotice.js'))
: null : null;
/* eslint-enable @typescript-eslint/no-require-imports */ /* eslint-enable @typescript-eslint/no-require-imports */
import { SandboxManager } from 'src/utils/sandbox/sandbox-adapter.js' import { SandboxManager } from 'src/utils/sandbox/sandbox-adapter.js';
import { import { useShowGuestPassesUpsell, incrementGuestPassesSeenCount } from './GuestPassesUpsell.js';
useShowGuestPassesUpsell,
incrementGuestPassesSeenCount,
} from './GuestPassesUpsell.js'
import { import {
useShowOverageCreditUpsell, useShowOverageCreditUpsell,
incrementOverageCreditUpsellSeenCount, incrementOverageCreditUpsellSeenCount,
createOverageCreditFeed, createOverageCreditFeed,
} from './OverageCreditUpsell.js' } from './OverageCreditUpsell.js';
import { plural } from '../../utils/stringUtils.js' import { plural } from '../../utils/stringUtils.js';
import { useAppState } from '../../state/AppState.js' import { useAppState } from '../../state/AppState.js';
import { getEffortSuffix } from '../../utils/effort.js' import { getEffortSuffix } from '../../utils/effort.js';
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';
import { renderModelSetting } from '../../utils/model/model.js' import { renderModelSetting } from '../../utils/model/model.js';
const LEFT_PANEL_MAX_WIDTH = 50 const LEFT_PANEL_MAX_WIDTH = 50;
export function LogoV2(): React.ReactNode { export function LogoV2(): React.ReactNode {
const activities = getRecentActivitySync() const activities = getRecentActivitySync();
const username = getGlobalConfig().oauthAccount?.displayName ?? '' const username = getGlobalConfig().oauthAccount?.displayName ?? '';
const { columns } = useTerminalSize() const { columns } = useTerminalSize();
const showOnboarding = shouldShowProjectOnboarding() const showOnboarding = shouldShowProjectOnboarding();
const showSandboxStatus = SandboxManager.isSandboxingEnabled() const showSandboxStatus = SandboxManager.isSandboxingEnabled();
const showGuestPassesUpsell = useShowGuestPassesUpsell() const showGuestPassesUpsell = useShowGuestPassesUpsell();
const showOverageCreditUpsell = useShowOverageCreditUpsell() const showOverageCreditUpsell = useShowOverageCreditUpsell();
const agent = useAppState(s => s.agent) const agent = useAppState(s => s.agent);
const effortValue = useAppState(s => s.effortValue) const effortValue = useAppState(s => s.effortValue);
const config = getGlobalConfig() const config = getGlobalConfig();
let changelog: string[] let changelog: string[];
try { try {
changelog = getRecentReleaseNotesSync(3) changelog = getRecentReleaseNotesSync(3);
} catch { } catch {
changelog = [] changelog = [];
} }
// Get company announcements and select one: // Get company announcements and select one:
// - First startup (numStartups === 1): show first announcement // - First startup (numStartups === 1): show first announcement
// - All other startups: randomly select from announcements // - All other startups: randomly select from announcements
const [announcement] = useState(() => { const [announcement] = useState(() => {
const announcements = getInitialSettings().companyAnnouncements const announcements = getInitialSettings().companyAnnouncements;
if (!announcements || announcements.length === 0) return undefined if (!announcements || announcements.length === 0) return undefined;
return config.numStartups === 1 return config.numStartups === 1
? announcements[0] ? announcements[0]
: announcements[Math.floor(Math.random() * announcements.length)] : announcements[Math.floor(Math.random() * announcements.length)];
}) });
const { hasReleaseNotes } = checkForReleaseNotesSync( const { hasReleaseNotes } = checkForReleaseNotesSync(config.lastReleaseNotesSeen);
config.lastReleaseNotesSeen,
)
useEffect(() => { useEffect(() => {
const currentConfig = getGlobalConfig() const currentConfig = getGlobalConfig();
if (currentConfig.lastReleaseNotesSeen === MACRO.VERSION) { if (currentConfig.lastReleaseNotesSeen === MACRO.VERSION) {
return return;
} }
saveGlobalConfig(current => { saveGlobalConfig(current => {
if (current.lastReleaseNotesSeen === MACRO.VERSION) return current if (current.lastReleaseNotesSeen === MACRO.VERSION) return current;
return { ...current, lastReleaseNotesSeen: MACRO.VERSION } return { ...current, lastReleaseNotesSeen: MACRO.VERSION };
}) });
if (showOnboarding) { if (showOnboarding) {
incrementProjectOnboardingSeenCount() incrementProjectOnboardingSeenCount();
} }
}, [config, showOnboarding]) }, [config, showOnboarding]);
// In condensed mode (early-return below renders <CondensedLogo/>), // In condensed mode (early-return below renders <CondensedLogo/>),
// CondensedLogo's own useEffect handles the impression count. Skipping // CondensedLogo's own useEffect handles the impression count. Skipping
// here avoids double-counting since hooks fire before the early return. // here avoids double-counting since hooks fire before the early return.
const isCondensedMode = const isCondensedMode = !hasReleaseNotes && !showOnboarding && !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO);
!hasReleaseNotes &&
!showOnboarding &&
!isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO)
useEffect(() => { useEffect(() => {
if (showGuestPassesUpsell && !showOnboarding && !isCondensedMode) { if (showGuestPassesUpsell && !showOnboarding && !isCondensedMode) {
incrementGuestPassesSeenCount() incrementGuestPassesSeenCount();
} }
}, [showGuestPassesUpsell, showOnboarding, isCondensedMode]) }, [showGuestPassesUpsell, showOnboarding, isCondensedMode]);
useEffect(() => { useEffect(() => {
if ( if (showOverageCreditUpsell && !showOnboarding && !showGuestPassesUpsell && !isCondensedMode) {
showOverageCreditUpsell && incrementOverageCreditUpsellSeenCount();
!showOnboarding &&
!showGuestPassesUpsell &&
!isCondensedMode
) {
incrementOverageCreditUpsellSeenCount()
} }
}, [ }, [showOverageCreditUpsell, showOnboarding, showGuestPassesUpsell, isCondensedMode]);
showOverageCreditUpsell,
showOnboarding,
showGuestPassesUpsell,
isCondensedMode,
])
const model = useMainLoopModel() const model = useMainLoopModel();
const fullModelDisplayName = renderModelSetting(model) const fullModelDisplayName = renderModelSetting(model);
const { const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData();
version,
cwd,
billingType,
agentName: agentNameFromSettings,
} = getLogoDisplayData()
// Prefer AppState.agent (set from --agent CLI flag) over settings // Prefer AppState.agent (set from --agent CLI flag) over settings
const agentName = agent ?? agentNameFromSettings const agentName = agent ?? agentNameFromSettings;
// -20 to account for the max length of subscription name " · Claude Enterprise". // -20 to account for the max length of subscription name " · Claude Enterprise".
const effortSuffix = getEffortSuffix(model, effortValue) const effortSuffix = getEffortSuffix(model, effortValue);
const modelDisplayName = truncate( const modelDisplayName = truncate(fullModelDisplayName + effortSuffix, LEFT_PANEL_MAX_WIDTH - 20);
fullModelDisplayName + effortSuffix,
LEFT_PANEL_MAX_WIDTH - 20,
)
// Show condensed logo if no new changelog and not showing onboarding and not forcing full logo // Show condensed logo if no new changelog and not showing onboarding and not forcing full logo
if ( if (!hasReleaseNotes && !showOnboarding && !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO)) {
!hasReleaseNotes &&
!showOnboarding &&
!isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO)
) {
return ( return (
<> <>
<CondensedLogo /> <CondensedLogo />
@ -192,17 +155,13 @@ export function LogoV2(): React.ReactNode {
{isDebugMode() && ( {isDebugMode() && (
<Box paddingLeft={2} flexDirection="column"> <Box paddingLeft={2} flexDirection="column">
<Text color="warning">Debug mode enabled</Text> <Text color="warning">Debug mode enabled</Text>
<Text dimColor> <Text dimColor>Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()}</Text>
Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()}
</Text>
</Box> </Box>
)} )}
<EmergencyTip /> <EmergencyTip />
{process.env.CLAUDE_CODE_TMUX_SESSION && ( {process.env.CLAUDE_CODE_TMUX_SESSION && (
<Box paddingLeft={2} flexDirection="column"> <Box paddingLeft={2} flexDirection="column">
<Text dimColor> <Text dimColor>tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION}</Text>
tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION}
</Text>
<Text dimColor> <Text dimColor>
{process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS {process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS
? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})` ? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})`
@ -213,9 +172,7 @@ export function LogoV2(): React.ReactNode {
{announcement && ( {announcement && (
<Box paddingLeft={2} flexDirection="column"> <Box paddingLeft={2} flexDirection="column">
{!process.env.IS_DEMO && config.oauthAccount?.organizationName && ( {!process.env.IS_DEMO && config.oauthAccount?.organizationName && (
<Text dimColor> <Text dimColor>Message from {config.oauthAccount.organizationName}:</Text>
Message from {config.oauthAccount.organizationName}:
</Text>
)} )}
<Text>{announcement}</Text> <Text>{announcement}</Text>
</Box> </Box>
@ -228,51 +185,41 @@ export function LogoV2(): React.ReactNode {
{process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && ( {process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && (
<Box paddingLeft={2} flexDirection="column"> <Box paddingLeft={2} flexDirection="column">
<Text color="warning">[ANT-ONLY] Logs:</Text> <Text color="warning">[ANT-ONLY] Logs:</Text>
<Text dimColor> <Text dimColor>API calls: {getDisplayPath(getDumpPromptsPath())}</Text>
API calls: {getDisplayPath(getDumpPromptsPath())} <Text dimColor>Debug logs: {getDisplayPath(getDebugLogPath())}</Text>
</Text>
<Text dimColor>
Debug logs: {getDisplayPath(getDebugLogPath())}
</Text>
{isDetailedProfilingEnabled() && ( {isDetailedProfilingEnabled() && (
<Text dimColor> <Text dimColor>Startup Perf: {getDisplayPath(getStartupPerfLogPath())}</Text>
Startup Perf: {getDisplayPath(getStartupPerfLogPath())}
</Text>
)} )}
</Box> </Box>
)} )}
{process.env.USER_TYPE === 'ant' && <GateOverridesWarning />} {process.env.USER_TYPE === 'ant' && <GateOverridesWarning />}
{process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />} {process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />}
</> </>
) );
} }
// Calculate layout and display values // Calculate layout and display values
const layoutMode = getLayoutMode(columns) const layoutMode = getLayoutMode(columns);
const userTheme = resolveThemeSetting(getGlobalConfig().theme) const userTheme = resolveThemeSetting(getGlobalConfig().theme);
const borderTitle = ` ${color('claude', userTheme)('Claude Code')} ${color('inactive', userTheme)(`v${version}`)} ` const borderTitle = ` ${color('claudeBlue_FOR_SYSTEM_SPINNER', userTheme)('CoStrict')} ${color('inactive', userTheme)(`v${version}`)} `;
const compactBorderTitle = color('claude', userTheme)(' Claude Code ') const compactBorderTitle = color('claudeBlue_FOR_SYSTEM_SPINNER', userTheme)(' CoStrict ');
// Early return for compact mode // Early return for compact mode
if (layoutMode === 'compact') { if (layoutMode === 'compact') {
const layoutWidth = 4 // border + padding const layoutWidth = 4; // border + padding
let welcomeMessage = formatWelcomeMessage(username) let welcomeMessage = formatWelcomeMessage(username);
if (stringWidth(welcomeMessage) > columns - layoutWidth) { if (stringWidth(welcomeMessage) > columns - layoutWidth) {
welcomeMessage = formatWelcomeMessage(null) welcomeMessage = formatWelcomeMessage(null);
} }
// Calculate cwd width accounting for agent name if present // Calculate cwd width accounting for agent name if present
const separator = ' · ' const separator = ' · ';
const atPrefix = '@' const atPrefix = '@';
const cwdAvailableWidth = agentName const cwdAvailableWidth = agentName
? columns - ? columns - layoutWidth - atPrefix.length - stringWidth(agentName) - separator.length
layoutWidth - : columns - layoutWidth;
atPrefix.length - const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10));
stringWidth(agentName) -
separator.length
: columns - layoutWidth
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10))
// OffscreenFreeze: logo is the first thing to enter scrollback; useMainLoopModel() // OffscreenFreeze: logo is the first thing to enter scrollback; useMainLoopModel()
// subscribes to model changes and getLogoDisplayData() reads cwd/subscription — // subscribes to model changes and getLogoDisplayData() reads cwd/subscription —
// any change while in scrollback forces a full reset. // any change while in scrollback forces a full reset.
@ -282,7 +229,7 @@ export function LogoV2(): React.ReactNode {
<Box <Box
flexDirection="column" flexDirection="column"
borderStyle="round" borderStyle="round"
borderColor="claude" borderColor="claudeBlue_FOR_SYSTEM_SPINNER"
borderText={{ borderText={{
content: compactBorderTitle, content: compactBorderTitle,
position: 'top', position: 'top',
@ -300,9 +247,7 @@ export function LogoV2(): React.ReactNode {
</Box> </Box>
<Text dimColor>{modelDisplayName}</Text> <Text dimColor>{modelDisplayName}</Text>
<Text dimColor>{billingType}</Text> <Text dimColor>{billingType}</Text>
<Text dimColor> <Text dimColor>{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}</Text>
{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}
</Text>
</Box> </Box>
</OffscreenFreeze> </OffscreenFreeze>
<VoiceModeNotice /> <VoiceModeNotice />
@ -310,45 +255,32 @@ export function LogoV2(): React.ReactNode {
{ChannelsNoticeModule && <ChannelsNoticeModule.ChannelsNotice />} {ChannelsNoticeModule && <ChannelsNoticeModule.ChannelsNotice />}
{showSandboxStatus && ( {showSandboxStatus && (
<Box marginTop={1} flexDirection="column"> <Box marginTop={1} flexDirection="column">
<Text color="warning"> <Text color="warning">Your bash commands will be sandboxed. Disable with /sandbox.</Text>
Your bash commands will be sandboxed. Disable with /sandbox.
</Text>
</Box> </Box>
)} )}
{process.env.USER_TYPE === 'ant' && <GateOverridesWarning />} {process.env.USER_TYPE === 'ant' && <GateOverridesWarning />}
{process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />} {process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />}
</> </>
) );
} }
const welcomeMessage = formatWelcomeMessage(username) const welcomeMessage = formatWelcomeMessage(username);
const modelLine = const modelLine =
!process.env.IS_DEMO && config.oauthAccount?.organizationName !process.env.IS_DEMO && config.oauthAccount?.organizationName
? `${modelDisplayName} · ${billingType} · ${config.oauthAccount.organizationName}` ? `${modelDisplayName} · ${billingType} · ${config.oauthAccount.organizationName}`
: `${modelDisplayName} · ${billingType}` : `${modelDisplayName} · ${billingType}`;
// Calculate cwd width accounting for agent name if present // Calculate cwd width accounting for agent name if present
const cwdSeparator = ' · ' const cwdSeparator = ' · ';
const cwdAtPrefix = '@' const cwdAtPrefix = '@';
const cwdAvailableWidth = agentName const cwdAvailableWidth = agentName
? LEFT_PANEL_MAX_WIDTH - ? LEFT_PANEL_MAX_WIDTH - cwdAtPrefix.length - stringWidth(agentName) - cwdSeparator.length
cwdAtPrefix.length - : LEFT_PANEL_MAX_WIDTH;
stringWidth(agentName) - const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10));
cwdSeparator.length const cwdLine = agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd;
: LEFT_PANEL_MAX_WIDTH const optimalLeftWidth = calculateOptimalLeftWidth(welcomeMessage, cwdLine, modelLine);
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10))
const cwdLine = agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd
const optimalLeftWidth = calculateOptimalLeftWidth(
welcomeMessage,
cwdLine,
modelLine,
)
// Calculate layout dimensions // Calculate layout dimensions
const { leftWidth, rightWidth } = calculateLayoutDimensions( const { leftWidth, rightWidth } = calculateLayoutDimensions(columns, layoutMode, optimalLeftWidth);
columns,
layoutMode,
optimalLeftWidth,
)
return ( return (
<> <>
@ -356,7 +288,7 @@ export function LogoV2(): React.ReactNode {
<Box <Box
flexDirection="column" flexDirection="column"
borderStyle="round" borderStyle="round"
borderColor="claude" borderColor="claudeBlue_FOR_SYSTEM_SPINNER"
borderText={{ borderText={{
content: borderTitle, content: borderTitle,
position: 'top', position: 'top',
@ -365,11 +297,7 @@ export function LogoV2(): React.ReactNode {
}} }}
> >
{/* Main content */} {/* Main content */}
<Box <Box flexDirection={layoutMode === 'horizontal' ? 'row' : 'column'} paddingX={1} gap={1}>
flexDirection={layoutMode === 'horizontal' ? 'row' : 'column'}
paddingX={1}
gap={1}
>
{/* Left Panel */} {/* Left Panel */}
<Box <Box
flexDirection="column" flexDirection="column"
@ -395,7 +323,7 @@ export function LogoV2(): React.ReactNode {
<Box <Box
height="100%" height="100%"
borderStyle="single" borderStyle="single"
borderColor="claude" borderColor="claudeBlue_FOR_SYSTEM_SPINNER"
borderDimColor borderDimColor
borderTop={false} borderTop={false}
borderBottom={false} borderBottom={false}
@ -408,24 +336,12 @@ export function LogoV2(): React.ReactNode {
<FeedColumn <FeedColumn
feeds={ feeds={
showOnboarding showOnboarding
? [ ? [createProjectOnboardingFeed(getSteps()), createRecentActivityFeed(activities)]
createProjectOnboardingFeed(getSteps()),
createRecentActivityFeed(activities),
]
: showGuestPassesUpsell : showGuestPassesUpsell
? [ ? [createRecentActivityFeed(activities), createGuestPassesFeed()]
createRecentActivityFeed(activities),
createGuestPassesFeed(),
]
: showOverageCreditUpsell : showOverageCreditUpsell
? [ ? [createRecentActivityFeed(activities), createOverageCreditFeed()]
createRecentActivityFeed(activities), : [createRecentActivityFeed(activities), createWhatsNewFeed(changelog)]
createOverageCreditFeed(),
]
: [
createRecentActivityFeed(activities),
createWhatsNewFeed(changelog),
]
} }
maxWidth={rightWidth} maxWidth={rightWidth}
/> />
@ -439,17 +355,13 @@ export function LogoV2(): React.ReactNode {
{isDebugMode() && ( {isDebugMode() && (
<Box paddingLeft={2} flexDirection="column"> <Box paddingLeft={2} flexDirection="column">
<Text color="warning">Debug mode enabled</Text> <Text color="warning">Debug mode enabled</Text>
<Text dimColor> <Text dimColor>Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()}</Text>
Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()}
</Text>
</Box> </Box>
)} )}
<EmergencyTip /> <EmergencyTip />
{process.env.CLAUDE_CODE_TMUX_SESSION && ( {process.env.CLAUDE_CODE_TMUX_SESSION && (
<Box paddingLeft={2} flexDirection="column"> <Box paddingLeft={2} flexDirection="column">
<Text dimColor> <Text dimColor>tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION}</Text>
tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION}
</Text>
<Text dimColor> <Text dimColor>
{process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS {process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS
? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})` ? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})`
@ -460,18 +372,14 @@ export function LogoV2(): React.ReactNode {
{announcement && ( {announcement && (
<Box paddingLeft={2} flexDirection="column"> <Box paddingLeft={2} flexDirection="column">
{!process.env.IS_DEMO && config.oauthAccount?.organizationName && ( {!process.env.IS_DEMO && config.oauthAccount?.organizationName && (
<Text dimColor> <Text dimColor>Message from {config.oauthAccount.organizationName}:</Text>
Message from {config.oauthAccount.organizationName}:
</Text>
)} )}
<Text>{announcement}</Text> <Text>{announcement}</Text>
</Box> </Box>
)} )}
{showSandboxStatus && ( {showSandboxStatus && (
<Box paddingLeft={2} flexDirection="column"> <Box paddingLeft={2} flexDirection="column">
<Text color="warning"> <Text color="warning">Your bash commands will be sandboxed. Disable with /sandbox.</Text>
Your bash commands will be sandboxed. Disable with /sandbox.
</Text>
</Box> </Box>
)} )}
{process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && ( {process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && (
@ -482,20 +390,15 @@ export function LogoV2(): React.ReactNode {
{process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && ( {process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && (
<Box paddingLeft={2} flexDirection="column"> <Box paddingLeft={2} flexDirection="column">
<Text color="warning">[ANT-ONLY] Logs:</Text> <Text color="warning">[ANT-ONLY] Logs:</Text>
<Text dimColor> <Text dimColor>API calls: {getDisplayPath(getDumpPromptsPath())}</Text>
API calls: {getDisplayPath(getDumpPromptsPath())}
</Text>
<Text dimColor>Debug logs: {getDisplayPath(getDebugLogPath())}</Text> <Text dimColor>Debug logs: {getDisplayPath(getDebugLogPath())}</Text>
{isDetailedProfilingEnabled() && ( {isDetailedProfilingEnabled() && (
<Text dimColor> <Text dimColor>Startup Perf: {getDisplayPath(getStartupPerfLogPath())}</Text>
Startup Perf: {getDisplayPath(getStartupPerfLogPath())}
</Text>
)} )}
</Box> </Box>
)} )}
{process.env.USER_TYPE === 'ant' && <GateOverridesWarning />} {process.env.USER_TYPE === 'ant' && <GateOverridesWarning />}
{process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />} {process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />}
</> </>
) );
} }

View File

@ -1,17 +1,15 @@
import React from 'react' import React from 'react';
import { Box, Text, useTheme } from '@anthropic/ink' import { Box, Text, useTheme } from '@anthropic/ink';
import { env } from '../../utils/env.js' import { env } from '../../utils/env.js';
const WELCOME_V2_WIDTH = 58 const WELCOME_V2_WIDTH = 58;
export function WelcomeV2(): React.ReactNode { export function WelcomeV2(): React.ReactNode {
const [theme] = useTheme() const [theme] = useTheme();
const welcomeMessage = 'Welcome to Claude Code' const welcomeMessage = 'Welcome to CoStrict';
if (env.terminal === 'Apple_Terminal') { if (env.terminal === 'Apple_Terminal') {
return ( return <AppleTerminalWelcomeV2 theme={theme} welcomeMessage={welcomeMessage} />;
<AppleTerminalWelcomeV2 theme={theme} welcomeMessage={welcomeMessage} />
)
} }
if (['light', 'light-daltonized', 'light-ansi'].includes(theme)) { if (['light', 'light-daltonized', 'light-ansi'].includes(theme)) {
@ -22,30 +20,14 @@ export function WelcomeV2(): React.ReactNode {
<Text color="claude">{welcomeMessage} </Text> <Text color="claude">{welcomeMessage} </Text>
<Text dimColor>v{MACRO.VERSION} </Text> <Text dimColor>v{MACRO.VERSION} </Text>
</Text> </Text>
<Text> <Text>{'…………………………………………………………………………………………………………………………………………………………'}</Text>
{'…………………………………………………………………………………………………………………………………………………………'} <Text>{' '}</Text>
</Text> <Text>{' '}</Text>
<Text> <Text>{' '}</Text>
{' '} <Text>{' ░░░░░░ '}</Text>
</Text> <Text>{' ░░░ ░░░░░░░░░░ '}</Text>
<Text> <Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
{' '} <Text>{' '}</Text>
</Text>
<Text>
{' '}
</Text>
<Text>
{' ░░░░░░ '}
</Text>
<Text>
{' ░░░ ░░░░░░░░░░ '}
</Text>
<Text>
{' ░░░░░░░░░░░░░░░░░░░ '}
</Text>
<Text>
{' '}
</Text>
<Text> <Text>
<Text dimColor>{' ░░░░'}</Text> <Text dimColor>{' ░░░░'}</Text>
<Text>{' ██ '}</Text> <Text>{' ██ '}</Text>
@ -54,9 +36,7 @@ export function WelcomeV2(): React.ReactNode {
<Text dimColor>{' ░░░░░░░░░░'}</Text> <Text dimColor>{' ░░░░░░░░░░'}</Text>
<Text>{' ██▒▒██ '}</Text> <Text>{' ██▒▒██ '}</Text>
</Text> </Text>
<Text> <Text>{' ▒▒ ██ ▒'}</Text>
{' ▒▒ ██ ▒'}
</Text>
<Text> <Text>
{' '} {' '}
<Text color="clawd_body"> </Text> <Text color="clawd_body"> </Text>
@ -81,7 +61,7 @@ export function WelcomeV2(): React.ReactNode {
</Text> </Text>
</Text> </Text>
</Box> </Box>
) );
} }
return ( return (
@ -91,41 +71,21 @@ export function WelcomeV2(): React.ReactNode {
<Text color="claude">{welcomeMessage} </Text> <Text color="claude">{welcomeMessage} </Text>
<Text dimColor>v{MACRO.VERSION} </Text> <Text dimColor>v{MACRO.VERSION} </Text>
</Text> </Text>
<Text> <Text>{'…………………………………………………………………………………………………………………………………………………………'}</Text>
{'…………………………………………………………………………………………………………………………………………………………'} <Text>{' '}</Text>
</Text> <Text>{' * █████▓▓░ '}</Text>
<Text> <Text>{' * ███▓░ ░░ '}</Text>
{' '} <Text>{' ░░░░░░ ███▓░ '}</Text>
</Text> <Text>{' ░░░ ░░░░░░░░░░ ███▓░ '}</Text>
<Text>
{' * █████▓▓░ '}
</Text>
<Text>
{' * ███▓░ ░░ '}
</Text>
<Text>
{' ░░░░░░ ███▓░ '}
</Text>
<Text>
{' ░░░ ░░░░░░░░░░ ███▓░ '}
</Text>
<Text> <Text>
<Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text> <Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
<Text bold>*</Text> <Text bold>*</Text>
<Text>{' ██▓░░ ▓ '}</Text> <Text>{' ██▓░░ ▓ '}</Text>
</Text> </Text>
<Text> <Text>{' ░▓▓███▓▓░ '}</Text>
{' ░▓▓███▓▓░ '} <Text dimColor>{' * ░░░░ '}</Text>
</Text> <Text dimColor>{' ░░░░░░░░ '}</Text>
<Text dimColor> <Text dimColor>{' ░░░░░░░░░░░░░░░░ '}</Text>
{' * ░░░░ '}
</Text>
<Text dimColor>
{' ░░░░░░░░ '}
</Text>
<Text dimColor>
{' ░░░░░░░░░░░░░░░░ '}
</Text>
<Text> <Text>
{' '} {' '}
<Text color="clawd_body"> </Text> <Text color="clawd_body"> </Text>
@ -152,21 +112,16 @@ export function WelcomeV2(): React.ReactNode {
</Text> </Text>
</Text> </Text>
</Box> </Box>
) );
} }
type AppleTerminalWelcomeV2Props = { type AppleTerminalWelcomeV2Props = {
theme: string theme: string;
welcomeMessage: string welcomeMessage: string;
} };
function AppleTerminalWelcomeV2({ function AppleTerminalWelcomeV2({ theme, welcomeMessage }: AppleTerminalWelcomeV2Props): React.ReactNode {
theme, const isLightTheme = ['light', 'light-daltonized', 'light-ansi'].includes(theme);
welcomeMessage,
}: AppleTerminalWelcomeV2Props): React.ReactNode {
const isLightTheme = ['light', 'light-daltonized', 'light-ansi'].includes(
theme,
)
if (isLightTheme) { if (isLightTheme) {
return ( return (
@ -176,30 +131,14 @@ function AppleTerminalWelcomeV2({
<Text color="claude">{welcomeMessage} </Text> <Text color="claude">{welcomeMessage} </Text>
<Text dimColor>v{MACRO.VERSION} </Text> <Text dimColor>v{MACRO.VERSION} </Text>
</Text> </Text>
<Text> <Text>{'…………………………………………………………………………………………………………………………………………………………'}</Text>
{'…………………………………………………………………………………………………………………………………………………………'} <Text>{' '}</Text>
</Text> <Text>{' '}</Text>
<Text> <Text>{' '}</Text>
{' '} <Text>{' ░░░░░░ '}</Text>
</Text> <Text>{' ░░░ ░░░░░░░░░░ '}</Text>
<Text> <Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
{' '} <Text>{' '}</Text>
</Text>
<Text>
{' '}
</Text>
<Text>
{' ░░░░░░ '}
</Text>
<Text>
{' ░░░ ░░░░░░░░░░ '}
</Text>
<Text>
{' ░░░░░░░░░░░░░░░░░░░ '}
</Text>
<Text>
{' '}
</Text>
<Text> <Text>
<Text dimColor>{' ░░░░'}</Text> <Text dimColor>{' ░░░░'}</Text>
<Text>{' ██ '}</Text> <Text>{' ██ '}</Text>
@ -208,12 +147,8 @@ function AppleTerminalWelcomeV2({
<Text dimColor>{' ░░░░░░░░░░'}</Text> <Text dimColor>{' ░░░░░░░░░░'}</Text>
<Text>{' ██▒▒██ '}</Text> <Text>{' ██▒▒██ '}</Text>
</Text> </Text>
<Text> <Text>{' ▒▒ ██ ▒'}</Text>
{' ▒▒ ██ ▒'} <Text>{' ▒▒░░▒▒ ▒ ▒▒'}</Text>
</Text>
<Text>
{' ▒▒░░▒▒ ▒ ▒▒'}
</Text>
<Text> <Text>
{' '} {' '}
<Text color="clawd_body"></Text> <Text color="clawd_body"></Text>
@ -242,7 +177,7 @@ function AppleTerminalWelcomeV2({
</Text> </Text>
</Text> </Text>
</Box> </Box>
) );
} }
return ( return (
@ -252,41 +187,21 @@ function AppleTerminalWelcomeV2({
<Text color="claude">{welcomeMessage} </Text> <Text color="claude">{welcomeMessage} </Text>
<Text dimColor>v{MACRO.VERSION} </Text> <Text dimColor>v{MACRO.VERSION} </Text>
</Text> </Text>
<Text> <Text>{'…………………………………………………………………………………………………………………………………………………………'}</Text>
{'…………………………………………………………………………………………………………………………………………………………'} <Text>{' '}</Text>
</Text> <Text>{' * █████▓▓░ '}</Text>
<Text> <Text>{' * ███▓░ ░░ '}</Text>
{' '} <Text>{' ░░░░░░ ███▓░ '}</Text>
</Text> <Text>{' ░░░ ░░░░░░░░░░ ███▓░ '}</Text>
<Text>
{' * █████▓▓░ '}
</Text>
<Text>
{' * ███▓░ ░░ '}
</Text>
<Text>
{' ░░░░░░ ███▓░ '}
</Text>
<Text>
{' ░░░ ░░░░░░░░░░ ███▓░ '}
</Text>
<Text> <Text>
<Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text> <Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
<Text bold>*</Text> <Text bold>*</Text>
<Text>{' ██▓░░ ▓ '}</Text> <Text>{' ██▓░░ ▓ '}</Text>
</Text> </Text>
<Text> <Text>{' ░▓▓███▓▓░ '}</Text>
{' ░▓▓███▓▓░ '} <Text dimColor>{' * ░░░░ '}</Text>
</Text> <Text dimColor>{' ░░░░░░░░ '}</Text>
<Text dimColor> <Text dimColor>{' ░░░░░░░░░░░░░░░░ '}</Text>
{' * ░░░░ '}
</Text>
<Text dimColor>
{' ░░░░░░░░ '}
</Text>
<Text dimColor>
{' ░░░░░░░░░░░░░░░░ '}
</Text>
<Text> <Text>
{' '} {' '}
<Text dimColor>*</Text> <Text dimColor>*</Text>
@ -322,5 +237,5 @@ function AppleTerminalWelcomeV2({
</Text> </Text>
</Text> </Text>
</Box> </Box>
) );
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB