diff --git a/src/commands/add-dir/add-dir.tsx b/src/commands/add-dir/add-dir.tsx
index 49304d2dc..cfb6c6687 100644
--- a/src/commands/add-dir/add-dir.tsx
+++ b/src/commands/add-dir/add-dir.tsx
@@ -1,125 +1,154 @@
-import { c as _c } from "react/compiler-runtime";
-import chalk from 'chalk';
-import figures from 'figures';
-import React, { useEffect } from 'react';
-import { getAdditionalDirectoriesForClaudeMd, setAdditionalDirectoriesForClaudeMd } from '../../bootstrap/state.js';
-import type { LocalJSXCommandContext } from '../../commands.js';
-import { MessageResponse } from '../../components/MessageResponse.js';
-import { AddWorkspaceDirectory } from '../../components/permissions/rules/AddWorkspaceDirectory.js';
-import { Box, Text } from '../../ink.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-import { applyPermissionUpdate, persistPermissionUpdate } from '../../utils/permissions/PermissionUpdate.js';
-import type { PermissionUpdateDestination } from '../../utils/permissions/PermissionUpdateSchema.js';
-import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js';
-import { addDirHelpMessage, validateDirectoryForWorkspace } from './validation.js';
-function AddDirError(t0) {
- const $ = _c(10);
- const {
- message,
- args,
- onDone
- } = t0;
- let t1;
- let t2;
- if ($[0] !== onDone) {
- t1 = () => {
- const timer = setTimeout(onDone, 0);
- return () => clearTimeout(timer);
- };
- t2 = [onDone];
- $[0] = onDone;
- $[1] = t1;
- $[2] = t2;
- } else {
- t1 = $[1];
- t2 = $[2];
- }
- useEffect(t1, t2);
- let t3;
- if ($[3] !== args) {
- t3 = {figures.pointer} /add-dir {args};
- $[3] = args;
- $[4] = t3;
- } else {
- t3 = $[4];
- }
- let t4;
- if ($[5] !== message) {
- t4 = {message};
- $[5] = message;
- $[6] = t4;
- } else {
- t4 = $[6];
- }
- let t5;
- if ($[7] !== t3 || $[8] !== t4) {
- t5 = {t3}{t4};
- $[7] = t3;
- $[8] = t4;
- $[9] = t5;
- } else {
- t5 = $[9];
- }
- return t5;
+import chalk from 'chalk'
+import figures from 'figures'
+import React, { useEffect } from 'react'
+import {
+ getAdditionalDirectoriesForClaudeMd,
+ setAdditionalDirectoriesForClaudeMd,
+} from '../../bootstrap/state.js'
+import type { LocalJSXCommandContext } from '../../commands.js'
+import { MessageResponse } from '../../components/MessageResponse.js'
+import { AddWorkspaceDirectory } from '../../components/permissions/rules/AddWorkspaceDirectory.js'
+import { Box, Text } from '../../ink.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import {
+ applyPermissionUpdate,
+ persistPermissionUpdate,
+} from '../../utils/permissions/PermissionUpdate.js'
+import type { PermissionUpdateDestination } from '../../utils/permissions/PermissionUpdateSchema.js'
+import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js'
+import {
+ addDirHelpMessage,
+ validateDirectoryForWorkspace,
+} from './validation.js'
+
+function AddDirError({
+ message,
+ args,
+ onDone,
+}: {
+ message: string
+ args: string
+ onDone: () => void
+}): React.ReactNode {
+ useEffect(() => {
+ // We need to defer calling onDone to avoid the "return null" bug where
+ // the component unmounts before React can render the error message.
+ // Using setTimeout ensures the error displays before the command exits.
+ const timer = setTimeout(onDone, 0)
+ return () => clearTimeout(timer)
+ }, [onDone])
+
+ return (
+
+
+ {figures.pointer} /add-dir {args}
+
+
+ {message}
+
+
+ )
}
-export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext, args?: string): Promise {
- const directoryPath = (args ?? '').trim();
- const appState = context.getAppState();
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: LocalJSXCommandContext,
+ args?: string,
+): Promise {
+ const directoryPath = (args ?? '').trim()
+ const appState = context.getAppState()
// Helper to handle adding a directory (shared by both with-path and no-path cases)
const handleAddDirectory = async (path: string, remember = false) => {
- const destination: PermissionUpdateDestination = remember ? 'localSettings' : 'session';
+ const destination: PermissionUpdateDestination = remember
+ ? 'localSettings'
+ : 'session'
+
const permissionUpdate = {
type: 'addDirectories' as const,
directories: [path],
- destination
- };
+ destination,
+ }
// Apply to session context
- const latestAppState = context.getAppState();
- const updatedContext = applyPermissionUpdate(latestAppState.toolPermissionContext, permissionUpdate);
+ const latestAppState = context.getAppState()
+ const updatedContext = applyPermissionUpdate(
+ latestAppState.toolPermissionContext,
+ permissionUpdate,
+ )
context.setAppState(prev => ({
...prev,
- toolPermissionContext: updatedContext
- }));
+ toolPermissionContext: updatedContext,
+ }))
// Update sandbox config so Bash commands can access the new directory.
// Bootstrap state is the source of truth for session-only dirs; persisted
// dirs are picked up via the settings subscription, but we refresh
// eagerly here to avoid a race when the user acts immediately.
- const currentDirs = getAdditionalDirectoriesForClaudeMd();
+ const currentDirs = getAdditionalDirectoriesForClaudeMd()
if (!currentDirs.includes(path)) {
- setAdditionalDirectoriesForClaudeMd([...currentDirs, path]);
+ setAdditionalDirectoriesForClaudeMd([...currentDirs, path])
}
- SandboxManager.refreshConfig();
- let message: string;
+ SandboxManager.refreshConfig()
+
+ let message: string
+
if (remember) {
try {
- persistPermissionUpdate(permissionUpdate);
- message = `Added ${chalk.bold(path)} as a working directory and saved to local settings`;
+ persistPermissionUpdate(permissionUpdate)
+ message = `Added ${chalk.bold(path)} as a working directory and saved to local settings`
} catch (error) {
- message = `Added ${chalk.bold(path)} as a working directory. Failed to save to local settings: ${error instanceof Error ? error.message : 'Unknown error'}`;
+ message = `Added ${chalk.bold(path)} as a working directory. Failed to save to local settings: ${error instanceof Error ? error.message : 'Unknown error'}`
}
} else {
- message = `Added ${chalk.bold(path)} as a working directory for this session`;
+ message = `Added ${chalk.bold(path)} as a working directory for this session`
}
- const messageWithHint = `${message} ${chalk.dim('· /permissions to manage')}`;
- onDone(messageWithHint);
- };
+
+ const messageWithHint = `${message} ${chalk.dim('· /permissions to manage')}`
+ onDone(messageWithHint)
+ }
// When no path is provided, show AddWorkspaceDirectory input form directly
// and return to REPL after confirmation
if (!directoryPath) {
- return {
- onDone('Did not add a working directory.');
- }} />;
+ return (
+ {
+ onDone('Did not add a working directory.')
+ }}
+ />
+ )
}
- const result = await validateDirectoryForWorkspace(directoryPath, appState.toolPermissionContext);
+
+ const result = await validateDirectoryForWorkspace(
+ directoryPath,
+ appState.toolPermissionContext,
+ )
+
if (result.resultType !== 'success') {
- const message = addDirHelpMessage(result);
- return onDone(message)} />;
+ const message = addDirHelpMessage(result)
+
+ return (
+ onDone(message)}
+ />
+ )
}
- return {
- onDone(`Did not add ${chalk.bold(result.absolutePath)} as a working directory.`);
- }} />;
+
+ return (
+ {
+ onDone(
+ `Did not add ${chalk.bold(result.absolutePath)} as a working directory.`,
+ )
+ }}
+ />
+ )
}
diff --git a/src/commands/agents/agents.tsx b/src/commands/agents/agents.tsx
index 1d2c55974..6a5931756 100644
--- a/src/commands/agents/agents.tsx
+++ b/src/commands/agents/agents.tsx
@@ -1,11 +1,16 @@
-import * as React from 'react';
-import { AgentsMenu } from '../../components/agents/AgentsMenu.js';
-import type { ToolUseContext } from '../../Tool.js';
-import { getTools } from '../../tools.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-export async function call(onDone: LocalJSXCommandOnDone, context: ToolUseContext): Promise {
- const appState = context.getAppState();
- const permissionContext = appState.toolPermissionContext;
- const tools = getTools(permissionContext);
- return ;
+import * as React from 'react'
+import { AgentsMenu } from '../../components/agents/AgentsMenu.js'
+import type { ToolUseContext } from '../../Tool.js'
+import { getTools } from '../../tools.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: ToolUseContext,
+): Promise {
+ const appState = context.getAppState()
+ const permissionContext = appState.toolPermissionContext
+ const tools = getTools(permissionContext)
+
+ return
}
diff --git a/src/commands/bridge/bridge.tsx b/src/commands/bridge/bridge.tsx
index dadf1c89f..33a681202 100644
--- a/src/commands/bridge/bridge.tsx
+++ b/src/commands/bridge/bridge.tsx
@@ -1,27 +1,40 @@
-import { c as _c } from "react/compiler-runtime";
-import { feature } from 'bun:bundle';
-import { toString as qrToString } from 'qrcode';
-import * as React from 'react';
-import { useEffect, useState } from 'react';
-import { getBridgeAccessToken } from '../../bridge/bridgeConfig.js';
-import { checkBridgeMinVersion, getBridgeDisabledReason, isEnvLessBridgeEnabled } from '../../bridge/bridgeEnabled.js';
-import { checkEnvLessBridgeMinVersion } from '../../bridge/envLessBridgeConfig.js';
-import { BRIDGE_LOGIN_INSTRUCTION, REMOTE_CONTROL_DISCONNECTED_MSG } from '../../bridge/types.js';
-import { Dialog } from '../../components/design-system/Dialog.js';
-import { ListItem } from '../../components/design-system/ListItem.js';
-import { shouldShowRemoteCallout } from '../../components/RemoteCallout.js';
-import { useRegisterOverlay } from '../../context/overlayContext.js';
-import { Box, Text } from '../../ink.js';
-import { useKeybindings } from '../../keybindings/useKeybinding.js';
-import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from '../../services/analytics/index.js';
-import { useAppState, useSetAppState } from '../../state/AppState.js';
-import type { ToolUseContext } from '../../Tool.js';
-import type { LocalJSXCommandContext, LocalJSXCommandOnDone } from '../../types/command.js';
-import { logForDebugging } from '../../utils/debug.js';
+import { feature } from 'bun:bundle'
+import { toString as qrToString } from 'qrcode'
+import * as React from 'react'
+import { useEffect, useState } from 'react'
+import { getBridgeAccessToken } from '../../bridge/bridgeConfig.js'
+import {
+ checkBridgeMinVersion,
+ getBridgeDisabledReason,
+ isEnvLessBridgeEnabled,
+} from '../../bridge/bridgeEnabled.js'
+import { checkEnvLessBridgeMinVersion } from '../../bridge/envLessBridgeConfig.js'
+import {
+ BRIDGE_LOGIN_INSTRUCTION,
+ REMOTE_CONTROL_DISCONNECTED_MSG,
+} from '../../bridge/types.js'
+import { Dialog } from '../../components/design-system/Dialog.js'
+import { ListItem } from '../../components/design-system/ListItem.js'
+import { shouldShowRemoteCallout } from '../../components/RemoteCallout.js'
+import { useRegisterOverlay } from '../../context/overlayContext.js'
+import { Box, Text } from '../../ink.js'
+import { useKeybindings } from '../../keybindings/useKeybinding.js'
+import {
+ type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ logEvent,
+} from '../../services/analytics/index.js'
+import { useAppState, useSetAppState } from '../../state/AppState.js'
+import type { ToolUseContext } from '../../Tool.js'
+import type {
+ LocalJSXCommandContext,
+ LocalJSXCommandOnDone,
+} from '../../types/command.js'
+import { logForDebugging } from '../../utils/debug.js'
+
type Props = {
- onDone: LocalJSXCommandOnDone;
- name?: string;
-};
+ onDone: LocalJSXCommandOnDone
+ name?: string
+}
/**
* /remote-control command — manages the bidirectional bridge connection.
@@ -35,392 +48,194 @@ type Props = {
* Running /remote-control when already connected shows a dialog with the session
* URL and options to disconnect or continue.
*/
-function BridgeToggle(t0) {
- const $ = _c(10);
- const {
- onDone,
- name
- } = t0;
- const setAppState = useSetAppState();
- const replBridgeConnected = useAppState(_temp);
- const replBridgeEnabled = useAppState(_temp2);
- const replBridgeOutboundOnly = useAppState(_temp3);
- const [showDisconnectDialog, setShowDisconnectDialog] = useState(false);
- let t1;
- if ($[0] !== name || $[1] !== onDone || $[2] !== replBridgeConnected || $[3] !== replBridgeEnabled || $[4] !== replBridgeOutboundOnly || $[5] !== setAppState) {
- t1 = () => {
- if ((replBridgeConnected || replBridgeEnabled) && !replBridgeOutboundOnly) {
- setShowDisconnectDialog(true);
- return;
- }
- let cancelled = false;
- (async () => {
- const error = await checkBridgePrerequisites();
- if (cancelled) {
- return;
- }
- if (error) {
- logEvent("tengu_bridge_command", {
- action: "preflight_failed" as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
- });
- onDone(error, {
- display: "system"
- });
- return;
- }
- if (shouldShowRemoteCallout()) {
- setAppState(prev => {
- if (prev.showRemoteCallout) {
- return prev;
- }
- return {
- ...prev,
- showRemoteCallout: true,
- replBridgeInitialName: name
- };
- });
- onDone("", {
- display: "system"
- });
- return;
- }
- logEvent("tengu_bridge_command", {
- action: "connect" as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
- });
- setAppState(prev_0 => {
- if (prev_0.replBridgeEnabled && !prev_0.replBridgeOutboundOnly) {
- return prev_0;
- }
- return {
- ...prev_0,
- replBridgeEnabled: true,
- replBridgeExplicit: true,
- replBridgeOutboundOnly: false,
- replBridgeInitialName: name
- };
- });
- onDone("Remote Control connecting\u2026", {
- display: "system"
- });
- })();
- return () => {
- cancelled = true;
- };
- };
- $[0] = name;
- $[1] = onDone;
- $[2] = replBridgeConnected;
- $[3] = replBridgeEnabled;
- $[4] = replBridgeOutboundOnly;
- $[5] = setAppState;
- $[6] = t1;
- } else {
- t1 = $[6];
- }
- let t2;
- if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
- t2 = [];
- $[7] = t2;
- } else {
- t2 = $[7];
- }
- useEffect(t1, t2);
- if (showDisconnectDialog) {
- let t3;
- if ($[8] !== onDone) {
- t3 = ;
- $[8] = onDone;
- $[9] = t3;
- } else {
- t3 = $[9];
+function BridgeToggle({ onDone, name }: Props): React.ReactNode {
+ const setAppState = useSetAppState()
+ const replBridgeConnected = useAppState(s => s.replBridgeConnected)
+ const replBridgeEnabled = useAppState(s => s.replBridgeEnabled)
+ const replBridgeOutboundOnly = useAppState(s => s.replBridgeOutboundOnly)
+ const [showDisconnectDialog, setShowDisconnectDialog] = useState(false)
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: bridge starts once, should not restart on state changes
+ useEffect(() => {
+ // If already connected or enabled in full bidirectional mode, show
+ // disconnect confirmation. Outbound-only (CCR mirror) doesn't count —
+ // /remote-control upgrades it to full RC instead.
+ if ((replBridgeConnected || replBridgeEnabled) && !replBridgeOutboundOnly) {
+ setShowDisconnectDialog(true)
+ return
}
- return t3;
+
+ let cancelled = false
+ void (async () => {
+ // Pre-flight checks before enabling (awaits GrowthBook init if disk
+ // cache is stale — so Max users don't get a false "not enabled" error)
+ const error = await checkBridgePrerequisites()
+ if (cancelled) return
+ if (error) {
+ logEvent('tengu_bridge_command', {
+ action:
+ 'preflight_failed' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ })
+ onDone(error, { display: 'system' })
+ return
+ }
+
+ // Show first-time remote dialog if not yet seen.
+ // Store the name now so it's in AppState when the callout handler later
+ // enables the bridge (the handler only sets replBridgeEnabled, not the name).
+ if (shouldShowRemoteCallout()) {
+ setAppState(prev => {
+ if (prev.showRemoteCallout) return prev
+ return {
+ ...prev,
+ showRemoteCallout: true,
+ replBridgeInitialName: name,
+ }
+ })
+ onDone('', { display: 'system' })
+ return
+ }
+
+ // Enable the bridge — useReplBridge in REPL.tsx handles the rest:
+ // registers environment, creates session with conversation, connects WebSocket
+ logEvent('tengu_bridge_command', {
+ action:
+ 'connect' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ })
+ setAppState(prev => {
+ if (prev.replBridgeEnabled && !prev.replBridgeOutboundOnly) return prev
+ return {
+ ...prev,
+ replBridgeEnabled: true,
+ replBridgeExplicit: true,
+ replBridgeOutboundOnly: false,
+ replBridgeInitialName: name,
+ }
+ })
+ onDone('Remote Control connecting\u2026', {
+ display: 'system',
+ })
+ })()
+
+ return () => {
+ cancelled = true
+ }
+ }, []) // eslint-disable-line react-hooks/exhaustive-deps -- run once on mount
+
+ if (showDisconnectDialog) {
+ return
}
- return null;
+
+ return null
}
/**
* Dialog shown when /remote-control is used while the bridge is already connected.
* Shows the session URL and lets the user disconnect or continue.
*/
-function _temp3(s_1) {
- return s_1.replBridgeOutboundOnly;
-}
-function _temp2(s_0) {
- return s_0.replBridgeEnabled;
-}
-function _temp(s) {
- return s.replBridgeConnected;
-}
-function BridgeDisconnectDialog(t0) {
- const $ = _c(61);
- const {
- onDone
- } = t0;
- useRegisterOverlay("bridge-disconnect-dialog", undefined);
- const setAppState = useSetAppState();
- const sessionUrl = useAppState(_temp4);
- const connectUrl = useAppState(_temp5);
- const sessionActive = useAppState(_temp6);
- const [focusIndex, setFocusIndex] = useState(2);
- const [showQR, setShowQR] = useState(false);
- const [qrText, setQrText] = useState("");
- const displayUrl = sessionActive ? sessionUrl : connectUrl;
- let t1;
- let t2;
- if ($[0] !== displayUrl || $[1] !== showQR) {
- t1 = () => {
- if (!showQR || !displayUrl) {
- setQrText("");
- return;
- }
- qrToString(displayUrl, {
- type: "utf8",
- errorCorrectionLevel: "L",
- small: true
- }).then(setQrText).catch(() => setQrText(""));
- };
- t2 = [showQR, displayUrl];
- $[0] = displayUrl;
- $[1] = showQR;
- $[2] = t1;
- $[3] = t2;
- } else {
- t1 = $[2];
- t2 = $[3];
- }
- useEffect(t1, t2);
- let t3;
- if ($[4] !== onDone || $[5] !== setAppState) {
- t3 = function handleDisconnect() {
- setAppState(_temp7);
- logEvent("tengu_bridge_command", {
- action: "disconnect" as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
- });
- onDone(REMOTE_CONTROL_DISCONNECTED_MSG, {
- display: "system"
- });
- };
- $[4] = onDone;
- $[5] = setAppState;
- $[6] = t3;
- } else {
- t3 = $[6];
- }
- const handleDisconnect = t3;
- let t4;
- if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
- t4 = function handleShowQR() {
- setShowQR(_temp8);
- };
- $[7] = t4;
- } else {
- t4 = $[7];
- }
- const handleShowQR = t4;
- let t5;
- if ($[8] !== onDone) {
- t5 = function handleContinue() {
- onDone(undefined, {
- display: "skip"
- });
- };
- $[8] = onDone;
- $[9] = t5;
- } else {
- t5 = $[9];
- }
- const handleContinue = t5;
- let t6;
- let t7;
- if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
- t6 = () => setFocusIndex(_temp9);
- t7 = () => setFocusIndex(_temp0);
- $[10] = t6;
- $[11] = t7;
- } else {
- t6 = $[10];
- t7 = $[11];
- }
- let t8;
- if ($[12] !== focusIndex || $[13] !== handleContinue || $[14] !== handleDisconnect) {
- t8 = {
- "select:next": t6,
- "select:previous": t7,
- "select:accept": () => {
- if (focusIndex === 0) {
- handleDisconnect();
- } else {
- if (focusIndex === 1) {
- handleShowQR();
- } else {
- handleContinue();
- }
- }
- }
- };
- $[12] = focusIndex;
- $[13] = handleContinue;
- $[14] = handleDisconnect;
- $[15] = t8;
- } else {
- t8 = $[15];
- }
- let t9;
- if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
- t9 = {
- context: "Select"
- };
- $[16] = t9;
- } else {
- t9 = $[16];
- }
- useKeybindings(t8, t9);
- let T0;
- let T1;
- let t10;
- let t11;
- let t12;
- let t13;
- let t14;
- let t15;
- let t16;
- if ($[17] !== displayUrl || $[18] !== handleContinue || $[19] !== qrText || $[20] !== showQR) {
- const qrLines = qrText ? qrText.split("\n").filter(_temp1) : [];
- T1 = Dialog;
- t14 = "Remote Control";
- t15 = handleContinue;
- t16 = true;
- T0 = Box;
- t10 = "column";
- t11 = 1;
- const t17 = displayUrl ? ` at ${displayUrl}` : "";
- if ($[30] !== t17) {
- t12 = This session is available via Remote Control{t17}.;
- $[30] = t17;
- $[31] = t12;
- } else {
- t12 = $[31];
+function BridgeDisconnectDialog({ onDone }: Props): React.ReactNode {
+ useRegisterOverlay('bridge-disconnect-dialog')
+ const setAppState = useSetAppState()
+ const sessionUrl = useAppState(s => s.replBridgeSessionUrl)
+ const connectUrl = useAppState(s => s.replBridgeConnectUrl)
+ const sessionActive = useAppState(s => s.replBridgeSessionActive)
+ const [focusIndex, setFocusIndex] = useState(2)
+ const [showQR, setShowQR] = useState(false)
+ const [qrText, setQrText] = useState('')
+
+ const displayUrl = sessionActive ? sessionUrl : connectUrl
+
+ // Generate QR code when URL changes or QR is toggled on
+ useEffect(() => {
+ if (!showQR || !displayUrl) {
+ setQrText('')
+ return
}
- t13 = showQR && qrLines.length > 0 && {qrLines.map(_temp10)};
- $[17] = displayUrl;
- $[18] = handleContinue;
- $[19] = qrText;
- $[20] = showQR;
- $[21] = T0;
- $[22] = T1;
- $[23] = t10;
- $[24] = t11;
- $[25] = t12;
- $[26] = t13;
- $[27] = t14;
- $[28] = t15;
- $[29] = t16;
- } else {
- T0 = $[21];
- T1 = $[22];
- t10 = $[23];
- t11 = $[24];
- t12 = $[25];
- t13 = $[26];
- t14 = $[27];
- t15 = $[28];
- t16 = $[29];
+ qrToString(displayUrl, {
+ type: 'utf8',
+ errorCorrectionLevel: 'L',
+ small: true,
+ })
+ .then(setQrText)
+ .catch(() => setQrText(''))
+ }, [showQR, displayUrl])
+
+ function handleDisconnect(): void {
+ setAppState(prev => {
+ if (!prev.replBridgeEnabled) return prev
+ return {
+ ...prev,
+ replBridgeEnabled: false,
+ replBridgeExplicit: false,
+ replBridgeOutboundOnly: false,
+ }
+ })
+ logEvent('tengu_bridge_command', {
+ action:
+ 'disconnect' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ })
+ onDone(REMOTE_CONTROL_DISCONNECTED_MSG, { display: 'system' })
}
- const t17 = focusIndex === 0;
- let t18;
- if ($[32] === Symbol.for("react.memo_cache_sentinel")) {
- t18 = Disconnect this session;
- $[32] = t18;
- } else {
- t18 = $[32];
+
+ function handleShowQR(): void {
+ setShowQR(prev => !prev)
}
- let t19;
- if ($[33] !== t17) {
- t19 = {t18};
- $[33] = t17;
- $[34] = t19;
- } else {
- t19 = $[34];
+
+ function handleContinue(): void {
+ onDone(undefined, { display: 'skip' })
}
- const t20 = focusIndex === 1;
- const t21 = showQR ? "Hide QR code" : "Show QR code";
- let t22;
- if ($[35] !== t21) {
- t22 = {t21};
- $[35] = t21;
- $[36] = t22;
- } else {
- t22 = $[36];
- }
- let t23;
- if ($[37] !== t20 || $[38] !== t22) {
- t23 = {t22};
- $[37] = t20;
- $[38] = t22;
- $[39] = t23;
- } else {
- t23 = $[39];
- }
- const t24 = focusIndex === 2;
- let t25;
- if ($[40] === Symbol.for("react.memo_cache_sentinel")) {
- t25 = Continue;
- $[40] = t25;
- } else {
- t25 = $[40];
- }
- let t26;
- if ($[41] !== t24) {
- t26 = {t25};
- $[41] = t24;
- $[42] = t26;
- } else {
- t26 = $[42];
- }
- let t27;
- if ($[43] !== t19 || $[44] !== t23 || $[45] !== t26) {
- t27 = {t19}{t23}{t26};
- $[43] = t19;
- $[44] = t23;
- $[45] = t26;
- $[46] = t27;
- } else {
- t27 = $[46];
- }
- let t28;
- if ($[47] === Symbol.for("react.memo_cache_sentinel")) {
- t28 = Enter to select · Esc to continue;
- $[47] = t28;
- } else {
- t28 = $[47];
- }
- let t29;
- if ($[48] !== T0 || $[49] !== t10 || $[50] !== t11 || $[51] !== t12 || $[52] !== t13 || $[53] !== t27) {
- t29 = {t12}{t13}{t27}{t28};
- $[48] = T0;
- $[49] = t10;
- $[50] = t11;
- $[51] = t12;
- $[52] = t13;
- $[53] = t27;
- $[54] = t29;
- } else {
- t29 = $[54];
- }
- let t30;
- if ($[55] !== T1 || $[56] !== t14 || $[57] !== t15 || $[58] !== t16 || $[59] !== t29) {
- t30 = {t29};
- $[55] = T1;
- $[56] = t14;
- $[57] = t15;
- $[58] = t16;
- $[59] = t29;
- $[60] = t30;
- } else {
- t30 = $[60];
- }
- return t30;
+
+ const ITEM_COUNT = 3
+
+ useKeybindings(
+ {
+ 'select:next': () => setFocusIndex(i => (i + 1) % ITEM_COUNT),
+ 'select:previous': () =>
+ setFocusIndex(i => (i - 1 + ITEM_COUNT) % ITEM_COUNT),
+ 'select:accept': () => {
+ if (focusIndex === 0) {
+ handleDisconnect()
+ } else if (focusIndex === 1) {
+ handleShowQR()
+ } else {
+ handleContinue()
+ }
+ },
+ },
+ { context: 'Select' },
+ )
+
+ const qrLines = qrText ? qrText.split('\n').filter(l => l.length > 0) : []
+
+ return (
+
+ )
}
/**
@@ -429,80 +244,52 @@ function BridgeDisconnectDialog(t0) {
* cache is stale, so a user who just became entitled (e.g. upgraded to Max,
* or the flag just launched) gets an accurate result on the first try.
*/
-function _temp10(line, i_1) {
- return {line};
-}
-function _temp1(l) {
- return l.length > 0;
-}
-function _temp0(i_0) {
- return (i_0 - 1 + 3) % 3;
-}
-function _temp9(i) {
- return (i + 1) % 3;
-}
-function _temp8(prev_0) {
- return !prev_0;
-}
-function _temp7(prev) {
- if (!prev.replBridgeEnabled) {
- return prev;
- }
- return {
- ...prev,
- replBridgeEnabled: false,
- replBridgeExplicit: false,
- replBridgeOutboundOnly: false
- };
-}
-function _temp6(s_1) {
- return s_1.replBridgeSessionActive;
-}
-function _temp5(s_0) {
- return s_0.replBridgeConnectUrl;
-}
-function _temp4(s) {
- return s.replBridgeSessionUrl;
-}
async function checkBridgePrerequisites(): Promise {
// Check organization policy — remote control may be disabled
- const {
- waitForPolicyLimitsToLoad,
- isPolicyAllowed
- } = await import('../../services/policyLimits/index.js');
- await waitForPolicyLimitsToLoad();
+ const { waitForPolicyLimitsToLoad, isPolicyAllowed } = await import(
+ '../../services/policyLimits/index.js'
+ )
+ await waitForPolicyLimitsToLoad()
if (!isPolicyAllowed('allow_remote_control')) {
- return "Remote Control is disabled by your organization's policy.";
+ return "Remote Control is disabled by your organization's policy."
}
- const disabledReason = await getBridgeDisabledReason();
+
+ const disabledReason = await getBridgeDisabledReason()
if (disabledReason) {
- return disabledReason;
+ return disabledReason
}
// Mirror the v1/v2 branching logic in initReplBridge: env-less (v2) is used
// only when the flag is on AND the session is not perpetual. In assistant
// mode (KAIROS) useReplBridge sets perpetual=true, which forces
// initReplBridge onto the v1 path — so the prerequisite check must match.
- let useV2 = isEnvLessBridgeEnabled();
+ let useV2 = isEnvLessBridgeEnabled()
if (feature('KAIROS') && useV2) {
- const {
- isAssistantMode
- } = await import('../../assistant/index.js');
+ const { isAssistantMode } = await import('../../assistant/index.js')
if (isAssistantMode()) {
- useV2 = false;
+ useV2 = false
}
}
- const versionError = useV2 ? await checkEnvLessBridgeMinVersion() : checkBridgeMinVersion();
+ const versionError = useV2
+ ? await checkEnvLessBridgeMinVersion()
+ : checkBridgeMinVersion()
if (versionError) {
- return versionError;
+ return versionError
}
+
if (!getBridgeAccessToken()) {
- return BRIDGE_LOGIN_INSTRUCTION;
+ return BRIDGE_LOGIN_INSTRUCTION
}
- logForDebugging('[bridge] Prerequisites passed, enabling bridge');
- return null;
+
+ logForDebugging('[bridge] Prerequisites passed, enabling bridge')
+ return null
}
-export async function call(onDone: LocalJSXCommandOnDone, _context: ToolUseContext & LocalJSXCommandContext, args: string): Promise {
- const name = args.trim() || undefined;
- return ;
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ _context: ToolUseContext & LocalJSXCommandContext,
+ args: string,
+): Promise {
+ const name = args.trim() || undefined
+ return
}
diff --git a/src/commands/btw/btw.tsx b/src/commands/btw/btw.tsx
index bf5daca30..28a83946b 100644
--- a/src/commands/btw/btw.tsx
+++ b/src/commands/btw/btw.tsx
@@ -1,183 +1,151 @@
-import { c as _c } from "react/compiler-runtime";
-import * as React from 'react';
-import { useEffect, useRef, useState } from 'react';
-import { useInterval } from 'usehooks-ts';
-import type { CommandResultDisplay } from '../../commands.js';
-import { Markdown } from '../../components/Markdown.js';
-import { SpinnerGlyph } from '../../components/Spinner/SpinnerGlyph.js';
-import { DOWN_ARROW, UP_ARROW } from '../../constants/figures.js';
-import { getSystemPrompt } from '../../constants/prompts.js';
-import { useModalOrTerminalSize } from '../../context/modalContext.js';
-import { getSystemContext, getUserContext } from '../../context.js';
-import { useTerminalSize } from '../../hooks/useTerminalSize.js';
-import ScrollBox, { type ScrollBoxHandle } from '../../ink/components/ScrollBox.js';
-import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
-import { Box, Text } from '../../ink.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-import type { Message } from '../../types/message.js';
-import { createAbortController } from '../../utils/abortController.js';
-import { saveGlobalConfig } from '../../utils/config.js';
-import { errorMessage } from '../../utils/errors.js';
-import { type CacheSafeParams, getLastCacheSafeParams } from '../../utils/forkedAgent.js';
-import { getMessagesAfterCompactBoundary } from '../../utils/messages.js';
-import type { ProcessUserInputContext } from '../../utils/processUserInput/processUserInput.js';
-import { runSideQuestion } from '../../utils/sideQuestion.js';
-import { asSystemPrompt } from '../../utils/systemPromptType.js';
+import * as React from 'react'
+import { useEffect, useRef, useState } from 'react'
+import { useInterval } from 'usehooks-ts'
+import type { CommandResultDisplay } from '../../commands.js'
+import { Markdown } from '../../components/Markdown.js'
+import { SpinnerGlyph } from '../../components/Spinner/SpinnerGlyph.js'
+import { DOWN_ARROW, UP_ARROW } from '../../constants/figures.js'
+import { getSystemPrompt } from '../../constants/prompts.js'
+import { useModalOrTerminalSize } from '../../context/modalContext.js'
+import { getSystemContext, getUserContext } from '../../context.js'
+import { useTerminalSize } from '../../hooks/useTerminalSize.js'
+import ScrollBox, {
+ type ScrollBoxHandle,
+} from '../../ink/components/ScrollBox.js'
+import type { KeyboardEvent } from '../../ink/events/keyboard-event.js'
+import { Box, Text } from '../../ink.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import type { Message } from '../../types/message.js'
+import { createAbortController } from '../../utils/abortController.js'
+import { saveGlobalConfig } from '../../utils/config.js'
+import { errorMessage } from '../../utils/errors.js'
+import {
+ type CacheSafeParams,
+ getLastCacheSafeParams,
+} from '../../utils/forkedAgent.js'
+import { getMessagesAfterCompactBoundary } from '../../utils/messages.js'
+import type { ProcessUserInputContext } from '../../utils/processUserInput/processUserInput.js'
+import { runSideQuestion } from '../../utils/sideQuestion.js'
+import { asSystemPrompt } from '../../utils/systemPromptType.js'
+
type BtwComponentProps = {
- question: string;
- context: ProcessUserInputContext;
- onDone: (result?: string, options?: {
- display?: CommandResultDisplay;
- }) => void;
-};
-const CHROME_ROWS = 5;
-const OUTER_CHROME_ROWS = 6;
-const SCROLL_LINES = 3;
-function BtwSideQuestion(t0) {
- const $ = _c(25);
- const {
- question,
- context,
- onDone
- } = t0;
- const [response, setResponse] = useState(null);
- const [error, setError] = useState(null);
- const [frame, setFrame] = useState(0);
- const scrollRef = useRef(null);
- const {
- rows
- } = useModalOrTerminalSize(useTerminalSize());
- let t1;
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
- t1 = () => setFrame(_temp);
- $[0] = t1;
- } else {
- t1 = $[0];
+ question: string
+ context: ProcessUserInputContext
+ onDone: (
+ result?: string,
+ options?: { display?: CommandResultDisplay },
+ ) => void
+}
+
+const CHROME_ROWS = 5
+const OUTER_CHROME_ROWS = 6
+const SCROLL_LINES = 3
+
+function BtwSideQuestion({
+ question,
+ context,
+ onDone,
+}: BtwComponentProps): React.ReactNode {
+ const [response, setResponse] = useState(null)
+ const [error, setError] = useState(null)
+ const [frame, setFrame] = useState(0)
+ const scrollRef = useRef(null)
+ const { rows } = useModalOrTerminalSize(useTerminalSize())
+
+ // Animate spinner while loading
+ useInterval(() => setFrame(f => f + 1), response || error ? null : 80)
+
+ function handleKeyDown(e: KeyboardEvent): void {
+ if (
+ e.key === 'escape' ||
+ e.key === 'return' ||
+ e.key === ' ' ||
+ (e.ctrl && (e.key === 'c' || e.key === 'd'))
+ ) {
+ e.preventDefault()
+ onDone(undefined, { display: 'skip' })
+ return
+ }
+ if (e.key === 'up' || (e.ctrl && e.key === 'p')) {
+ e.preventDefault()
+ scrollRef.current?.scrollBy(-SCROLL_LINES)
+ }
+ if (e.key === 'down' || (e.ctrl && e.key === 'n')) {
+ e.preventDefault()
+ scrollRef.current?.scrollBy(SCROLL_LINES)
+ }
}
- useInterval(t1, response || error ? null : 80);
- let t2;
- if ($[1] !== onDone) {
- t2 = function handleKeyDown(e) {
- if (e.key === "escape" || e.key === "return" || e.key === " " || e.ctrl && (e.key === "c" || e.key === "d")) {
- e.preventDefault();
- onDone(undefined, {
- display: "skip"
- });
- return;
- }
- if (e.key === "up" || e.ctrl && e.key === "p") {
- e.preventDefault();
- scrollRef.current?.scrollBy(-SCROLL_LINES);
- }
- if (e.key === "down" || e.ctrl && e.key === "n") {
- e.preventDefault();
- scrollRef.current?.scrollBy(SCROLL_LINES);
- }
- };
- $[1] = onDone;
- $[2] = t2;
- } else {
- t2 = $[2];
- }
- const handleKeyDown = t2;
- let t3;
- let t4;
- if ($[3] !== context || $[4] !== question) {
- t3 = () => {
- const abortController = createAbortController();
- const fetchResponse = async function fetchResponse() {
- ;
- try {
- const cacheSafeParams = await buildCacheSafeParams(context);
- const result = await runSideQuestion({
- question,
- cacheSafeParams
- });
- if (!abortController.signal.aborted) {
- if (result.response) {
- setResponse(result.response);
- } else {
- setError("No response received");
- }
- }
- } catch (t5) {
- const err = t5;
- if (!abortController.signal.aborted) {
- setError(errorMessage(err) || "Failed to get response");
+
+ useEffect(() => {
+ const abortController = createAbortController()
+
+ async function fetchResponse(): Promise {
+ try {
+ const cacheSafeParams = await buildCacheSafeParams(context)
+ const result = await runSideQuestion({ question, cacheSafeParams })
+
+ if (!abortController.signal.aborted) {
+ if (result.response) {
+ setResponse(result.response)
+ } else {
+ setError('No response received')
}
}
- };
- fetchResponse();
- return () => {
- abortController.abort();
- };
- };
- t4 = [question, context];
- $[3] = context;
- $[4] = question;
- $[5] = t3;
- $[6] = t4;
- } else {
- t3 = $[5];
- t4 = $[6];
- }
- useEffect(t3, t4);
- const maxContentHeight = Math.max(5, rows - CHROME_ROWS - OUTER_CHROME_ROWS);
- let t5;
- if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
- t5 = /btw{" "};
- $[7] = t5;
- } else {
- t5 = $[7];
- }
- let t6;
- if ($[8] !== question) {
- t6 = {t5}{question};
- $[8] = question;
- $[9] = t6;
- } else {
- t6 = $[9];
- }
- let t7;
- if ($[10] !== error || $[11] !== frame || $[12] !== response) {
- t7 = {error ? {error} : response ? {response} : Answering...};
- $[10] = error;
- $[11] = frame;
- $[12] = response;
- $[13] = t7;
- } else {
- t7 = $[13];
- }
- let t8;
- if ($[14] !== maxContentHeight || $[15] !== t7) {
- t8 = {t7};
- $[14] = maxContentHeight;
- $[15] = t7;
- $[16] = t8;
- } else {
- t8 = $[16];
- }
- let t9;
- if ($[17] !== error || $[18] !== response) {
- t9 = (response || error) && {UP_ARROW}/{DOWN_ARROW} to scroll · Space, Enter, or Escape to dismiss;
- $[17] = error;
- $[18] = response;
- $[19] = t9;
- } else {
- t9 = $[19];
- }
- let t10;
- if ($[20] !== handleKeyDown || $[21] !== t6 || $[22] !== t8 || $[23] !== t9) {
- t10 = {t6}{t8}{t9};
- $[20] = handleKeyDown;
- $[21] = t6;
- $[22] = t8;
- $[23] = t9;
- $[24] = t10;
- } else {
- t10 = $[24];
- }
- return t10;
+ } catch (err) {
+ if (!abortController.signal.aborted) {
+ setError(errorMessage(err) || 'Failed to get response')
+ }
+ }
+ }
+
+ void fetchResponse()
+
+ return () => {
+ abortController.abort()
+ }
+ }, [question, context])
+
+ const maxContentHeight = Math.max(5, rows - CHROME_ROWS - OUTER_CHROME_ROWS)
+
+ return (
+
+
+
+ /btw{' '}
+
+ {question}
+
+
+
+ {error ? (
+ {error}
+ ) : response ? (
+ {response}
+ ) : (
+
+
+ Answering...
+
+ )}
+
+
+ {(response || error) && (
+
+
+ {UP_ARROW}/{DOWN_ARROW} to scroll · Space, Enter, or Escape to
+ dismiss
+
+
+ )}
+
+ )
}
/**
@@ -195,48 +163,67 @@ function BtwSideQuestion(t0) {
* applied buildEffectiveSystemPrompt extras (--agent, --system-prompt,
* --append-system-prompt, coordinator mode).
*/
-function _temp(f) {
- return f + 1;
-}
function stripInProgressAssistantMessage(messages: Message[]): Message[] {
- const last = messages.at(-1);
+ const last = messages.at(-1)
if (last?.type === 'assistant' && last.message.stop_reason === null) {
- return messages.slice(0, -1);
+ return messages.slice(0, -1)
}
- return messages;
+ return messages
}
-async function buildCacheSafeParams(context: ProcessUserInputContext): Promise {
- const forkContextMessages = getMessagesAfterCompactBoundary(stripInProgressAssistantMessage(context.messages));
- const saved = getLastCacheSafeParams();
+
+async function buildCacheSafeParams(
+ context: ProcessUserInputContext,
+): Promise {
+ const forkContextMessages = getMessagesAfterCompactBoundary(
+ stripInProgressAssistantMessage(context.messages),
+ )
+ const saved = getLastCacheSafeParams()
if (saved) {
return {
systemPrompt: saved.systemPrompt,
userContext: saved.userContext,
systemContext: saved.systemContext,
toolUseContext: context,
- forkContextMessages
- };
+ forkContextMessages,
+ }
}
- const [rawSystemPrompt, userContext, systemContext] = await Promise.all([getSystemPrompt(context.options.tools, context.options.mainLoopModel, [], context.options.mcpClients), getUserContext(), getSystemContext()]);
+ const [rawSystemPrompt, userContext, systemContext] = await Promise.all([
+ getSystemPrompt(
+ context.options.tools,
+ context.options.mainLoopModel,
+ [],
+ context.options.mcpClients,
+ ),
+ getUserContext(),
+ getSystemContext(),
+ ])
return {
systemPrompt: asSystemPrompt(rawSystemPrompt),
userContext,
systemContext,
toolUseContext: context,
- forkContextMessages
- };
-}
-export async function call(onDone: LocalJSXCommandOnDone, context: ProcessUserInputContext, args: string): Promise {
- const question = args?.trim();
- if (!question) {
- onDone('Usage: /btw ', {
- display: 'system'
- });
- return null;
+ forkContextMessages,
}
+}
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: ProcessUserInputContext,
+ args: string,
+): Promise {
+ const question = args?.trim()
+
+ if (!question) {
+ onDone('Usage: /btw ', { display: 'system' })
+ return null
+ }
+
saveGlobalConfig(current => ({
...current,
- btwUseCount: current.btwUseCount + 1
- }));
- return ;
+ btwUseCount: current.btwUseCount + 1,
+ }))
+
+ return (
+
+ )
}
diff --git a/src/commands/chrome/chrome.tsx b/src/commands/chrome/chrome.tsx
index b659c2e80..3fd0dbca3 100644
--- a/src/commands/chrome/chrome.tsx
+++ b/src/commands/chrome/chrome.tsx
@@ -1,284 +1,240 @@
-import { c as _c } from "react/compiler-runtime";
-import React, { useState } from 'react';
-import { type OptionWithDescription, Select } from '../../components/CustomSelect/select.js';
-import { Dialog } from '../../components/design-system/Dialog.js';
-import { Box, Text } from '../../ink.js';
-import { useAppState } from '../../state/AppState.js';
-import { isClaudeAISubscriber } from '../../utils/auth.js';
-import { openBrowser } from '../../utils/browser.js';
-import { CLAUDE_IN_CHROME_MCP_SERVER_NAME, openInChrome } from '../../utils/claudeInChrome/common.js';
-import { isChromeExtensionInstalled } from '../../utils/claudeInChrome/setup.js';
-import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
-import { env } from '../../utils/env.js';
-import { isRunningOnHomespace } from '../../utils/envUtils.js';
-const CHROME_EXTENSION_URL = 'https://claude.ai/chrome';
-const CHROME_PERMISSIONS_URL = 'https://clau.de/chrome/permissions';
-const CHROME_RECONNECT_URL = 'https://clau.de/chrome/reconnect';
-type MenuAction = 'install-extension' | 'reconnect' | 'manage-permissions' | 'toggle-default';
+import React, { useState } from 'react'
+import {
+ type OptionWithDescription,
+ Select,
+} from '../../components/CustomSelect/select.js'
+import { Dialog } from '../../components/design-system/Dialog.js'
+import { Box, Text } from '../../ink.js'
+import { useAppState } from '../../state/AppState.js'
+import { isClaudeAISubscriber } from '../../utils/auth.js'
+import { openBrowser } from '../../utils/browser.js'
+import {
+ CLAUDE_IN_CHROME_MCP_SERVER_NAME,
+ openInChrome,
+} from '../../utils/claudeInChrome/common.js'
+import { isChromeExtensionInstalled } from '../../utils/claudeInChrome/setup.js'
+import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
+import { env } from '../../utils/env.js'
+import { isRunningOnHomespace } from '../../utils/envUtils.js'
+
+const CHROME_EXTENSION_URL = 'https://claude.ai/chrome'
+const CHROME_PERMISSIONS_URL = 'https://clau.de/chrome/permissions'
+const CHROME_RECONNECT_URL = 'https://clau.de/chrome/reconnect'
+
+type MenuAction =
+ | 'install-extension'
+ | 'reconnect'
+ | 'manage-permissions'
+ | 'toggle-default'
+
type Props = {
- onDone: (result?: string) => void;
- isExtensionInstalled: boolean;
- configEnabled: boolean | undefined;
- isClaudeAISubscriber: boolean;
- isWSL: boolean;
-};
-function ClaudeInChromeMenu(t0) {
- const $ = _c(41);
- const {
- onDone,
- isExtensionInstalled: installed,
- configEnabled,
- isClaudeAISubscriber,
- isWSL
- } = t0;
- const mcpClients = useAppState(_temp);
- const [selectKey, setSelectKey] = useState(0);
- const [enabledByDefault, setEnabledByDefault] = useState(configEnabled ?? false);
- const [showInstallHint, setShowInstallHint] = useState(false);
- const [isExtensionInstalled, setIsExtensionInstalled] = useState(installed);
- let t1;
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
- t1 = false && isRunningOnHomespace();
- $[0] = t1;
- } else {
- t1 = $[0];
+ onDone: (result?: string) => void
+ isExtensionInstalled: boolean
+ configEnabled: boolean | undefined
+ isClaudeAISubscriber: boolean
+ isWSL: boolean
+}
+
+function ClaudeInChromeMenu({
+ onDone,
+ isExtensionInstalled: installed,
+ configEnabled,
+ isClaudeAISubscriber,
+ isWSL,
+}: Props): React.ReactNode {
+ const mcpClients = useAppState(s => s.mcp.clients)
+ const [selectKey, setSelectKey] = useState(0)
+ const [enabledByDefault, setEnabledByDefault] = useState(
+ configEnabled ?? false,
+ )
+ const [showInstallHint, setShowInstallHint] = useState(false)
+ const [isExtensionInstalled, setIsExtensionInstalled] = useState(installed)
+
+ const isHomespace = process.env.USER_TYPE === 'ant' && isRunningOnHomespace()
+
+ const chromeClient = mcpClients.find(
+ c => c.name === CLAUDE_IN_CHROME_MCP_SERVER_NAME,
+ )
+ const isConnected = chromeClient?.type === 'connected'
+
+ function openUrl(url: string): void {
+ if (isHomespace) {
+ void openBrowser(url)
+ } else {
+ void openInChrome(url)
+ }
}
- const isHomespace = t1;
- let t2;
- if ($[1] !== mcpClients) {
- t2 = mcpClients.find(_temp2);
- $[1] = mcpClients;
- $[2] = t2;
- } else {
- t2 = $[2];
- }
- const chromeClient = t2;
- const isConnected = chromeClient?.type === "connected";
- let t3;
- if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
- t3 = function openUrl(url) {
- if (isHomespace) {
- openBrowser(url);
- } else {
- openInChrome(url);
+
+ function handleAction(action: MenuAction): void {
+ switch (action) {
+ case 'install-extension':
+ setSelectKey(k => k + 1)
+ setShowInstallHint(true)
+ openUrl(CHROME_EXTENSION_URL)
+ break
+ case 'reconnect':
+ setSelectKey(k => k + 1)
+ void isChromeExtensionInstalled().then(installed => {
+ setIsExtensionInstalled(installed)
+ if (installed) {
+ setShowInstallHint(false)
+ }
+ })
+ openUrl(CHROME_RECONNECT_URL)
+ break
+ case 'manage-permissions':
+ setSelectKey(k => k + 1)
+ openUrl(CHROME_PERMISSIONS_URL)
+ break
+ case 'toggle-default': {
+ const newValue = !enabledByDefault
+ saveGlobalConfig(current => ({
+ ...current,
+ claudeInChromeDefaultEnabled: newValue,
+ }))
+ setEnabledByDefault(newValue)
+ break
}
- };
- $[3] = t3;
- } else {
- t3 = $[3];
- }
- const openUrl = t3;
- let t4;
- if ($[4] !== enabledByDefault) {
- t4 = function handleAction(action) {
- bb22: switch (action) {
- case "install-extension":
- {
- setSelectKey(_temp3);
- setShowInstallHint(true);
- openUrl(CHROME_EXTENSION_URL);
- break bb22;
- }
- case "reconnect":
- {
- setSelectKey(_temp4);
- isChromeExtensionInstalled().then(installed_0 => {
- setIsExtensionInstalled(installed_0);
- if (installed_0) {
- setShowInstallHint(false);
- }
- });
- openUrl(CHROME_RECONNECT_URL);
- break bb22;
- }
- case "manage-permissions":
- {
- setSelectKey(_temp5);
- openUrl(CHROME_PERMISSIONS_URL);
- break bb22;
- }
- case "toggle-default":
- {
- const newValue = !enabledByDefault;
- saveGlobalConfig(current => ({
- ...current,
- claudeInChromeDefaultEnabled: newValue
- }));
- setEnabledByDefault(newValue);
- }
- }
- };
- $[4] = enabledByDefault;
- $[5] = t4;
- } else {
- t4 = $[5];
- }
- const handleAction = t4;
- let options;
- if ($[6] !== enabledByDefault || $[7] !== isExtensionInstalled) {
- options = [];
- const requiresExtensionSuffix = isExtensionInstalled ? "" : " (requires extension)";
- if (!isExtensionInstalled && !isHomespace) {
- let t5;
- if ($[9] === Symbol.for("react.memo_cache_sentinel")) {
- t5 = {
- label: "Install Chrome extension",
- value: "install-extension"
- };
- $[9] = t5;
- } else {
- t5 = $[9];
- }
- options.push(t5);
}
- let t5;
- if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
- t5 = Manage permissions;
- $[10] = t5;
- } else {
- t5 = $[10];
- }
- let t6;
- if ($[11] !== requiresExtensionSuffix) {
- t6 = {
- label: <>{t5}{requiresExtensionSuffix}>,
- value: "manage-permissions"
- };
- $[11] = requiresExtensionSuffix;
- $[12] = t6;
- } else {
- t6 = $[12];
- }
- let t7;
- if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
- t7 = Reconnect extension;
- $[13] = t7;
- } else {
- t7 = $[13];
- }
- let t8;
- if ($[14] !== requiresExtensionSuffix) {
- t8 = {
- label: <>{t7}{requiresExtensionSuffix}>,
- value: "reconnect"
- };
- $[14] = requiresExtensionSuffix;
- $[15] = t8;
- } else {
- t8 = $[15];
- }
- const t9 = `Enabled by default: ${enabledByDefault ? "Yes" : "No"}`;
- let t10;
- if ($[16] !== t9) {
- t10 = {
- label: t9,
- value: "toggle-default"
- };
- $[16] = t9;
- $[17] = t10;
- } else {
- t10 = $[17];
- }
- options.push(t6, t8, t10);
- $[6] = enabledByDefault;
- $[7] = isExtensionInstalled;
- $[8] = options;
- } else {
- options = $[8];
}
- const isDisabled = isWSL;
- let t5;
- if ($[18] !== onDone) {
- t5 = () => onDone();
- $[18] = onDone;
- $[19] = t5;
- } else {
- t5 = $[19];
+
+ const options: OptionWithDescription[] = []
+ const requiresExtensionSuffix = isExtensionInstalled
+ ? ''
+ : ' (requires extension)'
+
+ if (!isExtensionInstalled && !isHomespace) {
+ options.push({
+ label: 'Install Chrome extension',
+ value: 'install-extension',
+ })
}
- let t6;
- if ($[20] === Symbol.for("react.memo_cache_sentinel")) {
- t6 = Claude in Chrome works with the Chrome extension to let you control your browser directly from Claude Code. Navigate websites, fill forms, capture screenshots, record GIFs, and debug with console logs and network requests.;
- $[20] = t6;
- } else {
- t6 = $[20];
- }
- let t7;
- if ($[21] !== isWSL) {
- t7 = isWSL && Claude in Chrome is not supported in WSL at this time.;
- $[21] = isWSL;
- $[22] = t7;
- } else {
- t7 = $[22];
- }
- let t8;
- if ($[23] !== isClaudeAISubscriber) {
- t8 = false;
- $[23] = isClaudeAISubscriber;
- $[24] = t8;
- } else {
- t8 = $[24];
- }
- let t9;
- if ($[25] !== handleAction || $[26] !== isConnected || $[27] !== isDisabled || $[28] !== isExtensionInstalled || $[29] !== options || $[30] !== selectKey || $[31] !== showInstallHint) {
- t9 = !isDisabled && <>{!isHomespace && Status:{" "}{isConnected ? Enabled : Disabled}Extension:{" "}{isExtensionInstalled ? Installed : Not detected}}{showInstallHint && Once installed, select {"\"Reconnect extension\""} to connect.}Usage: claude --chrome or claude --no-chromeSite-level permissions are inherited from the Chrome extension. Manage permissions in the Chrome extension settings to control which sites Claude can browse, click, and type on.>;
- $[25] = handleAction;
- $[26] = isConnected;
- $[27] = isDisabled;
- $[28] = isExtensionInstalled;
- $[29] = options;
- $[30] = selectKey;
- $[31] = showInstallHint;
- $[32] = t9;
- } else {
- t9 = $[32];
- }
- let t10;
- if ($[33] === Symbol.for("react.memo_cache_sentinel")) {
- t10 = Learn more: https://code.claude.com/docs/en/chrome;
- $[33] = t10;
- } else {
- t10 = $[33];
- }
- let t11;
- if ($[34] !== t7 || $[35] !== t8 || $[36] !== t9) {
- t11 = {t6}{t7}{t8}{t9}{t10};
- $[34] = t7;
- $[35] = t8;
- $[36] = t9;
- $[37] = t11;
- } else {
- t11 = $[37];
- }
- let t12;
- if ($[38] !== t11 || $[39] !== t5) {
- t12 = ;
- $[38] = t11;
- $[39] = t5;
- $[40] = t12;
- } else {
- t12 = $[40];
- }
- return t12;
+
+ options.push(
+ {
+ label: (
+ <>
+ Manage permissions
+ {requiresExtensionSuffix}
+ >
+ ),
+ value: 'manage-permissions',
+ },
+ {
+ label: (
+ <>
+ Reconnect extension
+ {requiresExtensionSuffix}
+ >
+ ),
+ value: 'reconnect',
+ },
+ {
+ label: `Enabled by default: ${enabledByDefault ? 'Yes' : 'No'}`,
+ value: 'toggle-default',
+ },
+ )
+
+ const isDisabled =
+ isWSL || ("external" !== 'ant' && !isClaudeAISubscriber)
+
+ return (
+
+ )
}
-function _temp5(k) {
- return k + 1;
+
+export const call = async function (
+ onDone: (result?: string) => void,
+): Promise {
+ const isExtensionInstalled = await isChromeExtensionInstalled()
+ const config = getGlobalConfig()
+ const isSubscriber = isClaudeAISubscriber()
+ const isWSL = env.isWslEnvironment()
+
+ return (
+
+ )
}
-function _temp4(k_0) {
- return k_0 + 1;
-}
-function _temp3(k_1) {
- return k_1 + 1;
-}
-function _temp2(c) {
- return c.name === CLAUDE_IN_CHROME_MCP_SERVER_NAME;
-}
-function _temp(s) {
- return s.mcp.clients;
-}
-export const call = async function (onDone: (result?: string) => void): Promise {
- const isExtensionInstalled = await isChromeExtensionInstalled();
- const config = getGlobalConfig();
- const isSubscriber = isClaudeAISubscriber();
- const isWSL = env.isWslEnvironment();
- return ;
-};
diff --git a/src/commands/config/config.tsx b/src/commands/config/config.tsx
index b263e37ba..d4e216c38 100644
--- a/src/commands/config/config.tsx
+++ b/src/commands/config/config.tsx
@@ -1,6 +1,7 @@
-import * as React from 'react';
-import { Settings } from '../../components/Settings/Settings.js';
-import type { LocalJSXCommandCall } from '../../types/command.js';
+import * as React from 'react'
+import { Settings } from '../../components/Settings/Settings.js'
+import type { LocalJSXCommandCall } from '../../types/command.js'
+
export const call: LocalJSXCommandCall = async (onDone, context) => {
- return ;
-};
+ return
+}
diff --git a/src/commands/context/context.tsx b/src/commands/context/context.tsx
index 595a3c594..747c5a9de 100644
--- a/src/commands/context/context.tsx
+++ b/src/commands/context/context.tsx
@@ -1,13 +1,13 @@
-import { feature } from 'bun:bundle';
-import * as React from 'react';
-import type { LocalJSXCommandContext } from '../../commands.js';
-import { ContextVisualization } from '../../components/ContextVisualization.js';
-import { microcompactMessages } from '../../services/compact/microCompact.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-import type { Message } from '../../types/message.js';
-import { analyzeContextUsage } from '../../utils/analyzeContext.js';
-import { getMessagesAfterCompactBoundary } from '../../utils/messages.js';
-import { renderToAnsiString } from '../../utils/staticRender.js';
+import { feature } from 'bun:bundle'
+import * as React from 'react'
+import type { LocalJSXCommandContext } from '../../commands.js'
+import { ContextVisualization } from '../../components/ContextVisualization.js'
+import { microcompactMessages } from '../../services/compact/microCompact.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import type { Message } from '../../types/message.js'
+import { analyzeContextUsage } from '../../utils/analyzeContext.js'
+import { getMessagesAfterCompactBoundary } from '../../utils/messages.js'
+import { renderToAnsiString } from '../../utils/staticRender.js'
/**
* Apply the same context transforms query.ts does before the API call, so
@@ -16,48 +16,53 @@ import { renderToAnsiString } from '../../utils/staticRender.js';
* was collapsed — user sees "180k, 3 spans collapsed" when the API sees 120k.
*/
function toApiView(messages: Message[]): Message[] {
- let view = getMessagesAfterCompactBoundary(messages);
+ let view = getMessagesAfterCompactBoundary(messages)
if (feature('CONTEXT_COLLAPSE')) {
/* eslint-disable @typescript-eslint/no-require-imports */
- const {
- projectView
- } = require('../../services/contextCollapse/operations.js') as typeof import('../../services/contextCollapse/operations.js');
+ const { projectView } =
+ require('../../services/contextCollapse/operations.js') as typeof import('../../services/contextCollapse/operations.js')
/* eslint-enable @typescript-eslint/no-require-imports */
- view = projectView(view);
+ view = projectView(view)
}
- return view;
+ return view
}
-export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext): Promise {
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: LocalJSXCommandContext,
+): Promise {
const {
messages,
getAppState,
- options: {
- mainLoopModel,
- tools
- }
- } = context;
- const apiView = toApiView(messages);
+ options: { mainLoopModel, tools },
+ } = context
+
+ const apiView = toApiView(messages)
// Apply microcompact to get accurate representation of messages sent to API
- const {
- messages: compactedMessages
- } = await microcompactMessages(apiView);
+ const { messages: compactedMessages } = await microcompactMessages(apiView)
// Get terminal width for responsive sizing
- const terminalWidth = process.stdout.columns || 80;
- const appState = getAppState();
+ const terminalWidth = process.stdout.columns || 80
+
+ const appState = getAppState()
// Analyze context with compacted messages
// Pass original messages as last parameter for accurate API usage extraction
- const data = await analyzeContextUsage(compactedMessages, mainLoopModel, async () => appState.toolPermissionContext, tools, appState.agentDefinitions, terminalWidth, context,
- // Pass full context for system prompt calculation
- undefined,
- // mainThreadAgentDefinition
- apiView // Original messages for API usage extraction
- );
+ const data = await analyzeContextUsage(
+ compactedMessages,
+ mainLoopModel,
+ async () => appState.toolPermissionContext,
+ tools,
+ appState.agentDefinitions,
+ terminalWidth,
+ context, // Pass full context for system prompt calculation
+ undefined, // mainThreadAgentDefinition
+ apiView, // Original messages for API usage extraction
+ )
// Render to ANSI string to preserve colors and pass to onDone like local commands do
- const output = await renderToAnsiString();
- onDone(output);
- return null;
+ const output = await renderToAnsiString()
+ onDone(output)
+ return null
}
diff --git a/src/commands/copy/copy.tsx b/src/commands/copy/copy.tsx
index f9fc720d0..d5196de20 100644
--- a/src/commands/copy/copy.tsx
+++ b/src/commands/copy/copy.tsx
@@ -1,45 +1,44 @@
-import { c as _c } from "react/compiler-runtime";
-import { mkdir, writeFile } from 'fs/promises';
-import { marked, type Tokens } from 'marked';
-import { tmpdir } from 'os';
-import { join } from 'path';
-import React, { useRef } from 'react';
-import type { CommandResultDisplay } from '../../commands.js';
-import type { OptionWithDescription } from '../../components/CustomSelect/select.js';
-import { Select } from '../../components/CustomSelect/select.js';
-import { Byline } from '../../components/design-system/Byline.js';
-import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js';
-import { Pane } from '../../components/design-system/Pane.js';
-import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
-import { stringWidth } from '../../ink/stringWidth.js';
-import { setClipboard } from '../../ink/termio/osc.js';
-import { Box, Text } from '../../ink.js';
-import { logEvent } from '../../services/analytics/index.js';
-import type { LocalJSXCommandCall } from '../../types/command.js';
-import type { AssistantMessage, Message } from '../../types/message.js';
-import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
-import { extractTextContent, stripPromptXMLTags } from '../../utils/messages.js';
-import { countCharInString } from '../../utils/stringUtils.js';
-const COPY_DIR = join(tmpdir(), 'claude');
-const RESPONSE_FILENAME = 'response.md';
-const MAX_LOOKBACK = 20;
+import { mkdir, writeFile } from 'fs/promises'
+import { marked, type Tokens } from 'marked'
+import { tmpdir } from 'os'
+import { join } from 'path'
+import React, { useRef } from 'react'
+import type { CommandResultDisplay } from '../../commands.js'
+import type { OptionWithDescription } from '../../components/CustomSelect/select.js'
+import { Select } from '../../components/CustomSelect/select.js'
+import { Byline } from '../../components/design-system/Byline.js'
+import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js'
+import { Pane } from '../../components/design-system/Pane.js'
+import type { KeyboardEvent } from '../../ink/events/keyboard-event.js'
+import { stringWidth } from '../../ink/stringWidth.js'
+import { setClipboard } from '../../ink/termio/osc.js'
+import { Box, Text } from '../../ink.js'
+import { logEvent } from '../../services/analytics/index.js'
+import type { LocalJSXCommandCall } from '../../types/command.js'
+import type { AssistantMessage, Message } from '../../types/message.js'
+import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
+import { extractTextContent, stripPromptXMLTags } from '../../utils/messages.js'
+import { countCharInString } from '../../utils/stringUtils.js'
+
+const COPY_DIR = join(tmpdir(), 'claude')
+const RESPONSE_FILENAME = 'response.md'
+const MAX_LOOKBACK = 20
+
type CodeBlock = {
- code: string;
- lang: string | undefined;
-};
+ code: string
+ lang: string | undefined
+}
+
function extractCodeBlocks(markdown: string): CodeBlock[] {
- const tokens = marked.lexer(stripPromptXMLTags(markdown));
- const blocks: CodeBlock[] = [];
+ const tokens = marked.lexer(stripPromptXMLTags(markdown))
+ const blocks: CodeBlock[] = []
for (const token of tokens) {
if (token.type === 'code') {
- const codeToken = token as Tokens.Code;
- blocks.push({
- code: codeToken.text,
- lang: codeToken.lang
- });
+ const codeToken = token as Tokens.Code
+ blocks.push({ code: codeToken.text, lang: codeToken.lang })
}
}
- return blocks;
+ return blocks
}
/**
@@ -48,323 +47,267 @@ function extractCodeBlocks(markdown: string): CodeBlock[] {
* Index 0 = latest, 1 = second-to-latest, etc. Caps at MAX_LOOKBACK.
*/
export function collectRecentAssistantTexts(messages: Message[]): string[] {
- const texts: string[] = [];
- for (let i = messages.length - 1; i >= 0 && texts.length < MAX_LOOKBACK; i--) {
- const msg = messages[i];
- if (msg?.type !== 'assistant' || msg.isApiErrorMessage) continue;
- const content = (msg as AssistantMessage).message.content;
- if (!Array.isArray(content)) continue;
- const text = extractTextContent(content, '\n\n');
- if (text) texts.push(text);
+ const texts: string[] = []
+ for (
+ let i = messages.length - 1;
+ i >= 0 && texts.length < MAX_LOOKBACK;
+ i--
+ ) {
+ const msg = messages[i]
+ if (msg?.type !== 'assistant' || msg.isApiErrorMessage) continue
+ const content = (msg as AssistantMessage).message.content
+ if (!Array.isArray(content)) continue
+ const text = extractTextContent(content, '\n\n')
+ if (text) texts.push(text)
}
- return texts;
+ return texts
}
+
export function fileExtension(lang: string | undefined): string {
if (lang) {
// Sanitize to prevent path traversal (e.g. ```../../etc/passwd)
// Language identifiers are alphanumeric: python, tsx, jsonc, etc.
- const sanitized = lang.replace(/[^a-zA-Z0-9]/g, '');
+ const sanitized = lang.replace(/[^a-zA-Z0-9]/g, '')
if (sanitized && sanitized !== 'plaintext') {
- return `.${sanitized}`;
+ return `.${sanitized}`
}
}
- return '.txt';
+ return '.txt'
}
+
async function writeToFile(text: string, filename: string): Promise {
- const filePath = join(COPY_DIR, filename);
- await mkdir(COPY_DIR, {
- recursive: true
- });
- await writeFile(filePath, text, 'utf-8');
- return filePath;
+ const filePath = join(COPY_DIR, filename)
+ await mkdir(COPY_DIR, { recursive: true })
+ await writeFile(filePath, text, 'utf-8')
+ return filePath
}
-async function copyOrWriteToFile(text: string, filename: string): Promise {
- const raw = await setClipboard(text);
- if (raw) process.stdout.write(raw);
- const lineCount = countCharInString(text, '\n') + 1;
- const charCount = text.length;
+
+async function copyOrWriteToFile(
+ text: string,
+ filename: string,
+): Promise {
+ const raw = await setClipboard(text)
+ if (raw) process.stdout.write(raw)
+ const lineCount = countCharInString(text, '\n') + 1
+ const charCount = text.length
// Also write to a temp file — clipboard paths are best-effort (OSC 52 needs
// terminal support), so the file provides a reliable fallback.
try {
- const filePath = await writeToFile(text, filename);
- return `Copied to clipboard (${charCount} characters, ${lineCount} lines)\nAlso written to ${filePath}`;
+ const filePath = await writeToFile(text, filename)
+ return `Copied to clipboard (${charCount} characters, ${lineCount} lines)\nAlso written to ${filePath}`
} catch {
- return `Copied to clipboard (${charCount} characters, ${lineCount} lines)`;
+ return `Copied to clipboard (${charCount} characters, ${lineCount} lines)`
}
}
+
function truncateLine(text: string, maxLen: number): string {
- const firstLine = text.split('\n')[0] ?? '';
+ const firstLine = text.split('\n')[0] ?? ''
if (stringWidth(firstLine) <= maxLen) {
- return firstLine;
+ return firstLine
}
- let result = '';
- let width = 0;
- const targetWidth = maxLen - 1;
+ let result = ''
+ let width = 0
+ const targetWidth = maxLen - 1
for (const char of firstLine) {
- const charWidth = stringWidth(char);
- if (width + charWidth > targetWidth) break;
- result += char;
- width += charWidth;
+ const charWidth = stringWidth(char)
+ if (width + charWidth > targetWidth) break
+ result += char
+ width += charWidth
}
- return result + '\u2026';
+ return result + '\u2026'
}
+
type PickerProps = {
- fullText: string;
- codeBlocks: CodeBlock[];
- messageAge: number;
- onDone: (result?: string, options?: {
- display?: CommandResultDisplay;
- }) => void;
-};
-type PickerSelection = number | 'full' | 'always';
-function CopyPicker(t0) {
- const $ = _c(33);
- const {
- fullText,
- codeBlocks,
- messageAge,
- onDone
- } = t0;
- const focusedRef = useRef("full");
- const t1 = `${fullText.length} chars, ${countCharInString(fullText, "\n") + 1} lines`;
- let t2;
- if ($[0] !== t1) {
- t2 = {
- label: "Full response",
- value: "full" as const,
- description: t1
- };
- $[0] = t1;
- $[1] = t2;
- } else {
- t2 = $[1];
- }
- let t3;
- if ($[2] !== codeBlocks || $[3] !== t2) {
- let t4;
- if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
- t4 = {
- label: "Always copy full response",
- value: "always" as const,
- description: "Skip this picker in the future (revert via /config)"
- };
- $[5] = t4;
- } else {
- t4 = $[5];
- }
- t3 = [t2, ...codeBlocks.map(_temp), t4];
- $[2] = codeBlocks;
- $[3] = t2;
- $[4] = t3;
- } else {
- t3 = $[4];
- }
- const options = t3;
- let t4;
- if ($[6] !== codeBlocks || $[7] !== fullText) {
- t4 = function getSelectionContent(selected) {
- if (selected === "full" || selected === "always") {
- return {
- text: fullText,
- filename: RESPONSE_FILENAME
- };
- }
- const block_0 = codeBlocks[selected];
+ fullText: string
+ codeBlocks: CodeBlock[]
+ messageAge: number
+ onDone: (
+ result?: string,
+ options?: { display?: CommandResultDisplay },
+ ) => void
+}
+
+type PickerSelection = number | 'full' | 'always'
+
+function CopyPicker({
+ fullText,
+ codeBlocks,
+ messageAge,
+ onDone,
+}: PickerProps): React.ReactNode {
+ const focusedRef = useRef('full')
+
+ const options: OptionWithDescription[] = [
+ {
+ label: 'Full response',
+ value: 'full' as const,
+ description: `${fullText.length} chars, ${countCharInString(fullText, '\n') + 1} lines`,
+ },
+ ...codeBlocks.map((block, index) => {
+ const blockLines = countCharInString(block.code, '\n') + 1
return {
- text: block_0.code,
- filename: `copy${fileExtension(block_0.lang)}`,
- blockIndex: selected
- };
- };
- $[6] = codeBlocks;
- $[7] = fullText;
- $[8] = t4;
- } else {
- t4 = $[8];
- }
- const getSelectionContent = t4;
- let t5;
- if ($[9] !== codeBlocks.length || $[10] !== getSelectionContent || $[11] !== messageAge || $[12] !== onDone) {
- t5 = async function handleSelect(selected_0) {
- const content = getSelectionContent(selected_0);
- if (selected_0 === "always") {
- if (!getGlobalConfig().copyFullResponse) {
- saveGlobalConfig(_temp2);
- }
- logEvent("tengu_copy", {
- block_count: codeBlocks.length,
- always: true,
- message_age: messageAge
- });
- const result = await copyOrWriteToFile(content.text, content.filename);
- onDone(`${result}\nPreference saved. Use /config to change copyFullResponse`);
- return;
+ label: truncateLine(block.code, 60),
+ value: index,
+ description:
+ [block.lang, blockLines > 1 ? `${blockLines} lines` : undefined]
+ .filter(Boolean)
+ .join(', ') || undefined,
}
- logEvent("tengu_copy", {
- selected_block: content.blockIndex,
- block_count: codeBlocks.length,
- message_age: messageAge
- });
- const result_0 = await copyOrWriteToFile(content.text, content.filename);
- onDone(result_0);
- };
- $[9] = codeBlocks.length;
- $[10] = getSelectionContent;
- $[11] = messageAge;
- $[12] = onDone;
- $[13] = t5;
- } else {
- t5 = $[13];
+ }),
+ {
+ label: 'Always copy full response',
+ value: 'always' as const,
+ description: 'Skip this picker in the future (revert via /config)',
+ },
+ ]
+
+ function getSelectionContent(selected: PickerSelection): {
+ text: string
+ filename: string
+ blockIndex?: number
+ } {
+ if (selected === 'full' || selected === 'always') {
+ return { text: fullText, filename: RESPONSE_FILENAME }
+ }
+ const block = codeBlocks[selected]!
+ return {
+ text: block.code,
+ filename: `copy${fileExtension(block.lang)}`,
+ blockIndex: selected,
+ }
}
- const handleSelect = t5;
- let t6;
- if ($[14] !== codeBlocks.length || $[15] !== getSelectionContent || $[16] !== messageAge || $[17] !== onDone) {
- const handleWrite = async function handleWrite(selected_1) {
- const content_0 = getSelectionContent(selected_1);
- logEvent("tengu_copy", {
- selected_block: content_0.blockIndex,
+
+ async function handleSelect(selected: PickerSelection): Promise {
+ const content = getSelectionContent(selected)
+ if (selected === 'always') {
+ if (!getGlobalConfig().copyFullResponse) {
+ saveGlobalConfig(c => ({ ...c, copyFullResponse: true }))
+ }
+ logEvent('tengu_copy', {
block_count: codeBlocks.length,
+ always: true,
message_age: messageAge,
- write_shortcut: true
- });
- ;
- try {
- const filePath = await writeToFile(content_0.text, content_0.filename);
- onDone(`Written to ${filePath}`);
- } catch (t7) {
- const e = t7;
- onDone(`Failed to write file: ${e instanceof Error ? e.message : e}`);
- }
- };
- t6 = function handleKeyDown(e_0) {
- if (e_0.key === "w") {
- e_0.preventDefault();
- handleWrite(focusedRef.current);
- }
- };
- $[14] = codeBlocks.length;
- $[15] = getSelectionContent;
- $[16] = messageAge;
- $[17] = onDone;
- $[18] = t6;
- } else {
- t6 = $[18];
+ })
+ const result = await copyOrWriteToFile(content.text, content.filename)
+ onDone(
+ `${result}\nPreference saved. Use /config to change copyFullResponse`,
+ )
+ return
+ }
+ logEvent('tengu_copy', {
+ selected_block: content.blockIndex,
+ block_count: codeBlocks.length,
+ message_age: messageAge,
+ })
+ const result = await copyOrWriteToFile(content.text, content.filename)
+ onDone(result)
}
- const handleKeyDown = t6;
- let t7;
- if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
- t7 = Select content to copy:;
- $[19] = t7;
- } else {
- t7 = $[19];
+
+ async function handleWrite(selected: PickerSelection): Promise {
+ const content = getSelectionContent(selected)
+ logEvent('tengu_copy', {
+ selected_block: content.blockIndex,
+ block_count: codeBlocks.length,
+ message_age: messageAge,
+ write_shortcut: true,
+ })
+ try {
+ const filePath = await writeToFile(content.text, content.filename)
+ onDone(`Written to ${filePath}`)
+ } catch (e) {
+ onDone(`Failed to write file: ${e instanceof Error ? e.message : e}`)
+ }
}
- let t8;
- if ($[20] === Symbol.for("react.memo_cache_sentinel")) {
- t8 = value => {
- focusedRef.current = value;
- };
- $[20] = t8;
- } else {
- t8 = $[20];
+
+ function handleKeyDown(e: KeyboardEvent): void {
+ if (e.key === 'w') {
+ e.preventDefault()
+ void handleWrite(focusedRef.current)
+ }
}
- let t9;
- if ($[21] !== handleSelect) {
- t9 = selected_2 => {
- handleSelect(selected_2);
- };
- $[21] = handleSelect;
- $[22] = t9;
- } else {
- t9 = $[22];
- }
- let t10;
- if ($[23] !== onDone) {
- t10 = () => {
- onDone("Copy cancelled", {
- display: "system"
- });
- };
- $[23] = onDone;
- $[24] = t10;
- } else {
- t10 = $[24];
- }
- let t11;
- if ($[25] !== options || $[26] !== t10 || $[27] !== t9) {
- t11 = ;
- $[25] = options;
- $[26] = t10;
- $[27] = t9;
- $[28] = t11;
- } else {
- t11 = $[28];
- }
- let t12;
- if ($[29] === Symbol.for("react.memo_cache_sentinel")) {
- t12 = ;
- $[29] = t12;
- } else {
- t12 = $[29];
- }
- let t13;
- if ($[30] !== handleKeyDown || $[31] !== t11) {
- t13 = {t7}{t11}{t12};
- $[30] = handleKeyDown;
- $[31] = t11;
- $[32] = t13;
- } else {
- t13 = $[32];
- }
- return t13;
-}
-function _temp2(c) {
- return {
- ...c,
- copyFullResponse: true
- };
-}
-function _temp(block, index) {
- const blockLines = countCharInString(block.code, "\n") + 1;
- return {
- label: truncateLine(block.code, 60),
- value: index,
- description: [block.lang, blockLines > 1 ? `${blockLines} lines` : undefined].filter(Boolean).join(", ") || undefined
- };
+
+ return (
+
+
+ Select content to copy:
+
+
+ )
}
+
export const call: LocalJSXCommandCall = async (onDone, context, args) => {
- const texts = collectRecentAssistantTexts(context.messages);
+ const texts = collectRecentAssistantTexts(context.messages)
+
if (texts.length === 0) {
- onDone('No assistant message to copy');
- return null;
+ onDone('No assistant message to copy')
+ return null
}
// /copy N reaches back N-1 messages (1 = latest, 2 = second-to-latest, ...)
- let age = 0;
- const arg = args?.trim();
+ let age = 0
+ const arg = args?.trim()
if (arg) {
- const n = Number(arg);
+ const n = Number(arg)
if (!Number.isInteger(n) || n < 1) {
- onDone(`Usage: /copy [N] where N is 1 (latest), 2, 3, \u2026 Got: ${arg}`);
- return null;
+ onDone(`Usage: /copy [N] where N is 1 (latest), 2, 3, \u2026 Got: ${arg}`)
+ return null
}
if (n > texts.length) {
- onDone(`Only ${texts.length} assistant ${texts.length === 1 ? 'message' : 'messages'} available to copy`);
- return null;
+ onDone(
+ `Only ${texts.length} assistant ${texts.length === 1 ? 'message' : 'messages'} available to copy`,
+ )
+ return null
}
- age = n - 1;
+ age = n - 1
}
- const text = texts[age]!;
- const codeBlocks = extractCodeBlocks(text);
- const config = getGlobalConfig();
+
+ const text = texts[age]!
+ const codeBlocks = extractCodeBlocks(text)
+ const config = getGlobalConfig()
+
if (codeBlocks.length === 0 || config.copyFullResponse) {
logEvent('tengu_copy', {
always: config.copyFullResponse,
block_count: codeBlocks.length,
- message_age: age
- });
- const result = await copyOrWriteToFile(text, RESPONSE_FILENAME);
- onDone(result);
- return null;
+ message_age: age,
+ })
+ const result = await copyOrWriteToFile(text, RESPONSE_FILENAME)
+ onDone(result)
+ return null
}
- return ;
-};
+
+ return (
+
+ )
+}
diff --git a/src/commands/desktop/desktop.tsx b/src/commands/desktop/desktop.tsx
index 19a18eee6..b601be32d 100644
--- a/src/commands/desktop/desktop.tsx
+++ b/src/commands/desktop/desktop.tsx
@@ -1,8 +1,12 @@
-import React from 'react';
-import type { CommandResultDisplay } from '../../commands.js';
-import { DesktopHandoff } from '../../components/DesktopHandoff.js';
-export async function call(onDone: (result?: string, options?: {
- display?: CommandResultDisplay;
-}) => void): Promise {
- return ;
+import React from 'react'
+import type { CommandResultDisplay } from '../../commands.js'
+import { DesktopHandoff } from '../../components/DesktopHandoff.js'
+
+export async function call(
+ onDone: (
+ result?: string,
+ options?: { display?: CommandResultDisplay },
+ ) => void,
+): Promise {
+ return
}
diff --git a/src/commands/diff/diff.tsx b/src/commands/diff/diff.tsx
index ab11c3fad..cc3a41dbb 100644
--- a/src/commands/diff/diff.tsx
+++ b/src/commands/diff/diff.tsx
@@ -1,8 +1,7 @@
-import * as React from 'react';
-import type { LocalJSXCommandCall } from '../../types/command.js';
+import * as React from 'react'
+import type { LocalJSXCommandCall } from '../../types/command.js'
+
export const call: LocalJSXCommandCall = async (onDone, context) => {
- const {
- DiffDialog
- } = await import('../../components/diff/DiffDialog.js');
- return ;
-};
+ const { DiffDialog } = await import('../../components/diff/DiffDialog.js')
+ return
+}
diff --git a/src/commands/doctor/doctor.tsx b/src/commands/doctor/doctor.tsx
index e2ad2aff8..e696f0955 100644
--- a/src/commands/doctor/doctor.tsx
+++ b/src/commands/doctor/doctor.tsx
@@ -1,6 +1,7 @@
-import React from 'react';
-import { Doctor } from '../../screens/Doctor.js';
-import type { LocalJSXCommandCall } from '../../types/command.js';
+import React from 'react'
+import { Doctor } from '../../screens/Doctor.js'
+import type { LocalJSXCommandCall } from '../../types/command.js'
+
export const call: LocalJSXCommandCall = (onDone, _context, _args) => {
- return Promise.resolve();
-};
+ return Promise.resolve()
+}
diff --git a/src/commands/effort/effort.tsx b/src/commands/effort/effort.tsx
index 78ee7639b..2804233d0 100644
--- a/src/commands/effort/effort.tsx
+++ b/src/commands/effort/effort.tsx
@@ -1,182 +1,183 @@
-import { c as _c } from "react/compiler-runtime";
-import * as React from 'react';
-import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';
-import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from '../../services/analytics/index.js';
-import { useAppState, useSetAppState } from '../../state/AppState.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-import { type EffortValue, getDisplayedEffortLevel, getEffortEnvOverride, getEffortValueDescription, isEffortLevel, toPersistableEffort } from '../../utils/effort.js';
-import { updateSettingsForSource } from '../../utils/settings/settings.js';
-const COMMON_HELP_ARGS = ['help', '-h', '--help'];
+import * as React from 'react'
+import { useMainLoopModel } from '../../hooks/useMainLoopModel.js'
+import {
+ type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ logEvent,
+} from '../../services/analytics/index.js'
+import { useAppState, useSetAppState } from '../../state/AppState.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import {
+ type EffortValue,
+ getDisplayedEffortLevel,
+ getEffortEnvOverride,
+ getEffortValueDescription,
+ isEffortLevel,
+ toPersistableEffort,
+} from '../../utils/effort.js'
+import { updateSettingsForSource } from '../../utils/settings/settings.js'
+
+const COMMON_HELP_ARGS = ['help', '-h', '--help']
+
type EffortCommandResult = {
- message: string;
- effortUpdate?: {
- value: EffortValue | undefined;
- };
-};
+ message: string
+ effortUpdate?: { value: EffortValue | undefined }
+}
+
function setEffortValue(effortValue: EffortValue): EffortCommandResult {
- const persistable = toPersistableEffort(effortValue);
+ const persistable = toPersistableEffort(effortValue)
if (persistable !== undefined) {
const result = updateSettingsForSource('userSettings', {
- effortLevel: persistable
- });
+ effortLevel: persistable,
+ })
if (result.error) {
return {
- message: `Failed to set effort level: ${result.error.message}`
- };
+ message: `Failed to set effort level: ${result.error.message}`,
+ }
}
}
logEvent('tengu_effort_command', {
- effort: effortValue as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
- });
+ effort:
+ effortValue as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ })
// Env var wins at resolveAppliedEffort time. Only flag it when it actually
// conflicts — if env matches what the user just asked for, the outcome is
// the same, so "Set effort to X" is true and the note is noise.
- const envOverride = getEffortEnvOverride();
+ const envOverride = getEffortEnvOverride()
if (envOverride !== undefined && envOverride !== effortValue) {
- const envRaw = process.env.CLAUDE_CODE_EFFORT_LEVEL;
+ const envRaw = process.env.CLAUDE_CODE_EFFORT_LEVEL
if (persistable === undefined) {
return {
message: `Not applied: CLAUDE_CODE_EFFORT_LEVEL=${envRaw} overrides effort this session, and ${effortValue} is session-only (nothing saved)`,
- effortUpdate: {
- value: effortValue
- }
- };
+ effortUpdate: { value: effortValue },
+ }
}
return {
message: `CLAUDE_CODE_EFFORT_LEVEL=${envRaw} overrides this session — clear it and ${effortValue} takes over`,
- effortUpdate: {
- value: effortValue
- }
- };
+ effortUpdate: { value: effortValue },
+ }
}
- const description = getEffortValueDescription(effortValue);
- const suffix = persistable !== undefined ? '' : ' (this session only)';
+
+ const description = getEffortValueDescription(effortValue)
+ const suffix = persistable !== undefined ? '' : ' (this session only)'
return {
message: `Set effort level to ${effortValue}${suffix}: ${description}`,
- effortUpdate: {
- value: effortValue
- }
- };
-}
-export function showCurrentEffort(appStateEffort: EffortValue | undefined, model: string): EffortCommandResult {
- const envOverride = getEffortEnvOverride();
- const effectiveValue = envOverride === null ? undefined : envOverride ?? appStateEffort;
- if (effectiveValue === undefined) {
- const level = getDisplayedEffortLevel(model, appStateEffort);
- return {
- message: `Effort level: auto (currently ${level})`
- };
+ effortUpdate: { value: effortValue },
}
- const description = getEffortValueDescription(effectiveValue);
- return {
- message: `Current effort level: ${effectiveValue} (${description})`
- };
}
+
+export function showCurrentEffort(
+ appStateEffort: EffortValue | undefined,
+ model: string,
+): EffortCommandResult {
+ const envOverride = getEffortEnvOverride()
+ const effectiveValue =
+ envOverride === null ? undefined : (envOverride ?? appStateEffort)
+ if (effectiveValue === undefined) {
+ const level = getDisplayedEffortLevel(model, appStateEffort)
+ return { message: `Effort level: auto (currently ${level})` }
+ }
+ const description = getEffortValueDescription(effectiveValue)
+ return {
+ message: `Current effort level: ${effectiveValue} (${description})`,
+ }
+}
+
function unsetEffortLevel(): EffortCommandResult {
const result = updateSettingsForSource('userSettings', {
- effortLevel: undefined
- });
+ effortLevel: undefined,
+ })
if (result.error) {
return {
- message: `Failed to set effort level: ${result.error.message}`
- };
+ message: `Failed to set effort level: ${result.error.message}`,
+ }
}
logEvent('tengu_effort_command', {
- effort: 'auto' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
- });
+ effort:
+ 'auto' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ })
// env=auto/unset (null) matches what /effort auto asks for, so only warn
// when env is pinning a specific level that will keep overriding.
- const envOverride = getEffortEnvOverride();
+ const envOverride = getEffortEnvOverride()
if (envOverride !== undefined && envOverride !== null) {
- const envRaw = process.env.CLAUDE_CODE_EFFORT_LEVEL;
+ const envRaw = process.env.CLAUDE_CODE_EFFORT_LEVEL
return {
message: `Cleared effort from settings, but CLAUDE_CODE_EFFORT_LEVEL=${envRaw} still controls this session`,
- effortUpdate: {
- value: undefined
- }
- };
+ effortUpdate: { value: undefined },
+ }
}
return {
message: 'Effort level set to auto',
- effortUpdate: {
- value: undefined
- }
- };
-}
-export function executeEffort(args: string): EffortCommandResult {
- const normalized = args.toLowerCase();
- if (normalized === 'auto' || normalized === 'unset') {
- return unsetEffortLevel();
+ effortUpdate: { value: undefined },
}
+}
+
+export function executeEffort(args: string): EffortCommandResult {
+ const normalized = args.toLowerCase()
+ if (normalized === 'auto' || normalized === 'unset') {
+ return unsetEffortLevel()
+ }
+
if (!isEffortLevel(normalized)) {
return {
- message: `Invalid argument: ${args}. Valid options are: low, medium, high, max, auto`
- };
+ message: `Invalid argument: ${args}. Valid options are: low, medium, high, max, auto`,
+ }
}
- return setEffortValue(normalized);
+
+ return setEffortValue(normalized)
}
-function ShowCurrentEffort(t0) {
- const {
- onDone
- } = t0;
- const effortValue = useAppState(_temp);
- const model = useMainLoopModel();
- const {
- message
- } = showCurrentEffort(effortValue, model);
- onDone(message);
- return null;
+
+function ShowCurrentEffort({
+ onDone,
+}: {
+ onDone: (result: string) => void
+}): React.ReactNode {
+ const effortValue = useAppState(s => s.effortValue)
+ const model = useMainLoopModel()
+ const { message } = showCurrentEffort(effortValue, model)
+ onDone(message)
+ return null
}
-function _temp(s) {
- return s.effortValue;
+
+function ApplyEffortAndClose({
+ result,
+ onDone,
+}: {
+ result: EffortCommandResult
+ onDone: (result: string) => void
+}): React.ReactNode {
+ const setAppState = useSetAppState()
+ const { effortUpdate, message } = result
+ React.useEffect(() => {
+ if (effortUpdate) {
+ setAppState(prev => ({
+ ...prev,
+ effortValue: effortUpdate.value,
+ }))
+ }
+ onDone(message)
+ }, [setAppState, effortUpdate, message, onDone])
+ return null
}
-function ApplyEffortAndClose(t0) {
- const $ = _c(6);
- const {
- result,
- onDone
- } = t0;
- const setAppState = useSetAppState();
- const {
- effortUpdate,
- message
- } = result;
- let t1;
- let t2;
- if ($[0] !== effortUpdate || $[1] !== message || $[2] !== onDone || $[3] !== setAppState) {
- t1 = () => {
- if (effortUpdate) {
- setAppState(prev => ({
- ...prev,
- effortValue: effortUpdate.value
- }));
- }
- onDone(message);
- };
- t2 = [setAppState, effortUpdate, message, onDone];
- $[0] = effortUpdate;
- $[1] = message;
- $[2] = onDone;
- $[3] = setAppState;
- $[4] = t1;
- $[5] = t2;
- } else {
- t1 = $[4];
- t2 = $[5];
- }
- React.useEffect(t1, t2);
- return null;
-}
-export async function call(onDone: LocalJSXCommandOnDone, _context: unknown, args?: string): Promise {
- args = args?.trim() || '';
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ _context: unknown,
+ args?: string,
+): Promise {
+ args = args?.trim() || ''
+
if (COMMON_HELP_ARGS.includes(args)) {
- onDone('Usage: /effort [low|medium|high|max|auto]\n\nEffort levels:\n- low: Quick, straightforward implementation\n- medium: Balanced approach with standard testing\n- high: Comprehensive implementation with extensive testing\n- max: Maximum capability with deepest reasoning (Opus 4.6 only)\n- auto: Use the default effort level for your model');
- return;
+ onDone(
+ 'Usage: /effort [low|medium|high|max|auto]\n\nEffort levels:\n- low: Quick, straightforward implementation\n- medium: Balanced approach with standard testing\n- high: Comprehensive implementation with extensive testing\n- max: Maximum capability with deepest reasoning (Opus 4.6 only)\n- auto: Use the default effort level for your model',
+ )
+ return
}
+
if (!args || args === 'current' || args === 'status') {
- return ;
+ return
}
- const result = executeEffort(args);
- return ;
+
+ const result = executeEffort(args)
+ return
}
diff --git a/src/commands/exit/exit.tsx b/src/commands/exit/exit.tsx
index 414d238d8..64e9ed77c 100644
--- a/src/commands/exit/exit.tsx
+++ b/src/commands/exit/exit.tsx
@@ -1,32 +1,44 @@
-import { feature } from 'bun:bundle';
-import { spawnSync } from 'child_process';
-import sample from 'lodash-es/sample.js';
-import * as React from 'react';
-import { ExitFlow } from '../../components/ExitFlow.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-import { isBgSession } from '../../utils/concurrentSessions.js';
-import { gracefulShutdown } from '../../utils/gracefulShutdown.js';
-import { getCurrentWorktreeSession } from '../../utils/worktree.js';
-const GOODBYE_MESSAGES = ['Goodbye!', 'See ya!', 'Bye!', 'Catch you later!'];
+import { feature } from 'bun:bundle'
+import { spawnSync } from 'child_process'
+import sample from 'lodash-es/sample.js'
+import * as React from 'react'
+import { ExitFlow } from '../../components/ExitFlow.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import { isBgSession } from '../../utils/concurrentSessions.js'
+import { gracefulShutdown } from '../../utils/gracefulShutdown.js'
+import { getCurrentWorktreeSession } from '../../utils/worktree.js'
+
+const GOODBYE_MESSAGES = ['Goodbye!', 'See ya!', 'Bye!', 'Catch you later!']
+
function getRandomGoodbyeMessage(): string {
- return sample(GOODBYE_MESSAGES) ?? 'Goodbye!';
+ return sample(GOODBYE_MESSAGES) ?? 'Goodbye!'
}
-export async function call(onDone: LocalJSXCommandOnDone): Promise {
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+): Promise {
// Inside a `claude --bg` tmux session: detach instead of kill. The REPL
// keeps running; `claude attach` can reconnect. Covers /exit, /quit,
// ctrl+c, ctrl+d — all funnel through here via REPL's handleExit.
if (feature('BG_SESSIONS') && isBgSession()) {
- onDone();
- spawnSync('tmux', ['detach-client'], {
- stdio: 'ignore'
- });
- return null;
+ onDone()
+ spawnSync('tmux', ['detach-client'], { stdio: 'ignore' })
+ return null
}
- const showWorktree = getCurrentWorktreeSession() !== null;
+
+ const showWorktree = getCurrentWorktreeSession() !== null
+
if (showWorktree) {
- return onDone()} />;
+ return (
+ onDone()}
+ />
+ )
}
- onDone(getRandomGoodbyeMessage());
- await gracefulShutdown(0, 'prompt_input_exit');
- return null;
+
+ onDone(getRandomGoodbyeMessage())
+ await gracefulShutdown(0, 'prompt_input_exit')
+ return null
}
diff --git a/src/commands/export/export.tsx b/src/commands/export/export.tsx
index 8bc146cf3..d13436b02 100644
--- a/src/commands/export/export.tsx
+++ b/src/commands/export/export.tsx
@@ -1,90 +1,121 @@
-import { join } from 'path';
-import React from 'react';
-import { ExportDialog } from '../../components/ExportDialog.js';
-import type { ToolUseContext } from '../../Tool.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-import type { Message } from '../../types/message.js';
-import { getCwd } from '../../utils/cwd.js';
-import { renderMessagesToPlainText } from '../../utils/exportRenderer.js';
-import { writeFileSync_DEPRECATED } from '../../utils/slowOperations.js';
+import { join } from 'path'
+import React from 'react'
+import { ExportDialog } from '../../components/ExportDialog.js'
+import type { ToolUseContext } from '../../Tool.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import type { Message } from '../../types/message.js'
+import { getCwd } from '../../utils/cwd.js'
+import { renderMessagesToPlainText } from '../../utils/exportRenderer.js'
+import { writeFileSync_DEPRECATED } from '../../utils/slowOperations.js'
+
function formatTimestamp(date: Date): string {
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
- const hours = String(date.getHours()).padStart(2, '0');
- const minutes = String(date.getMinutes()).padStart(2, '0');
- const seconds = String(date.getSeconds()).padStart(2, '0');
- return `${year}-${month}-${day}-${hours}${minutes}${seconds}`;
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ const hours = String(date.getHours()).padStart(2, '0')
+ const minutes = String(date.getMinutes()).padStart(2, '0')
+ const seconds = String(date.getSeconds()).padStart(2, '0')
+ return `${year}-${month}-${day}-${hours}${minutes}${seconds}`
}
+
export function extractFirstPrompt(messages: Message[]): string {
- const firstUserMessage = messages.find(msg => msg.type === 'user');
+ const firstUserMessage = messages.find(msg => msg.type === 'user')
+
if (!firstUserMessage || firstUserMessage.type !== 'user') {
- return '';
+ return ''
}
- const content = firstUserMessage.message?.content;
- let result = '';
+
+ const content = firstUserMessage.message?.content
+ let result = ''
+
if (typeof content === 'string') {
- result = content.trim();
+ result = content.trim()
} else if (Array.isArray(content)) {
- const textContent = content.find(item => item.type === 'text');
+ const textContent = content.find(item => item.type === 'text')
if (textContent && 'text' in textContent) {
- result = textContent.text.trim();
+ result = textContent.text.trim()
}
}
// Take first line only and limit length
- result = result.split('\n')[0] || '';
+ result = result.split('\n')[0] || ''
if (result.length > 50) {
- result = result.substring(0, 49) + '…';
+ result = result.substring(0, 49) + '…'
}
- return result;
+
+ return result
}
+
export function sanitizeFilename(text: string): string {
// Replace special characters with hyphens
- return text.toLowerCase().replace(/[^a-z0-9\s-]/g, '') // Remove special chars
- .replace(/\s+/g, '-') // Replace spaces with hyphens
- .replace(/-+/g, '-') // Replace multiple hyphens with single
- .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
+ return text
+ .toLowerCase()
+ .replace(/[^a-z0-9\s-]/g, '') // Remove special chars
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
+ .replace(/-+/g, '-') // Replace multiple hyphens with single
+ .replace(/^-|-$/g, '') // Remove leading/trailing hyphens
}
-async function exportWithReactRenderer(context: ToolUseContext): Promise {
- const tools = context.options.tools || [];
- return renderMessagesToPlainText(context.messages, tools);
+
+async function exportWithReactRenderer(
+ context: ToolUseContext,
+): Promise {
+ const tools = context.options.tools || []
+ return renderMessagesToPlainText(context.messages, tools)
}
-export async function call(onDone: LocalJSXCommandOnDone, context: ToolUseContext, args: string): Promise {
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: ToolUseContext,
+ args: string,
+): Promise {
// Render the conversation content
- const content = await exportWithReactRenderer(context);
+ const content = await exportWithReactRenderer(context)
// If args are provided, write directly to file and skip dialog
- const filename = args.trim();
+ const filename = args.trim()
if (filename) {
- const finalFilename = filename.endsWith('.txt') ? filename : filename.replace(/\.[^.]+$/, '') + '.txt';
- const filepath = join(getCwd(), finalFilename);
+ const finalFilename = filename.endsWith('.txt')
+ ? filename
+ : filename.replace(/\.[^.]+$/, '') + '.txt'
+ const filepath = join(getCwd(), finalFilename)
+
try {
writeFileSync_DEPRECATED(filepath, content, {
encoding: 'utf-8',
- flush: true
- });
- onDone(`Conversation exported to: ${filepath}`);
- return null;
+ flush: true,
+ })
+ onDone(`Conversation exported to: ${filepath}`)
+ return null
} catch (error) {
- onDone(`Failed to export conversation: ${error instanceof Error ? error.message : 'Unknown error'}`);
- return null;
+ onDone(
+ `Failed to export conversation: ${error instanceof Error ? error.message : 'Unknown error'}`,
+ )
+ return null
}
}
// Generate default filename from first prompt or timestamp
- const firstPrompt = extractFirstPrompt(context.messages);
- const timestamp = formatTimestamp(new Date());
- let defaultFilename: string;
+ const firstPrompt = extractFirstPrompt(context.messages)
+ const timestamp = formatTimestamp(new Date())
+
+ let defaultFilename: string
if (firstPrompt) {
- const sanitized = sanitizeFilename(firstPrompt);
- defaultFilename = sanitized ? `${timestamp}-${sanitized}.txt` : `conversation-${timestamp}.txt`;
+ const sanitized = sanitizeFilename(firstPrompt)
+ defaultFilename = sanitized
+ ? `${timestamp}-${sanitized}.txt`
+ : `conversation-${timestamp}.txt`
} else {
- defaultFilename = `conversation-${timestamp}.txt`;
+ defaultFilename = `conversation-${timestamp}.txt`
}
// Return the dialog component when no args provided
- return {
- onDone(result.message);
- }} />;
+ return (
+ {
+ onDone(result.message)
+ }}
+ />
+ )
}
diff --git a/src/commands/extra-usage/extra-usage.tsx b/src/commands/extra-usage/extra-usage.tsx
index 2985d146e..4bdb6284b 100644
--- a/src/commands/extra-usage/extra-usage.tsx
+++ b/src/commands/extra-usage/extra-usage.tsx
@@ -1,16 +1,29 @@
-import React from 'react';
-import type { LocalJSXCommandContext } from '../../commands.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-import { Login } from '../login/login.js';
-import { runExtraUsage } from './extra-usage-core.js';
-export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext): Promise {
- const result = await runExtraUsage();
+import React from 'react'
+import type { LocalJSXCommandContext } from '../../commands.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import { Login } from '../login/login.js'
+import { runExtraUsage } from './extra-usage-core.js'
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: LocalJSXCommandContext,
+): Promise {
+ const result = await runExtraUsage()
+
if (result.type === 'message') {
- onDone(result.value);
- return null;
+ onDone(result.value)
+ return null
}
- return {
- context.onChangeAPIKey();
- onDone(success ? 'Login successful' : 'Login interrupted');
- }} />;
+
+ return (
+ {
+ context.onChangeAPIKey()
+ onDone(success ? 'Login successful' : 'Login interrupted')
+ }}
+ />
+ )
}
diff --git a/src/commands/fast/fast.tsx b/src/commands/fast/fast.tsx
index 57e7876b1..a959a909a 100644
--- a/src/commands/fast/fast.tsx
+++ b/src/commands/fast/fast.tsx
@@ -1,268 +1,260 @@
-import { c as _c } from "react/compiler-runtime";
-import * as React from 'react';
-import { useState } from 'react';
-import type { CommandResultDisplay, LocalJSXCommandContext } from '../../commands.js';
-import { Dialog } from '../../components/design-system/Dialog.js';
-import { FastIcon, getFastIconString } from '../../components/FastIcon.js';
-import { Box, Link, Text } from '../../ink.js';
-import { useKeybindings } from '../../keybindings/useKeybinding.js';
-import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from '../../services/analytics/index.js';
-import { type AppState, useAppState, useSetAppState } from '../../state/AppState.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-import { clearFastModeCooldown, FAST_MODE_MODEL_DISPLAY, getFastModeModel, getFastModeRuntimeState, getFastModeUnavailableReason, isFastModeEnabled, isFastModeSupportedByModel, prefetchFastModeStatus } from '../../utils/fastMode.js';
-import { formatDuration } from '../../utils/format.js';
-import { formatModelPricing, getOpus46CostTier } from '../../utils/modelCost.js';
-import { updateSettingsForSource } from '../../utils/settings/settings.js';
-function applyFastMode(enable: boolean, setAppState: (f: (prev: AppState) => AppState) => void): void {
- clearFastModeCooldown();
+import * as React from 'react'
+import { useState } from 'react'
+import type {
+ CommandResultDisplay,
+ LocalJSXCommandContext,
+} from '../../commands.js'
+import { Dialog } from '../../components/design-system/Dialog.js'
+import { FastIcon, getFastIconString } from '../../components/FastIcon.js'
+import { Box, Link, Text } from '../../ink.js'
+import { useKeybindings } from '../../keybindings/useKeybinding.js'
+import {
+ type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ logEvent,
+} from '../../services/analytics/index.js'
+import {
+ type AppState,
+ useAppState,
+ useSetAppState,
+} from '../../state/AppState.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import {
+ clearFastModeCooldown,
+ FAST_MODE_MODEL_DISPLAY,
+ getFastModeModel,
+ getFastModeRuntimeState,
+ getFastModeUnavailableReason,
+ isFastModeEnabled,
+ isFastModeSupportedByModel,
+ prefetchFastModeStatus,
+} from '../../utils/fastMode.js'
+import { formatDuration } from '../../utils/format.js'
+import { formatModelPricing, getOpus46CostTier } from '../../utils/modelCost.js'
+import { updateSettingsForSource } from '../../utils/settings/settings.js'
+
+function applyFastMode(
+ enable: boolean,
+ setAppState: (f: (prev: AppState) => AppState) => void,
+): void {
+ clearFastModeCooldown()
updateSettingsForSource('userSettings', {
- fastMode: enable ? true : undefined
- });
+ fastMode: enable ? true : undefined,
+ })
if (enable) {
setAppState(prev => {
// Only switch model if current model doesn't support fast mode
- const needsModelSwitch = !isFastModeSupportedByModel(prev.mainLoopModel);
+ const needsModelSwitch = !isFastModeSupportedByModel(prev.mainLoopModel)
return {
...prev,
- ...(needsModelSwitch ? {
- mainLoopModel: getFastModeModel(),
- mainLoopModelForSession: null
- } : {}),
- fastMode: true
- };
- });
- } else {
- setAppState(prev => ({
- ...prev,
- fastMode: false
- }));
- }
-}
-export function FastModePicker(t0) {
- const $ = _c(30);
- const {
- onDone,
- unavailableReason
- } = t0;
- const model = useAppState(_temp);
- const initialFastMode = useAppState(_temp2);
- const setAppState = useSetAppState();
- const [enableFastMode, setEnableFastMode] = useState(initialFastMode ?? false);
- let t1;
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
- t1 = getFastModeRuntimeState();
- $[0] = t1;
- } else {
- t1 = $[0];
- }
- const runtimeState = t1;
- const isCooldown = runtimeState.status === "cooldown";
- const isUnavailable = unavailableReason !== null;
- let t2;
- if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
- t2 = formatModelPricing(getOpus46CostTier(true));
- $[1] = t2;
- } else {
- t2 = $[1];
- }
- const pricing = t2;
- let t3;
- if ($[2] !== enableFastMode || $[3] !== isUnavailable || $[4] !== model || $[5] !== onDone || $[6] !== setAppState) {
- t3 = function handleConfirm() {
- if (isUnavailable) {
- return;
+ ...(needsModelSwitch
+ ? { mainLoopModel: getFastModeModel(), mainLoopModelForSession: null }
+ : {}),
+ fastMode: true,
}
- applyFastMode(enableFastMode, setAppState);
- logEvent("tengu_fast_mode_toggled", {
- enabled: enableFastMode,
- source: "picker" as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
- });
- if (enableFastMode) {
- const fastIcon = getFastIconString(enableFastMode);
- const modelUpdated = !isFastModeSupportedByModel(model) ? ` · model set to ${FAST_MODE_MODEL_DISPLAY}` : "";
- onDone(`${fastIcon} Fast mode ON${modelUpdated} · ${pricing}`);
- } else {
- setAppState(_temp3);
- onDone("Fast mode OFF");
+ })
+ } else {
+ setAppState(prev => ({ ...prev, fastMode: false }))
+ }
+}
+
+export function FastModePicker({
+ onDone,
+ unavailableReason,
+}: {
+ onDone: (
+ result?: string,
+ options?: { display?: CommandResultDisplay },
+ ) => void
+ unavailableReason: string | null
+}): React.ReactNode {
+ const model = useAppState(s => s.mainLoopModel)
+ const initialFastMode = useAppState(s => s.fastMode)
+ const setAppState = useSetAppState()
+ const [enableFastMode, setEnableFastMode] = useState(initialFastMode ?? false)
+ const runtimeState = getFastModeRuntimeState()
+ const isCooldown = runtimeState.status === 'cooldown'
+ const isUnavailable = unavailableReason !== null
+ const pricing = formatModelPricing(getOpus46CostTier(true))
+
+ function handleConfirm(): void {
+ if (isUnavailable) return
+ applyFastMode(enableFastMode, setAppState)
+ logEvent('tengu_fast_mode_toggled', {
+ enabled: enableFastMode,
+ source:
+ 'picker' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ })
+ if (enableFastMode) {
+ const fastIcon = getFastIconString(enableFastMode)
+ const modelUpdated = !isFastModeSupportedByModel(model)
+ ? ` · model set to ${FAST_MODE_MODEL_DISPLAY}`
+ : ''
+ onDone(`${fastIcon} Fast mode ON${modelUpdated} · ${pricing}`)
+ } else {
+ setAppState(prev => ({ ...prev, fastMode: false }))
+ onDone(`Fast mode OFF`)
+ }
+ }
+
+ function handleCancel(): void {
+ if (isUnavailable) {
+ // Ensure fast mode is off if the org has disabled it
+ if (initialFastMode) {
+ applyFastMode(false, setAppState)
}
- };
- $[2] = enableFastMode;
- $[3] = isUnavailable;
- $[4] = model;
- $[5] = onDone;
- $[6] = setAppState;
- $[7] = t3;
- } else {
- t3 = $[7];
+ onDone('Fast mode OFF', { display: 'system' })
+ return
+ }
+ const message = initialFastMode
+ ? `${getFastIconString()} Kept Fast mode ON`
+ : `Kept Fast mode OFF`
+ onDone(message, { display: 'system' })
}
- const handleConfirm = t3;
- let t4;
- if ($[8] !== initialFastMode || $[9] !== isUnavailable || $[10] !== onDone || $[11] !== setAppState) {
- t4 = function handleCancel() {
- if (isUnavailable) {
- if (initialFastMode) {
- applyFastMode(false, setAppState);
- }
- onDone("Fast mode OFF", {
- display: "system"
- });
- return;
+
+ function handleToggle(): void {
+ if (isUnavailable) return
+ setEnableFastMode(prev => !prev)
+ }
+
+ useKeybindings(
+ {
+ 'confirm:yes': handleConfirm,
+ 'confirm:nextField': handleToggle,
+ 'confirm:next': handleToggle,
+ 'confirm:previous': handleToggle,
+ 'confirm:cycleMode': handleToggle,
+ 'confirm:toggle': handleToggle,
+ },
+ { context: 'Confirmation' },
+ )
+
+ const title = (
+
+ Fast mode (research preview)
+
+ )
+
+ return (
+
+ )
}
-function _temp4(prev_0) {
- return !prev_0;
-}
-function _temp3(prev) {
- return {
- ...prev,
- fastMode: false
- };
-}
-function _temp2(s_0) {
- return s_0.fastMode;
-}
-function _temp(s) {
- return s.mainLoopModel;
-}
-async function handleFastModeShortcut(enable: boolean, getAppState: () => AppState, setAppState: (f: (prev: AppState) => AppState) => void): Promise {
- const unavailableReason = getFastModeUnavailableReason();
+
+async function handleFastModeShortcut(
+ enable: boolean,
+ getAppState: () => AppState,
+ setAppState: (f: (prev: AppState) => AppState) => void,
+): Promise {
+ const unavailableReason = getFastModeUnavailableReason()
if (unavailableReason) {
- return `Fast mode unavailable: ${unavailableReason}`;
+ return `Fast mode unavailable: ${unavailableReason}`
}
- const {
- mainLoopModel
- } = getAppState();
- applyFastMode(enable, setAppState);
+
+ const { mainLoopModel } = getAppState()
+ applyFastMode(enable, setAppState)
logEvent('tengu_fast_mode_toggled', {
enabled: enable,
- source: 'shortcut' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
- });
+ source:
+ 'shortcut' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ })
+
if (enable) {
- const fastIcon = getFastIconString(true);
- const modelUpdated = !isFastModeSupportedByModel(mainLoopModel) ? ` · model set to ${FAST_MODE_MODEL_DISPLAY}` : '';
- const pricing = formatModelPricing(getOpus46CostTier(true));
- return `${fastIcon} Fast mode ON${modelUpdated} · ${pricing}`;
+ const fastIcon = getFastIconString(true)
+ const modelUpdated = !isFastModeSupportedByModel(mainLoopModel)
+ ? ` · model set to ${FAST_MODE_MODEL_DISPLAY}`
+ : ''
+ const pricing = formatModelPricing(getOpus46CostTier(true))
+ return `${fastIcon} Fast mode ON${modelUpdated} · ${pricing}`
} else {
- return `Fast mode OFF`;
+ return `Fast mode OFF`
}
}
-export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext, args?: string): Promise {
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: LocalJSXCommandContext,
+ args?: string,
+): Promise {
if (!isFastModeEnabled()) {
- return null;
+ return null
}
// Fetch org fast mode status before showing the picker. We must know
// whether the org has disabled fast mode before allowing any toggle.
// If a startup prefetch is already in flight, this awaits it.
- await prefetchFastModeStatus();
- const arg = args?.trim().toLowerCase();
+ await prefetchFastModeStatus()
+
+ const arg = args?.trim().toLowerCase()
if (arg === 'on' || arg === 'off') {
- const result = await handleFastModeShortcut(arg === 'on', context.getAppState, context.setAppState);
- onDone(result);
- return null;
+ const result = await handleFastModeShortcut(
+ arg === 'on',
+ context.getAppState,
+ context.setAppState,
+ )
+ onDone(result)
+ return null
}
- const unavailableReason = getFastModeUnavailableReason();
+
+ const unavailableReason = getFastModeUnavailableReason()
logEvent('tengu_fast_mode_picker_shown', {
- unavailable_reason: (unavailableReason ?? '') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
- });
- return ;
+ unavailable_reason: (unavailableReason ??
+ '') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ })
+ return (
+
+ )
}
diff --git a/src/commands/feedback/feedback.tsx b/src/commands/feedback/feedback.tsx
index becb4a923..1c3fda4bd 100644
--- a/src/commands/feedback/feedback.tsx
+++ b/src/commands/feedback/feedback.tsx
@@ -1,24 +1,50 @@
-import * as React from 'react';
-import type { CommandResultDisplay, LocalJSXCommandContext } from '../../commands.js';
-import { Feedback } from '../../components/Feedback.js';
-import type { LocalJSXCommandOnDone } from '../../types/command.js';
-import type { Message } from '../../types/message.js';
+import * as React from 'react'
+import type {
+ CommandResultDisplay,
+ LocalJSXCommandContext,
+} from '../../commands.js'
+import { Feedback } from '../../components/Feedback.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import type { Message } from '../../types/message.js'
// Shared function to render the Feedback component
-export function renderFeedbackComponent(onDone: (result?: string, options?: {
- display?: CommandResultDisplay;
-}) => void, abortSignal: AbortSignal, messages: Message[], initialDescription: string = '', backgroundTasks: {
- [taskId: string]: {
- type: string;
- identity?: {
- agentId: string;
- };
- messages?: Message[];
- };
-} = {}): React.ReactNode {
- return ;
+export function renderFeedbackComponent(
+ onDone: (
+ result?: string,
+ options?: { display?: CommandResultDisplay },
+ ) => void,
+ abortSignal: AbortSignal,
+ messages: Message[],
+ initialDescription: string = '',
+ backgroundTasks: {
+ [taskId: string]: {
+ type: string
+ identity?: { agentId: string }
+ messages?: Message[]
+ }
+ } = {},
+): React.ReactNode {
+ return (
+
+ )
}
-export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext, args?: string): Promise {
- const initialDescription = args || '';
- return renderFeedbackComponent(onDone, context.abortController.signal, context.messages, initialDescription);
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: LocalJSXCommandContext,
+ args?: string,
+): Promise {
+ const initialDescription = args || ''
+ return renderFeedbackComponent(
+ onDone,
+ context.abortController.signal,
+ context.messages,
+ initialDescription,
+ )
}
diff --git a/src/commands/help/help.tsx b/src/commands/help/help.tsx
index c07199f8a..f4e066b5d 100644
--- a/src/commands/help/help.tsx
+++ b/src/commands/help/help.tsx
@@ -1,10 +1,10 @@
-import * as React from 'react';
-import { HelpV2 } from '../../components/HelpV2/HelpV2.js';
-import type { LocalJSXCommandCall } from '../../types/command.js';
-export const call: LocalJSXCommandCall = async (onDone, {
- options: {
- commands
- }
-}) => {
- return ;
-};
+import * as React from 'react'
+import { HelpV2 } from '../../components/HelpV2/HelpV2.js'
+import type { LocalJSXCommandCall } from '../../types/command.js'
+
+export const call: LocalJSXCommandCall = async (
+ onDone,
+ { options: { commands } },
+) => {
+ return
+}
diff --git a/src/commands/hooks/hooks.tsx b/src/commands/hooks/hooks.tsx
index bf101fc82..80d27e3ac 100644
--- a/src/commands/hooks/hooks.tsx
+++ b/src/commands/hooks/hooks.tsx
@@ -1,12 +1,13 @@
-import * as React from 'react';
-import { HooksConfigMenu } from '../../components/hooks/HooksConfigMenu.js';
-import { logEvent } from '../../services/analytics/index.js';
-import { getTools } from '../../tools.js';
-import type { LocalJSXCommandCall } from '../../types/command.js';
+import * as React from 'react'
+import { HooksConfigMenu } from '../../components/hooks/HooksConfigMenu.js'
+import { logEvent } from '../../services/analytics/index.js'
+import { getTools } from '../../tools.js'
+import type { LocalJSXCommandCall } from '../../types/command.js'
+
export const call: LocalJSXCommandCall = async (onDone, context) => {
- logEvent('tengu_hooks_command', {});
- const appState = context.getAppState();
- const permissionContext = appState.toolPermissionContext;
- const toolNames = getTools(permissionContext).map(tool => tool.name);
- return ;
-};
+ logEvent('tengu_hooks_command', {})
+ const appState = context.getAppState()
+ const permissionContext = appState.toolPermissionContext
+ const toolNames = getTools(permissionContext).map(tool => tool.name)
+ return
+}
diff --git a/src/commands/ide/ide.tsx b/src/commands/ide/ide.tsx
index 60b1618ea..f22c16e6a 100644
--- a/src/commands/ide/ide.tsx
+++ b/src/commands/ide/ide.tsx
@@ -1,606 +1,582 @@
-import { c as _c } from "react/compiler-runtime";
-import chalk from 'chalk';
-import * as path from 'path';
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { logEvent } from 'src/services/analytics/index.js';
-import type { CommandResultDisplay, LocalJSXCommandContext } from '../../commands.js';
-import { Select } from '../../components/CustomSelect/index.js';
-import { Dialog } from '../../components/design-system/Dialog.js';
-import { IdeAutoConnectDialog, IdeDisableAutoConnectDialog, shouldShowAutoConnectDialog, shouldShowDisableAutoConnectDialog } from '../../components/IdeAutoConnectDialog.js';
-import { Box, Text } from '../../ink.js';
-import { clearServerCache } from '../../services/mcp/client.js';
-import type { ScopedMcpServerConfig } from '../../services/mcp/types.js';
-import { useAppState, useSetAppState } from '../../state/AppState.js';
-import { getCwd } from '../../utils/cwd.js';
-import { execFileNoThrow } from '../../utils/execFileNoThrow.js';
-import { type DetectedIDEInfo, detectIDEs, detectRunningIDEs, type IdeType, isJetBrainsIde, isSupportedJetBrainsTerminal, isSupportedTerminal, toIDEDisplayName } from '../../utils/ide.js';
-import { getCurrentWorktreeSession } from '../../utils/worktree.js';
+import chalk from 'chalk'
+import * as path from 'path'
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import { logEvent } from 'src/services/analytics/index.js'
+import type {
+ CommandResultDisplay,
+ LocalJSXCommandContext,
+} from '../../commands.js'
+import { Select } from '../../components/CustomSelect/index.js'
+import { Dialog } from '../../components/design-system/Dialog.js'
+import {
+ IdeAutoConnectDialog,
+ IdeDisableAutoConnectDialog,
+ shouldShowAutoConnectDialog,
+ shouldShowDisableAutoConnectDialog,
+} from '../../components/IdeAutoConnectDialog.js'
+import { Box, Text } from '../../ink.js'
+import { clearServerCache } from '../../services/mcp/client.js'
+import type { ScopedMcpServerConfig } from '../../services/mcp/types.js'
+import { useAppState, useSetAppState } from '../../state/AppState.js'
+import { getCwd } from '../../utils/cwd.js'
+import { execFileNoThrow } from '../../utils/execFileNoThrow.js'
+import {
+ type DetectedIDEInfo,
+ detectIDEs,
+ detectRunningIDEs,
+ type IdeType,
+ isJetBrainsIde,
+ isSupportedJetBrainsTerminal,
+ isSupportedTerminal,
+ toIDEDisplayName,
+} from '../../utils/ide.js'
+import { getCurrentWorktreeSession } from '../../utils/worktree.js'
+
type IDEScreenProps = {
- availableIDEs: DetectedIDEInfo[];
- unavailableIDEs: DetectedIDEInfo[];
- selectedIDE?: DetectedIDEInfo | null;
- onClose: () => void;
- onSelect: (ide?: DetectedIDEInfo) => void;
-};
-function IDEScreen(t0) {
- const $ = _c(39);
- const {
- availableIDEs,
- unavailableIDEs,
- selectedIDE,
- onClose,
- onSelect
- } = t0;
- let t1;
- if ($[0] !== selectedIDE?.port) {
- t1 = selectedIDE?.port?.toString() ?? "None";
- $[0] = selectedIDE?.port;
- $[1] = t1;
- } else {
- t1 = $[1];
- }
- const [selectedValue, setSelectedValue] = useState(t1);
- const [showAutoConnectDialog, setShowAutoConnectDialog] = useState(false);
- const [showDisableAutoConnectDialog, setShowDisableAutoConnectDialog] = useState(false);
- let t2;
- if ($[2] !== availableIDEs || $[3] !== onSelect) {
- t2 = value => {
- if (value !== "None" && shouldShowAutoConnectDialog()) {
- setShowAutoConnectDialog(true);
+ availableIDEs: DetectedIDEInfo[]
+ unavailableIDEs: DetectedIDEInfo[]
+ selectedIDE?: DetectedIDEInfo | null
+ onClose: () => void
+ onSelect: (ide?: DetectedIDEInfo) => void
+}
+
+function IDEScreen({
+ availableIDEs,
+ unavailableIDEs,
+ selectedIDE,
+ onClose,
+ onSelect,
+}: IDEScreenProps): React.ReactNode {
+ const [selectedValue, setSelectedValue] = useState(
+ selectedIDE?.port?.toString() ?? 'None',
+ )
+ const [showAutoConnectDialog, setShowAutoConnectDialog] = useState(false)
+ const [showDisableAutoConnectDialog, setShowDisableAutoConnectDialog] =
+ useState(false)
+
+ const handleSelectIDE = useCallback(
+ (value: string) => {
+ if (value !== 'None' && shouldShowAutoConnectDialog()) {
+ setShowAutoConnectDialog(true)
+ } else if (value === 'None' && shouldShowDisableAutoConnectDialog()) {
+ setShowDisableAutoConnectDialog(true)
} else {
- if (value === "None" && shouldShowDisableAutoConnectDialog()) {
- setShowDisableAutoConnectDialog(true);
- } else {
- onSelect(availableIDEs.find(ide => ide.port === parseInt(value)));
- }
+ onSelect(availableIDEs.find(ide => ide.port === parseInt(value)))
}
- };
- $[2] = availableIDEs;
- $[3] = onSelect;
- $[4] = t2;
- } else {
- t2 = $[4];
- }
- const handleSelectIDE = t2;
- let t3;
- if ($[5] !== availableIDEs) {
- t3 = availableIDEs.reduce(_temp, {});
- $[5] = availableIDEs;
- $[6] = t3;
- } else {
- t3 = $[6];
- }
- const ideCounts = t3;
- let t4;
- if ($[7] !== availableIDEs || $[8] !== ideCounts) {
- let t5;
- if ($[10] !== ideCounts) {
- t5 = ide_1 => {
- const hasMultipleInstances = (ideCounts[ide_1.name] || 0) > 1;
- const showWorkspace = hasMultipleInstances && ide_1.workspaceFolders.length > 0;
- return {
- label: ide_1.name,
- value: ide_1.port.toString(),
- description: showWorkspace ? formatWorkspaceFolders(ide_1.workspaceFolders) : undefined
- };
- };
- $[10] = ideCounts;
- $[11] = t5;
- } else {
- t5 = $[11];
- }
- t4 = availableIDEs.map(t5).concat([{
- label: "None",
- value: "None",
- description: undefined
- }]);
- $[7] = availableIDEs;
- $[8] = ideCounts;
- $[9] = t4;
- } else {
- t4 = $[9];
- }
- const options = t4;
+ },
+ [availableIDEs, onSelect],
+ )
+
+ const ideCounts = availableIDEs.reduce>((acc, ide) => {
+ acc[ide.name] = (acc[ide.name] || 0) + 1
+ return acc
+ }, {})
+
+ const options = availableIDEs
+ .map(ide => {
+ const hasMultipleInstances = (ideCounts[ide.name] || 0) > 1
+ const showWorkspace =
+ hasMultipleInstances && ide.workspaceFolders.length > 0
+
+ return {
+ label: ide.name,
+ value: ide.port.toString(),
+ description: showWorkspace
+ ? formatWorkspaceFolders(ide.workspaceFolders)
+ : undefined,
+ }
+ })
+ .concat([{ label: 'None', value: 'None', description: undefined }])
+
if (showAutoConnectDialog) {
- let t5;
- if ($[12] !== handleSelectIDE || $[13] !== selectedValue) {
- t5 = handleSelectIDE(selectedValue)} />;
- $[12] = handleSelectIDE;
- $[13] = selectedValue;
- $[14] = t5;
- } else {
- t5 = $[14];
- }
- return t5;
+ return (
+ handleSelectIDE(selectedValue)} />
+ )
}
+
if (showDisableAutoConnectDialog) {
- let t5;
- if ($[15] !== onSelect) {
- t5 = {
- onSelect(undefined);
- }} />;
- $[15] = onSelect;
- $[16] = t5;
- } else {
- t5 = $[16];
- }
- return t5;
+ return (
+ {
+ // Always disconnect when user selects "None", regardless of their
+ // choice about disabling auto-connect
+ onSelect(undefined)
+ }}
+ />
+ )
}
- let t5;
- if ($[17] !== availableIDEs.length) {
- t5 = availableIDEs.length === 0 && {isSupportedJetBrainsTerminal() ? "No available IDEs detected. Please install the plugin and restart your IDE:\nhttps://docs.claude.com/s/claude-code-jetbrains" : "No available IDEs detected. Make sure your IDE has the Claude Code extension or plugin installed and is running."};
- $[17] = availableIDEs.length;
- $[18] = t5;
- } else {
- t5 = $[18];
- }
- let t6;
- if ($[19] !== availableIDEs.length || $[20] !== handleSelectIDE || $[21] !== options || $[22] !== selectedValue) {
- t6 = availableIDEs.length !== 0 &&