更新大量 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:
claude-code-best
2026-04-04 23:24:27 +08:00
committed by GitHub
parent 02694918b5
commit 5b1a52b8e0
559 changed files with 103807 additions and 101817 deletions

View File

@@ -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>
)
}

View File

@@ -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 }
}

View File

@@ -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
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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,
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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

View File

@@ -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>
)
}

View File

@@ -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),
},
}
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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',
}
}