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> {
return {
"MACRO.VERSION": JSON.stringify("2.1.888"),
"MACRO.VERSION": JSON.stringify("4.0.1"),
"MACRO.BUILD_TIME": JSON.stringify(new Date().toISOString()),
"MACRO.FEEDBACK_CHANNEL": JSON.stringify(""),
"MACRO.ISSUES_EXPLAINER": JSON.stringify(""),

View File

@ -1,98 +1,21 @@
import * as React from 'react'
import { Box, Text } from '@anthropic/ink'
import { env } from '../../utils/env.js'
import * as React from 'react';
import { Box, Text } from '@anthropic/ink';
export type ClawdPose =
| 'default'
| 'arms-up' // both arms raised (used during jump)
| 'look-left' // both pupils shifted left
| 'look-right' // both pupils shifted right
export type ClawdPose = 'default' | 'arms-up' | 'look-left' | 'look-right';
type Props = {
pose?: ClawdPose
}
pose?: ClawdPose;
};
// Standard-terminal pose fragments. Each row is split into segments so we can
// 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': ' ▗ ▖ ',
}
// CoStrict ASCII art 大字标志,两行块字符风格:
// █▀▀ █▀█ █▀ ▀█▀ █▀█ █ █▀▀ ▀█▀
// █▄▄ █▄█ ▄█ █ █▀▄ █ █▄▄ █
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 (
<Box flexDirection="column" alignItems="center">
<Text>
<Text color="clawd_body"></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>
<Text color="claudeBlue_FOR_SYSTEM_SPINNER">{'█▀▀ █▀█ █▀ ▀█▀ █▀█ █ █▀▀ ▀█▀'}</Text>
<Text color="claudeBlue_FOR_SYSTEM_SPINNER">{'█▄▄ █▄█ ▄█ █ █▀▄ █ █▄▄ █ '}</Text>
</Box>
)
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB