mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,49 +1,57 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { TEARDROP_ASTERISK } from '../../constants/figures.js';
|
||||
import { Box, Text, useAnimationFrame } from '../../ink.js';
|
||||
import { getInitialSettings } from '../../utils/settings/settings.js';
|
||||
import { hueToRgb, toRGBColor } from '../Spinner/utils.js';
|
||||
const SWEEP_DURATION_MS = 1500;
|
||||
const SWEEP_COUNT = 2;
|
||||
const TOTAL_ANIMATION_MS = SWEEP_DURATION_MS * SWEEP_COUNT;
|
||||
const SETTLED_GREY = toRGBColor({
|
||||
r: 153,
|
||||
g: 153,
|
||||
b: 153
|
||||
});
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { TEARDROP_ASTERISK } from '../../constants/figures.js'
|
||||
import { Box, Text, useAnimationFrame } from '../../ink.js'
|
||||
import { getInitialSettings } from '../../utils/settings/settings.js'
|
||||
import { hueToRgb, toRGBColor } from '../Spinner/utils.js'
|
||||
|
||||
const SWEEP_DURATION_MS = 1500
|
||||
const SWEEP_COUNT = 2
|
||||
const TOTAL_ANIMATION_MS = SWEEP_DURATION_MS * SWEEP_COUNT
|
||||
const SETTLED_GREY = toRGBColor({ r: 153, g: 153, b: 153 })
|
||||
|
||||
export function AnimatedAsterisk({
|
||||
char = TEARDROP_ASTERISK
|
||||
char = TEARDROP_ASTERISK,
|
||||
}: {
|
||||
char?: string;
|
||||
char?: string
|
||||
}): React.ReactNode {
|
||||
// Read prefersReducedMotion once at mount — no useSettings() subscription,
|
||||
// since that would re-render whenever settings change.
|
||||
const [reducedMotion] = useState(() => getInitialSettings().prefersReducedMotion ?? false);
|
||||
const [done, setDone] = useState(reducedMotion);
|
||||
const [reducedMotion] = useState(
|
||||
() => getInitialSettings().prefersReducedMotion ?? false,
|
||||
)
|
||||
const [done, setDone] = useState(reducedMotion)
|
||||
// useAnimationFrame's clock is shared — capture our start offset so the
|
||||
// sweep always begins at hue 0 regardless of when we mount.
|
||||
const startTimeRef = useRef<number | null>(null);
|
||||
const startTimeRef = useRef<number | null>(null)
|
||||
// Wire the ref so useAnimationFrame's viewport-pause kicks in: if the
|
||||
// user submits a message before the sweep finishes, the clock stops
|
||||
// automatically once this row enters scrollback (prevents flicker).
|
||||
const [ref, time] = useAnimationFrame(done ? null : 50);
|
||||
const [ref, time] = useAnimationFrame(done ? null : 50)
|
||||
|
||||
useEffect(() => {
|
||||
if (done) return;
|
||||
const t = setTimeout(setDone, TOTAL_ANIMATION_MS, true);
|
||||
return () => clearTimeout(t);
|
||||
}, [done]);
|
||||
if (done) return
|
||||
const t = setTimeout(setDone, TOTAL_ANIMATION_MS, true)
|
||||
return () => clearTimeout(t)
|
||||
}, [done])
|
||||
|
||||
if (done) {
|
||||
return <Box ref={ref}>
|
||||
return (
|
||||
<Box ref={ref}>
|
||||
<Text color={SETTLED_GREY}>{char}</Text>
|
||||
</Box>;
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
if (startTimeRef.current === null) {
|
||||
startTimeRef.current = time;
|
||||
startTimeRef.current = time
|
||||
}
|
||||
const elapsed = time - startTimeRef.current;
|
||||
const hue = elapsed / SWEEP_DURATION_MS * 360 % 360;
|
||||
return <Box ref={ref}>
|
||||
const elapsed = time - startTimeRef.current
|
||||
const hue = ((elapsed / SWEEP_DURATION_MS) * 360) % 360
|
||||
|
||||
return (
|
||||
<Box ref={ref}>
|
||||
<Text color={toRGBColor(hueToRgb(hue))}>{char}</Text>
|
||||
</Box>;
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Box } from '../../ink.js';
|
||||
import { getInitialSettings } from '../../utils/settings/settings.js';
|
||||
import { Clawd, type ClawdPose } from './Clawd.js';
|
||||
type Frame = {
|
||||
pose: ClawdPose;
|
||||
offset: number;
|
||||
};
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Box } from '../../ink.js'
|
||||
import { getInitialSettings } from '../../utils/settings/settings.js'
|
||||
import { Clawd, type ClawdPose } from './Clawd.js'
|
||||
|
||||
type Frame = { pose: ClawdPose; offset: number }
|
||||
|
||||
/** Hold a pose for n frames (60ms each). */
|
||||
function hold(pose: ClawdPose, offset: number, frames: number): Frame[] {
|
||||
return Array.from({
|
||||
length: frames
|
||||
}, () => ({
|
||||
pose,
|
||||
offset
|
||||
}));
|
||||
return Array.from({ length: frames }, () => ({ pose, offset }))
|
||||
}
|
||||
|
||||
// Offset semantics: marginTop in a fixed-height-3 container. 0 = normal,
|
||||
@@ -25,26 +17,28 @@ function hold(pose: ClawdPose, offset: number, frames: number): Frame[] {
|
||||
// clipped — reads as "ducking below the frame" before springing back up.
|
||||
|
||||
// Click animation: crouch, then spring up with both arms raised. Twice.
|
||||
const JUMP_WAVE: readonly Frame[] = [...hold('default', 1, 2),
|
||||
// crouch
|
||||
...hold('arms-up', 0, 3),
|
||||
// spring!
|
||||
...hold('default', 0, 1), ...hold('default', 1, 2),
|
||||
// crouch again
|
||||
...hold('arms-up', 0, 3),
|
||||
// spring!
|
||||
...hold('default', 0, 1)];
|
||||
const JUMP_WAVE: readonly Frame[] = [
|
||||
...hold('default', 1, 2), // crouch
|
||||
...hold('arms-up', 0, 3), // spring!
|
||||
...hold('default', 0, 1),
|
||||
...hold('default', 1, 2), // crouch again
|
||||
...hold('arms-up', 0, 3), // spring!
|
||||
...hold('default', 0, 1),
|
||||
]
|
||||
|
||||
// Click animation: glance right, then left, then back.
|
||||
const LOOK_AROUND: readonly Frame[] = [...hold('look-right', 0, 5), ...hold('look-left', 0, 5), ...hold('default', 0, 1)];
|
||||
const CLICK_ANIMATIONS: readonly (readonly Frame[])[] = [JUMP_WAVE, LOOK_AROUND];
|
||||
const IDLE: Frame = {
|
||||
pose: 'default',
|
||||
offset: 0
|
||||
};
|
||||
const FRAME_MS = 60;
|
||||
const incrementFrame = (i: number) => i + 1;
|
||||
const CLAWD_HEIGHT = 3;
|
||||
const LOOK_AROUND: readonly Frame[] = [
|
||||
...hold('look-right', 0, 5),
|
||||
...hold('look-left', 0, 5),
|
||||
...hold('default', 0, 1),
|
||||
]
|
||||
|
||||
const CLICK_ANIMATIONS: readonly (readonly Frame[])[] = [JUMP_WAVE, LOOK_AROUND]
|
||||
|
||||
const IDLE: Frame = { pose: 'default', offset: 0 }
|
||||
const FRAME_MS = 60
|
||||
const incrementFrame = (i: number) => i + 1
|
||||
const CLAWD_HEIGHT = 3
|
||||
|
||||
/**
|
||||
* Clawd with click-triggered animations (crouch-jump with arms up, or
|
||||
@@ -54,70 +48,49 @@ const CLAWD_HEIGHT = 3;
|
||||
* mouse tracking is enabled (i.e. inside `<AlternateScreen>` / fullscreen);
|
||||
* elsewhere this renders and behaves identically to plain `<Clawd />`.
|
||||
*/
|
||||
export function AnimatedClawd() {
|
||||
const $ = _c(8);
|
||||
const {
|
||||
pose,
|
||||
bounceOffset,
|
||||
onClick
|
||||
} = useClawdAnimation();
|
||||
let t0;
|
||||
if ($[0] !== pose) {
|
||||
t0 = <Clawd pose={pose} />;
|
||||
$[0] = pose;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] !== bounceOffset || $[3] !== t0) {
|
||||
t1 = <Box marginTop={bounceOffset} flexShrink={0}>{t0}</Box>;
|
||||
$[2] = bounceOffset;
|
||||
$[3] = t0;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
let t2;
|
||||
if ($[5] !== onClick || $[6] !== t1) {
|
||||
t2 = <Box height={CLAWD_HEIGHT} flexDirection="column" onClick={onClick}>{t1}</Box>;
|
||||
$[5] = onClick;
|
||||
$[6] = t1;
|
||||
$[7] = t2;
|
||||
} else {
|
||||
t2 = $[7];
|
||||
}
|
||||
return t2;
|
||||
export function AnimatedClawd(): React.ReactNode {
|
||||
const { pose, bounceOffset, onClick } = useClawdAnimation()
|
||||
return (
|
||||
<Box height={CLAWD_HEIGHT} flexDirection="column" onClick={onClick}>
|
||||
<Box marginTop={bounceOffset} flexShrink={0}>
|
||||
<Clawd pose={pose} />
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function useClawdAnimation(): {
|
||||
pose: ClawdPose;
|
||||
bounceOffset: number;
|
||||
onClick: () => void;
|
||||
pose: ClawdPose
|
||||
bounceOffset: number
|
||||
onClick: () => void
|
||||
} {
|
||||
// Read once at mount — no useSettings() subscription, since that would
|
||||
// re-render on any settings change.
|
||||
const [reducedMotion] = useState(() => getInitialSettings().prefersReducedMotion ?? false);
|
||||
const [frameIndex, setFrameIndex] = useState(-1);
|
||||
const sequenceRef = useRef<readonly Frame[]>(JUMP_WAVE);
|
||||
const [reducedMotion] = useState(
|
||||
() => getInitialSettings().prefersReducedMotion ?? false,
|
||||
)
|
||||
const [frameIndex, setFrameIndex] = useState(-1)
|
||||
const sequenceRef = useRef<readonly Frame[]>(JUMP_WAVE)
|
||||
|
||||
const onClick = () => {
|
||||
if (reducedMotion || frameIndex !== -1) return;
|
||||
sequenceRef.current = CLICK_ANIMATIONS[Math.floor(Math.random() * CLICK_ANIMATIONS.length)]!;
|
||||
setFrameIndex(0);
|
||||
};
|
||||
if (reducedMotion || frameIndex !== -1) return
|
||||
sequenceRef.current =
|
||||
CLICK_ANIMATIONS[Math.floor(Math.random() * CLICK_ANIMATIONS.length)]!
|
||||
setFrameIndex(0)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (frameIndex === -1) return;
|
||||
if (frameIndex === -1) return
|
||||
if (frameIndex >= sequenceRef.current.length) {
|
||||
setFrameIndex(-1);
|
||||
return;
|
||||
setFrameIndex(-1)
|
||||
return
|
||||
}
|
||||
const timer = setTimeout(setFrameIndex, FRAME_MS, incrementFrame);
|
||||
return () => clearTimeout(timer);
|
||||
}, [frameIndex]);
|
||||
const seq = sequenceRef.current;
|
||||
const current = frameIndex >= 0 && frameIndex < seq.length ? seq[frameIndex]! : IDLE;
|
||||
return {
|
||||
pose: current.pose,
|
||||
bounceOffset: current.offset,
|
||||
onClick
|
||||
};
|
||||
const timer = setTimeout(setFrameIndex, FRAME_MS, incrementFrame)
|
||||
return () => clearTimeout(timer)
|
||||
}, [frameIndex])
|
||||
|
||||
const seq = sequenceRef.current
|
||||
const current =
|
||||
frameIndex >= 0 && frameIndex < seq.length ? seq[frameIndex]! : IDLE
|
||||
return { pose: current.pose, bounceOffset: current.offset, onClick }
|
||||
}
|
||||
|
||||
@@ -1,265 +1,208 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
// Conditionally require()'d in LogoV2.tsx behind feature('KAIROS') ||
|
||||
// feature('KAIROS_CHANNELS'). No feature() guard here — the whole file
|
||||
// tree-shakes via the require pattern when both flags are false (see
|
||||
// docs/feature-gating.md). Do NOT import this module statically from
|
||||
// unguarded code.
|
||||
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { type ChannelEntry, getAllowedChannels, getHasDevChannels } from '../../bootstrap/state.js';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
import { isChannelsEnabled } from '../../services/mcp/channelAllowlist.js';
|
||||
import { getEffectiveChannelAllowlist } from '../../services/mcp/channelNotification.js';
|
||||
import { getMcpConfigsByScope } from '../../services/mcp/config.js';
|
||||
import { getClaudeAIOAuthTokens, getSubscriptionType } from '../../utils/auth.js';
|
||||
import { loadInstalledPluginsV2 } from '../../utils/plugins/installedPluginsManager.js';
|
||||
import { getSettingsForSource } from '../../utils/settings/settings.js';
|
||||
export function ChannelsNotice() {
|
||||
const $ = _c(32);
|
||||
const [t0] = useState(_temp);
|
||||
const {
|
||||
channels,
|
||||
disabled,
|
||||
noAuth,
|
||||
policyBlocked,
|
||||
list,
|
||||
unmatched
|
||||
} = t0;
|
||||
if (channels.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const hasNonDev = channels.some(_temp2);
|
||||
const flag = getHasDevChannels() && hasNonDev ? "Channels" : getHasDevChannels() ? "--dangerously-load-development-channels" : "--channels";
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
type ChannelEntry,
|
||||
getAllowedChannels,
|
||||
getHasDevChannels,
|
||||
} from '../../bootstrap/state.js'
|
||||
import { Box, Text } from '../../ink.js'
|
||||
import { isChannelsEnabled } from '../../services/mcp/channelAllowlist.js'
|
||||
import { getEffectiveChannelAllowlist } from '../../services/mcp/channelNotification.js'
|
||||
import { getMcpConfigsByScope } from '../../services/mcp/config.js'
|
||||
import {
|
||||
getClaudeAIOAuthTokens,
|
||||
getSubscriptionType,
|
||||
} from '../../utils/auth.js'
|
||||
import { loadInstalledPluginsV2 } from '../../utils/plugins/installedPluginsManager.js'
|
||||
import { getSettingsForSource } from '../../utils/settings/settings.js'
|
||||
|
||||
export function ChannelsNotice(): React.ReactNode {
|
||||
// Snapshot all reads at mount. This notice enters scrollback immediately
|
||||
// after the logo; any re-render past that point forces a full terminal
|
||||
// reset. getAllowedChannels (bootstrap state), getSettingsForSource
|
||||
// (session cache updated by background polling / /login), and
|
||||
// isChannelsEnabled (GrowthBook 5-min refresh) must be captured once
|
||||
// so a later re-render cannot flip branches.
|
||||
const [{ channels, disabled, noAuth, policyBlocked, list, unmatched }] =
|
||||
useState(() => {
|
||||
const ch = getAllowedChannels()
|
||||
if (ch.length === 0)
|
||||
return {
|
||||
channels: ch,
|
||||
disabled: false,
|
||||
noAuth: false,
|
||||
policyBlocked: false,
|
||||
list: '',
|
||||
unmatched: [] as Unmatched[],
|
||||
}
|
||||
const l = ch.map(formatEntry).join(', ')
|
||||
const sub = getSubscriptionType()
|
||||
const managed = sub === 'team' || sub === 'enterprise'
|
||||
const policy = getSettingsForSource('policySettings')
|
||||
const allowlist = getEffectiveChannelAllowlist(
|
||||
sub,
|
||||
policy?.allowedChannelPlugins,
|
||||
)
|
||||
return {
|
||||
channels: ch,
|
||||
disabled: !isChannelsEnabled(),
|
||||
noAuth: !getClaudeAIOAuthTokens()?.accessToken,
|
||||
policyBlocked: managed && policy?.channelsEnabled !== true,
|
||||
list: l,
|
||||
unmatched: findUnmatched(ch, allowlist),
|
||||
}
|
||||
})
|
||||
if (channels.length === 0) return null
|
||||
|
||||
// When both flags are passed, the list mixes entries and a single flag
|
||||
// name would be wrong for half of it. entry.dev distinguishes origin.
|
||||
const hasNonDev = channels.some(c => !c.dev)
|
||||
const flag =
|
||||
getHasDevChannels() && hasNonDev
|
||||
? 'Channels'
|
||||
: getHasDevChannels()
|
||||
? '--dangerously-load-development-channels'
|
||||
: '--channels'
|
||||
|
||||
if (disabled) {
|
||||
let t1;
|
||||
if ($[0] !== flag || $[1] !== list) {
|
||||
t1 = <Text color="error">{flag} ignored ({list})</Text>;
|
||||
$[0] = flag;
|
||||
$[1] = list;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
let t2;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Text dimColor={true}>Channels are not currently available</Text>;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== t1) {
|
||||
t3 = <Box paddingLeft={2} flexDirection="column">{t1}{t2}</Box>;
|
||||
$[4] = t1;
|
||||
$[5] = t3;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
}
|
||||
return t3;
|
||||
return (
|
||||
<Box paddingLeft={2} flexDirection="column">
|
||||
<Text color="error">
|
||||
{flag} ignored ({list})
|
||||
</Text>
|
||||
<Text dimColor>Channels are not currently available</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
if (noAuth) {
|
||||
let t1;
|
||||
if ($[6] !== flag || $[7] !== list) {
|
||||
t1 = <Text color="error">{flag} ignored ({list})</Text>;
|
||||
$[6] = flag;
|
||||
$[7] = list;
|
||||
$[8] = t1;
|
||||
} else {
|
||||
t1 = $[8];
|
||||
}
|
||||
let t2;
|
||||
if ($[9] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Text dimColor={true}>Channels require claude.ai authentication · run /login, then restart</Text>;
|
||||
$[9] = t2;
|
||||
} else {
|
||||
t2 = $[9];
|
||||
}
|
||||
let t3;
|
||||
if ($[10] !== t1) {
|
||||
t3 = <Box paddingLeft={2} flexDirection="column">{t1}{t2}</Box>;
|
||||
$[10] = t1;
|
||||
$[11] = t3;
|
||||
} else {
|
||||
t3 = $[11];
|
||||
}
|
||||
return t3;
|
||||
return (
|
||||
<Box paddingLeft={2} flexDirection="column">
|
||||
<Text color="error">
|
||||
{flag} ignored ({list})
|
||||
</Text>
|
||||
<Text dimColor>
|
||||
Channels require claude.ai authentication · run /login, then restart
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
if (policyBlocked) {
|
||||
let t1;
|
||||
if ($[12] !== flag || $[13] !== list) {
|
||||
t1 = <Text color="error">{flag} blocked by org policy ({list})</Text>;
|
||||
$[12] = flag;
|
||||
$[13] = list;
|
||||
$[14] = t1;
|
||||
} else {
|
||||
t1 = $[14];
|
||||
}
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Text dimColor={true}>Inbound messages will be silently dropped</Text>;
|
||||
t3 = <Text dimColor={true}>Have an administrator set channelsEnabled: true in managed settings to enable</Text>;
|
||||
$[15] = t2;
|
||||
$[16] = t3;
|
||||
} else {
|
||||
t2 = $[15];
|
||||
t3 = $[16];
|
||||
}
|
||||
let t4;
|
||||
if ($[17] !== unmatched) {
|
||||
t4 = unmatched.map(_temp3);
|
||||
$[17] = unmatched;
|
||||
$[18] = t4;
|
||||
} else {
|
||||
t4 = $[18];
|
||||
}
|
||||
let t5;
|
||||
if ($[19] !== t1 || $[20] !== t4) {
|
||||
t5 = <Box paddingLeft={2} flexDirection="column">{t1}{t2}{t3}{t4}</Box>;
|
||||
$[19] = t1;
|
||||
$[20] = t4;
|
||||
$[21] = t5;
|
||||
} else {
|
||||
t5 = $[21];
|
||||
}
|
||||
return t5;
|
||||
return (
|
||||
<Box paddingLeft={2} flexDirection="column">
|
||||
<Text color="error">
|
||||
{flag} blocked by org policy ({list})
|
||||
</Text>
|
||||
<Text dimColor>Inbound messages will be silently dropped</Text>
|
||||
<Text dimColor>
|
||||
Have an administrator set channelsEnabled: true in managed settings to
|
||||
enable
|
||||
</Text>
|
||||
{unmatched.map(u => (
|
||||
<Text key={`${formatEntry(u.entry)}:${u.why}`} color="warning">
|
||||
{formatEntry(u.entry)} · {u.why}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
let t1;
|
||||
if ($[22] !== list) {
|
||||
t1 = <Text color="error">Listening for channel messages from: {list}</Text>;
|
||||
$[22] = list;
|
||||
$[23] = t1;
|
||||
} else {
|
||||
t1 = $[23];
|
||||
}
|
||||
let t2;
|
||||
if ($[24] !== flag) {
|
||||
t2 = <Text dimColor={true}>Experimental · inbound messages will be pushed into this session, this carries prompt injection risks. Restart Claude Code without {flag} to disable.</Text>;
|
||||
$[24] = flag;
|
||||
$[25] = t2;
|
||||
} else {
|
||||
t2 = $[25];
|
||||
}
|
||||
let t3;
|
||||
if ($[26] !== unmatched) {
|
||||
t3 = unmatched.map(_temp4);
|
||||
$[26] = unmatched;
|
||||
$[27] = t3;
|
||||
} else {
|
||||
t3 = $[27];
|
||||
}
|
||||
let t4;
|
||||
if ($[28] !== t1 || $[29] !== t2 || $[30] !== t3) {
|
||||
t4 = <Box paddingLeft={2} flexDirection="column">{t1}{t2}{t3}</Box>;
|
||||
$[28] = t1;
|
||||
$[29] = t2;
|
||||
$[30] = t3;
|
||||
$[31] = t4;
|
||||
} else {
|
||||
t4 = $[31];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
function _temp4(u_0) {
|
||||
return <Text key={`${formatEntry(u_0.entry)}:${u_0.why}`} color="warning">{formatEntry(u_0.entry)} · {u_0.why}</Text>;
|
||||
}
|
||||
function _temp3(u) {
|
||||
return <Text key={`${formatEntry(u.entry)}:${u.why}`} color="warning">{formatEntry(u.entry)} · {u.why}</Text>;
|
||||
}
|
||||
function _temp2(c) {
|
||||
return !c.dev;
|
||||
}
|
||||
function _temp() {
|
||||
const ch = getAllowedChannels();
|
||||
if (ch.length === 0) {
|
||||
return {
|
||||
channels: ch,
|
||||
disabled: false,
|
||||
noAuth: false,
|
||||
policyBlocked: false,
|
||||
list: "",
|
||||
unmatched: [] as Unmatched[]
|
||||
};
|
||||
}
|
||||
const l = ch.map(formatEntry).join(", ");
|
||||
const sub = getSubscriptionType();
|
||||
const managed = sub === "team" || sub === "enterprise";
|
||||
const policy = getSettingsForSource("policySettings");
|
||||
const allowlist = getEffectiveChannelAllowlist(sub, policy?.allowedChannelPlugins);
|
||||
return {
|
||||
channels: ch,
|
||||
disabled: !isChannelsEnabled(),
|
||||
noAuth: !getClaudeAIOAuthTokens()?.accessToken,
|
||||
policyBlocked: managed && policy?.channelsEnabled !== true,
|
||||
list: l,
|
||||
unmatched: findUnmatched(ch, allowlist)
|
||||
};
|
||||
|
||||
// "Listening for" not "active" — at this point we only know the allowlist
|
||||
// was set. Server connection, capability declaration, and whether the name
|
||||
// even matches a configured MCP server are all still unknown.
|
||||
return (
|
||||
<Box paddingLeft={2} flexDirection="column">
|
||||
<Text color="error">Listening for channel messages from: {list}</Text>
|
||||
<Text dimColor>
|
||||
Experimental · inbound messages will be pushed into this session, this
|
||||
carries prompt injection risks. Restart Claude Code without {flag} to
|
||||
disable.
|
||||
</Text>
|
||||
{unmatched.map(u => (
|
||||
<Text key={`${formatEntry(u.entry)}:${u.why}`} color="warning">
|
||||
{formatEntry(u.entry)} · {u.why}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function formatEntry(c: ChannelEntry): string {
|
||||
return c.kind === 'plugin' ? `plugin:${c.name}@${c.marketplace}` : `server:${c.name}`;
|
||||
return c.kind === 'plugin'
|
||||
? `plugin:${c.name}@${c.marketplace}`
|
||||
: `server:${c.name}`
|
||||
}
|
||||
type Unmatched = {
|
||||
entry: ChannelEntry;
|
||||
why: string;
|
||||
};
|
||||
function findUnmatched(entries: readonly ChannelEntry[], allowlist: ReturnType<typeof getEffectiveChannelAllowlist>): Unmatched[] {
|
||||
|
||||
type Unmatched = { entry: ChannelEntry; why: string }
|
||||
|
||||
function findUnmatched(
|
||||
entries: readonly ChannelEntry[],
|
||||
allowlist: ReturnType<typeof getEffectiveChannelAllowlist>,
|
||||
): Unmatched[] {
|
||||
// Server-kind: build one Set from all scopes up front. getMcpConfigsByScope
|
||||
// is not cached (project scope walks the dir tree); getMcpConfigByName would
|
||||
// redo that walk per entry.
|
||||
const scopes = ['enterprise', 'user', 'project', 'local'] as const;
|
||||
const configured = new Set<string>();
|
||||
const scopes = ['enterprise', 'user', 'project', 'local'] as const
|
||||
const configured = new Set<string>()
|
||||
for (const scope of scopes) {
|
||||
for (const name of Object.keys(getMcpConfigsByScope(scope).servers)) {
|
||||
configured.add(name);
|
||||
configured.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin-kind installed check: installed_plugins.json keys are
|
||||
// `name@marketplace`. loadInstalledPluginsV2 is cached.
|
||||
const installedPluginIds = new Set(Object.keys(loadInstalledPluginsV2().plugins));
|
||||
const installedPluginIds = new Set(
|
||||
Object.keys(loadInstalledPluginsV2().plugins),
|
||||
)
|
||||
|
||||
// Plugin-kind allowlist check: same {marketplace, plugin} test as the
|
||||
// gate at channelNotification.ts. entry.dev bypasses (dev flag opts out
|
||||
// of the allowlist). Org list replaces ledger when set (team/enterprise).
|
||||
// GrowthBook _CACHED_MAY_BE_STALE — cold cache yields [] so every plugin
|
||||
// entry warns; same tradeoff the gate already accepts.
|
||||
const {
|
||||
entries: allowed,
|
||||
source
|
||||
} = allowlist;
|
||||
const { entries: allowed, source } = allowlist
|
||||
|
||||
// Independent ifs — a plugin entry that's both uninstalled AND
|
||||
// unlisted shows two lines. Server kind checks config + dev flag.
|
||||
const out: Unmatched[] = [];
|
||||
const out: Unmatched[] = []
|
||||
for (const entry of entries) {
|
||||
if (entry.kind === 'server') {
|
||||
if (!configured.has(entry.name)) {
|
||||
out.push({
|
||||
entry,
|
||||
why: 'no MCP server configured with that name'
|
||||
});
|
||||
out.push({ entry, why: 'no MCP server configured with that name' })
|
||||
}
|
||||
if (!entry.dev) {
|
||||
out.push({
|
||||
entry,
|
||||
why: 'server: entries need --dangerously-load-development-channels'
|
||||
});
|
||||
why: 'server: entries need --dangerously-load-development-channels',
|
||||
})
|
||||
}
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
if (!installedPluginIds.has(`${entry.name}@${entry.marketplace}`)) {
|
||||
out.push({
|
||||
entry,
|
||||
why: 'plugin not installed'
|
||||
});
|
||||
out.push({ entry, why: 'plugin not installed' })
|
||||
}
|
||||
if (!entry.dev && !allowed.some(e => e.plugin === entry.name && e.marketplace === entry.marketplace)) {
|
||||
if (
|
||||
!entry.dev &&
|
||||
!allowed.some(
|
||||
e => e.plugin === entry.name && e.marketplace === entry.marketplace,
|
||||
)
|
||||
) {
|
||||
out.push({
|
||||
entry,
|
||||
why: source === 'org' ? "not on your org's approved channels list" : 'not on the approved channels allowlist'
|
||||
});
|
||||
why:
|
||||
source === 'org'
|
||||
? "not on your org's approved channels list"
|
||||
: 'not on the approved channels allowlist',
|
||||
})
|
||||
}
|
||||
}
|
||||
return out;
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
import { env } from '../../utils/env.js';
|
||||
export type ClawdPose = 'default' | 'arms-up' // both arms raised (used during jump)
|
||||
| 'look-left' // both pupils shifted left
|
||||
| 'look-right'; // both pupils shifted right
|
||||
import * as React from 'react'
|
||||
import { Box, Text } from '../../ink.js'
|
||||
import { env } from '../../utils/env.js'
|
||||
|
||||
export type ClawdPose =
|
||||
| 'default'
|
||||
| 'arms-up' // both arms raised (used during jump)
|
||||
| 'look-left' // both pupils shifted left
|
||||
| 'look-right' // both pupils shifted 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
|
||||
@@ -21,46 +23,23 @@ type Props = {
|
||||
// default (▛/▜, bottom pupils) — otherwise only one eye would appear to move.
|
||||
type Segments = {
|
||||
/** row 1 left (no bg): optional raised arm + side */
|
||||
r1L: string;
|
||||
r1L: string
|
||||
/** row 1 eyes (with bg): left-eye, forehead, right-eye */
|
||||
r1E: string;
|
||||
r1E: string
|
||||
/** row 1 right (no bg): side + optional raised arm */
|
||||
r1R: string;
|
||||
r1R: string
|
||||
/** row 2 left (no bg): arm + body curve */
|
||||
r2L: string;
|
||||
r2L: string
|
||||
/** row 2 right (no bg): body curve + arm */
|
||||
r2R: string;
|
||||
};
|
||||
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: '▛ '
|
||||
}
|
||||
};
|
||||
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.
|
||||
@@ -68,172 +47,52 @@ const APPLE_EYES: Record<ClawdPose, string> = {
|
||||
default: ' ▗ ▖ ',
|
||||
'look-left': ' ▘ ▘ ',
|
||||
'look-right': ' ▝ ▝ ',
|
||||
'arms-up': ' ▗ ▖ '
|
||||
};
|
||||
export function Clawd(t0) {
|
||||
const $ = _c(26);
|
||||
let t1;
|
||||
if ($[0] !== t0) {
|
||||
t1 = t0 === undefined ? {} : t0;
|
||||
$[0] = t0;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const {
|
||||
pose: t2
|
||||
} = t1;
|
||||
const pose = t2 === undefined ? "default" : t2;
|
||||
if (env.terminal === "Apple_Terminal") {
|
||||
let t3;
|
||||
if ($[2] !== pose) {
|
||||
t3 = <AppleTerminalClawd pose={pose} />;
|
||||
$[2] = pose;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
const p = POSES[pose];
|
||||
let t3;
|
||||
if ($[4] !== p.r1L) {
|
||||
t3 = <Text color="clawd_body">{p.r1L}</Text>;
|
||||
$[4] = p.r1L;
|
||||
$[5] = t3;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
}
|
||||
let t4;
|
||||
if ($[6] !== p.r1E) {
|
||||
t4 = <Text color="clawd_body" backgroundColor="clawd_background">{p.r1E}</Text>;
|
||||
$[6] = p.r1E;
|
||||
$[7] = t4;
|
||||
} else {
|
||||
t4 = $[7];
|
||||
}
|
||||
let t5;
|
||||
if ($[8] !== p.r1R) {
|
||||
t5 = <Text color="clawd_body">{p.r1R}</Text>;
|
||||
$[8] = p.r1R;
|
||||
$[9] = t5;
|
||||
} else {
|
||||
t5 = $[9];
|
||||
}
|
||||
let t6;
|
||||
if ($[10] !== t3 || $[11] !== t4 || $[12] !== t5) {
|
||||
t6 = <Text>{t3}{t4}{t5}</Text>;
|
||||
$[10] = t3;
|
||||
$[11] = t4;
|
||||
$[12] = t5;
|
||||
$[13] = t6;
|
||||
} else {
|
||||
t6 = $[13];
|
||||
}
|
||||
let t7;
|
||||
if ($[14] !== p.r2L) {
|
||||
t7 = <Text color="clawd_body">{p.r2L}</Text>;
|
||||
$[14] = p.r2L;
|
||||
$[15] = t7;
|
||||
} else {
|
||||
t7 = $[15];
|
||||
}
|
||||
let t8;
|
||||
if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t8 = <Text color="clawd_body" backgroundColor="clawd_background">█████</Text>;
|
||||
$[16] = t8;
|
||||
} else {
|
||||
t8 = $[16];
|
||||
}
|
||||
let t9;
|
||||
if ($[17] !== p.r2R) {
|
||||
t9 = <Text color="clawd_body">{p.r2R}</Text>;
|
||||
$[17] = p.r2R;
|
||||
$[18] = t9;
|
||||
} else {
|
||||
t9 = $[18];
|
||||
}
|
||||
let t10;
|
||||
if ($[19] !== t7 || $[20] !== t9) {
|
||||
t10 = <Text>{t7}{t8}{t9}</Text>;
|
||||
$[19] = t7;
|
||||
$[20] = t9;
|
||||
$[21] = t10;
|
||||
} else {
|
||||
t10 = $[21];
|
||||
}
|
||||
let t11;
|
||||
if ($[22] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t11 = <Text color="clawd_body">{" "}▘▘ ▝▝{" "}</Text>;
|
||||
$[22] = t11;
|
||||
} else {
|
||||
t11 = $[22];
|
||||
}
|
||||
let t12;
|
||||
if ($[23] !== t10 || $[24] !== t6) {
|
||||
t12 = <Box flexDirection="column">{t6}{t10}{t11}</Box>;
|
||||
$[23] = t10;
|
||||
$[24] = t6;
|
||||
$[25] = t12;
|
||||
} else {
|
||||
t12 = $[25];
|
||||
}
|
||||
return t12;
|
||||
'arms-up': ' ▗ ▖ ',
|
||||
}
|
||||
function AppleTerminalClawd(t0) {
|
||||
const $ = _c(10);
|
||||
const {
|
||||
pose
|
||||
} = t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = <Text color="clawd_body">▗</Text>;
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
|
||||
export function Clawd({ pose = 'default' }: Props = {}): React.ReactNode {
|
||||
if (env.terminal === 'Apple_Terminal') {
|
||||
return <AppleTerminalClawd pose={pose} />
|
||||
}
|
||||
const t2 = APPLE_EYES[pose];
|
||||
let t3;
|
||||
if ($[1] !== t2) {
|
||||
t3 = <Text color="clawd_background" backgroundColor="clawd_body">{t2}</Text>;
|
||||
$[1] = t2;
|
||||
$[2] = t3;
|
||||
} else {
|
||||
t3 = $[2];
|
||||
}
|
||||
let t4;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = <Text color="clawd_body">▖</Text>;
|
||||
$[3] = t4;
|
||||
} else {
|
||||
t4 = $[3];
|
||||
}
|
||||
let t5;
|
||||
if ($[4] !== t3) {
|
||||
t5 = <Text>{t1}{t3}{t4}</Text>;
|
||||
$[4] = t3;
|
||||
$[5] = t5;
|
||||
} else {
|
||||
t5 = $[5];
|
||||
}
|
||||
let t6;
|
||||
let t7;
|
||||
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t6 = <Text backgroundColor="clawd_body">{" ".repeat(7)}</Text>;
|
||||
t7 = <Text color="clawd_body">▘▘ ▝▝</Text>;
|
||||
$[6] = t6;
|
||||
$[7] = t7;
|
||||
} else {
|
||||
t6 = $[6];
|
||||
t7 = $[7];
|
||||
}
|
||||
let t8;
|
||||
if ($[8] !== t5) {
|
||||
t8 = <Box flexDirection="column" alignItems="center">{t5}{t6}{t7}</Box>;
|
||||
$[8] = t5;
|
||||
$[9] = t8;
|
||||
} else {
|
||||
t8 = $[9];
|
||||
}
|
||||
return t8;
|
||||
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>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,160 +1,119 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { type ReactNode, useEffect } from 'react';
|
||||
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';
|
||||
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
||||
import { stringWidth } from '../../ink/stringWidth.js';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
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';
|
||||
export function CondensedLogo() {
|
||||
const $ = _c(29);
|
||||
const {
|
||||
columns
|
||||
} = useTerminalSize();
|
||||
const agent = useAppState(_temp);
|
||||
const effortValue = useAppState(_temp2);
|
||||
const model = useMainLoopModel();
|
||||
const modelDisplayName = renderModelSetting(model);
|
||||
const {
|
||||
import * as React from 'react'
|
||||
import { type ReactNode, useEffect } from 'react'
|
||||
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js'
|
||||
import { useTerminalSize } from '../../hooks/useTerminalSize.js'
|
||||
import { stringWidth } from '../../ink/stringWidth.js'
|
||||
import { Box, Text } from '../../ink.js'
|
||||
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'
|
||||
|
||||
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()
|
||||
|
||||
// Prefer AppState.agent (set from --agent CLI flag) over settings
|
||||
const agentName = agent ?? agentNameFromSettings
|
||||
const showGuestPassesUpsell = useShowGuestPassesUpsell()
|
||||
const showOverageCreditUpsell = useShowOverageCreditUpsell()
|
||||
|
||||
useEffect(() => {
|
||||
if (showGuestPassesUpsell) {
|
||||
incrementGuestPassesSeenCount()
|
||||
}
|
||||
}, [showGuestPassesUpsell])
|
||||
|
||||
useEffect(() => {
|
||||
if (showOverageCreditUpsell && !showGuestPassesUpsell) {
|
||||
incrementOverageCreditUpsellSeenCount()
|
||||
}
|
||||
}, [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)
|
||||
|
||||
// Truncate version to fit within available width, accounting for "Claude Code v" prefix
|
||||
const versionPrefix = 'Claude Code v'
|
||||
const truncatedVersion = truncate(
|
||||
version,
|
||||
cwd,
|
||||
billingType,
|
||||
agentName: agentNameFromSettings
|
||||
} = getLogoDisplayData();
|
||||
const agentName = agent ?? agentNameFromSettings;
|
||||
const showGuestPassesUpsell = useShowGuestPassesUpsell();
|
||||
const showOverageCreditUpsell = useShowOverageCreditUpsell();
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== showGuestPassesUpsell) {
|
||||
t0 = () => {
|
||||
if (showGuestPassesUpsell) {
|
||||
incrementGuestPassesSeenCount();
|
||||
}
|
||||
};
|
||||
t1 = [showGuestPassesUpsell];
|
||||
$[0] = showGuestPassesUpsell;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[3] !== showGuestPassesUpsell || $[4] !== showOverageCreditUpsell) {
|
||||
t2 = () => {
|
||||
if (showOverageCreditUpsell && !showGuestPassesUpsell) {
|
||||
incrementOverageCreditUpsellSeenCount();
|
||||
}
|
||||
};
|
||||
t3 = [showOverageCreditUpsell, showGuestPassesUpsell];
|
||||
$[3] = showGuestPassesUpsell;
|
||||
$[4] = showOverageCreditUpsell;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
t3 = $[6];
|
||||
}
|
||||
useEffect(t2, t3);
|
||||
const textWidth = Math.max(columns - 15, 20);
|
||||
const truncatedVersion = truncate(version, Math.max(textWidth - 13, 6));
|
||||
const effortSuffix = getEffortSuffix(model, effortValue);
|
||||
const {
|
||||
shouldSplit,
|
||||
truncatedModel,
|
||||
truncatedBilling
|
||||
} = formatModelAndBilling(modelDisplayName + effortSuffix, billingType, textWidth);
|
||||
const cwdAvailableWidth = agentName ? textWidth - 1 - stringWidth(agentName) - 3 : textWidth;
|
||||
const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10));
|
||||
let t4;
|
||||
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = isFullscreenEnvEnabled() ? <AnimatedClawd /> : <Clawd />;
|
||||
$[7] = t4;
|
||||
} else {
|
||||
t4 = $[7];
|
||||
}
|
||||
let t5;
|
||||
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t5 = <Text bold={true}>Claude Code</Text>;
|
||||
$[8] = t5;
|
||||
} else {
|
||||
t5 = $[8];
|
||||
}
|
||||
let t6;
|
||||
if ($[9] !== truncatedVersion) {
|
||||
t6 = <Text>{t5}{" "}<Text dimColor={true}>v{truncatedVersion}</Text></Text>;
|
||||
$[9] = truncatedVersion;
|
||||
$[10] = t6;
|
||||
} else {
|
||||
t6 = $[10];
|
||||
}
|
||||
let t7;
|
||||
if ($[11] !== shouldSplit || $[12] !== truncatedBilling || $[13] !== truncatedModel) {
|
||||
t7 = shouldSplit ? <><Text dimColor={true}>{truncatedModel}</Text><Text dimColor={true}>{truncatedBilling}</Text></> : <Text dimColor={true}>{truncatedModel} · {truncatedBilling}</Text>;
|
||||
$[11] = shouldSplit;
|
||||
$[12] = truncatedBilling;
|
||||
$[13] = truncatedModel;
|
||||
$[14] = t7;
|
||||
} else {
|
||||
t7 = $[14];
|
||||
}
|
||||
const t8 = agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd;
|
||||
let t9;
|
||||
if ($[15] !== t8) {
|
||||
t9 = <Text dimColor={true}>{t8}</Text>;
|
||||
$[15] = t8;
|
||||
$[16] = t9;
|
||||
} else {
|
||||
t9 = $[16];
|
||||
}
|
||||
let t10;
|
||||
if ($[17] !== showGuestPassesUpsell) {
|
||||
t10 = showGuestPassesUpsell && <GuestPassesUpsell />;
|
||||
$[17] = showGuestPassesUpsell;
|
||||
$[18] = t10;
|
||||
} else {
|
||||
t10 = $[18];
|
||||
}
|
||||
let t11;
|
||||
if ($[19] !== showGuestPassesUpsell || $[20] !== showOverageCreditUpsell || $[21] !== textWidth) {
|
||||
t11 = !showGuestPassesUpsell && showOverageCreditUpsell && <OverageCreditUpsell maxWidth={textWidth} twoLine={true} />;
|
||||
$[19] = showGuestPassesUpsell;
|
||||
$[20] = showOverageCreditUpsell;
|
||||
$[21] = textWidth;
|
||||
$[22] = t11;
|
||||
} else {
|
||||
t11 = $[22];
|
||||
}
|
||||
let t12;
|
||||
if ($[23] !== t10 || $[24] !== t11 || $[25] !== t6 || $[26] !== t7 || $[27] !== t9) {
|
||||
t12 = <OffscreenFreeze><Box flexDirection="row" gap={2} alignItems="center">{t4}<Box flexDirection="column">{t6}{t7}{t9}{t10}{t11}</Box></Box></OffscreenFreeze>;
|
||||
$[23] = t10;
|
||||
$[24] = t11;
|
||||
$[25] = t6;
|
||||
$[26] = t7;
|
||||
$[27] = t9;
|
||||
$[28] = t12;
|
||||
} else {
|
||||
t12 = $[28];
|
||||
}
|
||||
return t12;
|
||||
}
|
||||
function _temp2(s_0) {
|
||||
return s_0.effortValue;
|
||||
}
|
||||
function _temp(s) {
|
||||
return s.agent;
|
||||
Math.max(textWidth - versionPrefix.length, 6),
|
||||
)
|
||||
|
||||
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 cwdAvailableWidth = agentName
|
||||
? textWidth - atPrefix.length - stringWidth(agentName) - separator.length
|
||||
: 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
|
||||
// changes and getLogoDisplayData() reads getCwd()/subscription state — any
|
||||
// of which changing while in scrollback would force a full terminal reset.
|
||||
return (
|
||||
<OffscreenFreeze>
|
||||
<Box flexDirection="row" gap={2} alignItems="center">
|
||||
{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}
|
||||
</Text>
|
||||
)}
|
||||
<Text dimColor>
|
||||
{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}
|
||||
</Text>
|
||||
{showGuestPassesUpsell && <GuestPassesUpsell />}
|
||||
{!showGuestPassesUpsell && showOverageCreditUpsell && (
|
||||
<OverageCreditUpsell maxWidth={textWidth} twoLine />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</OffscreenFreeze>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,57 +1,65 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Box, Text } from 'src/ink.js';
|
||||
import { getDynamicConfig_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js';
|
||||
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js';
|
||||
const CONFIG_NAME = 'tengu-top-of-feed-tip';
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { Box, Text } from 'src/ink.js'
|
||||
import { getDynamicConfig_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js'
|
||||
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js'
|
||||
|
||||
const CONFIG_NAME = 'tengu-top-of-feed-tip'
|
||||
|
||||
export function EmergencyTip(): React.ReactNode {
|
||||
const tip = useMemo(getTipOfFeed, []);
|
||||
const tip = useMemo(getTipOfFeed, [])
|
||||
// Memoize to prevent re-reads after we save - we want the value at mount time
|
||||
const lastShownTip = useMemo(() => getGlobalConfig().lastShownEmergencyTip, []);
|
||||
const lastShownTip = useMemo(
|
||||
() => getGlobalConfig().lastShownEmergencyTip,
|
||||
[],
|
||||
)
|
||||
|
||||
// Only show if this is a new/different tip
|
||||
const shouldShow = tip.tip && tip.tip !== lastShownTip;
|
||||
const shouldShow = tip.tip && tip.tip !== lastShownTip
|
||||
|
||||
// Save the tip we're showing so we don't show it again
|
||||
useEffect(() => {
|
||||
if (shouldShow) {
|
||||
saveGlobalConfig(current => {
|
||||
if (current.lastShownEmergencyTip === tip.tip) return current;
|
||||
return {
|
||||
...current,
|
||||
lastShownEmergencyTip: tip.tip
|
||||
};
|
||||
});
|
||||
if (current.lastShownEmergencyTip === tip.tip) return current
|
||||
return { ...current, lastShownEmergencyTip: tip.tip }
|
||||
})
|
||||
}
|
||||
}, [shouldShow, tip.tip]);
|
||||
}, [shouldShow, tip.tip])
|
||||
|
||||
if (!shouldShow) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
return <Box paddingLeft={2} flexDirection="column">
|
||||
<Text {...tip.color === 'warning' ? {
|
||||
color: 'warning'
|
||||
} : tip.color === 'error' ? {
|
||||
color: 'error'
|
||||
} : {
|
||||
dimColor: true
|
||||
}}>
|
||||
|
||||
return (
|
||||
<Box paddingLeft={2} flexDirection="column">
|
||||
<Text
|
||||
{...(tip.color === 'warning'
|
||||
? { color: 'warning' }
|
||||
: tip.color === 'error'
|
||||
? { color: 'error' }
|
||||
: { dimColor: true })}
|
||||
>
|
||||
{tip.tip}
|
||||
</Text>
|
||||
</Box>;
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
type TipOfFeed = {
|
||||
tip: string;
|
||||
color?: 'dim' | 'warning' | 'error';
|
||||
};
|
||||
const DEFAULT_TIP: TipOfFeed = {
|
||||
tip: '',
|
||||
color: 'dim'
|
||||
};
|
||||
tip: string
|
||||
color?: 'dim' | 'warning' | 'error'
|
||||
}
|
||||
|
||||
const DEFAULT_TIP: TipOfFeed = { tip: '', color: 'dim' }
|
||||
|
||||
/**
|
||||
* Get the tip of the feed from dynamic config with caching
|
||||
* Returns cached value immediately, updates in background
|
||||
*/
|
||||
function getTipOfFeed(): TipOfFeed {
|
||||
return getDynamicConfig_CACHED_MAY_BE_STALE<TipOfFeed>(CONFIG_NAME, DEFAULT_TIP);
|
||||
return getDynamicConfig_CACHED_MAY_BE_STALE<TipOfFeed>(
|
||||
CONFIG_NAME,
|
||||
DEFAULT_TIP,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,111 +1,113 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { stringWidth } from '../../ink/stringWidth.js';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
import { truncate } from '../../utils/format.js';
|
||||
import * as React from 'react'
|
||||
import { stringWidth } from '../../ink/stringWidth.js'
|
||||
import { Box, Text } from '../../ink.js'
|
||||
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;
|
||||
let maxWidth = stringWidth(title);
|
||||
const { title, lines, footer, emptyMessage, customContent } = config
|
||||
|
||||
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(t0) {
|
||||
const $ = _c(15);
|
||||
const {
|
||||
config,
|
||||
actualWidth
|
||||
} = t0;
|
||||
const {
|
||||
title,
|
||||
lines,
|
||||
footer,
|
||||
emptyMessage,
|
||||
customContent
|
||||
} = config;
|
||||
let t1;
|
||||
if ($[0] !== lines) {
|
||||
t1 = Math.max(0, ...lines.map(_temp));
|
||||
$[0] = lines;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const maxTimestampWidth = t1;
|
||||
let t2;
|
||||
if ($[2] !== title) {
|
||||
t2 = <Text bold={true} color="claude">{title}</Text>;
|
||||
$[2] = title;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== actualWidth || $[5] !== customContent || $[6] !== emptyMessage || $[7] !== footer || $[8] !== lines || $[9] !== maxTimestampWidth) {
|
||||
t3 = customContent ? <>{customContent.content}{footer && <Text dimColor={true} italic={true}>{truncate(footer, actualWidth)}</Text>}</> : lines.length === 0 && emptyMessage ? <Text dimColor={true}>{truncate(emptyMessage, actualWidth)}</Text> : <>{lines.map((line_0, index) => {
|
||||
const textWidth = Math.max(10, actualWidth - (maxTimestampWidth > 0 ? maxTimestampWidth + 2 : 0));
|
||||
return <Text key={index}>{maxTimestampWidth > 0 && <><Text dimColor={true}>{(line_0.timestamp || "").padEnd(maxTimestampWidth)}</Text>{" "}</>}<Text>{truncate(line_0.text, textWidth)}</Text></Text>;
|
||||
})}{footer && <Text dimColor={true} italic={true}>{truncate(footer, actualWidth)}</Text>}</>;
|
||||
$[4] = actualWidth;
|
||||
$[5] = customContent;
|
||||
$[6] = emptyMessage;
|
||||
$[7] = footer;
|
||||
$[8] = lines;
|
||||
$[9] = maxTimestampWidth;
|
||||
$[10] = t3;
|
||||
} else {
|
||||
t3 = $[10];
|
||||
}
|
||||
let t4;
|
||||
if ($[11] !== actualWidth || $[12] !== t2 || $[13] !== t3) {
|
||||
t4 = <Box flexDirection="column" width={actualWidth}>{t2}{t3}</Box>;
|
||||
$[11] = actualWidth;
|
||||
$[12] = t2;
|
||||
$[13] = t3;
|
||||
$[14] = t4;
|
||||
} else {
|
||||
t4 = $[14];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
function _temp(line) {
|
||||
return line.timestamp ? stringWidth(line.timestamp) : 0;
|
||||
|
||||
export function Feed({ config, actualWidth }: FeedProps): React.ReactNode {
|
||||
const { title, lines, footer, emptyMessage, customContent } = config
|
||||
|
||||
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">
|
||||
{title}
|
||||
</Text>
|
||||
{customContent ? (
|
||||
<>
|
||||
{customContent.content}
|
||||
{footer && (
|
||||
<Text dimColor italic>
|
||||
{truncate(footer, actualWidth)}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
) : lines.length === 0 && emptyMessage ? (
|
||||
<Text dimColor>{truncate(emptyMessage, actualWidth)}</Text>
|
||||
) : (
|
||||
<>
|
||||
{lines.map((line, index) => {
|
||||
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>
|
||||
{gap}
|
||||
</>
|
||||
)}
|
||||
<Text>{truncate(line.text, textWidth)}</Text>
|
||||
</Text>
|
||||
)
|
||||
})}
|
||||
{footer && (
|
||||
<Text dimColor italic>
|
||||
{truncate(footer, actualWidth)}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,58 +1,32 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { Box } from '../../ink.js';
|
||||
import { Divider } from '../design-system/Divider.js';
|
||||
import type { FeedConfig } from './Feed.js';
|
||||
import { calculateFeedWidth, Feed } from './Feed.js';
|
||||
import * as React from 'react'
|
||||
import { Box } from '../../ink.js'
|
||||
import { Divider } from '../design-system/Divider.js'
|
||||
import type { FeedConfig } from './Feed.js'
|
||||
import { calculateFeedWidth, Feed } from './Feed.js'
|
||||
|
||||
type FeedColumnProps = {
|
||||
feeds: FeedConfig[];
|
||||
maxWidth: number;
|
||||
};
|
||||
export function FeedColumn(t0) {
|
||||
const $ = _c(10);
|
||||
const {
|
||||
feeds,
|
||||
maxWidth
|
||||
} = t0;
|
||||
let t1;
|
||||
if ($[0] !== feeds) {
|
||||
const feedWidths = feeds.map(_temp);
|
||||
t1 = Math.max(...feedWidths);
|
||||
$[0] = feeds;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const maxOfAllFeeds = t1;
|
||||
const actualWidth = Math.min(maxOfAllFeeds, maxWidth);
|
||||
let t2;
|
||||
if ($[2] !== actualWidth || $[3] !== feeds) {
|
||||
let t3;
|
||||
if ($[5] !== actualWidth || $[6] !== feeds.length) {
|
||||
t3 = (feed_0, index) => <React.Fragment key={index}><Feed config={feed_0} actualWidth={actualWidth} />{index < feeds.length - 1 && <Divider color="claude" width={actualWidth} />}</React.Fragment>;
|
||||
$[5] = actualWidth;
|
||||
$[6] = feeds.length;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
t2 = feeds.map(t3);
|
||||
$[2] = actualWidth;
|
||||
$[3] = feeds;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
let t3;
|
||||
if ($[8] !== t2) {
|
||||
t3 = <Box flexDirection="column">{t2}</Box>;
|
||||
$[8] = t2;
|
||||
$[9] = t3;
|
||||
} else {
|
||||
t3 = $[9];
|
||||
}
|
||||
return t3;
|
||||
feeds: FeedConfig[]
|
||||
maxWidth: number
|
||||
}
|
||||
function _temp(feed) {
|
||||
return calculateFeedWidth(feed);
|
||||
|
||||
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} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,69 +1,73 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Text } from '../../ink.js';
|
||||
import { logEvent } from '../../services/analytics/index.js';
|
||||
import { checkCachedPassesEligibility, formatCreditAmount, getCachedReferrerReward, getCachedRemainingPasses } from '../../services/api/referral.js';
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Text } from '../../ink.js'
|
||||
import { logEvent } from '../../services/analytics/index.js'
|
||||
import {
|
||||
checkCachedPassesEligibility,
|
||||
formatCreditAmount,
|
||||
getCachedReferrerReward,
|
||||
getCachedRemainingPasses,
|
||||
} from '../../services/api/referral.js'
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
|
||||
|
||||
function resetIfPassesRefreshed(): void {
|
||||
const remaining = getCachedRemainingPasses();
|
||||
if (remaining == null || remaining <= 0) return;
|
||||
const config = getGlobalConfig();
|
||||
const lastSeen = config.passesLastSeenRemaining ?? 0;
|
||||
const remaining = getCachedRemainingPasses()
|
||||
if (remaining == null || remaining <= 0) return
|
||||
const config = getGlobalConfig()
|
||||
const lastSeen = config.passesLastSeenRemaining ?? 0
|
||||
if (remaining > lastSeen) {
|
||||
saveGlobalConfig(prev => ({
|
||||
...prev,
|
||||
passesUpsellSeenCount: 0,
|
||||
hasVisitedPasses: false,
|
||||
passesLastSeenRemaining: remaining
|
||||
}));
|
||||
passesLastSeenRemaining: remaining,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
function shouldShowGuestPassesUpsell(): boolean {
|
||||
const {
|
||||
eligible,
|
||||
hasCache
|
||||
} = checkCachedPassesEligibility();
|
||||
const { eligible, hasCache } = checkCachedPassesEligibility()
|
||||
// Only show if eligible and cache exists (don't block on fetch)
|
||||
if (!eligible || !hasCache) return false;
|
||||
if (!eligible || !hasCache) return false
|
||||
// Reset upsell counters if passes were refreshed (covers both campaign change and pass refresh)
|
||||
resetIfPassesRefreshed();
|
||||
const config = getGlobalConfig();
|
||||
if ((config.passesUpsellSeenCount ?? 0) >= 3) return false;
|
||||
if (config.hasVisitedPasses) return false;
|
||||
return true;
|
||||
resetIfPassesRefreshed()
|
||||
|
||||
const config = getGlobalConfig()
|
||||
if ((config.passesUpsellSeenCount ?? 0) >= 3) return false
|
||||
if (config.hasVisitedPasses) return false
|
||||
|
||||
return true
|
||||
}
|
||||
export function useShowGuestPassesUpsell() {
|
||||
const [show] = useState(_temp);
|
||||
return show;
|
||||
}
|
||||
function _temp() {
|
||||
return shouldShowGuestPassesUpsell();
|
||||
|
||||
export function useShowGuestPassesUpsell(): boolean {
|
||||
const [show] = useState(() => shouldShowGuestPassesUpsell())
|
||||
return show
|
||||
}
|
||||
|
||||
export function incrementGuestPassesSeenCount(): void {
|
||||
let newCount = 0;
|
||||
let newCount = 0
|
||||
saveGlobalConfig(prev => {
|
||||
newCount = (prev.passesUpsellSeenCount ?? 0) + 1;
|
||||
newCount = (prev.passesUpsellSeenCount ?? 0) + 1
|
||||
return {
|
||||
...prev,
|
||||
passesUpsellSeenCount: newCount
|
||||
};
|
||||
});
|
||||
passesUpsellSeenCount: newCount,
|
||||
}
|
||||
})
|
||||
logEvent('tengu_guest_passes_upsell_shown', {
|
||||
seen_count: newCount
|
||||
});
|
||||
seen_count: newCount,
|
||||
})
|
||||
}
|
||||
|
||||
// Condensed layout for mini welcome screen
|
||||
export function GuestPassesUpsell() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const reward = getCachedReferrerReward();
|
||||
t0 = <Text dimColor={true}><Text color="claude">[✻]</Text> <Text color="claude">[✻]</Text>{" "}<Text color="claude">[✻]</Text> ·{" "}{reward ? `Share Claude Code and earn ${formatCreditAmount(reward)} of extra usage · /passes` : "3 guest passes at /passes"}</Text>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
export function GuestPassesUpsell(): React.ReactNode {
|
||||
const reward = getCachedReferrerReward()
|
||||
return (
|
||||
<Text dimColor>
|
||||
<Text color="claude">[✻]</Text> <Text color="claude">[✻]</Text>{' '}
|
||||
<Text color="claude">[✻]</Text> ·{' '}
|
||||
{reward
|
||||
? `Share Claude Code and earn ${formatCreditAmount(reward)} of extra usage · /passes`
|
||||
: '3 guest passes at /passes'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,54 +1,41 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { UP_ARROW } from '../../constants/figures.js';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
|
||||
import { isOpus1mMergeEnabled } from '../../utils/model/model.js';
|
||||
import { AnimatedAsterisk } from './AnimatedAsterisk.js';
|
||||
const MAX_SHOW_COUNT = 6;
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { UP_ARROW } from '../../constants/figures.js'
|
||||
import { Box, Text } from '../../ink.js'
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
|
||||
import { isOpus1mMergeEnabled } from '../../utils/model/model.js'
|
||||
import { AnimatedAsterisk } from './AnimatedAsterisk.js'
|
||||
|
||||
const MAX_SHOW_COUNT = 6
|
||||
|
||||
export function shouldShowOpus1mMergeNotice(): boolean {
|
||||
return isOpus1mMergeEnabled() && (getGlobalConfig().opus1mMergeNoticeSeenCount ?? 0) < MAX_SHOW_COUNT;
|
||||
return (
|
||||
isOpus1mMergeEnabled() &&
|
||||
(getGlobalConfig().opus1mMergeNoticeSeenCount ?? 0) < MAX_SHOW_COUNT
|
||||
)
|
||||
}
|
||||
export function Opus1mMergeNotice() {
|
||||
const $ = _c(4);
|
||||
const [show] = useState(shouldShowOpus1mMergeNotice);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== show) {
|
||||
t0 = () => {
|
||||
if (!show) {
|
||||
return;
|
||||
}
|
||||
const newCount = (getGlobalConfig().opus1mMergeNoticeSeenCount ?? 0) + 1;
|
||||
saveGlobalConfig(prev => {
|
||||
if ((prev.opus1mMergeNoticeSeenCount ?? 0) >= newCount) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
opus1mMergeNoticeSeenCount: newCount
|
||||
};
|
||||
});
|
||||
};
|
||||
t1 = [show];
|
||||
$[0] = show;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
let t2;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Box paddingLeft={2}><AnimatedAsterisk char={UP_ARROW} /><Text dimColor={true}>{" "}Opus now defaults to 1M context · 5x more room, same pricing</Text></Box>;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
|
||||
export function Opus1mMergeNotice(): React.ReactNode {
|
||||
const [show] = useState(shouldShowOpus1mMergeNotice)
|
||||
|
||||
useEffect(() => {
|
||||
if (!show) return
|
||||
const newCount = (getGlobalConfig().opus1mMergeNoticeSeenCount ?? 0) + 1
|
||||
saveGlobalConfig(prev => {
|
||||
if ((prev.opus1mMergeNoticeSeenCount ?? 0) >= newCount) return prev
|
||||
return { ...prev, opus1mMergeNoticeSeenCount: newCount }
|
||||
})
|
||||
}, [show])
|
||||
|
||||
if (!show) return null
|
||||
|
||||
return (
|
||||
<Box paddingLeft={2}>
|
||||
<AnimatedAsterisk char={UP_ARROW} />
|
||||
<Text dimColor>
|
||||
{' '}
|
||||
Opus now defaults to 1M context · 5x more room, same pricing
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Text } from '../../ink.js';
|
||||
import { logEvent } from '../../services/analytics/index.js';
|
||||
import { formatGrantAmount, getCachedOverageCreditGrant, refreshOverageCreditGrantCache } from '../../services/api/overageCreditGrant.js';
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
|
||||
import { truncate } from '../../utils/format.js';
|
||||
import type { FeedConfig } from './Feed.js';
|
||||
const MAX_IMPRESSIONS = 3;
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Text } from '../../ink.js'
|
||||
import { logEvent } from '../../services/analytics/index.js'
|
||||
import {
|
||||
formatGrantAmount,
|
||||
getCachedOverageCreditGrant,
|
||||
refreshOverageCreditGrantCache,
|
||||
} from '../../services/api/overageCreditGrant.js'
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
|
||||
import { truncate } from '../../utils/format.js'
|
||||
import type { FeedConfig } from './Feed.js'
|
||||
|
||||
const MAX_IMPRESSIONS = 3
|
||||
|
||||
/**
|
||||
* Whether to show the overage credit upsell on any surface.
|
||||
@@ -25,16 +29,20 @@ const MAX_IMPRESSIONS = 3;
|
||||
* (welcome feed, tips).
|
||||
*/
|
||||
export function isEligibleForOverageCreditGrant(): boolean {
|
||||
const info = getCachedOverageCreditGrant();
|
||||
if (!info || !info.available || info.granted) return false;
|
||||
return formatGrantAmount(info) !== null;
|
||||
const info = getCachedOverageCreditGrant()
|
||||
if (!info || !info.available || info.granted) return false
|
||||
return formatGrantAmount(info) !== null
|
||||
}
|
||||
|
||||
export function shouldShowOverageCreditUpsell(): boolean {
|
||||
if (!isEligibleForOverageCreditGrant()) return false;
|
||||
const config = getGlobalConfig();
|
||||
if (config.hasVisitedExtraUsage) return false;
|
||||
if ((config.overageCreditUpsellSeenCount ?? 0) >= MAX_IMPRESSIONS) return false;
|
||||
return true;
|
||||
if (!isEligibleForOverageCreditGrant()) return false
|
||||
|
||||
const config = getGlobalConfig()
|
||||
if (config.hasVisitedExtraUsage) return false
|
||||
if ((config.overageCreditUpsellSeenCount ?? 0) >= MAX_IMPRESSIONS)
|
||||
return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,105 +50,78 @@ export function shouldShowOverageCreditUpsell(): boolean {
|
||||
* unconditionally on mount — it no-ops if cache is fresh.
|
||||
*/
|
||||
export function maybeRefreshOverageCreditCache(): void {
|
||||
if (getCachedOverageCreditGrant() !== null) return;
|
||||
void refreshOverageCreditGrantCache();
|
||||
if (getCachedOverageCreditGrant() !== null) return
|
||||
void refreshOverageCreditGrantCache()
|
||||
}
|
||||
export function useShowOverageCreditUpsell() {
|
||||
const [show] = useState(_temp);
|
||||
return show;
|
||||
}
|
||||
function _temp() {
|
||||
maybeRefreshOverageCreditCache();
|
||||
return shouldShowOverageCreditUpsell();
|
||||
|
||||
export function useShowOverageCreditUpsell(): boolean {
|
||||
const [show] = useState(() => {
|
||||
maybeRefreshOverageCreditCache()
|
||||
return shouldShowOverageCreditUpsell()
|
||||
})
|
||||
return show
|
||||
}
|
||||
|
||||
export function incrementOverageCreditUpsellSeenCount(): void {
|
||||
let newCount = 0;
|
||||
let newCount = 0
|
||||
saveGlobalConfig(prev => {
|
||||
newCount = (prev.overageCreditUpsellSeenCount ?? 0) + 1;
|
||||
newCount = (prev.overageCreditUpsellSeenCount ?? 0) + 1
|
||||
return {
|
||||
...prev,
|
||||
overageCreditUpsellSeenCount: newCount
|
||||
};
|
||||
});
|
||||
logEvent('tengu_overage_credit_upsell_shown', {
|
||||
seen_count: newCount
|
||||
});
|
||||
overageCreditUpsellSeenCount: newCount,
|
||||
}
|
||||
})
|
||||
logEvent('tengu_overage_credit_upsell_shown', { seen_count: newCount })
|
||||
}
|
||||
|
||||
// Copy from "OC & Bulk Overages copy" doc (#6 — CLI /usage)
|
||||
function getUsageText(amount: string): string {
|
||||
return `${amount} in extra usage for third-party apps · /extra-usage`;
|
||||
return `${amount} in extra usage for third-party apps · /extra-usage`
|
||||
}
|
||||
|
||||
// Copy from "OC & Bulk Overages copy" doc (#4 — CLI Welcome screen).
|
||||
// Char budgets: title ≤19, subtitle ≤48.
|
||||
const FEED_SUBTITLE = 'On us. Works on third-party apps · /extra-usage';
|
||||
const FEED_SUBTITLE = 'On us. Works on third-party apps · /extra-usage'
|
||||
|
||||
function getFeedTitle(amount: string): string {
|
||||
return `${amount} in extra usage`;
|
||||
return `${amount} in extra usage`
|
||||
}
|
||||
type Props = {
|
||||
maxWidth?: number;
|
||||
twoLine?: boolean;
|
||||
};
|
||||
export function OverageCreditUpsell(t0) {
|
||||
const $ = _c(8);
|
||||
const {
|
||||
maxWidth,
|
||||
twoLine
|
||||
} = t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== maxWidth || $[1] !== twoLine) {
|
||||
t2 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
const info = getCachedOverageCreditGrant();
|
||||
if (!info) {
|
||||
t2 = null;
|
||||
break bb0;
|
||||
}
|
||||
const amount = formatGrantAmount(info);
|
||||
if (!amount) {
|
||||
t2 = null;
|
||||
break bb0;
|
||||
}
|
||||
if (twoLine) {
|
||||
const title = getFeedTitle(amount);
|
||||
let t3;
|
||||
if ($[4] !== maxWidth) {
|
||||
t3 = maxWidth ? truncate(FEED_SUBTITLE, maxWidth) : FEED_SUBTITLE;
|
||||
$[4] = maxWidth;
|
||||
$[5] = t3;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
}
|
||||
let t4;
|
||||
if ($[6] !== t3) {
|
||||
t4 = <Text dimColor={true}>{t3}</Text>;
|
||||
$[6] = t3;
|
||||
$[7] = t4;
|
||||
} else {
|
||||
t4 = $[7];
|
||||
}
|
||||
t2 = <><Text color="claude">{maxWidth ? truncate(title, maxWidth) : title}</Text>{t4}</>;
|
||||
break bb0;
|
||||
}
|
||||
const text = getUsageText(amount);
|
||||
const display = maxWidth ? truncate(text, maxWidth) : text;
|
||||
const highlightLen = Math.min(getFeedTitle(amount).length, display.length);
|
||||
t1 = <Text dimColor={true}><Text color="claude">{display.slice(0, highlightLen)}</Text>{display.slice(highlightLen)}</Text>;
|
||||
}
|
||||
$[0] = maxWidth;
|
||||
$[1] = twoLine;
|
||||
$[2] = t1;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
t2 = $[3];
|
||||
|
||||
type Props = { maxWidth?: number; twoLine?: boolean }
|
||||
|
||||
export function OverageCreditUpsell({
|
||||
maxWidth,
|
||||
twoLine,
|
||||
}: Props): React.ReactNode {
|
||||
const info = getCachedOverageCreditGrant()
|
||||
if (!info) return null
|
||||
const amount = formatGrantAmount(info)
|
||||
if (!amount) return null
|
||||
|
||||
if (twoLine) {
|
||||
const title = getFeedTitle(amount)
|
||||
return (
|
||||
<>
|
||||
<Text color="claude">
|
||||
{maxWidth ? truncate(title, maxWidth) : title}
|
||||
</Text>
|
||||
<Text dimColor>
|
||||
{maxWidth ? truncate(FEED_SUBTITLE, maxWidth) : FEED_SUBTITLE}
|
||||
</Text>
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (t2 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t2;
|
||||
}
|
||||
return t1;
|
||||
|
||||
const text = getUsageText(amount)
|
||||
const display = maxWidth ? truncate(text, maxWidth) : text
|
||||
const highlightLen = Math.min(getFeedTitle(amount).length, display.length)
|
||||
|
||||
return (
|
||||
<Text dimColor>
|
||||
<Text color="claude">{display.slice(0, highlightLen)}</Text>
|
||||
{display.slice(highlightLen)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,15 +132,15 @@ export function OverageCreditUpsell(t0) {
|
||||
* Char budgets: title ≤19, subtitle ≤48.
|
||||
*/
|
||||
export function createOverageCreditFeed(): FeedConfig {
|
||||
const info = getCachedOverageCreditGrant();
|
||||
const amount = info ? formatGrantAmount(info) : null;
|
||||
const title = amount ? getFeedTitle(amount) : 'extra usage credit';
|
||||
const info = getCachedOverageCreditGrant()
|
||||
const amount = info ? formatGrantAmount(info) : null
|
||||
const title = amount ? getFeedTitle(amount) : 'extra usage credit'
|
||||
return {
|
||||
title,
|
||||
lines: [],
|
||||
customContent: {
|
||||
content: <Text dimColor>{FEED_SUBTITLE}</Text>,
|
||||
width: Math.max(title.length, FEED_SUBTITLE.length)
|
||||
}
|
||||
};
|
||||
width: Math.max(title.length, FEED_SUBTITLE.length),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +1,51 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { feature } from 'bun:bundle';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
|
||||
import { getInitialSettings } from '../../utils/settings/settings.js';
|
||||
import { isVoiceModeEnabled } from '../../voice/voiceModeEnabled.js';
|
||||
import { AnimatedAsterisk } from './AnimatedAsterisk.js';
|
||||
import { shouldShowOpus1mMergeNotice } from './Opus1mMergeNotice.js';
|
||||
const MAX_SHOW_COUNT = 3;
|
||||
export function VoiceModeNotice() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = feature("VOICE_MODE") ? <VoiceModeNoticeInner /> : null;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
import { feature } from 'bun:bundle'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Box, Text } from '../../ink.js'
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
|
||||
import { getInitialSettings } from '../../utils/settings/settings.js'
|
||||
import { isVoiceModeEnabled } from '../../voice/voiceModeEnabled.js'
|
||||
import { AnimatedAsterisk } from './AnimatedAsterisk.js'
|
||||
import { shouldShowOpus1mMergeNotice } from './Opus1mMergeNotice.js'
|
||||
|
||||
const MAX_SHOW_COUNT = 3
|
||||
|
||||
export function VoiceModeNotice(): React.ReactNode {
|
||||
// Positive ternary pattern — see docs/feature-gating.md.
|
||||
// All strings must be inside the guarded branch for dead-code elimination.
|
||||
return feature('VOICE_MODE') ? <VoiceModeNoticeInner /> : null
|
||||
}
|
||||
function VoiceModeNoticeInner() {
|
||||
const $ = _c(4);
|
||||
const [show] = useState(_temp);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== show) {
|
||||
t0 = () => {
|
||||
if (!show) {
|
||||
return;
|
||||
}
|
||||
const newCount = (getGlobalConfig().voiceNoticeSeenCount ?? 0) + 1;
|
||||
saveGlobalConfig(prev => {
|
||||
if ((prev.voiceNoticeSeenCount ?? 0) >= newCount) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
voiceNoticeSeenCount: newCount
|
||||
};
|
||||
});
|
||||
};
|
||||
t1 = [show];
|
||||
$[0] = show;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
let t2;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Box paddingLeft={2}><AnimatedAsterisk /><Text dimColor={true}> Voice mode is now available · /voice to enable</Text></Box>;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
function _temp() {
|
||||
return isVoiceModeEnabled() && getInitialSettings().voiceEnabled !== true && (getGlobalConfig().voiceNoticeSeenCount ?? 0) < MAX_SHOW_COUNT && !shouldShowOpus1mMergeNotice();
|
||||
|
||||
function VoiceModeNoticeInner(): React.ReactNode {
|
||||
// Capture eligibility once at mount — no reactive subscriptions. This sits
|
||||
// at the top of the message list and enters scrollback quickly; any
|
||||
// re-render after it's in scrollback would force a full terminal reset.
|
||||
// If the user runs /voice this session, the notice stays visible; it won't
|
||||
// show next session since voiceEnabled will be true on disk.
|
||||
const [show] = useState(
|
||||
() =>
|
||||
isVoiceModeEnabled() &&
|
||||
getInitialSettings().voiceEnabled !== true &&
|
||||
(getGlobalConfig().voiceNoticeSeenCount ?? 0) < MAX_SHOW_COUNT &&
|
||||
!shouldShowOpus1mMergeNotice(),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!show) return
|
||||
// Capture outside the updater so StrictMode's second invocation is a no-op.
|
||||
const newCount = (getGlobalConfig().voiceNoticeSeenCount ?? 0) + 1
|
||||
saveGlobalConfig(prev => {
|
||||
if ((prev.voiceNoticeSeenCount ?? 0) >= newCount) return prev
|
||||
return { ...prev, voiceNoticeSeenCount: newCount }
|
||||
})
|
||||
}, [show])
|
||||
|
||||
if (!show) return null
|
||||
|
||||
return (
|
||||
<Box paddingLeft={2}>
|
||||
<AnimatedAsterisk />
|
||||
<Text dimColor> Voice mode is now available · /voice to enable</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,432 +1,326 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import React from 'react';
|
||||
import { Box, Text, useTheme } from 'src/ink.js';
|
||||
import { env } from '../../utils/env.js';
|
||||
const WELCOME_V2_WIDTH = 58;
|
||||
export function WelcomeV2() {
|
||||
const $ = _c(35);
|
||||
const [theme] = useTheme();
|
||||
if (env.terminal === "Apple_Terminal") {
|
||||
let t0;
|
||||
if ($[0] !== theme) {
|
||||
t0 = <AppleTerminalWelcomeV2 theme={theme} welcomeMessage="Welcome to Claude Code" />;
|
||||
$[0] = theme;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
import React from 'react'
|
||||
import { Box, Text, useTheme } from 'src/ink.js'
|
||||
import { env } from '../../utils/env.js'
|
||||
|
||||
const WELCOME_V2_WIDTH = 58
|
||||
|
||||
export function WelcomeV2(): React.ReactNode {
|
||||
const [theme] = useTheme()
|
||||
const welcomeMessage = 'Welcome to Claude Code'
|
||||
|
||||
if (env.terminal === 'Apple_Terminal') {
|
||||
return (
|
||||
<AppleTerminalWelcomeV2 theme={theme} welcomeMessage={welcomeMessage} />
|
||||
)
|
||||
}
|
||||
if (["light", "light-daltonized", "light-ansi"].includes(theme)) {
|
||||
let t0;
|
||||
let t1;
|
||||
let t2;
|
||||
let t3;
|
||||
let t4;
|
||||
let t5;
|
||||
let t6;
|
||||
let t7;
|
||||
let t8;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <Text><Text color="claude">{"Welcome to Claude Code"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>;
|
||||
t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
|
||||
t2 = <Text>{" "}</Text>;
|
||||
t3 = <Text>{" "}</Text>;
|
||||
t4 = <Text>{" "}</Text>;
|
||||
t5 = <Text>{" \u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t6 = <Text>{" \u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t7 = <Text>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t8 = <Text>{" "}</Text>;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
$[4] = t2;
|
||||
$[5] = t3;
|
||||
$[6] = t4;
|
||||
$[7] = t5;
|
||||
$[8] = t6;
|
||||
$[9] = t7;
|
||||
$[10] = t8;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
t1 = $[3];
|
||||
t2 = $[4];
|
||||
t3 = $[5];
|
||||
t4 = $[6];
|
||||
t5 = $[7];
|
||||
t6 = $[8];
|
||||
t7 = $[9];
|
||||
t8 = $[10];
|
||||
}
|
||||
let t9;
|
||||
if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t9 = <Text><Text dimColor={true}>{" \u2591\u2591\u2591\u2591"}</Text><Text>{" \u2588\u2588 "}</Text></Text>;
|
||||
$[11] = t9;
|
||||
} else {
|
||||
t9 = $[11];
|
||||
}
|
||||
let t10;
|
||||
let t11;
|
||||
if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t10 = <Text><Text dimColor={true}>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591"}</Text><Text>{" \u2588\u2588\u2592\u2592\u2588\u2588 "}</Text></Text>;
|
||||
t11 = <Text>{" \u2592\u2592 \u2588\u2588 \u2592"}</Text>;
|
||||
$[12] = t10;
|
||||
$[13] = t11;
|
||||
} else {
|
||||
t10 = $[12];
|
||||
t11 = $[13];
|
||||
}
|
||||
let t12;
|
||||
if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t12 = <Text>{" "}<Text color="clawd_body"> █████████ </Text>{" \u2592\u2592\u2591\u2591\u2592\u2592 \u2592 \u2592\u2592"}</Text>;
|
||||
$[14] = t12;
|
||||
} else {
|
||||
t12 = $[14];
|
||||
}
|
||||
let t13;
|
||||
if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t13 = <Text>{" "}<Text color="clawd_body" backgroundColor="clawd_background">██▄█████▄██</Text>{" \u2592\u2592 \u2592\u2592 "}</Text>;
|
||||
$[15] = t13;
|
||||
} else {
|
||||
t13 = $[15];
|
||||
}
|
||||
let t14;
|
||||
if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t14 = <Text>{" "}<Text color="clawd_body"> █████████ </Text>{" \u2591 \u2592 "}</Text>;
|
||||
$[16] = t14;
|
||||
} else {
|
||||
t14 = $[16];
|
||||
}
|
||||
let t15;
|
||||
if ($[17] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t15 = <Box width={WELCOME_V2_WIDTH}><Text>{t0}{t1}{t2}{t3}{t4}{t5}{t6}{t7}{t8}{t9}{t10}{t11}{t12}{t13}{t14}<Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}<Text color="clawd_body">{"\u2588 \u2588 \u2588 \u2588"}</Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2591\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2592\u2026\u2026\u2026\u2026"}</Text></Text></Box>;
|
||||
$[17] = t15;
|
||||
} else {
|
||||
t15 = $[17];
|
||||
}
|
||||
return t15;
|
||||
|
||||
if (['light', 'light-daltonized', 'light-ansi'].includes(theme)) {
|
||||
return (
|
||||
<Box width={WELCOME_V2_WIDTH}>
|
||||
<Text>
|
||||
<Text>
|
||||
<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 dimColor>{' ░░░░'}</Text>
|
||||
<Text>{' ██ '}</Text>
|
||||
</Text>
|
||||
<Text>
|
||||
<Text dimColor>{' ░░░░░░░░░░'}</Text>
|
||||
<Text>{' ██▒▒██ '}</Text>
|
||||
</Text>
|
||||
<Text>
|
||||
{' ▒▒ ██ ▒'}
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text color="clawd_body"> █████████ </Text>
|
||||
{' ▒▒░░▒▒ ▒ ▒▒'}
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text color="clawd_body" backgroundColor="clawd_background">
|
||||
██▄█████▄██
|
||||
</Text>
|
||||
{' ▒▒ ▒▒ '}
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text color="clawd_body"> █████████ </Text>
|
||||
{' ░ ▒ '}
|
||||
</Text>
|
||||
<Text>
|
||||
{'…………………'}
|
||||
<Text color="clawd_body">{'█ █ █ █'}</Text>
|
||||
{'……………………………………………………………………░…………………………▒…………'}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
let t0;
|
||||
let t1;
|
||||
let t2;
|
||||
let t3;
|
||||
let t4;
|
||||
let t5;
|
||||
let t6;
|
||||
if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <Text><Text color="claude">{"Welcome to Claude Code"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>;
|
||||
t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
|
||||
t2 = <Text>{" "}</Text>;
|
||||
t3 = <Text>{" * \u2588\u2588\u2588\u2588\u2588\u2593\u2593\u2591 "}</Text>;
|
||||
t4 = <Text>{" * \u2588\u2588\u2588\u2593\u2591 \u2591\u2591 "}</Text>;
|
||||
t5 = <Text>{" \u2591\u2591\u2591\u2591\u2591\u2591 \u2588\u2588\u2588\u2593\u2591 "}</Text>;
|
||||
t6 = <Text>{" \u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2588\u2588\u2588\u2593\u2591 "}</Text>;
|
||||
$[18] = t0;
|
||||
$[19] = t1;
|
||||
$[20] = t2;
|
||||
$[21] = t3;
|
||||
$[22] = t4;
|
||||
$[23] = t5;
|
||||
$[24] = t6;
|
||||
} else {
|
||||
t0 = $[18];
|
||||
t1 = $[19];
|
||||
t2 = $[20];
|
||||
t3 = $[21];
|
||||
t4 = $[22];
|
||||
t5 = $[23];
|
||||
t6 = $[24];
|
||||
}
|
||||
let t10;
|
||||
let t11;
|
||||
let t7;
|
||||
let t8;
|
||||
let t9;
|
||||
if ($[25] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t7 = <Text><Text>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text><Text bold={true}>*</Text><Text>{" \u2588\u2588\u2593\u2591\u2591 \u2593 "}</Text></Text>;
|
||||
t8 = <Text>{" \u2591\u2593\u2593\u2588\u2588\u2588\u2593\u2593\u2591 "}</Text>;
|
||||
t9 = <Text dimColor={true}>{" * \u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t10 = <Text dimColor={true}>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t11 = <Text dimColor={true}>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
$[25] = t10;
|
||||
$[26] = t11;
|
||||
$[27] = t7;
|
||||
$[28] = t8;
|
||||
$[29] = t9;
|
||||
} else {
|
||||
t10 = $[25];
|
||||
t11 = $[26];
|
||||
t7 = $[27];
|
||||
t8 = $[28];
|
||||
t9 = $[29];
|
||||
}
|
||||
let t12;
|
||||
if ($[30] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t12 = <Text color="clawd_body"> █████████ </Text>;
|
||||
$[30] = t12;
|
||||
} else {
|
||||
t12 = $[30];
|
||||
}
|
||||
let t13;
|
||||
if ($[31] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t13 = <Text>{" "}{t12}{" "}<Text dimColor={true}>*</Text><Text> </Text></Text>;
|
||||
$[31] = t13;
|
||||
} else {
|
||||
t13 = $[31];
|
||||
}
|
||||
let t14;
|
||||
if ($[32] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t14 = <Text>{" "}<Text color="clawd_body">██▄█████▄██</Text><Text>{" "}</Text><Text bold={true}>*</Text><Text>{" "}</Text></Text>;
|
||||
$[32] = t14;
|
||||
} else {
|
||||
t14 = $[32];
|
||||
}
|
||||
let t15;
|
||||
if ($[33] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t15 = <Text>{" "}<Text color="clawd_body"> █████████ </Text>{" * "}</Text>;
|
||||
$[33] = t15;
|
||||
} else {
|
||||
t15 = $[33];
|
||||
}
|
||||
let t16;
|
||||
if ($[34] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t16 = <Box width={WELCOME_V2_WIDTH}><Text>{t0}{t1}{t2}{t3}{t4}{t5}{t6}{t7}{t8}{t9}{t10}{t11}{t13}{t14}{t15}<Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}<Text color="clawd_body">{"\u2588 \u2588 \u2588 \u2588"}</Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text></Text></Box>;
|
||||
$[34] = t16;
|
||||
} else {
|
||||
t16 = $[34];
|
||||
}
|
||||
return t16;
|
||||
|
||||
return (
|
||||
<Box width={WELCOME_V2_WIDTH}>
|
||||
<Text>
|
||||
<Text>
|
||||
<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 bold>*</Text>
|
||||
<Text>{' ██▓░░ ▓ '}</Text>
|
||||
</Text>
|
||||
<Text>
|
||||
{' ░▓▓███▓▓░ '}
|
||||
</Text>
|
||||
<Text dimColor>
|
||||
{' * ░░░░ '}
|
||||
</Text>
|
||||
<Text dimColor>
|
||||
{' ░░░░░░░░ '}
|
||||
</Text>
|
||||
<Text dimColor>
|
||||
{' ░░░░░░░░░░░░░░░░ '}
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text color="clawd_body"> █████████ </Text>
|
||||
{' '}
|
||||
<Text dimColor>*</Text>
|
||||
<Text> </Text>
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text color="clawd_body">██▄█████▄██</Text>
|
||||
<Text>{' '}</Text>
|
||||
<Text bold>*</Text>
|
||||
<Text>{' '}</Text>
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text color="clawd_body"> █████████ </Text>
|
||||
{' * '}
|
||||
</Text>
|
||||
<Text>
|
||||
{'…………………'}
|
||||
<Text color="clawd_body">{'█ █ █ █'}</Text>
|
||||
{'………………………………………………………………………………………………………………'}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
type AppleTerminalWelcomeV2Props = {
|
||||
theme: string;
|
||||
welcomeMessage: string;
|
||||
};
|
||||
function AppleTerminalWelcomeV2(t0) {
|
||||
const $ = _c(44);
|
||||
const {
|
||||
theme,
|
||||
welcomeMessage
|
||||
} = t0;
|
||||
const isLightTheme = ["light", "light-daltonized", "light-ansi"].includes(theme);
|
||||
if (isLightTheme) {
|
||||
let t1;
|
||||
if ($[0] !== welcomeMessage) {
|
||||
t1 = <Text color="claude">{welcomeMessage} </Text>;
|
||||
$[0] = welcomeMessage;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Text dimColor={true}>v{MACRO.VERSION} </Text>;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
let t3;
|
||||
if ($[3] !== t1) {
|
||||
t3 = <Text>{t1}{t2}</Text>;
|
||||
$[3] = t1;
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
}
|
||||
let t10;
|
||||
let t11;
|
||||
let t4;
|
||||
let t5;
|
||||
let t6;
|
||||
let t7;
|
||||
let t8;
|
||||
let t9;
|
||||
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
|
||||
t5 = <Text>{" "}</Text>;
|
||||
t6 = <Text>{" "}</Text>;
|
||||
t7 = <Text>{" "}</Text>;
|
||||
t8 = <Text>{" \u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t9 = <Text>{" \u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t10 = <Text>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t11 = <Text>{" "}</Text>;
|
||||
$[5] = t10;
|
||||
$[6] = t11;
|
||||
$[7] = t4;
|
||||
$[8] = t5;
|
||||
$[9] = t6;
|
||||
$[10] = t7;
|
||||
$[11] = t8;
|
||||
$[12] = t9;
|
||||
} else {
|
||||
t10 = $[5];
|
||||
t11 = $[6];
|
||||
t4 = $[7];
|
||||
t5 = $[8];
|
||||
t6 = $[9];
|
||||
t7 = $[10];
|
||||
t8 = $[11];
|
||||
t9 = $[12];
|
||||
}
|
||||
let t12;
|
||||
if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t12 = <Text><Text dimColor={true}>{" \u2591\u2591\u2591\u2591"}</Text><Text>{" \u2588\u2588 "}</Text></Text>;
|
||||
$[13] = t12;
|
||||
} else {
|
||||
t12 = $[13];
|
||||
}
|
||||
let t13;
|
||||
let t14;
|
||||
let t15;
|
||||
if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t13 = <Text><Text dimColor={true}>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591"}</Text><Text>{" \u2588\u2588\u2592\u2592\u2588\u2588 "}</Text></Text>;
|
||||
t14 = <Text>{" \u2592\u2592 \u2588\u2588 \u2592"}</Text>;
|
||||
t15 = <Text>{" \u2592\u2592\u2591\u2591\u2592\u2592 \u2592 \u2592\u2592"}</Text>;
|
||||
$[14] = t13;
|
||||
$[15] = t14;
|
||||
$[16] = t15;
|
||||
} else {
|
||||
t13 = $[14];
|
||||
t14 = $[15];
|
||||
t15 = $[16];
|
||||
}
|
||||
let t16;
|
||||
if ($[17] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t16 = <Text>{" "}<Text color="clawd_body">▗</Text><Text color="clawd_background" backgroundColor="clawd_body">{" "}▗{" "}▖{" "}</Text><Text color="clawd_body">▖</Text>{" \u2592\u2592 \u2592\u2592 "}</Text>;
|
||||
$[17] = t16;
|
||||
} else {
|
||||
t16 = $[17];
|
||||
}
|
||||
let t17;
|
||||
if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t17 = <Text>{" "}<Text backgroundColor="clawd_body">{" ".repeat(9)}</Text>{" \u2591 \u2592 "}</Text>;
|
||||
$[18] = t17;
|
||||
} else {
|
||||
t17 = $[18];
|
||||
}
|
||||
let t18;
|
||||
if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t18 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}<Text backgroundColor="clawd_body"> </Text><Text> </Text><Text backgroundColor="clawd_body"> </Text><Text>{" "}</Text><Text backgroundColor="clawd_body"> </Text><Text> </Text><Text backgroundColor="clawd_body"> </Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2591\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2592\u2026\u2026\u2026\u2026"}</Text>;
|
||||
$[19] = t18;
|
||||
} else {
|
||||
t18 = $[19];
|
||||
}
|
||||
let t19;
|
||||
if ($[20] !== t3) {
|
||||
t19 = <Box width={WELCOME_V2_WIDTH}><Text>{t3}{t4}{t5}{t6}{t7}{t8}{t9}{t10}{t11}{t12}{t13}{t14}{t15}{t16}{t17}{t18}</Text></Box>;
|
||||
$[20] = t3;
|
||||
$[21] = t19;
|
||||
} else {
|
||||
t19 = $[21];
|
||||
}
|
||||
return t19;
|
||||
}
|
||||
let t1;
|
||||
if ($[22] !== welcomeMessage) {
|
||||
t1 = <Text color="claude">{welcomeMessage} </Text>;
|
||||
$[22] = welcomeMessage;
|
||||
$[23] = t1;
|
||||
} else {
|
||||
t1 = $[23];
|
||||
}
|
||||
let t2;
|
||||
if ($[24] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Text dimColor={true}>v{MACRO.VERSION} </Text>;
|
||||
$[24] = t2;
|
||||
} else {
|
||||
t2 = $[24];
|
||||
}
|
||||
let t3;
|
||||
if ($[25] !== t1) {
|
||||
t3 = <Text>{t1}{t2}</Text>;
|
||||
$[25] = t1;
|
||||
$[26] = t3;
|
||||
} else {
|
||||
t3 = $[26];
|
||||
}
|
||||
let t4;
|
||||
let t5;
|
||||
let t6;
|
||||
let t7;
|
||||
let t8;
|
||||
let t9;
|
||||
if ($[27] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
|
||||
t5 = <Text>{" "}</Text>;
|
||||
t6 = <Text>{" * \u2588\u2588\u2588\u2588\u2588\u2593\u2593\u2591 "}</Text>;
|
||||
t7 = <Text>{" * \u2588\u2588\u2588\u2593\u2591 \u2591\u2591 "}</Text>;
|
||||
t8 = <Text>{" \u2591\u2591\u2591\u2591\u2591\u2591 \u2588\u2588\u2588\u2593\u2591 "}</Text>;
|
||||
t9 = <Text>{" \u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2588\u2588\u2588\u2593\u2591 "}</Text>;
|
||||
$[27] = t4;
|
||||
$[28] = t5;
|
||||
$[29] = t6;
|
||||
$[30] = t7;
|
||||
$[31] = t8;
|
||||
$[32] = t9;
|
||||
} else {
|
||||
t4 = $[27];
|
||||
t5 = $[28];
|
||||
t6 = $[29];
|
||||
t7 = $[30];
|
||||
t8 = $[31];
|
||||
t9 = $[32];
|
||||
}
|
||||
let t10;
|
||||
let t11;
|
||||
let t12;
|
||||
let t13;
|
||||
let t14;
|
||||
if ($[33] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t10 = <Text><Text>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text><Text bold={true}>*</Text><Text>{" \u2588\u2588\u2593\u2591\u2591 \u2593 "}</Text></Text>;
|
||||
t11 = <Text>{" \u2591\u2593\u2593\u2588\u2588\u2588\u2593\u2593\u2591 "}</Text>;
|
||||
t12 = <Text dimColor={true}>{" * \u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t13 = <Text dimColor={true}>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
t14 = <Text dimColor={true}>{" \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 "}</Text>;
|
||||
$[33] = t10;
|
||||
$[34] = t11;
|
||||
$[35] = t12;
|
||||
$[36] = t13;
|
||||
$[37] = t14;
|
||||
} else {
|
||||
t10 = $[33];
|
||||
t11 = $[34];
|
||||
t12 = $[35];
|
||||
t13 = $[36];
|
||||
t14 = $[37];
|
||||
}
|
||||
let t15;
|
||||
if ($[38] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t15 = <Text>{" "}<Text dimColor={true}>*</Text><Text> </Text></Text>;
|
||||
$[38] = t15;
|
||||
} else {
|
||||
t15 = $[38];
|
||||
}
|
||||
let t16;
|
||||
if ($[39] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t16 = <Text>{" "}<Text color="clawd_body">▗</Text><Text color="clawd_background" backgroundColor="clawd_body">{" "}▗{" "}▖{" "}</Text><Text color="clawd_body">▖</Text><Text>{" "}</Text><Text bold={true}>*</Text><Text>{" "}</Text></Text>;
|
||||
$[39] = t16;
|
||||
} else {
|
||||
t16 = $[39];
|
||||
}
|
||||
let t17;
|
||||
if ($[40] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t17 = <Text>{" "}<Text backgroundColor="clawd_body">{" ".repeat(9)}</Text>{" * "}</Text>;
|
||||
$[40] = t17;
|
||||
} else {
|
||||
t17 = $[40];
|
||||
}
|
||||
let t18;
|
||||
if ($[41] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t18 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}<Text backgroundColor="clawd_body"> </Text><Text> </Text><Text backgroundColor="clawd_body"> </Text><Text>{" "}</Text><Text backgroundColor="clawd_body"> </Text><Text> </Text><Text backgroundColor="clawd_body"> </Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
|
||||
$[41] = t18;
|
||||
} else {
|
||||
t18 = $[41];
|
||||
}
|
||||
let t19;
|
||||
if ($[42] !== t3) {
|
||||
t19 = <Box width={WELCOME_V2_WIDTH}><Text>{t3}{t4}{t5}{t6}{t7}{t8}{t9}{t10}{t11}{t12}{t13}{t14}{t15}{t16}{t17}{t18}</Text></Box>;
|
||||
$[42] = t3;
|
||||
$[43] = t19;
|
||||
} else {
|
||||
t19 = $[43];
|
||||
}
|
||||
return t19;
|
||||
theme: string
|
||||
welcomeMessage: string
|
||||
}
|
||||
|
||||
function AppleTerminalWelcomeV2({
|
||||
theme,
|
||||
welcomeMessage,
|
||||
}: AppleTerminalWelcomeV2Props): React.ReactNode {
|
||||
const isLightTheme = ['light', 'light-daltonized', 'light-ansi'].includes(
|
||||
theme,
|
||||
)
|
||||
|
||||
if (isLightTheme) {
|
||||
return (
|
||||
<Box width={WELCOME_V2_WIDTH}>
|
||||
<Text>
|
||||
<Text>
|
||||
<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 dimColor>{' ░░░░'}</Text>
|
||||
<Text>{' ██ '}</Text>
|
||||
</Text>
|
||||
<Text>
|
||||
<Text dimColor>{' ░░░░░░░░░░'}</Text>
|
||||
<Text>{' ██▒▒██ '}</Text>
|
||||
</Text>
|
||||
<Text>
|
||||
{' ▒▒ ██ ▒'}
|
||||
</Text>
|
||||
<Text>
|
||||
{' ▒▒░░▒▒ ▒ ▒▒'}
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text color="clawd_body">▗</Text>
|
||||
<Text color="clawd_background" backgroundColor="clawd_body">
|
||||
{' '}
|
||||
▗{' '}▖{' '}
|
||||
</Text>
|
||||
<Text color="clawd_body">▖</Text>
|
||||
{' ▒▒ ▒▒ '}
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text backgroundColor="clawd_body">{' '.repeat(9)}</Text>
|
||||
{' ░ ▒ '}
|
||||
</Text>
|
||||
<Text>
|
||||
{'…………………'}
|
||||
<Text backgroundColor="clawd_body"> </Text>
|
||||
<Text> </Text>
|
||||
<Text backgroundColor="clawd_body"> </Text>
|
||||
<Text>{' '}</Text>
|
||||
<Text backgroundColor="clawd_body"> </Text>
|
||||
<Text> </Text>
|
||||
<Text backgroundColor="clawd_body"> </Text>
|
||||
{'……………………………………………………………………░…………………………▒…………'}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box width={WELCOME_V2_WIDTH}>
|
||||
<Text>
|
||||
<Text>
|
||||
<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 bold>*</Text>
|
||||
<Text>{' ██▓░░ ▓ '}</Text>
|
||||
</Text>
|
||||
<Text>
|
||||
{' ░▓▓███▓▓░ '}
|
||||
</Text>
|
||||
<Text dimColor>
|
||||
{' * ░░░░ '}
|
||||
</Text>
|
||||
<Text dimColor>
|
||||
{' ░░░░░░░░ '}
|
||||
</Text>
|
||||
<Text dimColor>
|
||||
{' ░░░░░░░░░░░░░░░░ '}
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text dimColor>*</Text>
|
||||
<Text> </Text>
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text color="clawd_body">▗</Text>
|
||||
<Text color="clawd_background" backgroundColor="clawd_body">
|
||||
{' '}
|
||||
▗{' '}▖{' '}
|
||||
</Text>
|
||||
<Text color="clawd_body">▖</Text>
|
||||
<Text>{' '}</Text>
|
||||
<Text bold>*</Text>
|
||||
<Text>{' '}</Text>
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
<Text backgroundColor="clawd_body">{' '.repeat(9)}</Text>
|
||||
{' * '}
|
||||
</Text>
|
||||
<Text>
|
||||
{'…………………'}
|
||||
<Text backgroundColor="clawd_body"> </Text>
|
||||
<Text> </Text>
|
||||
<Text backgroundColor="clawd_body"> </Text>
|
||||
<Text>{' '}</Text>
|
||||
<Text backgroundColor="clawd_body"> </Text>
|
||||
<Text> </Text>
|
||||
<Text backgroundColor="clawd_body"> </Text>
|
||||
{'………………………………………………………………………………………………………………'}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,91 +1,117 @@
|
||||
import figures from 'figures';
|
||||
import { homedir } from 'os';
|
||||
import * as React from 'react';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
import type { Step } from '../../projectOnboardingState.js';
|
||||
import { formatCreditAmount, getCachedReferrerReward } from '../../services/api/referral.js';
|
||||
import type { LogOption } from '../../types/logs.js';
|
||||
import { getCwd } from '../../utils/cwd.js';
|
||||
import { formatRelativeTimeAgo } from '../../utils/format.js';
|
||||
import type { FeedConfig, FeedLine } from './Feed.js';
|
||||
import figures from 'figures'
|
||||
import { homedir } from 'os'
|
||||
import * as React from 'react'
|
||||
import { Box, Text } from '../../ink.js'
|
||||
import type { Step } from '../../projectOnboardingState.js'
|
||||
import {
|
||||
formatCreditAmount,
|
||||
getCachedReferrerReward,
|
||||
} from '../../services/api/referral.js'
|
||||
import type { LogOption } from '../../types/logs.js'
|
||||
import { getCwd } from '../../utils/cwd.js'
|
||||
import { formatRelativeTimeAgo } from '../../utils/format.js'
|
||||
import type { FeedConfig, FeedLine } from './Feed.js'
|
||||
|
||||
export function createRecentActivityFeed(activities: LogOption[]): FeedConfig {
|
||||
const lines: FeedLine[] = activities.map(log => {
|
||||
const time = formatRelativeTimeAgo(log.modified);
|
||||
const description = log.summary && log.summary !== 'No prompt' ? log.summary : log.firstPrompt;
|
||||
const time = formatRelativeTimeAgo(log.modified)
|
||||
const description =
|
||||
log.summary && log.summary !== 'No prompt' ? log.summary : log.firstPrompt
|
||||
|
||||
return {
|
||||
text: description || '',
|
||||
timestamp: time
|
||||
};
|
||||
});
|
||||
timestamp: time,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
title: 'Recent activity',
|
||||
lines,
|
||||
footer: lines.length > 0 ? '/resume for more' : undefined,
|
||||
emptyMessage: 'No recent activity'
|
||||
};
|
||||
emptyMessage: 'No recent activity',
|
||||
}
|
||||
}
|
||||
|
||||
export function createWhatsNewFeed(releaseNotes: string[]): FeedConfig {
|
||||
const lines: FeedLine[] = releaseNotes.map(note => {
|
||||
if ((process.env.USER_TYPE) === 'ant') {
|
||||
const match = note.match(/^(\d+\s+\w+\s+ago)\s+(.+)$/);
|
||||
if (process.env.USER_TYPE === 'ant') {
|
||||
const match = note.match(/^(\d+\s+\w+\s+ago)\s+(.+)$/)
|
||||
if (match) {
|
||||
return {
|
||||
timestamp: match[1],
|
||||
text: match[2] || ''
|
||||
};
|
||||
text: match[2] || '',
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
text: note
|
||||
};
|
||||
});
|
||||
const emptyMessage = (process.env.USER_TYPE) === 'ant' ? 'Unable to fetch latest claude-cli-internal commits' : 'Check the Claude Code changelog for updates';
|
||||
text: note,
|
||||
}
|
||||
})
|
||||
|
||||
const emptyMessage =
|
||||
process.env.USER_TYPE === 'ant'
|
||||
? 'Unable to fetch latest claude-cli-internal commits'
|
||||
: 'Check the Claude Code changelog for updates'
|
||||
|
||||
return {
|
||||
title: (process.env.USER_TYPE) === 'ant' ? "What's new [ANT-ONLY: Latest CC commits]" : "What's new",
|
||||
title:
|
||||
process.env.USER_TYPE === 'ant'
|
||||
? "What's new [ANT-ONLY: Latest CC commits]"
|
||||
: "What's new",
|
||||
lines,
|
||||
footer: lines.length > 0 ? '/release-notes for more' : undefined,
|
||||
emptyMessage
|
||||
};
|
||||
emptyMessage,
|
||||
}
|
||||
}
|
||||
|
||||
export function createProjectOnboardingFeed(steps: Step[]): FeedConfig {
|
||||
const enabledSteps = steps.filter(({
|
||||
isEnabled
|
||||
}) => isEnabled).sort((a, b) => Number(a.isComplete) - Number(b.isComplete));
|
||||
const lines: FeedLine[] = enabledSteps.map(({
|
||||
text,
|
||||
isComplete
|
||||
}) => {
|
||||
const checkmark = isComplete ? `${figures.tick} ` : '';
|
||||
const enabledSteps = steps
|
||||
.filter(({ isEnabled }) => isEnabled)
|
||||
.sort((a, b) => Number(a.isComplete) - Number(b.isComplete))
|
||||
|
||||
const lines: FeedLine[] = enabledSteps.map(({ text, isComplete }) => {
|
||||
const checkmark = isComplete ? `${figures.tick} ` : ''
|
||||
return {
|
||||
text: `${checkmark}${text}`
|
||||
};
|
||||
});
|
||||
const warningText = getCwd() === homedir() ? 'Note: You have launched claude in your home directory. For the best experience, launch it in a project directory instead.' : undefined;
|
||||
text: `${checkmark}${text}`,
|
||||
}
|
||||
})
|
||||
|
||||
const warningText =
|
||||
getCwd() === homedir()
|
||||
? 'Note: You have launched claude in your home directory. For the best experience, launch it in a project directory instead.'
|
||||
: undefined
|
||||
|
||||
if (warningText) {
|
||||
lines.push({
|
||||
text: warningText
|
||||
});
|
||||
text: warningText,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
title: 'Tips for getting started',
|
||||
lines
|
||||
};
|
||||
lines,
|
||||
}
|
||||
}
|
||||
|
||||
export function createGuestPassesFeed(): FeedConfig {
|
||||
const reward = getCachedReferrerReward();
|
||||
const subtitle = reward ? `Share Claude Code and earn ${formatCreditAmount(reward)} of extra usage` : 'Share Claude Code with friends';
|
||||
const reward = getCachedReferrerReward()
|
||||
const subtitle = reward
|
||||
? `Share Claude Code and earn ${formatCreditAmount(reward)} of extra usage`
|
||||
: 'Share Claude Code with friends'
|
||||
return {
|
||||
title: '3 guest passes',
|
||||
lines: [],
|
||||
customContent: {
|
||||
content: <>
|
||||
content: (
|
||||
<>
|
||||
<Box marginY={1}>
|
||||
<Text color="claude">[✻] [✻] [✻]</Text>
|
||||
</Box>
|
||||
<Text dimColor>{subtitle}</Text>
|
||||
</>,
|
||||
width: 48
|
||||
</>
|
||||
),
|
||||
width: 48,
|
||||
},
|
||||
footer: '/passes'
|
||||
};
|
||||
footer: '/passes',
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user