Merge pull request #5 from y574444354/feat/basic-config
feat: 品牌化首页为 CoStrict 风格
This commit is contained in:
commit
523db59b4b
|
|
@ -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(""),
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 />}
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
BIN
src/components/LogoV2/costrict-logo.png
Normal file
BIN
src/components/LogoV2/costrict-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Loading…
Reference in New Issue
Block a user