更新大量 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,59 +1,67 @@
import * as React from 'react';
import { getOauthProfileFromApiKey } from 'src/services/oauth/getOauthProfile.js';
import { isClaudeAISubscriber } from 'src/utils/auth.js';
import { Text } from '../../ink.js';
import { logEvent } from '../../services/analytics/index.js';
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
import { useStartupNotification } from './useStartupNotification.js';
const MAX_SHOW_COUNT = 3;
import * as React from 'react'
import { getOauthProfileFromApiKey } from 'src/services/oauth/getOauthProfile.js'
import { isClaudeAISubscriber } from 'src/utils/auth.js'
import { Text } from '../../ink.js'
import { logEvent } from '../../services/analytics/index.js'
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
import { useStartupNotification } from './useStartupNotification.js'
const MAX_SHOW_COUNT = 3
/**
* Hook to check if the user has a subscription on Console but isn't logged into it.
*/
export function useCanSwitchToExistingSubscription() {
useStartupNotification(_temp2);
export function useCanSwitchToExistingSubscription(): void {
useStartupNotification(async () => {
if ((getGlobalConfig().subscriptionNoticeCount ?? 0) >= MAX_SHOW_COUNT) {
return null
}
const subscriptionType = await getExistingClaudeSubscription()
if (subscriptionType === null) return null
saveGlobalConfig(current => ({
...current,
subscriptionNoticeCount: (current.subscriptionNoticeCount ?? 0) + 1,
}))
logEvent('tengu_switch_to_subscription_notice_shown', {})
return {
key: 'switch-to-subscription',
jsx: (
<Text color="suggestion">
Use your existing Claude {subscriptionType} plan with Claude Code
<Text color="text" dimColor>
{' '}
· /login to activate
</Text>
</Text>
),
priority: 'low',
}
})
}
/**
* Checks if the user has a subscription but is not currently logged into it.
* This helps inform users they should run /login to access their subscription.
*/
async function _temp2() {
if ((getGlobalConfig().subscriptionNoticeCount ?? 0) >= MAX_SHOW_COUNT) {
return null;
}
const subscriptionType = await getExistingClaudeSubscription();
if (subscriptionType === null) {
return null;
}
saveGlobalConfig(_temp);
logEvent("tengu_switch_to_subscription_notice_shown", {});
return {
key: "switch-to-subscription",
jsx: <Text color="suggestion">Use your existing Claude {subscriptionType} plan with Claude Code<Text color="text" dimColor={true}>{" "}· /login to activate</Text></Text>,
priority: "low"
};
}
function _temp(current) {
return {
...current,
subscriptionNoticeCount: (current.subscriptionNoticeCount ?? 0) + 1
};
}
async function getExistingClaudeSubscription(): Promise<'Max' | 'Pro' | null> {
// If already using subscription auth, there is nothing to switch to
if (isClaudeAISubscriber()) {
return null;
return null
}
const profile = await getOauthProfileFromApiKey();
const profile = await getOauthProfileFromApiKey()
if (!profile) {
return null;
return null
}
if (profile.account.has_claude_max) {
return 'Max';
return 'Max'
}
if (profile.account.has_claude_pro) {
return 'Pro';
return 'Pro'
}
return null;
return null
}

View File

@@ -1,43 +1,30 @@
import { c as _c } from "react/compiler-runtime";
import { useEffect, useRef } from 'react';
import { useNotifications } from 'src/context/notifications.js';
import { getModelDeprecationWarning } from 'src/utils/model/deprecation.js';
import { getIsRemoteMode } from '../../bootstrap/state.js';
export function useDeprecationWarningNotification(model) {
const $ = _c(4);
const {
addNotification
} = useNotifications();
const lastWarningRef = useRef(null);
let t0;
let t1;
if ($[0] !== addNotification || $[1] !== model) {
t0 = () => {
if (getIsRemoteMode()) {
return;
}
const deprecationWarning = getModelDeprecationWarning(model);
if (deprecationWarning && deprecationWarning !== lastWarningRef.current) {
lastWarningRef.current = deprecationWarning;
addNotification({
key: "model-deprecation-warning",
text: deprecationWarning,
color: "warning",
priority: "high"
});
}
if (!deprecationWarning) {
lastWarningRef.current = null;
}
};
t1 = [model, addNotification];
$[0] = addNotification;
$[1] = model;
$[2] = t0;
$[3] = t1;
} else {
t0 = $[2];
t1 = $[3];
}
useEffect(t0, t1);
import { useEffect, useRef } from 'react'
import { useNotifications } from 'src/context/notifications.js'
import { getModelDeprecationWarning } from 'src/utils/model/deprecation.js'
import { getIsRemoteMode } from '../../bootstrap/state.js'
export function useDeprecationWarningNotification(model: string): void {
const { addNotification } = useNotifications()
const lastWarningRef = useRef<string | null>(null)
useEffect(() => {
if (getIsRemoteMode()) return
const deprecationWarning = getModelDeprecationWarning(model)
// Show warning if model is deprecated and we haven't shown this exact warning yet
if (deprecationWarning && deprecationWarning !== lastWarningRef.current) {
lastWarningRef.current = deprecationWarning
addNotification({
key: 'model-deprecation-warning',
text: deprecationWarning,
color: 'warning',
priority: 'high',
})
}
// Reset tracking if model changes to non-deprecated
if (!deprecationWarning) {
lastWarningRef.current = null
}
}, [model, addNotification])
}

View File

@@ -1,161 +1,111 @@
import { c as _c } from "react/compiler-runtime";
import { useEffect } from 'react';
import { useNotifications } from 'src/context/notifications.js';
import { useAppState, useSetAppState } from 'src/state/AppState.js';
import { type CooldownReason, isFastModeEnabled, onCooldownExpired, onCooldownTriggered, onFastModeOverageRejection, onOrgFastModeChanged } from 'src/utils/fastMode.js';
import { formatDuration } from 'src/utils/format.js';
import { getIsRemoteMode } from '../../bootstrap/state.js';
const COOLDOWN_STARTED_KEY = 'fast-mode-cooldown-started';
const COOLDOWN_EXPIRED_KEY = 'fast-mode-cooldown-expired';
const ORG_CHANGED_KEY = 'fast-mode-org-changed';
const OVERAGE_REJECTED_KEY = 'fast-mode-overage-rejected';
export function useFastModeNotification() {
const $ = _c(13);
const {
addNotification
} = useNotifications();
const isFastMode = useAppState(_temp);
const setAppState = useSetAppState();
let t0;
let t1;
if ($[0] !== addNotification || $[1] !== isFastMode || $[2] !== setAppState) {
t0 = () => {
if (getIsRemoteMode()) {
return;
}
if (!isFastModeEnabled()) {
return;
}
return onOrgFastModeChanged(orgEnabled => {
if (orgEnabled) {
addNotification({
key: ORG_CHANGED_KEY,
color: "fastMode",
priority: "immediate",
text: "Fast mode is now available \xB7 /fast to turn on"
});
} else {
if (isFastMode) {
setAppState(_temp2);
addNotification({
key: ORG_CHANGED_KEY,
color: "warning",
priority: "immediate",
text: "Fast mode has been disabled by your organization"
});
}
}
});
};
t1 = [addNotification, isFastMode, setAppState];
$[0] = addNotification;
$[1] = isFastMode;
$[2] = setAppState;
$[3] = t0;
$[4] = t1;
} else {
t0 = $[3];
t1 = $[4];
}
useEffect(t0, t1);
let t2;
let t3;
if ($[5] !== addNotification || $[6] !== setAppState) {
t2 = () => {
if (getIsRemoteMode()) {
return;
}
if (!isFastModeEnabled()) {
return;
}
return onFastModeOverageRejection(message => {
setAppState(_temp3);
import { useEffect } from 'react'
import { useNotifications } from 'src/context/notifications.js'
import { useAppState, useSetAppState } from 'src/state/AppState.js'
import {
type CooldownReason,
isFastModeEnabled,
onCooldownExpired,
onCooldownTriggered,
onFastModeOverageRejection,
onOrgFastModeChanged,
} from 'src/utils/fastMode.js'
import { formatDuration } from 'src/utils/format.js'
import { getIsRemoteMode } from '../../bootstrap/state.js'
const COOLDOWN_STARTED_KEY = 'fast-mode-cooldown-started'
const COOLDOWN_EXPIRED_KEY = 'fast-mode-cooldown-expired'
const ORG_CHANGED_KEY = 'fast-mode-org-changed'
const OVERAGE_REJECTED_KEY = 'fast-mode-overage-rejected'
export function useFastModeNotification(): void {
const { addNotification } = useNotifications()
const isFastMode = useAppState(s => s.fastMode)
const setAppState = useSetAppState()
// Notify when org fast mode status changes
useEffect(() => {
if (getIsRemoteMode()) return
if (!isFastModeEnabled()) {
return
}
return onOrgFastModeChanged(orgEnabled => {
if (orgEnabled) {
addNotification({
key: OVERAGE_REJECTED_KEY,
color: "warning",
priority: "immediate",
text: message
});
});
};
t3 = [addNotification, setAppState];
$[5] = addNotification;
$[6] = setAppState;
$[7] = t2;
$[8] = t3;
} else {
t2 = $[7];
t3 = $[8];
}
useEffect(t2, t3);
let t4;
let t5;
if ($[9] !== addNotification || $[10] !== isFastMode) {
t4 = () => {
if (getIsRemoteMode()) {
return;
}
if (!isFastMode) {
return;
}
const unsubTriggered = onCooldownTriggered((resetAt, reason) => {
const resetIn = formatDuration(resetAt - Date.now(), {
hideTrailingZeros: true
});
const message_0 = getCooldownMessage(reason, resetIn);
key: ORG_CHANGED_KEY,
color: 'fastMode',
priority: 'immediate',
text: 'Fast mode is now available · /fast to turn on',
})
} else if (isFastMode) {
// Org disabled fast mode — permanently turn off fast mode
setAppState(prev => ({ ...prev, fastMode: false }))
addNotification({
key: COOLDOWN_STARTED_KEY,
invalidates: [COOLDOWN_EXPIRED_KEY],
text: message_0,
color: "warning",
priority: "immediate"
});
});
const unsubExpired = onCooldownExpired(() => {
addNotification({
key: COOLDOWN_EXPIRED_KEY,
invalidates: [COOLDOWN_STARTED_KEY],
color: "fastMode",
text: "Fast limit reset \xB7 now using fast mode",
priority: "immediate"
});
});
return () => {
unsubTriggered();
unsubExpired();
};
};
t5 = [addNotification, isFastMode];
$[9] = addNotification;
$[10] = isFastMode;
$[11] = t4;
$[12] = t5;
} else {
t4 = $[11];
t5 = $[12];
}
useEffect(t4, t5);
}
function _temp3(prev_0) {
return {
...prev_0,
fastMode: false
};
}
function _temp2(prev) {
return {
...prev,
fastMode: false
};
}
function _temp(s) {
return s.fastMode;
key: ORG_CHANGED_KEY,
color: 'warning',
priority: 'immediate',
text: 'Fast mode has been disabled by your organization',
})
}
})
}, [addNotification, isFastMode, setAppState])
// Notify when fast mode is rejected due to overage/extra usage issues
useEffect(() => {
if (getIsRemoteMode()) return
if (!isFastModeEnabled()) return
return onFastModeOverageRejection(message => {
setAppState(prev => ({ ...prev, fastMode: false }))
addNotification({
key: OVERAGE_REJECTED_KEY,
color: 'warning',
priority: 'immediate',
text: message,
})
})
}, [addNotification, setAppState])
useEffect(() => {
if (getIsRemoteMode()) return
if (!isFastMode) {
return
}
const unsubTriggered = onCooldownTriggered((resetAt, reason) => {
const resetIn = formatDuration(resetAt - Date.now(), {
hideTrailingZeros: true,
})
const message = getCooldownMessage(reason, resetIn)
addNotification({
key: COOLDOWN_STARTED_KEY,
invalidates: [COOLDOWN_EXPIRED_KEY],
text: message,
color: 'warning',
priority: 'immediate',
})
})
const unsubExpired = onCooldownExpired(() => {
addNotification({
key: COOLDOWN_EXPIRED_KEY,
invalidates: [COOLDOWN_STARTED_KEY],
color: 'fastMode',
text: `Fast limit reset · now using fast mode`,
priority: 'immediate',
})
})
return () => {
unsubTriggered()
unsubExpired()
}
}, [addNotification, isFastMode])
}
function getCooldownMessage(reason: CooldownReason, resetIn: string): string {
switch (reason) {
case 'overloaded':
return `Fast mode overloaded and is temporarily unavailable · resets in ${resetIn}`;
return `Fast mode overloaded and is temporarily unavailable · resets in ${resetIn}`
case 'rate_limit':
return `Fast limit reached and temporarily disabled · resets in ${resetIn}`;
return `Fast limit reached and temporarily disabled · resets in ${resetIn}`
}
}

View File

@@ -1,185 +1,159 @@
import { c as _c } from "react/compiler-runtime";
import React, { useEffect, useRef } from 'react';
import { useNotifications } from 'src/context/notifications.js';
import { Text } from 'src/ink.js';
import type { MCPServerConnection } from 'src/services/mcp/types.js';
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js';
import { detectIDEs, type IDEExtensionInstallationStatus, isJetBrainsIde, isSupportedTerminal } from 'src/utils/ide.js';
import { getIsRemoteMode } from '../../bootstrap/state.js';
import { useIdeConnectionStatus } from '../useIdeConnectionStatus.js';
import type { IDESelection } from '../useIdeSelection.js';
const MAX_IDE_HINT_SHOW_COUNT = 5;
import React, { useEffect, useRef } from 'react'
import { useNotifications } from 'src/context/notifications.js'
import { Text } from 'src/ink.js'
import type { MCPServerConnection } from 'src/services/mcp/types.js'
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js'
import {
detectIDEs,
type IDEExtensionInstallationStatus,
isJetBrainsIde,
isSupportedTerminal,
} from 'src/utils/ide.js'
import { getIsRemoteMode } from '../../bootstrap/state.js'
import { useIdeConnectionStatus } from '../useIdeConnectionStatus.js'
import type { IDESelection } from '../useIdeSelection.js'
const MAX_IDE_HINT_SHOW_COUNT = 5
type Props = {
ideInstallationStatus: IDEExtensionInstallationStatus | null;
ideSelection: IDESelection | undefined;
mcpClients: MCPServerConnection[];
};
export function useIDEStatusIndicator(t0) {
const $ = _c(26);
const {
ideSelection,
mcpClients,
ideInstallationStatus
} = t0;
const {
addNotification,
removeNotification
} = useNotifications();
const {
status: ideStatus,
ideName
} = useIdeConnectionStatus(mcpClients);
const hasShownHintRef = useRef(false);
let t1;
if ($[0] !== ideInstallationStatus) {
t1 = ideInstallationStatus ? isJetBrainsIde(ideInstallationStatus?.ideType) : false;
$[0] = ideInstallationStatus;
$[1] = t1;
} else {
t1 = $[1];
}
const isJetBrains = t1;
const showIDEInstallErrorOrJetBrainsInfo = ideInstallationStatus?.error || isJetBrains;
const shouldShowIdeSelection = ideStatus === "connected" && (ideSelection?.filePath || ideSelection?.text && ideSelection.lineCount > 0);
const shouldShowConnected = ideStatus === "connected" && !shouldShowIdeSelection;
const showIDEInstallError = showIDEInstallErrorOrJetBrainsInfo && !isJetBrains && !shouldShowConnected && !shouldShowIdeSelection;
const showJetBrainsInfo = showIDEInstallErrorOrJetBrainsInfo && isJetBrains && !shouldShowConnected && !shouldShowIdeSelection;
let t2;
let t3;
if ($[2] !== addNotification || $[3] !== ideStatus || $[4] !== removeNotification || $[5] !== showJetBrainsInfo) {
t2 = () => {
if (getIsRemoteMode()) {
return;
}
if (isSupportedTerminal() || ideStatus !== null || showJetBrainsInfo) {
removeNotification("ide-status-hint");
return;
}
if (hasShownHintRef.current || (getGlobalConfig().ideHintShownCount ?? 0) >= MAX_IDE_HINT_SHOW_COUNT) {
return;
}
const timeoutId = setTimeout(_temp2, 3000, hasShownHintRef, addNotification);
return () => clearTimeout(timeoutId);
};
t3 = [addNotification, removeNotification, ideStatus, showJetBrainsInfo];
$[2] = addNotification;
$[3] = ideStatus;
$[4] = removeNotification;
$[5] = showJetBrainsInfo;
$[6] = t2;
$[7] = t3;
} else {
t2 = $[6];
t3 = $[7];
}
useEffect(t2, t3);
let t4;
let t5;
if ($[8] !== addNotification || $[9] !== ideName || $[10] !== ideStatus || $[11] !== removeNotification || $[12] !== showIDEInstallError || $[13] !== showJetBrainsInfo) {
t4 = () => {
if (getIsRemoteMode()) {
return;
}
if (showIDEInstallError || showJetBrainsInfo || ideStatus !== "disconnected" || !ideName) {
removeNotification("ide-status-disconnected");
return;
}
addNotification({
key: "ide-status-disconnected",
text: `${ideName} disconnected`,
color: "error",
priority: "medium"
});
};
t5 = [addNotification, removeNotification, ideStatus, ideName, showIDEInstallError, showJetBrainsInfo];
$[8] = addNotification;
$[9] = ideName;
$[10] = ideStatus;
$[11] = removeNotification;
$[12] = showIDEInstallError;
$[13] = showJetBrainsInfo;
$[14] = t4;
$[15] = t5;
} else {
t4 = $[14];
t5 = $[15];
}
useEffect(t4, t5);
let t6;
let t7;
if ($[16] !== addNotification || $[17] !== removeNotification || $[18] !== showJetBrainsInfo) {
t6 = () => {
if (getIsRemoteMode()) {
return;
}
if (!showJetBrainsInfo) {
removeNotification("ide-status-jetbrains-disconnected");
return;
}
addNotification({
key: "ide-status-jetbrains-disconnected",
text: "IDE plugin not connected \xB7 /status for info",
priority: "medium"
});
};
t7 = [addNotification, removeNotification, showJetBrainsInfo];
$[16] = addNotification;
$[17] = removeNotification;
$[18] = showJetBrainsInfo;
$[19] = t6;
$[20] = t7;
} else {
t6 = $[19];
t7 = $[20];
}
useEffect(t6, t7);
let t8;
let t9;
if ($[21] !== addNotification || $[22] !== removeNotification || $[23] !== showIDEInstallError) {
t8 = () => {
if (getIsRemoteMode()) {
return;
}
if (!showIDEInstallError) {
removeNotification("ide-status-install-error");
return;
}
addNotification({
key: "ide-status-install-error",
text: "IDE extension install failed (see /status for info)",
color: "error",
priority: "medium"
});
};
t9 = [addNotification, removeNotification, showIDEInstallError];
$[21] = addNotification;
$[22] = removeNotification;
$[23] = showIDEInstallError;
$[24] = t8;
$[25] = t9;
} else {
t8 = $[24];
t9 = $[25];
}
useEffect(t8, t9);
ideInstallationStatus: IDEExtensionInstallationStatus | null
ideSelection: IDESelection | undefined
mcpClients: MCPServerConnection[]
}
function _temp2(hasShownHintRef_0, addNotification_0) {
detectIDEs(true).then(infos => {
const ideName_0 = infos[0]?.name;
if (ideName_0 && !hasShownHintRef_0.current) {
hasShownHintRef_0.current = true;
saveGlobalConfig(_temp);
addNotification_0({
key: "ide-status-hint",
jsx: <Text dimColor={true}>/ide for <Text color="ide">{ideName_0}</Text></Text>,
priority: "low"
});
export function useIDEStatusIndicator({
ideSelection,
mcpClients,
ideInstallationStatus,
}: Props): void {
const { addNotification, removeNotification } = useNotifications()
const { status: ideStatus, ideName } = useIdeConnectionStatus(mcpClients)
const hasShownHintRef = useRef(false)
const isJetBrains = ideInstallationStatus
? isJetBrainsIde(ideInstallationStatus?.ideType)
: false
const showIDEInstallErrorOrJetBrainsInfo =
ideInstallationStatus?.error || isJetBrains
const shouldShowIdeSelection =
ideStatus === 'connected' &&
(ideSelection?.filePath ||
(ideSelection?.text && ideSelection.lineCount > 0))
// Only show the connected if not showing context
const shouldShowConnected =
ideStatus === 'connected' && !shouldShowIdeSelection
const showIDEInstallError =
showIDEInstallErrorOrJetBrainsInfo &&
!isJetBrains &&
!shouldShowConnected &&
!shouldShowIdeSelection
const showJetBrainsInfo =
showIDEInstallErrorOrJetBrainsInfo &&
isJetBrains &&
!shouldShowConnected &&
!shouldShowIdeSelection
// Show the /ide command hint if running from an external terminal and found running IDE(s)
// Delay showing hint to avoid brief flash during auto-connect startup
useEffect(() => {
if (getIsRemoteMode()) return
if (isSupportedTerminal() || ideStatus !== null || showJetBrainsInfo) {
removeNotification('ide-status-hint')
return
}
});
}
function _temp(current) {
return {
...current,
ideHintShownCount: (current.ideHintShownCount ?? 0) + 1
};
// Wait a bit to let auto-connect happen first, avoiding brief hint flash
if (
hasShownHintRef.current ||
(getGlobalConfig().ideHintShownCount ?? 0) >= MAX_IDE_HINT_SHOW_COUNT
) {
return
}
const timeoutId = setTimeout(
(hasShownHintRef, addNotification) => {
void detectIDEs(true).then(infos => {
const ideName = infos[0]?.name
if (ideName && !hasShownHintRef.current) {
hasShownHintRef.current = true
saveGlobalConfig(current => ({
...current,
ideHintShownCount: (current.ideHintShownCount ?? 0) + 1,
}))
addNotification({
key: 'ide-status-hint',
jsx: (
<Text dimColor>
/ide for <Text color="ide">{ideName}</Text>
</Text>
),
priority: 'low',
})
}
})
},
3000,
hasShownHintRef,
addNotification,
)
return () => clearTimeout(timeoutId)
}, [addNotification, removeNotification, ideStatus, showJetBrainsInfo])
// Show IDE disconnected/failed notification when status is disconnected
useEffect(() => {
if (getIsRemoteMode()) return
if (
showIDEInstallError ||
showJetBrainsInfo ||
ideStatus !== 'disconnected' ||
!ideName
) {
removeNotification('ide-status-disconnected')
return
}
addNotification({
key: 'ide-status-disconnected',
text: `${ideName} disconnected`,
color: 'error',
priority: 'medium',
})
}, [
addNotification,
removeNotification,
ideStatus,
ideName,
showIDEInstallError,
showJetBrainsInfo,
])
// Show JetBrains plugin not connected hint
useEffect(() => {
if (getIsRemoteMode()) return
if (!showJetBrainsInfo) {
removeNotification('ide-status-jetbrains-disconnected')
return
}
addNotification({
key: 'ide-status-jetbrains-disconnected',
text: 'IDE plugin not connected · /status for info',
priority: 'medium',
})
}, [addNotification, removeNotification, showJetBrainsInfo])
// Show IDE install error
useEffect(() => {
if (getIsRemoteMode()) return
if (!showIDEInstallError) {
removeNotification('ide-status-install-error')
return
}
addNotification({
key: 'ide-status-install-error',
text: 'IDE extension install failed (see /status for info)',
color: 'error',
priority: 'medium',
})
}, [addNotification, removeNotification, showIDEInstallError])
}

View File

@@ -1,25 +1,22 @@
import { checkInstall } from 'src/utils/nativeInstaller/index.js';
import { useStartupNotification } from './useStartupNotification.js';
export function useInstallMessages() {
useStartupNotification(_temp2);
}
async function _temp2() {
const messages = await checkInstall();
return messages.map(_temp);
}
function _temp(message, index) {
let priority = "low";
if (message.type === "error" || message.userActionRequired) {
priority = "high";
} else {
if (message.type === "path" || message.type === "alias") {
priority = "medium";
}
}
return {
key: `install-message-${index}-${message.type}`,
text: message.message,
priority,
color: message.type === "error" ? "error" : "warning"
};
import { checkInstall } from 'src/utils/nativeInstaller/index.js'
import { useStartupNotification } from './useStartupNotification.js'
export function useInstallMessages(): void {
useStartupNotification(async () => {
const messages = await checkInstall()
return messages.map((message, index) => {
let priority: 'low' | 'medium' | 'high' | 'immediate' = 'low'
if (message.type === 'error' || message.userActionRequired) {
priority = 'high'
} else if (message.type === 'path' || message.type === 'alias') {
priority = 'medium'
}
return {
key: `install-message-${index}-${message.type}`,
text: message.message,
priority,
color: message.type === 'error' ? 'error' : 'warning',
}
})
})
}

View File

@@ -1,14 +1,17 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { useInterval } from 'usehooks-ts';
import { getIsRemoteMode, getIsScrollDraining } from '../../bootstrap/state.js';
import { useNotifications } from '../../context/notifications.js';
import { Text } from '../../ink.js';
import { getInitializationStatus, getLspServerManager } from '../../services/lsp/manager.js';
import { useSetAppState } from '../../state/AppState.js';
import { logForDebugging } from '../../utils/debug.js';
import { isEnvTruthy } from '../../utils/envUtils.js';
const LSP_POLL_INTERVAL_MS = 5000;
import * as React from 'react'
import { useInterval } from 'usehooks-ts'
import { getIsRemoteMode, getIsScrollDraining } from '../../bootstrap/state.js'
import { useNotifications } from '../../context/notifications.js'
import { Text } from '../../ink.js'
import {
getInitializationStatus,
getLspServerManager,
} from '../../services/lsp/manager.js'
import { useSetAppState } from '../../state/AppState.js'
import { logForDebugging } from '../../utils/debug.js'
import { isEnvTruthy } from '../../utils/envUtils.js'
const LSP_POLL_INTERVAL_MS = 5000
/**
* Hook that polls LSP status and shows a notification when:
@@ -19,124 +22,120 @@ const LSP_POLL_INTERVAL_MS = 5000;
*
* Only active when ENABLE_LSP_TOOL is set.
*/
export function useLspInitializationNotification() {
const $ = _c(10);
const {
addNotification
} = useNotifications();
const setAppState = useSetAppState();
const [shouldPoll, setShouldPoll] = React.useState(_temp);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = new Set();
$[0] = t0;
} else {
t0 = $[0];
}
const notifiedErrorsRef = React.useRef(t0);
let t1;
if ($[1] !== addNotification || $[2] !== setAppState) {
t1 = (source, errorMessage) => {
const errorKey = `${source}:${errorMessage}`;
export function useLspInitializationNotification(): void {
const { addNotification } = useNotifications()
const setAppState = useSetAppState()
// Lazy initializer — eager form re-evaluates isEnvTruthy on every REPL
// render (the arg expression runs even though useState ignores it after
// mount). Showed up as 7.2s isEnvTruthy self-time during PageUp spam
// after #24498 swapped cheap !!process.env.X for isEnvTruthy().
const [shouldPoll, setShouldPoll] = React.useState(() =>
isEnvTruthy("true"),
)
// Track which errors we've already notified about to avoid duplicates
const notifiedErrorsRef = React.useRef<Set<string>>(new Set())
const addError = React.useCallback(
(source: string, errorMessage: string) => {
const errorKey = `${source}:${errorMessage}`
if (notifiedErrorsRef.current.has(errorKey)) {
return;
return // Already notified
}
notifiedErrorsRef.current.add(errorKey);
logForDebugging(`LSP error: ${source} - ${errorMessage}`);
notifiedErrorsRef.current.add(errorKey)
logForDebugging(`LSP error: ${source} - ${errorMessage}`)
// Add error to appState.plugins.errors
setAppState(prev => {
const existingKeys = new Set(prev.plugins.errors.map(_temp2));
const stateErrorKey = `generic-error:${source}:${errorMessage}`;
// Check if this error already exists to avoid duplicates
const existingKeys = new Set(
prev.plugins.errors.map(e => {
if (e.type === 'generic-error') {
return `generic-error:${e.source}:${e.error}`
}
return `${e.type}:${e.source}`
}),
)
const stateErrorKey = `generic-error:${source}:${errorMessage}`
if (existingKeys.has(stateErrorKey)) {
return prev;
return prev
}
return {
...prev,
plugins: {
...prev.plugins,
errors: [...prev.plugins.errors, {
type: "generic-error" as const,
source,
error: errorMessage
}]
}
};
});
const displayName = source.startsWith("plugin:") ? source.split(":")[1] ?? source : source;
errors: [
...prev.plugins.errors,
{
type: 'generic-error' as const,
source,
error: errorMessage,
},
],
},
}
})
// Show notification - extract plugin name from source like "plugin:typescript-lsp:typescript"
const displayName = source.startsWith('plugin:')
? (source.split(':')[1] ?? source)
: source
addNotification({
key: `lsp-error-${source}`,
jsx: <><Text color="error">LSP for {displayName} failed</Text><Text dimColor={true}> · /plugin for details</Text></>,
priority: "medium",
timeoutMs: 8000
});
};
$[1] = addNotification;
$[2] = setAppState;
$[3] = t1;
} else {
t1 = $[3];
}
const addError = t1;
let t2;
if ($[4] !== addError) {
t2 = () => {
if (getIsRemoteMode()) {
return;
}
if (getIsScrollDraining()) {
return;
}
const status = getInitializationStatus();
if (status.status === "failed") {
addError("lsp-manager", status.error.message);
setShouldPoll(false);
return;
}
if (status.status === "pending" || status.status === "not-started") {
return;
}
const manager = getLspServerManager();
if (manager) {
const servers = manager.getAllServers();
for (const [serverName, server] of servers) {
if (server.state === "error" && server.lastError) {
addError(serverName, server.lastError.message);
}
jsx: (
<>
<Text color="error">LSP for {displayName} failed</Text>
<Text dimColor> · /plugin for details</Text>
</>
),
priority: 'medium',
timeoutMs: 8000,
})
},
[addNotification, setAppState],
)
const poll = React.useCallback(() => {
if (getIsRemoteMode()) return
// Skip during scroll drain — iterating all LSP servers + setAppState
// competes for the event loop with scroll frames. Next interval picks up.
if (getIsScrollDraining()) return
const status = getInitializationStatus()
// Check manager initialization status
if (status.status === 'failed') {
addError('lsp-manager', status.error.message)
setShouldPoll(false)
return
}
if (status.status === 'pending' || status.status === 'not-started') {
// Still initializing, continue polling
return
}
// Manager initialized successfully - check for server errors
const manager = getLspServerManager()
if (manager) {
const servers = manager.getAllServers()
for (const [serverName, server] of servers) {
if (server.state === 'error' && server.lastError) {
addError(serverName, server.lastError.message)
}
}
};
$[4] = addError;
$[5] = t2;
} else {
t2 = $[5];
}
const poll = t2;
useInterval(poll, shouldPoll ? LSP_POLL_INTERVAL_MS : null);
let t3;
let t4;
if ($[6] !== poll || $[7] !== shouldPoll) {
t3 = () => {
if (getIsRemoteMode() || !shouldPoll) {
return;
}
poll();
};
t4 = [poll, shouldPoll];
$[6] = poll;
$[7] = shouldPoll;
$[8] = t3;
$[9] = t4;
} else {
t3 = $[8];
t4 = $[9];
}
React.useEffect(t3, t4);
}
function _temp2(e) {
if (e.type === "generic-error") {
return `generic-error:${e.source}:${e.error}`;
}
return `${e.type}:${e.source}`;
}
function _temp() {
return isEnvTruthy("true");
}
// Continue polling to detect future server errors
}, [addError])
useInterval(poll, shouldPoll ? LSP_POLL_INTERVAL_MS : null)
// Initial poll on mount
React.useEffect(() => {
if (getIsRemoteMode() || !shouldPoll) return
poll()
}, [poll, shouldPoll])
}

View File

@@ -1,87 +1,126 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { useEffect } from 'react';
import { useNotifications } from 'src/context/notifications.js';
import { getIsRemoteMode } from '../../bootstrap/state.js';
import { Text } from '../../ink.js';
import { hasClaudeAiMcpEverConnected } from '../../services/mcp/claudeai.js';
import type { MCPServerConnection } from '../../services/mcp/types.js';
import * as React from 'react'
import { useEffect } from 'react'
import { useNotifications } from 'src/context/notifications.js'
import { getIsRemoteMode } from '../../bootstrap/state.js'
import { Text } from '../../ink.js'
import { hasClaudeAiMcpEverConnected } from '../../services/mcp/claudeai.js'
import type { MCPServerConnection } from '../../services/mcp/types.js'
type Props = {
mcpClients?: MCPServerConnection[];
};
const EMPTY_MCP_CLIENTS: MCPServerConnection[] = [];
export function useMcpConnectivityStatus(t0) {
const $ = _c(4);
const {
mcpClients: t1
} = t0;
const mcpClients = t1 === undefined ? EMPTY_MCP_CLIENTS : t1;
const {
addNotification
} = useNotifications();
let t2;
let t3;
if ($[0] !== addNotification || $[1] !== mcpClients) {
t2 = () => {
if (getIsRemoteMode()) {
return;
}
const failedLocalClients = mcpClients.filter(_temp);
const failedClaudeAiClients = mcpClients.filter(_temp2);
const needsAuthLocalServers = mcpClients.filter(_temp3);
const needsAuthClaudeAiServers = mcpClients.filter(_temp4);
if (failedLocalClients.length === 0 && failedClaudeAiClients.length === 0 && needsAuthLocalServers.length === 0 && needsAuthClaudeAiServers.length === 0) {
return;
}
if (failedLocalClients.length > 0) {
addNotification({
key: "mcp-failed",
jsx: <><Text color="error">{failedLocalClients.length} MCP{" "}{failedLocalClients.length === 1 ? "server" : "servers"} failed</Text><Text dimColor={true}> · /mcp</Text></>,
priority: "medium"
});
}
if (failedClaudeAiClients.length > 0) {
addNotification({
key: "mcp-claudeai-failed",
jsx: <><Text color="error">{failedClaudeAiClients.length} claude.ai{" "}{failedClaudeAiClients.length === 1 ? "connector" : "connectors"}{" "}unavailable</Text><Text dimColor={true}> · /mcp</Text></>,
priority: "medium"
});
}
if (needsAuthLocalServers.length > 0) {
addNotification({
key: "mcp-needs-auth",
jsx: <><Text color="warning">{needsAuthLocalServers.length} MCP{" "}{needsAuthLocalServers.length === 1 ? "server needs" : "servers need"}{" "}auth</Text><Text dimColor={true}> · /mcp</Text></>,
priority: "medium"
});
}
if (needsAuthClaudeAiServers.length > 0) {
addNotification({
key: "mcp-claudeai-needs-auth",
jsx: <><Text color="warning">{needsAuthClaudeAiServers.length} claude.ai{" "}{needsAuthClaudeAiServers.length === 1 ? "connector needs" : "connectors need"}{" "}auth</Text><Text dimColor={true}> · /mcp</Text></>,
priority: "medium"
});
}
};
t3 = [addNotification, mcpClients];
$[0] = addNotification;
$[1] = mcpClients;
$[2] = t2;
$[3] = t3;
} else {
t2 = $[2];
t3 = $[3];
}
useEffect(t2, t3);
mcpClients?: MCPServerConnection[]
}
function _temp4(client_2) {
return client_2.type === "needs-auth" && client_2.config.type === "claudeai-proxy" && hasClaudeAiMcpEverConnected(client_2.name);
}
function _temp3(client_1) {
return client_1.type === "needs-auth" && client_1.config.type !== "claudeai-proxy";
}
function _temp2(client_0) {
return client_0.type === "failed" && client_0.config.type === "claudeai-proxy" && hasClaudeAiMcpEverConnected(client_0.name);
}
function _temp(client) {
return client.type === "failed" && client.config.type !== "sse-ide" && client.config.type !== "ws-ide" && client.config.type !== "claudeai-proxy";
const EMPTY_MCP_CLIENTS: MCPServerConnection[] = []
export function useMcpConnectivityStatus({
mcpClients = EMPTY_MCP_CLIENTS,
}: Props): void {
const { addNotification } = useNotifications()
useEffect(() => {
if (getIsRemoteMode()) return
const failedLocalClients = mcpClients.filter(
client =>
client.type === 'failed' &&
client.config.type !== 'sse-ide' &&
client.config.type !== 'ws-ide' &&
client.config.type !== 'claudeai-proxy',
)
// claude.ai failures get a separate notification: they almost always indicate
// a toolbox-service outage (shared auth backend), not a local config issue.
// Only flag connectors that have previously connected successfully — an
// org-configured connector that's been needs-auth since it appeared is one
// the user has ignored and shouldn't nag about; one that was working
// yesterday and is now failed is a state change worth surfacing.
const failedClaudeAiClients = mcpClients.filter(
client =>
client.type === 'failed' &&
client.config.type === 'claudeai-proxy' &&
hasClaudeAiMcpEverConnected(client.name),
)
const needsAuthLocalServers = mcpClients.filter(
client =>
client.type === 'needs-auth' && client.config.type !== 'claudeai-proxy',
)
const needsAuthClaudeAiServers = mcpClients.filter(
client =>
client.type === 'needs-auth' &&
client.config.type === 'claudeai-proxy' &&
hasClaudeAiMcpEverConnected(client.name),
)
if (
failedLocalClients.length === 0 &&
failedClaudeAiClients.length === 0 &&
needsAuthLocalServers.length === 0 &&
needsAuthClaudeAiServers.length === 0
) {
return
}
if (failedLocalClients.length > 0) {
addNotification({
key: 'mcp-failed',
jsx: (
<>
<Text color="error">
{failedLocalClients.length} MCP{' '}
{failedLocalClients.length === 1 ? 'server' : 'servers'} failed
</Text>
<Text dimColor> · /mcp</Text>
</>
),
priority: 'medium',
})
}
if (failedClaudeAiClients.length > 0) {
addNotification({
key: 'mcp-claudeai-failed',
jsx: (
<>
<Text color="error">
{failedClaudeAiClients.length} claude.ai{' '}
{failedClaudeAiClients.length === 1 ? 'connector' : 'connectors'}{' '}
unavailable
</Text>
<Text dimColor> · /mcp</Text>
</>
),
priority: 'medium',
})
}
if (needsAuthLocalServers.length > 0) {
addNotification({
key: 'mcp-needs-auth',
jsx: (
<>
<Text color="warning">
{needsAuthLocalServers.length} MCP{' '}
{needsAuthLocalServers.length === 1
? 'server needs'
: 'servers need'}{' '}
auth
</Text>
<Text dimColor> · /mcp</Text>
</>
),
priority: 'medium',
})
}
if (needsAuthClaudeAiServers.length > 0) {
addNotification({
key: 'mcp-claudeai-needs-auth',
jsx: (
<>
<Text color="warning">
{needsAuthClaudeAiServers.length} claude.ai{' '}
{needsAuthClaudeAiServers.length === 1
? 'connector needs'
: 'connectors need'}{' '}
auth
</Text>
<Text dimColor> · /mcp</Text>
</>
),
priority: 'medium',
})
}
}, [addNotification, mcpClients])
}

View File

@@ -1,51 +1,53 @@
import type { Notification } from 'src/context/notifications.js';
import { type GlobalConfig, getGlobalConfig } from 'src/utils/config.js';
import { useStartupNotification } from './useStartupNotification.js';
import type { Notification } from 'src/context/notifications.js'
import { type GlobalConfig, getGlobalConfig } from 'src/utils/config.js'
import { useStartupNotification } from './useStartupNotification.js'
// Shows a one-time notification right after a model migration writes its
// timestamp to config. Each entry reads its own timestamp field(s) and emits
// a notification if the write happened within the last 3s (i.e. this launch).
// Future model migrations: add an entry to MIGRATIONS below.
const MIGRATIONS: ((c: GlobalConfig) => Notification | undefined)[] = [
// Sonnet 4.5 → 4.6 (pro/max/team premium)
c => {
if (!recent(c.sonnet45To46MigrationTimestamp)) return;
return {
key: 'sonnet-46-update',
text: 'Model updated to Sonnet 4.6',
color: 'suggestion',
priority: 'high',
timeoutMs: 3000
};
},
// Opus Pro → default, or pinned 4.0/4.1 → opus alias. Both land on the
// current Opus default (4.6 for 1P).
c => {
const isLegacyRemap = Boolean(c.legacyOpusMigrationTimestamp);
const ts = c.legacyOpusMigrationTimestamp ?? c.opusProMigrationTimestamp;
if (!recent(ts)) return;
return {
key: 'opus-pro-update',
text: isLegacyRemap ? 'Model updated to Opus 4.6 · Set CLAUDE_CODE_DISABLE_LEGACY_MODEL_REMAP=1 to opt out' : 'Model updated to Opus 4.6',
color: 'suggestion',
priority: 'high',
timeoutMs: isLegacyRemap ? 8000 : 3000
};
}];
export function useModelMigrationNotifications() {
useStartupNotification(_temp);
}
function _temp() {
const config = getGlobalConfig();
const notifs = [];
for (const migration of MIGRATIONS) {
const notif = migration(config);
if (notif) {
notifs.push(notif);
// Sonnet 4.5 → 4.6 (pro/max/team premium)
c => {
if (!recent(c.sonnet45To46MigrationTimestamp)) return
return {
key: 'sonnet-46-update',
text: 'Model updated to Sonnet 4.6',
color: 'suggestion',
priority: 'high',
timeoutMs: 3000,
}
}
return notifs.length > 0 ? notifs : null;
},
// Opus Pro → default, or pinned 4.0/4.1 → opus alias. Both land on the
// current Opus default (4.6 for 1P).
c => {
const isLegacyRemap = Boolean(c.legacyOpusMigrationTimestamp)
const ts = c.legacyOpusMigrationTimestamp ?? c.opusProMigrationTimestamp
if (!recent(ts)) return
return {
key: 'opus-pro-update',
text: isLegacyRemap
? 'Model updated to Opus 4.6 · Set CLAUDE_CODE_DISABLE_LEGACY_MODEL_REMAP=1 to opt out'
: 'Model updated to Opus 4.6',
color: 'suggestion',
priority: 'high',
timeoutMs: isLegacyRemap ? 8000 : 3000,
}
},
]
export function useModelMigrationNotifications(): void {
useStartupNotification(() => {
const config = getGlobalConfig()
const notifs: Notification[] = []
for (const migration of MIGRATIONS) {
const notif = migration(config)
if (notif) notifs.push(notif)
}
return notifs.length > 0 ? notifs : null
})
}
function recent(ts: number | undefined): boolean {
return ts !== undefined && Date.now() - ts < 3000;
return ts !== undefined && Date.now() - ts < 3000
}

View File

@@ -1,25 +1,27 @@
import { isInBundledMode } from 'src/utils/bundledMode.js';
import { getCurrentInstallationType } from 'src/utils/doctorDiagnostic.js';
import { isEnvTruthy } from 'src/utils/envUtils.js';
import { useStartupNotification } from './useStartupNotification.js';
const NPM_DEPRECATION_MESSAGE = '';
// const NPM_DEPRECATION_MESSAGE = 'Claude Code has switched from npm to native installer. Run `claude install` or see https://docs.anthropic.com/en/docs/claude-code/getting-started for more options.';
export function useNpmDeprecationNotification() {
useStartupNotification(_temp);
}
async function _temp() {
if (isInBundledMode() || isEnvTruthy(process.env.DISABLE_INSTALLATION_CHECKS)) {
return null;
}
const installationType = await getCurrentInstallationType();
if (installationType === "development") {
return null;
}
return {
timeoutMs: 15000,
key: "npm-deprecation-warning",
text: NPM_DEPRECATION_MESSAGE,
color: "warning",
priority: "high"
};
import { isInBundledMode } from 'src/utils/bundledMode.js'
import { getCurrentInstallationType } from 'src/utils/doctorDiagnostic.js'
import { isEnvTruthy } from 'src/utils/envUtils.js'
import { useStartupNotification } from './useStartupNotification.js'
const NPM_DEPRECATION_MESSAGE =
'Claude Code has switched from npm to native installer. Run `claude install` or see https://docs.anthropic.com/en/docs/claude-code/getting-started for more options.'
export function useNpmDeprecationNotification(): void {
useStartupNotification(async () => {
if (
isInBundledMode() ||
isEnvTruthy(process.env.DISABLE_INSTALLATION_CHECKS)
) {
return null
}
const installationType = await getCurrentInstallationType()
if (installationType === 'development') return null
return {
timeoutMs: 15000,
key: 'npm-deprecation-warning',
text: NPM_DEPRECATION_MESSAGE,
color: 'warning',
priority: 'high',
}
})
}

View File

@@ -1,82 +1,67 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { useEffect, useState } from 'react';
import { getIsRemoteMode } from '../../bootstrap/state.js';
import { useNotifications } from '../../context/notifications.js';
import { Text } from '../../ink.js';
import { logForDebugging } from '../../utils/debug.js';
import { onPluginsAutoUpdated } from '../../utils/plugins/pluginAutoupdate.js';
import * as React from 'react'
import { useEffect, useState } from 'react'
import { getIsRemoteMode } from '../../bootstrap/state.js'
import { useNotifications } from '../../context/notifications.js'
import { Text } from '../../ink.js'
import { logForDebugging } from '../../utils/debug.js'
import { onPluginsAutoUpdated } from '../../utils/plugins/pluginAutoupdate.js'
/**
* Hook that displays a notification when plugins have been auto-updated.
* The notification tells the user to run /reload-plugins to apply the updates.
*/
export function usePluginAutoupdateNotification() {
const $ = _c(7);
const {
addNotification
} = useNotifications();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [];
$[0] = t0;
} else {
t0 = $[0];
}
const [updatedPlugins, setUpdatedPlugins] = useState(t0);
let t1;
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
if (getIsRemoteMode()) {
return;
}
const unsubscribe = onPluginsAutoUpdated(plugins => {
logForDebugging(`Plugin autoupdate notification: ${plugins.length} plugin(s) updated`);
setUpdatedPlugins(plugins);
});
return unsubscribe;
};
t2 = [];
$[1] = t1;
$[2] = t2;
} else {
t1 = $[1];
t2 = $[2];
}
useEffect(t1, t2);
let t3;
let t4;
if ($[3] !== addNotification || $[4] !== updatedPlugins) {
t3 = () => {
if (getIsRemoteMode()) {
return;
}
if (updatedPlugins.length === 0) {
return;
}
const pluginNames = updatedPlugins.map(_temp);
const displayNames = pluginNames.length <= 2 ? pluginNames.join(" and ") : `${pluginNames.length} plugins`;
addNotification({
key: "plugin-autoupdate-restart",
jsx: <><Text color="success">{pluginNames.length === 1 ? "Plugin" : "Plugins"} updated:{" "}{displayNames}</Text><Text dimColor={true}> · Run /reload-plugins to apply</Text></>,
priority: "low",
timeoutMs: 10000
});
logForDebugging(`Showing plugin autoupdate notification for: ${pluginNames.join(", ")}`);
};
t4 = [updatedPlugins, addNotification];
$[3] = addNotification;
$[4] = updatedPlugins;
$[5] = t3;
$[6] = t4;
} else {
t3 = $[5];
t4 = $[6];
}
useEffect(t3, t4);
}
function _temp(id) {
const atIndex = id.indexOf("@");
return atIndex > 0 ? id.substring(0, atIndex) : id;
export function usePluginAutoupdateNotification(): void {
const { addNotification } = useNotifications()
const [updatedPlugins, setUpdatedPlugins] = useState<string[]>([])
// Register for autoupdate notifications
useEffect(() => {
if (getIsRemoteMode()) return
const unsubscribe = onPluginsAutoUpdated(plugins => {
logForDebugging(
`Plugin autoupdate notification: ${plugins.length} plugin(s) updated`,
)
setUpdatedPlugins(plugins)
})
return unsubscribe
}, [])
// Show notification when plugins are updated
useEffect(() => {
if (getIsRemoteMode()) return
if (updatedPlugins.length === 0) {
return
}
// Extract plugin names from plugin IDs (format: "name@marketplace")
const pluginNames = updatedPlugins.map(id => {
const atIndex = id.indexOf('@')
return atIndex > 0 ? id.substring(0, atIndex) : id
})
const displayNames =
pluginNames.length <= 2
? pluginNames.join(' and ')
: `${pluginNames.length} plugins`
addNotification({
key: 'plugin-autoupdate-restart',
jsx: (
<>
<Text color="success">
{pluginNames.length === 1 ? 'Plugin' : 'Plugins'} updated:{' '}
{displayNames}
</Text>
<Text dimColor> · Run /reload-plugins to apply</Text>
</>
),
priority: 'low',
timeoutMs: 10000,
})
logForDebugging(
`Showing plugin autoupdate notification for: ${pluginNames.join(', ')}`,
)
}, [updatedPlugins, addNotification])
}

View File

@@ -1,127 +1,80 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { useEffect, useMemo } from 'react';
import { getIsRemoteMode } from '../../bootstrap/state.js';
import { useNotifications } from '../../context/notifications.js';
import { Text } from '../../ink.js';
import { useAppState } from '../../state/AppState.js';
import { logForDebugging } from '../../utils/debug.js';
import { plural } from '../../utils/stringUtils.js';
export function usePluginInstallationStatus() {
const $ = _c(20);
const {
addNotification
} = useNotifications();
const installationStatus = useAppState(_temp);
let t0;
bb0: {
if (!installationStatus) {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = {
import * as React from 'react'
import { useEffect, useMemo } from 'react'
import { getIsRemoteMode } from '../../bootstrap/state.js'
import { useNotifications } from '../../context/notifications.js'
import { Text } from '../../ink.js'
import { useAppState } from '../../state/AppState.js'
import { logForDebugging } from '../../utils/debug.js'
import { plural } from '../../utils/stringUtils.js'
export function usePluginInstallationStatus(): void {
const { addNotification } = useNotifications()
const installationStatus = useAppState(s => s.plugins.installationStatus)
// Memoize the failed counts to prevent unnecessary effect triggers
const { totalFailed, failedMarketplacesCount, failedPluginsCount } =
useMemo(() => {
if (!installationStatus) {
return {
totalFailed: 0,
failedMarketplacesCount: 0,
failedPluginsCount: 0
};
$[0] = t1;
} else {
t1 = $[0];
failedPluginsCount: 0,
}
}
t0 = t1;
break bb0;
}
let t1;
if ($[1] !== installationStatus.marketplaces) {
t1 = installationStatus.marketplaces.filter(_temp2);
$[1] = installationStatus.marketplaces;
$[2] = t1;
} else {
t1 = $[2];
}
const failedMarketplaces = t1;
let t2;
if ($[3] !== installationStatus.plugins) {
t2 = installationStatus.plugins.filter(_temp3);
$[3] = installationStatus.plugins;
$[4] = t2;
} else {
t2 = $[4];
}
const failedPlugins = t2;
const t3 = failedMarketplaces.length + failedPlugins.length;
let t4;
if ($[5] !== failedMarketplaces.length || $[6] !== failedPlugins.length || $[7] !== t3) {
t4 = {
totalFailed: t3,
const failedMarketplaces = installationStatus.marketplaces.filter(
m => m.status === 'failed',
)
const failedPlugins = installationStatus.plugins.filter(
p => p.status === 'failed',
)
return {
totalFailed: failedMarketplaces.length + failedPlugins.length,
failedMarketplacesCount: failedMarketplaces.length,
failedPluginsCount: failedPlugins.length
};
$[5] = failedMarketplaces.length;
$[6] = failedPlugins.length;
$[7] = t3;
$[8] = t4;
} else {
t4 = $[8];
failedPluginsCount: failedPlugins.length,
}
}, [installationStatus])
useEffect(() => {
if (getIsRemoteMode()) return
if (!installationStatus) {
logForDebugging('No installation status to monitor')
return
}
t0 = t4;
}
const {
if (totalFailed === 0) {
return
}
logForDebugging(
`Plugin installation status: ${failedMarketplacesCount} failed marketplaces, ${failedPluginsCount} failed plugins`,
)
if (totalFailed === 0) {
return
}
// Add notification for failures
logForDebugging(
`Adding notification for ${totalFailed} failed installations`,
)
addNotification({
key: 'plugin-install-failed',
jsx: (
<>
<Text color="error">
{totalFailed} {plural(totalFailed, 'plugin')} failed to install
</Text>
<Text dimColor> · /plugin for details</Text>
</>
),
priority: 'medium',
})
}, [
addNotification,
totalFailed,
failedMarketplacesCount,
failedPluginsCount
} = t0;
let t1;
if ($[9] !== addNotification || $[10] !== failedMarketplacesCount || $[11] !== failedPluginsCount || $[12] !== installationStatus || $[13] !== totalFailed) {
t1 = () => {
if (getIsRemoteMode()) {
return;
}
if (!installationStatus) {
logForDebugging("No installation status to monitor");
return;
}
if (totalFailed === 0) {
return;
}
logForDebugging(`Plugin installation status: ${failedMarketplacesCount} failed marketplaces, ${failedPluginsCount} failed plugins`);
if (totalFailed === 0) {
return;
}
logForDebugging(`Adding notification for ${totalFailed} failed installations`);
addNotification({
key: "plugin-install-failed",
jsx: <><Text color="error">{totalFailed} {plural(totalFailed, "plugin")} failed to install</Text><Text dimColor={true}> · /plugin for details</Text></>,
priority: "medium"
});
};
$[9] = addNotification;
$[10] = failedMarketplacesCount;
$[11] = failedPluginsCount;
$[12] = installationStatus;
$[13] = totalFailed;
$[14] = t1;
} else {
t1 = $[14];
}
let t2;
if ($[15] !== addNotification || $[16] !== failedMarketplacesCount || $[17] !== failedPluginsCount || $[18] !== totalFailed) {
t2 = [addNotification, totalFailed, failedMarketplacesCount, failedPluginsCount];
$[15] = addNotification;
$[16] = failedMarketplacesCount;
$[17] = failedPluginsCount;
$[18] = totalFailed;
$[19] = t2;
} else {
t2 = $[19];
}
useEffect(t1, t2);
}
function _temp3(p) {
return p.status === "failed";
}
function _temp2(m) {
return m.status === "failed";
}
function _temp(s) {
return s.plugins.installationStatus;
failedPluginsCount,
])
}

View File

@@ -1,113 +1,80 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useNotifications } from 'src/context/notifications.js';
import { Text } from 'src/ink.js';
import { getRateLimitWarning, getUsingOverageText } from 'src/services/claudeAiLimits.js';
import { useClaudeAiLimits } from 'src/services/claudeAiLimitsHook.js';
import { getSubscriptionType } from 'src/utils/auth.js';
import { hasClaudeAiBillingAccess } from 'src/utils/billing.js';
import { getIsRemoteMode } from '../../bootstrap/state.js';
export function useRateLimitWarningNotification(model) {
const $ = _c(17);
const {
addNotification
} = useNotifications();
const claudeAiLimits = useClaudeAiLimits();
let t0;
if ($[0] !== claudeAiLimits || $[1] !== model) {
t0 = getRateLimitWarning(claudeAiLimits, model);
$[0] = claudeAiLimits;
$[1] = model;
$[2] = t0;
} else {
t0 = $[2];
}
const rateLimitWarning = t0;
let t1;
if ($[3] !== claudeAiLimits) {
t1 = getUsingOverageText(claudeAiLimits);
$[3] = claudeAiLimits;
$[4] = t1;
} else {
t1 = $[4];
}
const usingOverageText = t1;
const shownWarningRef = useRef(null);
let t2;
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
t2 = getSubscriptionType();
$[5] = t2;
} else {
t2 = $[5];
}
const subscriptionType = t2;
let t3;
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
t3 = hasClaudeAiBillingAccess();
$[6] = t3;
} else {
t3 = $[6];
}
const hasBillingAccess = t3;
const isTeamOrEnterprise = subscriptionType === "team" || subscriptionType === "enterprise";
const [hasShownOverageNotification, setHasShownOverageNotification] = useState(false);
let t4;
let t5;
if ($[7] !== addNotification || $[8] !== claudeAiLimits.isUsingOverage || $[9] !== hasShownOverageNotification || $[10] !== usingOverageText) {
t4 = () => {
if (getIsRemoteMode()) {
return;
}
if (claudeAiLimits.isUsingOverage && !hasShownOverageNotification && (!isTeamOrEnterprise || hasBillingAccess)) {
addNotification({
key: "limit-reached",
text: usingOverageText,
priority: "immediate"
});
setHasShownOverageNotification(true);
} else {
if (!claudeAiLimits.isUsingOverage && hasShownOverageNotification) {
setHasShownOverageNotification(false);
}
}
};
t5 = [claudeAiLimits.isUsingOverage, usingOverageText, hasShownOverageNotification, addNotification, hasBillingAccess, isTeamOrEnterprise];
$[7] = addNotification;
$[8] = claudeAiLimits.isUsingOverage;
$[9] = hasShownOverageNotification;
$[10] = usingOverageText;
$[11] = t4;
$[12] = t5;
} else {
t4 = $[11];
t5 = $[12];
}
useEffect(t4, t5);
let t6;
let t7;
if ($[13] !== addNotification || $[14] !== rateLimitWarning) {
t6 = () => {
if (getIsRemoteMode()) {
return;
}
if (rateLimitWarning && rateLimitWarning !== shownWarningRef.current) {
shownWarningRef.current = rateLimitWarning;
addNotification({
key: "rate-limit-warning",
jsx: <Text><Text color="warning">{rateLimitWarning}</Text></Text>,
priority: "high"
});
}
};
t7 = [rateLimitWarning, addNotification];
$[13] = addNotification;
$[14] = rateLimitWarning;
$[15] = t6;
$[16] = t7;
} else {
t6 = $[15];
t7 = $[16];
}
useEffect(t6, t7);
import * as React from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useNotifications } from 'src/context/notifications.js'
import { Text } from 'src/ink.js'
import {
getRateLimitWarning,
getUsingOverageText,
} from 'src/services/claudeAiLimits.js'
import { useClaudeAiLimits } from 'src/services/claudeAiLimitsHook.js'
import { getSubscriptionType } from 'src/utils/auth.js'
import { hasClaudeAiBillingAccess } from 'src/utils/billing.js'
import { getIsRemoteMode } from '../../bootstrap/state.js'
export function useRateLimitWarningNotification(model: string): void {
const { addNotification } = useNotifications()
const claudeAiLimits = useClaudeAiLimits()
// claudeAiLimits reference is stable until statusListeners fire (API
// response), so these skip the Intl formatting work on most REPL renders.
const rateLimitWarning = useMemo(
() => getRateLimitWarning(claudeAiLimits, model),
[claudeAiLimits, model],
)
const usingOverageText = useMemo(
() => getUsingOverageText(claudeAiLimits),
[claudeAiLimits],
)
const shownWarningRef = useRef<string | null>(null)
const subscriptionType = getSubscriptionType()
const hasBillingAccess = hasClaudeAiBillingAccess()
const isTeamOrEnterprise =
subscriptionType === 'team' || subscriptionType === 'enterprise'
// Track overage mode transitions
const [hasShownOverageNotification, setHasShownOverageNotification] =
useState(false)
// Show immediate notification when entering overage mode
useEffect(() => {
if (getIsRemoteMode()) return
if (
claudeAiLimits.isUsingOverage &&
!hasShownOverageNotification &&
(!isTeamOrEnterprise || hasBillingAccess)
) {
addNotification({
key: 'limit-reached',
text: usingOverageText,
priority: 'immediate',
})
setHasShownOverageNotification(true)
} else if (!claudeAiLimits.isUsingOverage && hasShownOverageNotification) {
// Reset when no longer in overage mode
setHasShownOverageNotification(false)
}
}, [
claudeAiLimits.isUsingOverage,
usingOverageText,
hasShownOverageNotification,
addNotification,
hasBillingAccess,
isTeamOrEnterprise,
])
// Show warning notification for approaching limits
useEffect(() => {
if (getIsRemoteMode()) return
if (rateLimitWarning && rateLimitWarning !== shownWarningRef.current) {
shownWarningRef.current = rateLimitWarning
addNotification({
key: 'rate-limit-warning',
jsx: (
<Text>
<Text color="warning">{rateLimitWarning}</Text>
</Text>
),
priority: 'high',
})
}
}, [rateLimitWarning, addNotification])
}

View File

@@ -1,68 +1,41 @@
import { c as _c } from "react/compiler-runtime";
import { useCallback, useEffect, useState } from 'react';
import { useNotifications } from 'src/context/notifications.js';
import { getIsRemoteMode } from '../../bootstrap/state.js';
import { getSettingsWithAllErrors } from '../../utils/settings/allErrors.js';
import type { ValidationError } from '../../utils/settings/validation.js';
import { useSettingsChange } from '../useSettingsChange.js';
const SETTINGS_ERRORS_NOTIFICATION_KEY = 'settings-errors';
export function useSettingsErrors() {
const $ = _c(6);
const {
addNotification,
removeNotification
} = useNotifications();
const [errors_0, setErrors] = useState(_temp);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
const {
errors: errors_1
} = getSettingsWithAllErrors();
setErrors(errors_1);
};
$[0] = t0;
} else {
t0 = $[0];
}
const handleSettingsChange = t0;
useSettingsChange(handleSettingsChange);
let t1;
let t2;
if ($[1] !== addNotification || $[2] !== errors_0 || $[3] !== removeNotification) {
t1 = () => {
if (getIsRemoteMode()) {
return;
}
if (errors_0.length > 0) {
const message = `Found ${errors_0.length} settings ${errors_0.length === 1 ? "issue" : "issues"} · /doctor for details`;
addNotification({
key: SETTINGS_ERRORS_NOTIFICATION_KEY,
text: message,
color: "warning",
priority: "high",
timeoutMs: 60000
});
} else {
removeNotification(SETTINGS_ERRORS_NOTIFICATION_KEY);
}
};
t2 = [errors_0, addNotification, removeNotification];
$[1] = addNotification;
$[2] = errors_0;
$[3] = removeNotification;
$[4] = t1;
$[5] = t2;
} else {
t1 = $[4];
t2 = $[5];
}
useEffect(t1, t2);
return errors_0;
}
function _temp() {
const {
errors
} = getSettingsWithAllErrors();
return errors;
import { useCallback, useEffect, useState } from 'react'
import { useNotifications } from 'src/context/notifications.js'
import { getIsRemoteMode } from '../../bootstrap/state.js'
import { getSettingsWithAllErrors } from '../../utils/settings/allErrors.js'
import type { ValidationError } from '../../utils/settings/validation.js'
import { useSettingsChange } from '../useSettingsChange.js'
const SETTINGS_ERRORS_NOTIFICATION_KEY = 'settings-errors'
export function useSettingsErrors(): ValidationError[] {
const { addNotification, removeNotification } = useNotifications()
const [errors, setErrors] = useState<ValidationError[]>(() => {
const { errors } = getSettingsWithAllErrors()
return errors
})
const handleSettingsChange = useCallback(() => {
const { errors } = getSettingsWithAllErrors()
setErrors(errors)
}, [])
useSettingsChange(handleSettingsChange)
useEffect(() => {
if (getIsRemoteMode()) return
if (errors.length > 0) {
const message = `Found ${errors.length} settings ${errors.length === 1 ? 'issue' : 'issues'} · /doctor for details`
addNotification({
key: SETTINGS_ERRORS_NOTIFICATION_KEY,
text: message,
color: 'warning',
priority: 'high',
timeoutMs: 60000,
})
} else {
removeNotification(SETTINGS_ERRORS_NOTIFICATION_KEY)
}
}, [errors, addNotification, removeNotification])
return errors
}