source dump of claude code
at main 163 lines 5.1 kB view raw
1import { 2 getClaudeAiBaseUrl, 3 getRemoteSessionUrl, 4} from '../constants/product.js' 5import { stringWidth } from '../ink/stringWidth.js' 6import { formatDuration, truncateToWidth } from '../utils/format.js' 7import { getGraphemeSegmenter } from '../utils/intl.js' 8 9/** Bridge status state machine states. */ 10export type StatusState = 11 | 'idle' 12 | 'attached' 13 | 'titled' 14 | 'reconnecting' 15 | 'failed' 16 17/** How long a tool activity line stays visible after last tool_start (ms). */ 18export const TOOL_DISPLAY_EXPIRY_MS = 30_000 19 20/** Interval for the shimmer animation tick (ms). */ 21export const SHIMMER_INTERVAL_MS = 150 22 23export function timestamp(): string { 24 const now = new Date() 25 const h = String(now.getHours()).padStart(2, '0') 26 const m = String(now.getMinutes()).padStart(2, '0') 27 const s = String(now.getSeconds()).padStart(2, '0') 28 return `${h}:${m}:${s}` 29} 30 31export { formatDuration, truncateToWidth as truncatePrompt } 32 33/** Abbreviate a tool activity summary for the trail display. */ 34export function abbreviateActivity(summary: string): string { 35 return truncateToWidth(summary, 30) 36} 37 38/** Build the connect URL shown when the bridge is idle. */ 39export function buildBridgeConnectUrl( 40 environmentId: string, 41 ingressUrl?: string, 42): string { 43 const baseUrl = getClaudeAiBaseUrl(undefined, ingressUrl) 44 return `${baseUrl}/code?bridge=${environmentId}` 45} 46 47/** 48 * Build the session URL shown when a session is attached. Delegates to 49 * getRemoteSessionUrl for the cse_→session_ prefix translation, then appends 50 * the v1-specific ?bridge={environmentId} query. 51 */ 52export function buildBridgeSessionUrl( 53 sessionId: string, 54 environmentId: string, 55 ingressUrl?: string, 56): string { 57 return `${getRemoteSessionUrl(sessionId, ingressUrl)}?bridge=${environmentId}` 58} 59 60/** Compute the glimmer index for a reverse-sweep shimmer animation. */ 61export function computeGlimmerIndex( 62 tick: number, 63 messageWidth: number, 64): number { 65 const cycleLength = messageWidth + 20 66 return messageWidth + 10 - (tick % cycleLength) 67} 68 69/** 70 * Split text into three segments by visual column position for shimmer rendering. 71 * 72 * Uses grapheme segmentation and `stringWidth` so the split is correct for 73 * multi-byte characters, emoji, and CJK glyphs. 74 * 75 * Returns `{ before, shimmer, after }` strings. Both renderers (chalk in 76 * bridgeUI.ts and React/Ink in bridge.tsx) apply their own coloring to 77 * these segments. 78 */ 79export function computeShimmerSegments( 80 text: string, 81 glimmerIndex: number, 82): { before: string; shimmer: string; after: string } { 83 const messageWidth = stringWidth(text) 84 const shimmerStart = glimmerIndex - 1 85 const shimmerEnd = glimmerIndex + 1 86 87 // When shimmer is offscreen, return all text as "before" 88 if (shimmerStart >= messageWidth || shimmerEnd < 0) { 89 return { before: text, shimmer: '', after: '' } 90 } 91 92 // Split into at most 3 segments by visual column position 93 const clampedStart = Math.max(0, shimmerStart) 94 let colPos = 0 95 let before = '' 96 let shimmer = '' 97 let after = '' 98 for (const { segment } of getGraphemeSegmenter().segment(text)) { 99 const segWidth = stringWidth(segment) 100 if (colPos + segWidth <= clampedStart) { 101 before += segment 102 } else if (colPos > shimmerEnd) { 103 after += segment 104 } else { 105 shimmer += segment 106 } 107 colPos += segWidth 108 } 109 110 return { before, shimmer, after } 111} 112 113/** Computed bridge status label and color from connection state. */ 114export type BridgeStatusInfo = { 115 label: 116 | 'Remote Control failed' 117 | 'Remote Control reconnecting' 118 | 'Remote Control active' 119 | 'Remote Control connecting\u2026' 120 color: 'error' | 'warning' | 'success' 121} 122 123/** Derive a status label and color from the bridge connection state. */ 124export function getBridgeStatus({ 125 error, 126 connected, 127 sessionActive, 128 reconnecting, 129}: { 130 error: string | undefined 131 connected: boolean 132 sessionActive: boolean 133 reconnecting: boolean 134}): BridgeStatusInfo { 135 if (error) return { label: 'Remote Control failed', color: 'error' } 136 if (reconnecting) 137 return { label: 'Remote Control reconnecting', color: 'warning' } 138 if (sessionActive || connected) 139 return { label: 'Remote Control active', color: 'success' } 140 return { label: 'Remote Control connecting\u2026', color: 'warning' } 141} 142 143/** Footer text shown when bridge is idle (Ready state). */ 144export function buildIdleFooterText(url: string): string { 145 return `Code everywhere with the Claude app or ${url}` 146} 147 148/** Footer text shown when a session is active (Connected state). */ 149export function buildActiveFooterText(url: string): string { 150 return `Continue coding in the Claude app or ${url}` 151} 152 153/** Footer text shown when the bridge has failed. */ 154export const FAILED_FOOTER_TEXT = 'Something went wrong, please try again' 155 156/** 157 * Wrap text in an OSC 8 terminal hyperlink. Zero visual width for layout purposes. 158 * strip-ansi (used by stringWidth) correctly strips these sequences, so 159 * countVisualLines in bridgeUI.ts remains accurate. 160 */ 161export function wrapWithOsc8Link(text: string, url: string): string { 162 return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07` 163}