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> {
|
export function getMacroDefines(): Record<string, string> {
|
||||||
return {
|
return {
|
||||||
"MACRO.VERSION": JSON.stringify("2.1.888"),
|
"MACRO.VERSION": JSON.stringify("4.0.1"),
|
||||||
"MACRO.BUILD_TIME": JSON.stringify(new Date().toISOString()),
|
"MACRO.BUILD_TIME": JSON.stringify(new Date().toISOString()),
|
||||||
"MACRO.FEEDBACK_CHANNEL": JSON.stringify(""),
|
"MACRO.FEEDBACK_CHANNEL": JSON.stringify(""),
|
||||||
"MACRO.ISSUES_EXPLAINER": JSON.stringify(""),
|
"MACRO.ISSUES_EXPLAINER": JSON.stringify(""),
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,21 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react';
|
||||||
import { Box, Text } from '@anthropic/ink'
|
import { Box, Text } from '@anthropic/ink';
|
||||||
import { env } from '../../utils/env.js'
|
|
||||||
|
|
||||||
export type ClawdPose =
|
export type ClawdPose = 'default' | 'arms-up' | 'look-left' | 'look-right';
|
||||||
| 'default'
|
|
||||||
| 'arms-up' // both arms raised (used during jump)
|
|
||||||
| 'look-left' // both pupils shifted left
|
|
||||||
| 'look-right' // both pupils shifted right
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pose?: ClawdPose
|
pose?: ClawdPose;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Standard-terminal pose fragments. Each row is split into segments so we can
|
// CoStrict ASCII art 大字标志,两行块字符风格:
|
||||||
// vary only the parts that change (eyes, arms) while keeping the body/bg spans
|
// █▀▀ █▀█ █▀ ▀█▀ █▀█ █ █▀▀ ▀█▀
|
||||||
// stable. All poses end up 9 cols wide.
|
// █▄▄ █▄█ ▄█ █ █▀▄ █ █▄▄ █
|
||||||
//
|
|
||||||
// arms-up: the row-2 arm shapes (▝▜ / ▛▘) move to row 1 as their
|
|
||||||
// bottom-heavy mirrors (▗▟ / ▙▖) — same silhouette, one row higher.
|
|
||||||
//
|
|
||||||
// look-* use top-quadrant eye chars (▙/▟) so both eyes change from the
|
|
||||||
// default (▛/▜, bottom pupils) — otherwise only one eye would appear to move.
|
|
||||||
type Segments = {
|
|
||||||
/** row 1 left (no bg): optional raised arm + side */
|
|
||||||
r1L: string
|
|
||||||
/** row 1 eyes (with bg): left-eye, forehead, right-eye */
|
|
||||||
r1E: string
|
|
||||||
/** row 1 right (no bg): side + optional raised arm */
|
|
||||||
r1R: string
|
|
||||||
/** row 2 left (no bg): arm + body curve */
|
|
||||||
r2L: string
|
|
||||||
/** row 2 right (no bg): body curve + arm */
|
|
||||||
r2R: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const POSES: Record<ClawdPose, Segments> = {
|
|
||||||
default: { r1L: ' ▐', r1E: '▛███▜', r1R: '▌', r2L: '▝▜', r2R: '▛▘' },
|
|
||||||
'look-left': { r1L: ' ▐', r1E: '▟███▟', r1R: '▌', r2L: '▝▜', r2R: '▛▘' },
|
|
||||||
'look-right': { r1L: ' ▐', r1E: '▙███▙', r1R: '▌', r2L: '▝▜', r2R: '▛▘' },
|
|
||||||
'arms-up': { r1L: '▗▟', r1E: '▛███▜', r1R: '▙▖', r2L: ' ▜', r2R: '▛ ' },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apple Terminal uses a bg-fill trick (see below), so only eye poses make
|
|
||||||
// sense. Arm poses fall back to default.
|
|
||||||
const APPLE_EYES: Record<ClawdPose, string> = {
|
|
||||||
default: ' ▗ ▖ ',
|
|
||||||
'look-left': ' ▘ ▘ ',
|
|
||||||
'look-right': ' ▝ ▝ ',
|
|
||||||
'arms-up': ' ▗ ▖ ',
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Clawd({ pose = 'default' }: Props = {}): React.ReactNode {
|
export function Clawd({ pose = 'default' }: Props = {}): React.ReactNode {
|
||||||
if (env.terminal === 'Apple_Terminal') {
|
|
||||||
return <AppleTerminalClawd pose={pose} />
|
|
||||||
}
|
|
||||||
const p = POSES[pose]
|
|
||||||
return (
|
|
||||||
<Box flexDirection="column">
|
|
||||||
<Text>
|
|
||||||
<Text color="clawd_body">{p.r1L}</Text>
|
|
||||||
<Text color="clawd_body" backgroundColor="clawd_background">
|
|
||||||
{p.r1E}
|
|
||||||
</Text>
|
|
||||||
<Text color="clawd_body">{p.r1R}</Text>
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
<Text color="clawd_body">{p.r2L}</Text>
|
|
||||||
<Text color="clawd_body" backgroundColor="clawd_background">
|
|
||||||
█████
|
|
||||||
</Text>
|
|
||||||
<Text color="clawd_body">{p.r2R}</Text>
|
|
||||||
</Text>
|
|
||||||
<Text color="clawd_body">
|
|
||||||
{' '}▘▘ ▝▝{' '}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function AppleTerminalClawd({ pose }: { pose: ClawdPose }): React.ReactNode {
|
|
||||||
// Apple's Terminal renders vertical space between chars by default.
|
|
||||||
// It does NOT render vertical space between background colors
|
|
||||||
// so we use background color to draw the main shape.
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" alignItems="center">
|
<Box flexDirection="column" alignItems="center">
|
||||||
<Text>
|
<Text color="claudeBlue_FOR_SYSTEM_SPINNER">{'█▀▀ █▀█ █▀ ▀█▀ █▀█ █ █▀▀ ▀█▀'}</Text>
|
||||||
<Text color="clawd_body">▗</Text>
|
<Text color="claudeBlue_FOR_SYSTEM_SPINNER">{'█▄▄ █▄█ ▄█ █ █▀▄ █ █▄▄ █ '}</Text>
|
||||||
<Text color="clawd_background" backgroundColor="clawd_body">
|
|
||||||
{APPLE_EYES[pose]}
|
|
||||||
</Text>
|
|
||||||
<Text color="clawd_body">▖</Text>
|
|
||||||
</Text>
|
|
||||||
<Text backgroundColor="clawd_body">{' '.repeat(7)}</Text>
|
|
||||||
<Text color="clawd_body">▘▘ ▝▝</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,83 +1,71 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react';
|
||||||
import { type ReactNode, useEffect } from 'react'
|
import { type ReactNode, useEffect } from 'react';
|
||||||
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js'
|
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';
|
||||||
import { useTerminalSize } from '../../hooks/useTerminalSize.js'
|
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
||||||
import { Box, Text, stringWidth } from '@anthropic/ink'
|
import { Box, Text, stringWidth } from '@anthropic/ink';
|
||||||
import { useAppState } from '../../state/AppState.js'
|
import { useAppState } from '../../state/AppState.js';
|
||||||
import { getEffortSuffix } from '../../utils/effort.js'
|
import { getEffortSuffix } from '../../utils/effort.js';
|
||||||
import { truncate } from '../../utils/format.js'
|
import { truncate } from '../../utils/format.js';
|
||||||
import { isFullscreenEnvEnabled } from '../../utils/fullscreen.js'
|
import { isFullscreenEnvEnabled } from '../../utils/fullscreen.js';
|
||||||
import {
|
import { formatModelAndBilling, getLogoDisplayData, truncatePath } from '../../utils/logoV2Utils.js';
|
||||||
formatModelAndBilling,
|
import { renderModelSetting } from '../../utils/model/model.js';
|
||||||
getLogoDisplayData,
|
import { OffscreenFreeze } from '../OffscreenFreeze.js';
|
||||||
truncatePath,
|
import { AnimatedClawd } from './AnimatedClawd.js';
|
||||||
} from '../../utils/logoV2Utils.js'
|
import { Clawd } from './Clawd.js';
|
||||||
import { renderModelSetting } from '../../utils/model/model.js'
|
import { GuestPassesUpsell, incrementGuestPassesSeenCount, useShowGuestPassesUpsell } from './GuestPassesUpsell.js';
|
||||||
import { OffscreenFreeze } from '../OffscreenFreeze.js'
|
|
||||||
import { AnimatedClawd } from './AnimatedClawd.js'
|
|
||||||
import { Clawd } from './Clawd.js'
|
|
||||||
import {
|
|
||||||
GuestPassesUpsell,
|
|
||||||
incrementGuestPassesSeenCount,
|
|
||||||
useShowGuestPassesUpsell,
|
|
||||||
} from './GuestPassesUpsell.js'
|
|
||||||
import {
|
import {
|
||||||
incrementOverageCreditUpsellSeenCount,
|
incrementOverageCreditUpsellSeenCount,
|
||||||
OverageCreditUpsell,
|
OverageCreditUpsell,
|
||||||
useShowOverageCreditUpsell,
|
useShowOverageCreditUpsell,
|
||||||
} from './OverageCreditUpsell.js'
|
} from './OverageCreditUpsell.js';
|
||||||
|
|
||||||
export function CondensedLogo(): ReactNode {
|
export function CondensedLogo(): ReactNode {
|
||||||
const { columns } = useTerminalSize()
|
const { columns } = useTerminalSize();
|
||||||
const agent = useAppState(s => s.agent)
|
const agent = useAppState(s => s.agent);
|
||||||
const effortValue = useAppState(s => s.effortValue)
|
const effortValue = useAppState(s => s.effortValue);
|
||||||
const model = useMainLoopModel()
|
const model = useMainLoopModel();
|
||||||
const modelDisplayName = renderModelSetting(model)
|
const modelDisplayName = renderModelSetting(model);
|
||||||
const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData()
|
const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData();
|
||||||
|
|
||||||
// Prefer AppState.agent (set from --agent CLI flag) over settings
|
// Prefer AppState.agent (set from --agent CLI flag) over settings
|
||||||
const agentName = agent ?? agentNameFromSettings
|
const agentName = agent ?? agentNameFromSettings;
|
||||||
const showGuestPassesUpsell = useShowGuestPassesUpsell()
|
const showGuestPassesUpsell = useShowGuestPassesUpsell();
|
||||||
const showOverageCreditUpsell = useShowOverageCreditUpsell()
|
const showOverageCreditUpsell = useShowOverageCreditUpsell();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showGuestPassesUpsell) {
|
if (showGuestPassesUpsell) {
|
||||||
incrementGuestPassesSeenCount()
|
incrementGuestPassesSeenCount();
|
||||||
}
|
}
|
||||||
}, [showGuestPassesUpsell])
|
}, [showGuestPassesUpsell]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showOverageCreditUpsell && !showGuestPassesUpsell) {
|
if (showOverageCreditUpsell && !showGuestPassesUpsell) {
|
||||||
incrementOverageCreditUpsellSeenCount()
|
incrementOverageCreditUpsellSeenCount();
|
||||||
}
|
}
|
||||||
}, [showOverageCreditUpsell, showGuestPassesUpsell])
|
}, [showOverageCreditUpsell, showGuestPassesUpsell]);
|
||||||
|
|
||||||
// Calculate available width for text content
|
// Calculate available width for text content
|
||||||
// Account for: condensed clawd width (11 chars) + gap (2) + padding (2) = 15 chars
|
// Account for: condensed clawd width (11 chars) + gap (2) + padding (2) = 15 chars
|
||||||
const textWidth = Math.max(columns - 15, 20)
|
const textWidth = Math.max(columns - 15, 20);
|
||||||
|
|
||||||
// Truncate version to fit within available width, accounting for "Claude Code v" prefix
|
// Truncate version to fit within available width, accounting for "CoStrict v" prefix
|
||||||
const versionPrefix = 'Claude Code v'
|
const versionPrefix = 'CoStrict v';
|
||||||
const truncatedVersion = truncate(
|
const truncatedVersion = truncate(version, Math.max(textWidth - versionPrefix.length, 6));
|
||||||
version,
|
|
||||||
Math.max(textWidth - versionPrefix.length, 6),
|
|
||||||
)
|
|
||||||
|
|
||||||
const effortSuffix = getEffortSuffix(model, effortValue)
|
const effortSuffix = getEffortSuffix(model, effortValue);
|
||||||
const { shouldSplit, truncatedModel, truncatedBilling } =
|
const { shouldSplit, truncatedModel, truncatedBilling } = formatModelAndBilling(
|
||||||
formatModelAndBilling(
|
modelDisplayName + effortSuffix,
|
||||||
modelDisplayName + effortSuffix,
|
billingType,
|
||||||
billingType,
|
textWidth,
|
||||||
textWidth,
|
);
|
||||||
)
|
|
||||||
|
|
||||||
// Truncate path, accounting for agent name if present
|
// Truncate path, accounting for agent name if present
|
||||||
const separator = ' · '
|
const separator = ' · ';
|
||||||
const atPrefix = '@'
|
const atPrefix = '@';
|
||||||
const cwdAvailableWidth = agentName
|
const cwdAvailableWidth = agentName
|
||||||
? textWidth - atPrefix.length - stringWidth(agentName) - separator.length
|
? textWidth - atPrefix.length - stringWidth(agentName) - separator.length
|
||||||
: textWidth
|
: textWidth;
|
||||||
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10))
|
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10));
|
||||||
|
|
||||||
// OffscreenFreeze: the logo sits at the top of the message list and is the
|
// OffscreenFreeze: the logo sits at the top of the message list and is the
|
||||||
// first thing to enter scrollback. useMainLoopModel() subscribes to model
|
// first thing to enter scrollback. useMainLoopModel() subscribes to model
|
||||||
|
|
@ -85,34 +73,40 @@ export function CondensedLogo(): ReactNode {
|
||||||
// of which changing while in scrollback would force a full terminal reset.
|
// of which changing while in scrollback would force a full terminal reset.
|
||||||
return (
|
return (
|
||||||
<OffscreenFreeze>
|
<OffscreenFreeze>
|
||||||
<Box flexDirection="row" gap={2} alignItems="center">
|
<Box
|
||||||
{isFullscreenEnvEnabled() ? <AnimatedClawd /> : <Clawd />}
|
flexDirection="row"
|
||||||
|
gap={2}
|
||||||
|
alignItems="center"
|
||||||
|
borderStyle="round"
|
||||||
|
borderColor="claudeBlue_FOR_SYSTEM_SPINNER"
|
||||||
|
paddingX={1}
|
||||||
|
paddingY={1}
|
||||||
|
>
|
||||||
|
{isFullscreenEnvEnabled() ? <AnimatedClawd /> : <Clawd />}
|
||||||
|
|
||||||
{/* Info */}
|
{/* Info */}
|
||||||
<Box flexDirection="column">
|
<Box flexDirection="column">
|
||||||
<Text>
|
<Text>
|
||||||
<Text bold>Claude Code</Text>{' '}
|
<Text bold color="claudeBlue_FOR_SYSTEM_SPINNER">
|
||||||
<Text dimColor>v{truncatedVersion}</Text>
|
CoStrict
|
||||||
</Text>
|
</Text>{' '}
|
||||||
{shouldSplit ? (
|
<Text dimColor>v{truncatedVersion}</Text>
|
||||||
<>
|
|
||||||
<Text dimColor>{truncatedModel}</Text>
|
|
||||||
<Text dimColor>{truncatedBilling}</Text>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Text dimColor>
|
|
||||||
{truncatedModel} · {truncatedBilling}
|
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
{shouldSplit ? (
|
||||||
<Text dimColor>
|
<>
|
||||||
{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}
|
<Text dimColor>{truncatedModel}</Text>
|
||||||
</Text>
|
<Text dimColor>{truncatedBilling}</Text>
|
||||||
{showGuestPassesUpsell && <GuestPassesUpsell />}
|
</>
|
||||||
{!showGuestPassesUpsell && showOverageCreditUpsell && (
|
) : (
|
||||||
<OverageCreditUpsell maxWidth={textWidth} twoLine />
|
<Text dimColor>
|
||||||
)}
|
{truncatedModel} · {truncatedBilling}
|
||||||
</Box>
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text dimColor>{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}</Text>
|
||||||
|
{showGuestPassesUpsell && <GuestPassesUpsell />}
|
||||||
|
{!showGuestPassesUpsell && showOverageCreditUpsell && <OverageCreditUpsell maxWidth={textWidth} twoLine />}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</OffscreenFreeze>
|
</OffscreenFreeze>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,61 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react';
|
||||||
import { Box, Text, stringWidth } from '@anthropic/ink'
|
import { Box, Text, stringWidth } from '@anthropic/ink';
|
||||||
import { truncate } from '../../utils/format.js'
|
import { truncate } from '../../utils/format.js';
|
||||||
|
|
||||||
export type FeedLine = {
|
export type FeedLine = {
|
||||||
text: string
|
text: string;
|
||||||
timestamp?: string
|
timestamp?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type FeedConfig = {
|
export type FeedConfig = {
|
||||||
title: string
|
title: string;
|
||||||
lines: FeedLine[]
|
lines: FeedLine[];
|
||||||
footer?: string
|
footer?: string;
|
||||||
emptyMessage?: string
|
emptyMessage?: string;
|
||||||
customContent?: { content: React.ReactNode; width: number }
|
customContent?: { content: React.ReactNode; width: number };
|
||||||
}
|
};
|
||||||
|
|
||||||
type FeedProps = {
|
type FeedProps = {
|
||||||
config: FeedConfig
|
config: FeedConfig;
|
||||||
actualWidth: number
|
actualWidth: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function calculateFeedWidth(config: FeedConfig): number {
|
export function calculateFeedWidth(config: FeedConfig): number {
|
||||||
const { title, lines, footer, emptyMessage, customContent } = config
|
const { title, lines, footer, emptyMessage, customContent } = config;
|
||||||
|
|
||||||
let maxWidth = stringWidth(title)
|
let maxWidth = stringWidth(title);
|
||||||
|
|
||||||
if (customContent !== undefined) {
|
if (customContent !== undefined) {
|
||||||
maxWidth = Math.max(maxWidth, customContent.width)
|
maxWidth = Math.max(maxWidth, customContent.width);
|
||||||
} else if (lines.length === 0 && emptyMessage) {
|
} else if (lines.length === 0 && emptyMessage) {
|
||||||
maxWidth = Math.max(maxWidth, stringWidth(emptyMessage))
|
maxWidth = Math.max(maxWidth, stringWidth(emptyMessage));
|
||||||
} else {
|
} else {
|
||||||
const gap = ' '
|
const gap = ' ';
|
||||||
const maxTimestampWidth = Math.max(
|
const maxTimestampWidth = Math.max(0, ...lines.map(line => (line.timestamp ? stringWidth(line.timestamp) : 0)));
|
||||||
0,
|
|
||||||
...lines.map(line => (line.timestamp ? stringWidth(line.timestamp) : 0)),
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const timestampWidth = maxTimestampWidth > 0 ? maxTimestampWidth : 0
|
const timestampWidth = maxTimestampWidth > 0 ? maxTimestampWidth : 0;
|
||||||
const lineWidth =
|
const lineWidth = stringWidth(line.text) + (timestampWidth > 0 ? timestampWidth + gap.length : 0);
|
||||||
stringWidth(line.text) +
|
maxWidth = Math.max(maxWidth, lineWidth);
|
||||||
(timestampWidth > 0 ? timestampWidth + gap.length : 0)
|
|
||||||
maxWidth = Math.max(maxWidth, lineWidth)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (footer) {
|
if (footer) {
|
||||||
maxWidth = Math.max(maxWidth, stringWidth(footer))
|
maxWidth = Math.max(maxWidth, stringWidth(footer));
|
||||||
}
|
}
|
||||||
|
|
||||||
return maxWidth
|
return maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Feed({ config, actualWidth }: FeedProps): React.ReactNode {
|
export function Feed({ config, actualWidth }: FeedProps): React.ReactNode {
|
||||||
const { title, lines, footer, emptyMessage, customContent } = config
|
const { title, lines, footer, emptyMessage, customContent } = config;
|
||||||
|
|
||||||
const gap = ' '
|
const gap = ' ';
|
||||||
const maxTimestampWidth = Math.max(
|
const maxTimestampWidth = Math.max(0, ...lines.map(line => (line.timestamp ? stringWidth(line.timestamp) : 0)));
|
||||||
0,
|
|
||||||
...lines.map(line => (line.timestamp ? stringWidth(line.timestamp) : 0)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" width={actualWidth}>
|
<Box flexDirection="column" width={actualWidth}>
|
||||||
<Text bold color="claude">
|
<Text bold color="claudeBlue_FOR_SYSTEM_SPINNER">
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
{customContent ? (
|
{customContent ? (
|
||||||
|
|
@ -80,25 +72,19 @@ export function Feed({ config, actualWidth }: FeedProps): React.ReactNode {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{lines.map((line, index) => {
|
{lines.map((line, index) => {
|
||||||
const textWidth = Math.max(
|
const textWidth = Math.max(10, actualWidth - (maxTimestampWidth > 0 ? maxTimestampWidth + gap.length : 0));
|
||||||
10,
|
|
||||||
actualWidth -
|
|
||||||
(maxTimestampWidth > 0 ? maxTimestampWidth + gap.length : 0),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text key={index}>
|
<Text key={index}>
|
||||||
{maxTimestampWidth > 0 && (
|
{maxTimestampWidth > 0 && (
|
||||||
<>
|
<>
|
||||||
<Text dimColor>
|
<Text dimColor>{(line.timestamp || '').padEnd(maxTimestampWidth)}</Text>
|
||||||
{(line.timestamp || '').padEnd(maxTimestampWidth)}
|
|
||||||
</Text>
|
|
||||||
{gap}
|
{gap}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Text>{truncate(line.text, textWidth)}</Text>
|
<Text>{truncate(line.text, textWidth)}</Text>
|
||||||
</Text>
|
</Text>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
{footer && (
|
{footer && (
|
||||||
<Text dimColor italic>
|
<Text dimColor italic>
|
||||||
|
|
@ -108,5 +94,5 @@ export function Feed({ config, actualWidth }: FeedProps): React.ReactNode {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,27 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react';
|
||||||
import { Box } from '@anthropic/ink'
|
import { Box } from '@anthropic/ink';
|
||||||
import { Divider } from '@anthropic/ink'
|
import { Divider } from '@anthropic/ink';
|
||||||
import type { FeedConfig } from './Feed.js'
|
import type { FeedConfig } from './Feed.js';
|
||||||
import { calculateFeedWidth, Feed } from './Feed.js'
|
import { calculateFeedWidth, Feed } from './Feed.js';
|
||||||
|
|
||||||
type FeedColumnProps = {
|
type FeedColumnProps = {
|
||||||
feeds: FeedConfig[]
|
feeds: FeedConfig[];
|
||||||
maxWidth: number
|
maxWidth: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function FeedColumn({
|
export function FeedColumn({ feeds, maxWidth }: FeedColumnProps): React.ReactNode {
|
||||||
feeds,
|
const feedWidths = feeds.map(feed => calculateFeedWidth(feed));
|
||||||
maxWidth,
|
const maxOfAllFeeds = Math.max(...feedWidths);
|
||||||
}: FeedColumnProps): React.ReactNode {
|
const actualWidth = Math.min(maxOfAllFeeds, maxWidth);
|
||||||
const feedWidths = feeds.map(feed => calculateFeedWidth(feed))
|
|
||||||
const maxOfAllFeeds = Math.max(...feedWidths)
|
|
||||||
const actualWidth = Math.min(maxOfAllFeeds, maxWidth)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column">
|
<Box flexDirection="column">
|
||||||
{feeds.map((feed, index) => (
|
{feeds.map((feed, index) => (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
<Feed config={feed} actualWidth={actualWidth} />
|
<Feed config={feed} actualWidth={actualWidth} />
|
||||||
{index < feeds.length - 1 && (
|
{index < feeds.length - 1 && <Divider color="claudeBlue_FOR_SYSTEM_SPINNER" width={actualWidth} />}
|
||||||
<Divider color="claude" width={actualWidth} />
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
||||||
import * as React from 'react'
|
import * as React from 'react';
|
||||||
import { Box, Text, color, stringWidth } from '@anthropic/ink'
|
import { Box, Text, color, stringWidth } from '@anthropic/ink';
|
||||||
import { useTerminalSize } from '../../hooks/useTerminalSize.js'
|
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
||||||
import {
|
import {
|
||||||
getLayoutMode,
|
getLayoutMode,
|
||||||
calculateLayoutDimensions,
|
calculateLayoutDimensions,
|
||||||
|
|
@ -11,46 +11,39 @@ import {
|
||||||
getRecentActivitySync,
|
getRecentActivitySync,
|
||||||
getRecentReleaseNotesSync,
|
getRecentReleaseNotesSync,
|
||||||
getLogoDisplayData,
|
getLogoDisplayData,
|
||||||
} from '../../utils/logoV2Utils.js'
|
} from '../../utils/logoV2Utils.js';
|
||||||
import { truncate } from '../../utils/format.js'
|
import { truncate } from '../../utils/format.js';
|
||||||
import { getDisplayPath } from '../../utils/file.js'
|
import { getDisplayPath } from '../../utils/file.js';
|
||||||
import { Clawd } from './Clawd.js'
|
import { Clawd } from './Clawd.js';
|
||||||
import { FeedColumn } from './FeedColumn.js'
|
import { FeedColumn } from './FeedColumn.js';
|
||||||
import {
|
import {
|
||||||
createRecentActivityFeed,
|
createRecentActivityFeed,
|
||||||
createWhatsNewFeed,
|
createWhatsNewFeed,
|
||||||
createProjectOnboardingFeed,
|
createProjectOnboardingFeed,
|
||||||
createGuestPassesFeed,
|
createGuestPassesFeed,
|
||||||
} from './feedConfigs.js'
|
} from './feedConfigs.js';
|
||||||
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js'
|
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js';
|
||||||
import { resolveThemeSetting } from 'src/utils/systemTheme.js'
|
import { resolveThemeSetting } from 'src/utils/systemTheme.js';
|
||||||
import { getInitialSettings } from 'src/utils/settings/settings.js'
|
import { getInitialSettings } from 'src/utils/settings/settings.js';
|
||||||
import {
|
import { isDebugMode, isDebugToStdErr, getDebugLogPath } from 'src/utils/debug.js';
|
||||||
isDebugMode,
|
import { useEffect, useState } from 'react';
|
||||||
isDebugToStdErr,
|
|
||||||
getDebugLogPath,
|
|
||||||
} from 'src/utils/debug.js'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import {
|
import {
|
||||||
getSteps,
|
getSteps,
|
||||||
shouldShowProjectOnboarding,
|
shouldShowProjectOnboarding,
|
||||||
incrementProjectOnboardingSeenCount,
|
incrementProjectOnboardingSeenCount,
|
||||||
} from '../../projectOnboardingState.js'
|
} from '../../projectOnboardingState.js';
|
||||||
import { CondensedLogo } from './CondensedLogo.js'
|
import { CondensedLogo } from './CondensedLogo.js';
|
||||||
import { OffscreenFreeze } from '../OffscreenFreeze.js'
|
import { OffscreenFreeze } from '../OffscreenFreeze.js';
|
||||||
import { checkForReleaseNotesSync } from '../../utils/releaseNotes.js'
|
import { checkForReleaseNotesSync } from '../../utils/releaseNotes.js';
|
||||||
import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js'
|
import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js';
|
||||||
import { isEnvTruthy } from 'src/utils/envUtils.js'
|
import { isEnvTruthy } from 'src/utils/envUtils.js';
|
||||||
import {
|
import { getStartupPerfLogPath, isDetailedProfilingEnabled } from 'src/utils/startupProfiler.js';
|
||||||
getStartupPerfLogPath,
|
import { EmergencyTip } from './EmergencyTip.js';
|
||||||
isDetailedProfilingEnabled,
|
import { VoiceModeNotice } from './VoiceModeNotice.js';
|
||||||
} from 'src/utils/startupProfiler.js'
|
import { Opus1mMergeNotice } from './Opus1mMergeNotice.js';
|
||||||
import { EmergencyTip } from './EmergencyTip.js'
|
import { GateOverridesWarning } from './GateOverridesWarning.js';
|
||||||
import { VoiceModeNotice } from './VoiceModeNotice.js'
|
import { ExperimentEnrollmentNotice } from './ExperimentEnrollmentNotice.js';
|
||||||
import { Opus1mMergeNotice } from './Opus1mMergeNotice.js'
|
import { feature } from 'bun:bundle';
|
||||||
import { GateOverridesWarning } from './GateOverridesWarning.js'
|
|
||||||
import { ExperimentEnrollmentNotice } from './ExperimentEnrollmentNotice.js'
|
|
||||||
import { feature } from 'bun:bundle'
|
|
||||||
|
|
||||||
// Conditional require so ChannelsNotice.tsx tree-shakes when both flags are
|
// Conditional require so ChannelsNotice.tsx tree-shakes when both flags are
|
||||||
// false. A module-scope helper component inside a feature() ternary does NOT
|
// false. A module-scope helper component inside a feature() ternary does NOT
|
||||||
|
|
@ -61,128 +54,98 @@ import { feature } from 'bun:bundle'
|
||||||
const ChannelsNoticeModule =
|
const ChannelsNoticeModule =
|
||||||
feature('KAIROS') || feature('KAIROS_CHANNELS')
|
feature('KAIROS') || feature('KAIROS_CHANNELS')
|
||||||
? (require('./ChannelsNotice.js') as typeof import('./ChannelsNotice.js'))
|
? (require('./ChannelsNotice.js') as typeof import('./ChannelsNotice.js'))
|
||||||
: null
|
: null;
|
||||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||||
import { SandboxManager } from 'src/utils/sandbox/sandbox-adapter.js'
|
import { SandboxManager } from 'src/utils/sandbox/sandbox-adapter.js';
|
||||||
import {
|
import { useShowGuestPassesUpsell, incrementGuestPassesSeenCount } from './GuestPassesUpsell.js';
|
||||||
useShowGuestPassesUpsell,
|
|
||||||
incrementGuestPassesSeenCount,
|
|
||||||
} from './GuestPassesUpsell.js'
|
|
||||||
import {
|
import {
|
||||||
useShowOverageCreditUpsell,
|
useShowOverageCreditUpsell,
|
||||||
incrementOverageCreditUpsellSeenCount,
|
incrementOverageCreditUpsellSeenCount,
|
||||||
createOverageCreditFeed,
|
createOverageCreditFeed,
|
||||||
} from './OverageCreditUpsell.js'
|
} from './OverageCreditUpsell.js';
|
||||||
import { plural } from '../../utils/stringUtils.js'
|
import { plural } from '../../utils/stringUtils.js';
|
||||||
import { useAppState } from '../../state/AppState.js'
|
import { useAppState } from '../../state/AppState.js';
|
||||||
import { getEffortSuffix } from '../../utils/effort.js'
|
import { getEffortSuffix } from '../../utils/effort.js';
|
||||||
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js'
|
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';
|
||||||
import { renderModelSetting } from '../../utils/model/model.js'
|
import { renderModelSetting } from '../../utils/model/model.js';
|
||||||
|
|
||||||
const LEFT_PANEL_MAX_WIDTH = 50
|
const LEFT_PANEL_MAX_WIDTH = 50;
|
||||||
|
|
||||||
export function LogoV2(): React.ReactNode {
|
export function LogoV2(): React.ReactNode {
|
||||||
const activities = getRecentActivitySync()
|
const activities = getRecentActivitySync();
|
||||||
const username = getGlobalConfig().oauthAccount?.displayName ?? ''
|
const username = getGlobalConfig().oauthAccount?.displayName ?? '';
|
||||||
|
|
||||||
const { columns } = useTerminalSize()
|
const { columns } = useTerminalSize();
|
||||||
const showOnboarding = shouldShowProjectOnboarding()
|
const showOnboarding = shouldShowProjectOnboarding();
|
||||||
const showSandboxStatus = SandboxManager.isSandboxingEnabled()
|
const showSandboxStatus = SandboxManager.isSandboxingEnabled();
|
||||||
const showGuestPassesUpsell = useShowGuestPassesUpsell()
|
const showGuestPassesUpsell = useShowGuestPassesUpsell();
|
||||||
const showOverageCreditUpsell = useShowOverageCreditUpsell()
|
const showOverageCreditUpsell = useShowOverageCreditUpsell();
|
||||||
const agent = useAppState(s => s.agent)
|
const agent = useAppState(s => s.agent);
|
||||||
const effortValue = useAppState(s => s.effortValue)
|
const effortValue = useAppState(s => s.effortValue);
|
||||||
|
|
||||||
const config = getGlobalConfig()
|
const config = getGlobalConfig();
|
||||||
|
|
||||||
let changelog: string[]
|
let changelog: string[];
|
||||||
try {
|
try {
|
||||||
changelog = getRecentReleaseNotesSync(3)
|
changelog = getRecentReleaseNotesSync(3);
|
||||||
} catch {
|
} catch {
|
||||||
changelog = []
|
changelog = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get company announcements and select one:
|
// Get company announcements and select one:
|
||||||
// - First startup (numStartups === 1): show first announcement
|
// - First startup (numStartups === 1): show first announcement
|
||||||
// - All other startups: randomly select from announcements
|
// - All other startups: randomly select from announcements
|
||||||
const [announcement] = useState(() => {
|
const [announcement] = useState(() => {
|
||||||
const announcements = getInitialSettings().companyAnnouncements
|
const announcements = getInitialSettings().companyAnnouncements;
|
||||||
if (!announcements || announcements.length === 0) return undefined
|
if (!announcements || announcements.length === 0) return undefined;
|
||||||
return config.numStartups === 1
|
return config.numStartups === 1
|
||||||
? announcements[0]
|
? announcements[0]
|
||||||
: announcements[Math.floor(Math.random() * announcements.length)]
|
: announcements[Math.floor(Math.random() * announcements.length)];
|
||||||
})
|
});
|
||||||
const { hasReleaseNotes } = checkForReleaseNotesSync(
|
const { hasReleaseNotes } = checkForReleaseNotesSync(config.lastReleaseNotesSeen);
|
||||||
config.lastReleaseNotesSeen,
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentConfig = getGlobalConfig()
|
const currentConfig = getGlobalConfig();
|
||||||
if (currentConfig.lastReleaseNotesSeen === MACRO.VERSION) {
|
if (currentConfig.lastReleaseNotesSeen === MACRO.VERSION) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
saveGlobalConfig(current => {
|
saveGlobalConfig(current => {
|
||||||
if (current.lastReleaseNotesSeen === MACRO.VERSION) return current
|
if (current.lastReleaseNotesSeen === MACRO.VERSION) return current;
|
||||||
return { ...current, lastReleaseNotesSeen: MACRO.VERSION }
|
return { ...current, lastReleaseNotesSeen: MACRO.VERSION };
|
||||||
})
|
});
|
||||||
if (showOnboarding) {
|
if (showOnboarding) {
|
||||||
incrementProjectOnboardingSeenCount()
|
incrementProjectOnboardingSeenCount();
|
||||||
}
|
}
|
||||||
}, [config, showOnboarding])
|
}, [config, showOnboarding]);
|
||||||
|
|
||||||
// In condensed mode (early-return below renders <CondensedLogo/>),
|
// In condensed mode (early-return below renders <CondensedLogo/>),
|
||||||
// CondensedLogo's own useEffect handles the impression count. Skipping
|
// CondensedLogo's own useEffect handles the impression count. Skipping
|
||||||
// here avoids double-counting since hooks fire before the early return.
|
// here avoids double-counting since hooks fire before the early return.
|
||||||
const isCondensedMode =
|
const isCondensedMode = !hasReleaseNotes && !showOnboarding && !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO);
|
||||||
!hasReleaseNotes &&
|
|
||||||
!showOnboarding &&
|
|
||||||
!isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showGuestPassesUpsell && !showOnboarding && !isCondensedMode) {
|
if (showGuestPassesUpsell && !showOnboarding && !isCondensedMode) {
|
||||||
incrementGuestPassesSeenCount()
|
incrementGuestPassesSeenCount();
|
||||||
}
|
}
|
||||||
}, [showGuestPassesUpsell, showOnboarding, isCondensedMode])
|
}, [showGuestPassesUpsell, showOnboarding, isCondensedMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (showOverageCreditUpsell && !showOnboarding && !showGuestPassesUpsell && !isCondensedMode) {
|
||||||
showOverageCreditUpsell &&
|
incrementOverageCreditUpsellSeenCount();
|
||||||
!showOnboarding &&
|
|
||||||
!showGuestPassesUpsell &&
|
|
||||||
!isCondensedMode
|
|
||||||
) {
|
|
||||||
incrementOverageCreditUpsellSeenCount()
|
|
||||||
}
|
}
|
||||||
}, [
|
}, [showOverageCreditUpsell, showOnboarding, showGuestPassesUpsell, isCondensedMode]);
|
||||||
showOverageCreditUpsell,
|
|
||||||
showOnboarding,
|
|
||||||
showGuestPassesUpsell,
|
|
||||||
isCondensedMode,
|
|
||||||
])
|
|
||||||
|
|
||||||
const model = useMainLoopModel()
|
const model = useMainLoopModel();
|
||||||
const fullModelDisplayName = renderModelSetting(model)
|
const fullModelDisplayName = renderModelSetting(model);
|
||||||
const {
|
const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData();
|
||||||
version,
|
|
||||||
cwd,
|
|
||||||
billingType,
|
|
||||||
agentName: agentNameFromSettings,
|
|
||||||
} = getLogoDisplayData()
|
|
||||||
// Prefer AppState.agent (set from --agent CLI flag) over settings
|
// Prefer AppState.agent (set from --agent CLI flag) over settings
|
||||||
const agentName = agent ?? agentNameFromSettings
|
const agentName = agent ?? agentNameFromSettings;
|
||||||
// -20 to account for the max length of subscription name " · Claude Enterprise".
|
// -20 to account for the max length of subscription name " · Claude Enterprise".
|
||||||
const effortSuffix = getEffortSuffix(model, effortValue)
|
const effortSuffix = getEffortSuffix(model, effortValue);
|
||||||
const modelDisplayName = truncate(
|
const modelDisplayName = truncate(fullModelDisplayName + effortSuffix, LEFT_PANEL_MAX_WIDTH - 20);
|
||||||
fullModelDisplayName + effortSuffix,
|
|
||||||
LEFT_PANEL_MAX_WIDTH - 20,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Show condensed logo if no new changelog and not showing onboarding and not forcing full logo
|
// Show condensed logo if no new changelog and not showing onboarding and not forcing full logo
|
||||||
if (
|
if (!hasReleaseNotes && !showOnboarding && !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO)) {
|
||||||
!hasReleaseNotes &&
|
|
||||||
!showOnboarding &&
|
|
||||||
!isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO)
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CondensedLogo />
|
<CondensedLogo />
|
||||||
|
|
@ -192,17 +155,13 @@ export function LogoV2(): React.ReactNode {
|
||||||
{isDebugMode() && (
|
{isDebugMode() && (
|
||||||
<Box paddingLeft={2} flexDirection="column">
|
<Box paddingLeft={2} flexDirection="column">
|
||||||
<Text color="warning">Debug mode enabled</Text>
|
<Text color="warning">Debug mode enabled</Text>
|
||||||
<Text dimColor>
|
<Text dimColor>Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()}</Text>
|
||||||
Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<EmergencyTip />
|
<EmergencyTip />
|
||||||
{process.env.CLAUDE_CODE_TMUX_SESSION && (
|
{process.env.CLAUDE_CODE_TMUX_SESSION && (
|
||||||
<Box paddingLeft={2} flexDirection="column">
|
<Box paddingLeft={2} flexDirection="column">
|
||||||
<Text dimColor>
|
<Text dimColor>tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION}</Text>
|
||||||
tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION}
|
|
||||||
</Text>
|
|
||||||
<Text dimColor>
|
<Text dimColor>
|
||||||
{process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS
|
{process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS
|
||||||
? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})`
|
? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})`
|
||||||
|
|
@ -213,9 +172,7 @@ export function LogoV2(): React.ReactNode {
|
||||||
{announcement && (
|
{announcement && (
|
||||||
<Box paddingLeft={2} flexDirection="column">
|
<Box paddingLeft={2} flexDirection="column">
|
||||||
{!process.env.IS_DEMO && config.oauthAccount?.organizationName && (
|
{!process.env.IS_DEMO && config.oauthAccount?.organizationName && (
|
||||||
<Text dimColor>
|
<Text dimColor>Message from {config.oauthAccount.organizationName}:</Text>
|
||||||
Message from {config.oauthAccount.organizationName}:
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
<Text>{announcement}</Text>
|
<Text>{announcement}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -228,51 +185,41 @@ export function LogoV2(): React.ReactNode {
|
||||||
{process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && (
|
{process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && (
|
||||||
<Box paddingLeft={2} flexDirection="column">
|
<Box paddingLeft={2} flexDirection="column">
|
||||||
<Text color="warning">[ANT-ONLY] Logs:</Text>
|
<Text color="warning">[ANT-ONLY] Logs:</Text>
|
||||||
<Text dimColor>
|
<Text dimColor>API calls: {getDisplayPath(getDumpPromptsPath())}</Text>
|
||||||
API calls: {getDisplayPath(getDumpPromptsPath())}
|
<Text dimColor>Debug logs: {getDisplayPath(getDebugLogPath())}</Text>
|
||||||
</Text>
|
|
||||||
<Text dimColor>
|
|
||||||
Debug logs: {getDisplayPath(getDebugLogPath())}
|
|
||||||
</Text>
|
|
||||||
{isDetailedProfilingEnabled() && (
|
{isDetailedProfilingEnabled() && (
|
||||||
<Text dimColor>
|
<Text dimColor>Startup Perf: {getDisplayPath(getStartupPerfLogPath())}</Text>
|
||||||
Startup Perf: {getDisplayPath(getStartupPerfLogPath())}
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{process.env.USER_TYPE === 'ant' && <GateOverridesWarning />}
|
{process.env.USER_TYPE === 'ant' && <GateOverridesWarning />}
|
||||||
{process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />}
|
{process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate layout and display values
|
// Calculate layout and display values
|
||||||
const layoutMode = getLayoutMode(columns)
|
const layoutMode = getLayoutMode(columns);
|
||||||
|
|
||||||
const userTheme = resolveThemeSetting(getGlobalConfig().theme)
|
const userTheme = resolveThemeSetting(getGlobalConfig().theme);
|
||||||
const borderTitle = ` ${color('claude', userTheme)('Claude Code')} ${color('inactive', userTheme)(`v${version}`)} `
|
const borderTitle = ` ${color('claudeBlue_FOR_SYSTEM_SPINNER', userTheme)('CoStrict')} ${color('inactive', userTheme)(`v${version}`)} `;
|
||||||
const compactBorderTitle = color('claude', userTheme)(' Claude Code ')
|
const compactBorderTitle = color('claudeBlue_FOR_SYSTEM_SPINNER', userTheme)(' CoStrict ');
|
||||||
|
|
||||||
// Early return for compact mode
|
// Early return for compact mode
|
||||||
if (layoutMode === 'compact') {
|
if (layoutMode === 'compact') {
|
||||||
const layoutWidth = 4 // border + padding
|
const layoutWidth = 4; // border + padding
|
||||||
let welcomeMessage = formatWelcomeMessage(username)
|
let welcomeMessage = formatWelcomeMessage(username);
|
||||||
if (stringWidth(welcomeMessage) > columns - layoutWidth) {
|
if (stringWidth(welcomeMessage) > columns - layoutWidth) {
|
||||||
welcomeMessage = formatWelcomeMessage(null)
|
welcomeMessage = formatWelcomeMessage(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate cwd width accounting for agent name if present
|
// Calculate cwd width accounting for agent name if present
|
||||||
const separator = ' · '
|
const separator = ' · ';
|
||||||
const atPrefix = '@'
|
const atPrefix = '@';
|
||||||
const cwdAvailableWidth = agentName
|
const cwdAvailableWidth = agentName
|
||||||
? columns -
|
? columns - layoutWidth - atPrefix.length - stringWidth(agentName) - separator.length
|
||||||
layoutWidth -
|
: columns - layoutWidth;
|
||||||
atPrefix.length -
|
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10));
|
||||||
stringWidth(agentName) -
|
|
||||||
separator.length
|
|
||||||
: columns - layoutWidth
|
|
||||||
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10))
|
|
||||||
// OffscreenFreeze: logo is the first thing to enter scrollback; useMainLoopModel()
|
// OffscreenFreeze: logo is the first thing to enter scrollback; useMainLoopModel()
|
||||||
// subscribes to model changes and getLogoDisplayData() reads cwd/subscription —
|
// subscribes to model changes and getLogoDisplayData() reads cwd/subscription —
|
||||||
// any change while in scrollback forces a full reset.
|
// any change while in scrollback forces a full reset.
|
||||||
|
|
@ -282,7 +229,7 @@ export function LogoV2(): React.ReactNode {
|
||||||
<Box
|
<Box
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
borderStyle="round"
|
borderStyle="round"
|
||||||
borderColor="claude"
|
borderColor="claudeBlue_FOR_SYSTEM_SPINNER"
|
||||||
borderText={{
|
borderText={{
|
||||||
content: compactBorderTitle,
|
content: compactBorderTitle,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
|
|
@ -300,9 +247,7 @@ export function LogoV2(): React.ReactNode {
|
||||||
</Box>
|
</Box>
|
||||||
<Text dimColor>{modelDisplayName}</Text>
|
<Text dimColor>{modelDisplayName}</Text>
|
||||||
<Text dimColor>{billingType}</Text>
|
<Text dimColor>{billingType}</Text>
|
||||||
<Text dimColor>
|
<Text dimColor>{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}</Text>
|
||||||
{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
</OffscreenFreeze>
|
</OffscreenFreeze>
|
||||||
<VoiceModeNotice />
|
<VoiceModeNotice />
|
||||||
|
|
@ -310,45 +255,32 @@ export function LogoV2(): React.ReactNode {
|
||||||
{ChannelsNoticeModule && <ChannelsNoticeModule.ChannelsNotice />}
|
{ChannelsNoticeModule && <ChannelsNoticeModule.ChannelsNotice />}
|
||||||
{showSandboxStatus && (
|
{showSandboxStatus && (
|
||||||
<Box marginTop={1} flexDirection="column">
|
<Box marginTop={1} flexDirection="column">
|
||||||
<Text color="warning">
|
<Text color="warning">Your bash commands will be sandboxed. Disable with /sandbox.</Text>
|
||||||
Your bash commands will be sandboxed. Disable with /sandbox.
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{process.env.USER_TYPE === 'ant' && <GateOverridesWarning />}
|
{process.env.USER_TYPE === 'ant' && <GateOverridesWarning />}
|
||||||
{process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />}
|
{process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const welcomeMessage = formatWelcomeMessage(username)
|
const welcomeMessage = formatWelcomeMessage(username);
|
||||||
const modelLine =
|
const modelLine =
|
||||||
!process.env.IS_DEMO && config.oauthAccount?.organizationName
|
!process.env.IS_DEMO && config.oauthAccount?.organizationName
|
||||||
? `${modelDisplayName} · ${billingType} · ${config.oauthAccount.organizationName}`
|
? `${modelDisplayName} · ${billingType} · ${config.oauthAccount.organizationName}`
|
||||||
: `${modelDisplayName} · ${billingType}`
|
: `${modelDisplayName} · ${billingType}`;
|
||||||
// Calculate cwd width accounting for agent name if present
|
// Calculate cwd width accounting for agent name if present
|
||||||
const cwdSeparator = ' · '
|
const cwdSeparator = ' · ';
|
||||||
const cwdAtPrefix = '@'
|
const cwdAtPrefix = '@';
|
||||||
const cwdAvailableWidth = agentName
|
const cwdAvailableWidth = agentName
|
||||||
? LEFT_PANEL_MAX_WIDTH -
|
? LEFT_PANEL_MAX_WIDTH - cwdAtPrefix.length - stringWidth(agentName) - cwdSeparator.length
|
||||||
cwdAtPrefix.length -
|
: LEFT_PANEL_MAX_WIDTH;
|
||||||
stringWidth(agentName) -
|
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10));
|
||||||
cwdSeparator.length
|
const cwdLine = agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd;
|
||||||
: LEFT_PANEL_MAX_WIDTH
|
const optimalLeftWidth = calculateOptimalLeftWidth(welcomeMessage, cwdLine, modelLine);
|
||||||
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10))
|
|
||||||
const cwdLine = agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd
|
|
||||||
const optimalLeftWidth = calculateOptimalLeftWidth(
|
|
||||||
welcomeMessage,
|
|
||||||
cwdLine,
|
|
||||||
modelLine,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Calculate layout dimensions
|
// Calculate layout dimensions
|
||||||
const { leftWidth, rightWidth } = calculateLayoutDimensions(
|
const { leftWidth, rightWidth } = calculateLayoutDimensions(columns, layoutMode, optimalLeftWidth);
|
||||||
columns,
|
|
||||||
layoutMode,
|
|
||||||
optimalLeftWidth,
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -356,7 +288,7 @@ export function LogoV2(): React.ReactNode {
|
||||||
<Box
|
<Box
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
borderStyle="round"
|
borderStyle="round"
|
||||||
borderColor="claude"
|
borderColor="claudeBlue_FOR_SYSTEM_SPINNER"
|
||||||
borderText={{
|
borderText={{
|
||||||
content: borderTitle,
|
content: borderTitle,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
|
|
@ -365,11 +297,7 @@ export function LogoV2(): React.ReactNode {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Main content */}
|
{/* Main content */}
|
||||||
<Box
|
<Box flexDirection={layoutMode === 'horizontal' ? 'row' : 'column'} paddingX={1} gap={1}>
|
||||||
flexDirection={layoutMode === 'horizontal' ? 'row' : 'column'}
|
|
||||||
paddingX={1}
|
|
||||||
gap={1}
|
|
||||||
>
|
|
||||||
{/* Left Panel */}
|
{/* Left Panel */}
|
||||||
<Box
|
<Box
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
|
|
@ -395,7 +323,7 @@ export function LogoV2(): React.ReactNode {
|
||||||
<Box
|
<Box
|
||||||
height="100%"
|
height="100%"
|
||||||
borderStyle="single"
|
borderStyle="single"
|
||||||
borderColor="claude"
|
borderColor="claudeBlue_FOR_SYSTEM_SPINNER"
|
||||||
borderDimColor
|
borderDimColor
|
||||||
borderTop={false}
|
borderTop={false}
|
||||||
borderBottom={false}
|
borderBottom={false}
|
||||||
|
|
@ -408,24 +336,12 @@ export function LogoV2(): React.ReactNode {
|
||||||
<FeedColumn
|
<FeedColumn
|
||||||
feeds={
|
feeds={
|
||||||
showOnboarding
|
showOnboarding
|
||||||
? [
|
? [createProjectOnboardingFeed(getSteps()), createRecentActivityFeed(activities)]
|
||||||
createProjectOnboardingFeed(getSteps()),
|
|
||||||
createRecentActivityFeed(activities),
|
|
||||||
]
|
|
||||||
: showGuestPassesUpsell
|
: showGuestPassesUpsell
|
||||||
? [
|
? [createRecentActivityFeed(activities), createGuestPassesFeed()]
|
||||||
createRecentActivityFeed(activities),
|
|
||||||
createGuestPassesFeed(),
|
|
||||||
]
|
|
||||||
: showOverageCreditUpsell
|
: showOverageCreditUpsell
|
||||||
? [
|
? [createRecentActivityFeed(activities), createOverageCreditFeed()]
|
||||||
createRecentActivityFeed(activities),
|
: [createRecentActivityFeed(activities), createWhatsNewFeed(changelog)]
|
||||||
createOverageCreditFeed(),
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
createRecentActivityFeed(activities),
|
|
||||||
createWhatsNewFeed(changelog),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
maxWidth={rightWidth}
|
maxWidth={rightWidth}
|
||||||
/>
|
/>
|
||||||
|
|
@ -439,17 +355,13 @@ export function LogoV2(): React.ReactNode {
|
||||||
{isDebugMode() && (
|
{isDebugMode() && (
|
||||||
<Box paddingLeft={2} flexDirection="column">
|
<Box paddingLeft={2} flexDirection="column">
|
||||||
<Text color="warning">Debug mode enabled</Text>
|
<Text color="warning">Debug mode enabled</Text>
|
||||||
<Text dimColor>
|
<Text dimColor>Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()}</Text>
|
||||||
Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<EmergencyTip />
|
<EmergencyTip />
|
||||||
{process.env.CLAUDE_CODE_TMUX_SESSION && (
|
{process.env.CLAUDE_CODE_TMUX_SESSION && (
|
||||||
<Box paddingLeft={2} flexDirection="column">
|
<Box paddingLeft={2} flexDirection="column">
|
||||||
<Text dimColor>
|
<Text dimColor>tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION}</Text>
|
||||||
tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION}
|
|
||||||
</Text>
|
|
||||||
<Text dimColor>
|
<Text dimColor>
|
||||||
{process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS
|
{process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS
|
||||||
? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})`
|
? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})`
|
||||||
|
|
@ -460,18 +372,14 @@ export function LogoV2(): React.ReactNode {
|
||||||
{announcement && (
|
{announcement && (
|
||||||
<Box paddingLeft={2} flexDirection="column">
|
<Box paddingLeft={2} flexDirection="column">
|
||||||
{!process.env.IS_DEMO && config.oauthAccount?.organizationName && (
|
{!process.env.IS_DEMO && config.oauthAccount?.organizationName && (
|
||||||
<Text dimColor>
|
<Text dimColor>Message from {config.oauthAccount.organizationName}:</Text>
|
||||||
Message from {config.oauthAccount.organizationName}:
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
<Text>{announcement}</Text>
|
<Text>{announcement}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{showSandboxStatus && (
|
{showSandboxStatus && (
|
||||||
<Box paddingLeft={2} flexDirection="column">
|
<Box paddingLeft={2} flexDirection="column">
|
||||||
<Text color="warning">
|
<Text color="warning">Your bash commands will be sandboxed. Disable with /sandbox.</Text>
|
||||||
Your bash commands will be sandboxed. Disable with /sandbox.
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && (
|
{process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && (
|
||||||
|
|
@ -482,20 +390,15 @@ export function LogoV2(): React.ReactNode {
|
||||||
{process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && (
|
{process.env.USER_TYPE === 'ant' && !process.env.DEMO_VERSION && (
|
||||||
<Box paddingLeft={2} flexDirection="column">
|
<Box paddingLeft={2} flexDirection="column">
|
||||||
<Text color="warning">[ANT-ONLY] Logs:</Text>
|
<Text color="warning">[ANT-ONLY] Logs:</Text>
|
||||||
<Text dimColor>
|
<Text dimColor>API calls: {getDisplayPath(getDumpPromptsPath())}</Text>
|
||||||
API calls: {getDisplayPath(getDumpPromptsPath())}
|
|
||||||
</Text>
|
|
||||||
<Text dimColor>Debug logs: {getDisplayPath(getDebugLogPath())}</Text>
|
<Text dimColor>Debug logs: {getDisplayPath(getDebugLogPath())}</Text>
|
||||||
{isDetailedProfilingEnabled() && (
|
{isDetailedProfilingEnabled() && (
|
||||||
<Text dimColor>
|
<Text dimColor>Startup Perf: {getDisplayPath(getStartupPerfLogPath())}</Text>
|
||||||
Startup Perf: {getDisplayPath(getStartupPerfLogPath())}
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{process.env.USER_TYPE === 'ant' && <GateOverridesWarning />}
|
{process.env.USER_TYPE === 'ant' && <GateOverridesWarning />}
|
||||||
{process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />}
|
{process.env.USER_TYPE === 'ant' && <ExperimentEnrollmentNotice />}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { Box, Text, useTheme } from '@anthropic/ink'
|
import { Box, Text, useTheme } from '@anthropic/ink';
|
||||||
import { env } from '../../utils/env.js'
|
import { env } from '../../utils/env.js';
|
||||||
|
|
||||||
const WELCOME_V2_WIDTH = 58
|
const WELCOME_V2_WIDTH = 58;
|
||||||
|
|
||||||
export function WelcomeV2(): React.ReactNode {
|
export function WelcomeV2(): React.ReactNode {
|
||||||
const [theme] = useTheme()
|
const [theme] = useTheme();
|
||||||
const welcomeMessage = 'Welcome to Claude Code'
|
const welcomeMessage = 'Welcome to CoStrict';
|
||||||
|
|
||||||
if (env.terminal === 'Apple_Terminal') {
|
if (env.terminal === 'Apple_Terminal') {
|
||||||
return (
|
return <AppleTerminalWelcomeV2 theme={theme} welcomeMessage={welcomeMessage} />;
|
||||||
<AppleTerminalWelcomeV2 theme={theme} welcomeMessage={welcomeMessage} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['light', 'light-daltonized', 'light-ansi'].includes(theme)) {
|
if (['light', 'light-daltonized', 'light-ansi'].includes(theme)) {
|
||||||
|
|
@ -22,30 +20,14 @@ export function WelcomeV2(): React.ReactNode {
|
||||||
<Text color="claude">{welcomeMessage} </Text>
|
<Text color="claude">{welcomeMessage} </Text>
|
||||||
<Text dimColor>v{MACRO.VERSION} </Text>
|
<Text dimColor>v{MACRO.VERSION} </Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>{'…………………………………………………………………………………………………………………………………………………………'}</Text>
|
||||||
{'…………………………………………………………………………………………………………………………………………………………'}
|
<Text>{' '}</Text>
|
||||||
</Text>
|
<Text>{' '}</Text>
|
||||||
<Text>
|
<Text>{' '}</Text>
|
||||||
{' '}
|
<Text>{' ░░░░░░ '}</Text>
|
||||||
</Text>
|
<Text>{' ░░░ ░░░░░░░░░░ '}</Text>
|
||||||
<Text>
|
<Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
|
||||||
{' '}
|
<Text>{' '}</Text>
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░ ░░░░░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░░░░░░░░░░░░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
<Text dimColor>{' ░░░░'}</Text>
|
<Text dimColor>{' ░░░░'}</Text>
|
||||||
<Text>{' ██ '}</Text>
|
<Text>{' ██ '}</Text>
|
||||||
|
|
@ -54,9 +36,7 @@ export function WelcomeV2(): React.ReactNode {
|
||||||
<Text dimColor>{' ░░░░░░░░░░'}</Text>
|
<Text dimColor>{' ░░░░░░░░░░'}</Text>
|
||||||
<Text>{' ██▒▒██ '}</Text>
|
<Text>{' ██▒▒██ '}</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>{' ▒▒ ██ ▒'}</Text>
|
||||||
{' ▒▒ ██ ▒'}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
{' '}
|
{' '}
|
||||||
<Text color="clawd_body"> █████████ </Text>
|
<Text color="clawd_body"> █████████ </Text>
|
||||||
|
|
@ -81,7 +61,7 @@ export function WelcomeV2(): React.ReactNode {
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -91,41 +71,21 @@ export function WelcomeV2(): React.ReactNode {
|
||||||
<Text color="claude">{welcomeMessage} </Text>
|
<Text color="claude">{welcomeMessage} </Text>
|
||||||
<Text dimColor>v{MACRO.VERSION} </Text>
|
<Text dimColor>v{MACRO.VERSION} </Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>{'…………………………………………………………………………………………………………………………………………………………'}</Text>
|
||||||
{'…………………………………………………………………………………………………………………………………………………………'}
|
<Text>{' '}</Text>
|
||||||
</Text>
|
<Text>{' * █████▓▓░ '}</Text>
|
||||||
<Text>
|
<Text>{' * ███▓░ ░░ '}</Text>
|
||||||
{' '}
|
<Text>{' ░░░░░░ ███▓░ '}</Text>
|
||||||
</Text>
|
<Text>{' ░░░ ░░░░░░░░░░ ███▓░ '}</Text>
|
||||||
<Text>
|
|
||||||
{' * █████▓▓░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' * ███▓░ ░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░░░░ ███▓░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░ ░░░░░░░░░░ ███▓░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
<Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
|
<Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
|
||||||
<Text bold>*</Text>
|
<Text bold>*</Text>
|
||||||
<Text>{' ██▓░░ ▓ '}</Text>
|
<Text>{' ██▓░░ ▓ '}</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>{' ░▓▓███▓▓░ '}</Text>
|
||||||
{' ░▓▓███▓▓░ '}
|
<Text dimColor>{' * ░░░░ '}</Text>
|
||||||
</Text>
|
<Text dimColor>{' ░░░░░░░░ '}</Text>
|
||||||
<Text dimColor>
|
<Text dimColor>{' ░░░░░░░░░░░░░░░░ '}</Text>
|
||||||
{' * ░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text dimColor>
|
|
||||||
{' ░░░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text dimColor>
|
|
||||||
{' ░░░░░░░░░░░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
{' '}
|
{' '}
|
||||||
<Text color="clawd_body"> █████████ </Text>
|
<Text color="clawd_body"> █████████ </Text>
|
||||||
|
|
@ -152,21 +112,16 @@ export function WelcomeV2(): React.ReactNode {
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppleTerminalWelcomeV2Props = {
|
type AppleTerminalWelcomeV2Props = {
|
||||||
theme: string
|
theme: string;
|
||||||
welcomeMessage: string
|
welcomeMessage: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
function AppleTerminalWelcomeV2({
|
function AppleTerminalWelcomeV2({ theme, welcomeMessage }: AppleTerminalWelcomeV2Props): React.ReactNode {
|
||||||
theme,
|
const isLightTheme = ['light', 'light-daltonized', 'light-ansi'].includes(theme);
|
||||||
welcomeMessage,
|
|
||||||
}: AppleTerminalWelcomeV2Props): React.ReactNode {
|
|
||||||
const isLightTheme = ['light', 'light-daltonized', 'light-ansi'].includes(
|
|
||||||
theme,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isLightTheme) {
|
if (isLightTheme) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -176,30 +131,14 @@ function AppleTerminalWelcomeV2({
|
||||||
<Text color="claude">{welcomeMessage} </Text>
|
<Text color="claude">{welcomeMessage} </Text>
|
||||||
<Text dimColor>v{MACRO.VERSION} </Text>
|
<Text dimColor>v{MACRO.VERSION} </Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>{'…………………………………………………………………………………………………………………………………………………………'}</Text>
|
||||||
{'…………………………………………………………………………………………………………………………………………………………'}
|
<Text>{' '}</Text>
|
||||||
</Text>
|
<Text>{' '}</Text>
|
||||||
<Text>
|
<Text>{' '}</Text>
|
||||||
{' '}
|
<Text>{' ░░░░░░ '}</Text>
|
||||||
</Text>
|
<Text>{' ░░░ ░░░░░░░░░░ '}</Text>
|
||||||
<Text>
|
<Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
|
||||||
{' '}
|
<Text>{' '}</Text>
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░ ░░░░░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░░░░░░░░░░░░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
<Text dimColor>{' ░░░░'}</Text>
|
<Text dimColor>{' ░░░░'}</Text>
|
||||||
<Text>{' ██ '}</Text>
|
<Text>{' ██ '}</Text>
|
||||||
|
|
@ -208,12 +147,8 @@ function AppleTerminalWelcomeV2({
|
||||||
<Text dimColor>{' ░░░░░░░░░░'}</Text>
|
<Text dimColor>{' ░░░░░░░░░░'}</Text>
|
||||||
<Text>{' ██▒▒██ '}</Text>
|
<Text>{' ██▒▒██ '}</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>{' ▒▒ ██ ▒'}</Text>
|
||||||
{' ▒▒ ██ ▒'}
|
<Text>{' ▒▒░░▒▒ ▒ ▒▒'}</Text>
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ▒▒░░▒▒ ▒ ▒▒'}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
{' '}
|
{' '}
|
||||||
<Text color="clawd_body">▗</Text>
|
<Text color="clawd_body">▗</Text>
|
||||||
|
|
@ -242,7 +177,7 @@ function AppleTerminalWelcomeV2({
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -252,41 +187,21 @@ function AppleTerminalWelcomeV2({
|
||||||
<Text color="claude">{welcomeMessage} </Text>
|
<Text color="claude">{welcomeMessage} </Text>
|
||||||
<Text dimColor>v{MACRO.VERSION} </Text>
|
<Text dimColor>v{MACRO.VERSION} </Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>{'…………………………………………………………………………………………………………………………………………………………'}</Text>
|
||||||
{'…………………………………………………………………………………………………………………………………………………………'}
|
<Text>{' '}</Text>
|
||||||
</Text>
|
<Text>{' * █████▓▓░ '}</Text>
|
||||||
<Text>
|
<Text>{' * ███▓░ ░░ '}</Text>
|
||||||
{' '}
|
<Text>{' ░░░░░░ ███▓░ '}</Text>
|
||||||
</Text>
|
<Text>{' ░░░ ░░░░░░░░░░ ███▓░ '}</Text>
|
||||||
<Text>
|
|
||||||
{' * █████▓▓░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' * ███▓░ ░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░░░░ ███▓░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{' ░░░ ░░░░░░░░░░ ███▓░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
<Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
|
<Text>{' ░░░░░░░░░░░░░░░░░░░ '}</Text>
|
||||||
<Text bold>*</Text>
|
<Text bold>*</Text>
|
||||||
<Text>{' ██▓░░ ▓ '}</Text>
|
<Text>{' ██▓░░ ▓ '}</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>{' ░▓▓███▓▓░ '}</Text>
|
||||||
{' ░▓▓███▓▓░ '}
|
<Text dimColor>{' * ░░░░ '}</Text>
|
||||||
</Text>
|
<Text dimColor>{' ░░░░░░░░ '}</Text>
|
||||||
<Text dimColor>
|
<Text dimColor>{' ░░░░░░░░░░░░░░░░ '}</Text>
|
||||||
{' * ░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text dimColor>
|
|
||||||
{' ░░░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text dimColor>
|
|
||||||
{' ░░░░░░░░░░░░░░░░ '}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
{' '}
|
{' '}
|
||||||
<Text dimColor>*</Text>
|
<Text dimColor>*</Text>
|
||||||
|
|
@ -322,5 +237,5 @@ function AppleTerminalWelcomeV2({
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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