diff --git a/bun.lock b/bun.lock index af8cd232e..06021cca8 100644 --- a/bun.lock +++ b/bun.lock @@ -17,6 +17,7 @@ "@anthropic-ai/sandbox-runtime": "^0.0.44", "@anthropic-ai/sdk": "^0.80.0", "@anthropic-ai/vertex-sdk": "^0.14.4", + "@anthropic/ink": "workspace:*", "@aws-sdk/client-bedrock": "^3.1020.0", "@aws-sdk/client-bedrock-runtime": "^3.1020.0", "@aws-sdk/client-sts": "^3.1020.0", @@ -138,6 +139,29 @@ "name": "@ant/computer-use-swift", "version": "1.0.0", }, + "packages/@ant/ink": { + "name": "@anthropic/ink", + "version": "1.0.0", + "dependencies": { + "auto-bind": "^5.0.1", + "bidi-js": "^1.0.3", + "chalk": "^5.6.2", + "cli-boxes": "^4.0.1", + "emoji-regex": "^10.6.0", + "figures": "^6.1.0", + "get-east-asian-width": "^1.5.0", + "indent-string": "^5.0.0", + "lodash-es": "^4.17.23", + "react": "^19.2.4", + "react-reconciler": "^0.33.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^7.2.0", + "supports-hyperlinks": "^4.4.0", + "type-fest": "^5.5.0", + "usehooks-ts": "^3.1.1", + "wrap-ansi": "^10.0.0", + }, + }, "packages/audio-capture-napi": { "name": "audio-capture-napi", "version": "1.0.0", @@ -190,6 +214,8 @@ "@anthropic-ai/vertex-sdk": ["@anthropic-ai/vertex-sdk@0.14.4", "https://registry.npmmirror.com/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.14.4.tgz", { "dependencies": { "@anthropic-ai/sdk": ">=0.50.3 <1", "google-auth-library": "^9.4.2" } }, "sha512-BZUPRWghZxfSFtAxU563wH+jfWBPoedAwsVxG35FhmNsjeV8tyfN+lFriWhCpcZApxA4NdT6Soov+PzfnxxD5g=="], + "@anthropic/ink": ["@anthropic/ink@workspace:packages/@ant/ink"], + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "https://registry.npmmirror.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "https://registry.npmmirror.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], @@ -1198,7 +1224,7 @@ "open": ["open@10.2.0", "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], - "openai": ["openai@6.33.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw=="], + "openai": ["openai@6.33.0", "https://registry.npmmirror.com/openai/-/openai-6.33.0.tgz", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw=="], "os-tmpdir": ["os-tmpdir@1.0.2", "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], diff --git a/package.json b/package.json index 6d066da69..586b78fbe 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ "highlight.js": "^11.11.1", "https-proxy-agent": "^8.0.0", "ignore": "^7.0.5", + "@anthropic/ink": "workspace:*", "image-processor-napi": "workspace:*", "indent-string": "^5.0.0", "jsonc-parser": "^3.3.1", diff --git a/packages/@ant/ink/package.json b/packages/@ant/ink/package.json new file mode 100644 index 000000000..aec1f4eeb --- /dev/null +++ b/packages/@ant/ink/package.json @@ -0,0 +1,30 @@ +{ + "name": "@anthropic/ink", + "version": "1.0.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "dependencies": { + "auto-bind": "^5.0.1", + "bidi-js": "^1.0.3", + "chalk": "^5.6.2", + "cli-boxes": "^4.0.1", + "emoji-regex": "^10.6.0", + "figures": "^6.1.0", + "get-east-asian-width": "^1.5.0", + "indent-string": "^5.0.0", + "lodash-es": "^4.17.23", + "react": "^19.2.4", + "react-reconciler": "^0.33.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^7.2.0", + "supports-hyperlinks": "^4.4.0", + "type-fest": "^5.5.0", + "usehooks-ts": "^3.1.1", + "wrap-ansi": "^10.0.0" + } +} diff --git a/src/ink/components/AlternateScreen.tsx b/packages/@ant/ink/src/components/AlternateScreen.tsx similarity index 95% rename from src/ink/components/AlternateScreen.tsx rename to packages/@ant/ink/src/components/AlternateScreen.tsx index eeeb1152e..2e07a4cc7 100644 --- a/src/ink/components/AlternateScreen.tsx +++ b/packages/@ant/ink/src/components/AlternateScreen.tsx @@ -3,14 +3,14 @@ import React, { useContext, useInsertionEffect, } from 'react' -import instances from '../instances.js' +import instances from '../core/instances.js' import { DISABLE_MOUSE_TRACKING, ENABLE_MOUSE_TRACKING, ENTER_ALT_SCREEN, EXIT_ALT_SCREEN, -} from '../termio/dec.js' -import { TerminalWriteContext } from '../useTerminalNotification.js' +} from '../core/termio/dec.js' +import { TerminalWriteContext } from '../hooks/useTerminalNotification.js' import Box from './Box.js' import { TerminalSizeContext } from './TerminalSizeContext.js' diff --git a/src/ink/components/App.tsx b/packages/@ant/ink/src/components/App.tsx similarity index 93% rename from src/ink/components/App.tsx rename to packages/@ant/ink/src/components/App.tsx index 9bbb0c06a..6796e1370 100644 --- a/src/ink/components/App.tsx +++ b/packages/@ant/ink/src/components/App.tsx @@ -1,37 +1,62 @@ import React, { PureComponent, type ReactNode } from 'react' -import { updateLastInteractionTime } from '../../bootstrap/state.js' -import { logForDebugging } from '../../utils/debug.js' -import { stopCapturingEarlyInput } from '../../utils/earlyInput.js' -import { isEnvTruthy } from '../../utils/envUtils.js' -import { isMouseClicksDisabled } from '../../utils/fullscreen.js' -import { logError } from '../../utils/log.js' -import { EventEmitter } from '../events/emitter.js' -import { InputEvent } from '../events/input-event.js' -import { TerminalFocusEvent } from '../events/terminal-focus-event.js' +// Business-layer callbacks — replaced with inline defaults so this package +// has zero dependencies on business code. The business layer can inject +// implementations via AppCallbacks when needed. +type AppCallbacks = { + updateLastInteractionTime?: () => void + stopCapturingEarlyInput?: () => void + isMouseClicksDisabled?: () => boolean + logError?: (error: unknown) => void + logForDebugging?: (message: string, opts?: { level?: string }) => void +} + +/** Default no-op / safe-default implementations */ +const defaultCallbacks: Required = { + updateLastInteractionTime: () => {}, + stopCapturingEarlyInput: () => {}, + isMouseClicksDisabled: () => false, + logError: (error: unknown) => console.error(error), + logForDebugging: (_message: string, _opts?: { level?: string }) => {}, +} + +/** + * Override the default no-op callbacks. Call this from the business layer + * (e.g. src/ink.tsx) before mounting . + */ +export function setAppCallbacks(cb: AppCallbacks): void { + Object.assign(defaultCallbacks, cb) +} + +function isEnvTruthy(value: string | undefined): boolean { + return value === '1' || value === 'true' +} +import { EventEmitter } from '../core/events/emitter.js' +import { InputEvent } from '../core/events/input-event.js' +import { TerminalFocusEvent } from '../core/events/terminal-focus-event.js' import { INITIAL_STATE, type ParsedInput, type ParsedKey, type ParsedMouse, parseMultipleKeypresses, -} from '../parse-keypress.js' -import reconciler from '../reconciler.js' +} from '../core/parse-keypress.js' +import reconciler from '../core/reconciler.js' import { finishSelection, hasSelection, type SelectionState, startSelection, -} from '../selection.js' +} from '../core/selection.js' import { isXtermJs, setXtversionName, supportsExtendedKeys, -} from '../terminal.js' +} from '../core/terminal.js' import { getTerminalFocused, setTerminalFocused, -} from '../terminal-focus-state.js' -import { TerminalQuerier, xtversion } from '../terminal-querier.js' +} from '../core/terminal-focus-state.js' +import { TerminalQuerier, xtversion } from '../core/terminal-querier.js' import { DISABLE_KITTY_KEYBOARD, DISABLE_MODIFY_OTHER_KEYS, @@ -39,7 +64,7 @@ import { ENABLE_MODIFY_OTHER_KEYS, FOCUS_IN, FOCUS_OUT, -} from '../termio/csi.js' +} from '../core/termio/csi.js' import { DBP, DFE, @@ -48,7 +73,7 @@ import { EFE, HIDE_CURSOR, SHOW_CURSOR, -} from '../termio/dec.js' +} from '../core/termio/dec.js' import AppContext from './AppContext.js' import { ClockProvider } from './ClockContext.js' import CursorDeclarationContext, { @@ -292,7 +317,7 @@ export default class App extends PureComponent { // Both use the same stdin 'readable' + read() pattern, so they can't // coexist -- our handler would drain stdin before Ink's can see it. // The buffered text is preserved for REPL.tsx via consumeEarlyInput(). - stopCapturingEarlyInput() + defaultCallbacks.stopCapturingEarlyInput() stdin.ref() stdin.setRawMode(true) stdin.addListener('readable', this.handleReadable) @@ -324,9 +349,9 @@ export default class App extends PureComponent { ]).then(([r]) => { if (r) { setXtversionName(r.name) - logForDebugging(`XTVERSION: terminal identified as "${r.name}"`) + defaultCallbacks.logForDebugging(`XTVERSION: terminal identified as "${r.name}"`) } else { - logForDebugging('XTVERSION: no reply (terminal ignored query)') + defaultCallbacks.logForDebugging('XTVERSION: no reply (terminal ignored query)') } }) }) @@ -436,7 +461,7 @@ export default class App extends PureComponent { // permanently wedge the stream: data stays buffered and 'readable' // never re-emits. Catching here ensures the stream stays healthy so // subsequent keystrokes are still delivered. - logError(error) + defaultCallbacks.logError(error) // Re-attach the listener in case the exception detached it. // Bun may remove the listener after an error; without this, @@ -446,7 +471,7 @@ export default class App extends PureComponent { this.rawModeEnabledCount > 0 && !stdin.listeners('readable').includes(this.handleReadable) ) { - logForDebugging( + defaultCallbacks.logForDebugging( 'handleReadable: re-attaching stdin readable listener after error recovery', { level: 'warn' }, ) @@ -556,7 +581,7 @@ function processKeysInBatch( !((i.button & 0x20) !== 0 && (i.button & 0x03) === 3)), ) ) { - updateLastInteractionTime() + defaultCallbacks.updateLastInteractionTime() } for (const item of items) { @@ -625,7 +650,7 @@ function processKeysInBatch( export function handleMouseEvent(app: App, m: ParsedMouse): void { // Allow disabling click handling while keeping wheel scroll (which goes // through the keybinding system as 'wheelup'/'wheeldown', not here). - if (isMouseClicksDisabled()) return + if (defaultCallbacks.isMouseClicksDisabled()) return const sel = app.props.selection // Terminal coords are 1-indexed; screen buffer is 0-indexed diff --git a/src/ink/components/AppContext.ts b/packages/@ant/ink/src/components/AppContext.ts similarity index 100% rename from src/ink/components/AppContext.ts rename to packages/@ant/ink/src/components/AppContext.ts diff --git a/src/ink/components/Box.tsx b/packages/@ant/ink/src/components/Box.tsx similarity index 91% rename from src/ink/components/Box.tsx rename to packages/@ant/ink/src/components/Box.tsx index 42785f523..d07a2dd3d 100644 --- a/src/ink/components/Box.tsx +++ b/packages/@ant/ink/src/components/Box.tsx @@ -1,11 +1,11 @@ import React, { type PropsWithChildren, type Ref } from 'react' import type { Except } from 'type-fest' -import type { DOMElement } from '../dom.js' -import type { ClickEvent } from '../events/click-event.js' -import type { FocusEvent } from '../events/focus-event.js' -import type { KeyboardEvent } from '../events/keyboard-event.js' -import type { Styles } from '../styles.js' -import * as warn from '../warn.js' +import type { DOMElement } from '../core/dom.js' +import type { ClickEvent } from '../core/events/click-event.js' +import type { FocusEvent } from '../core/events/focus-event.js' +import type { KeyboardEvent } from '../core/events/keyboard-event.js' +import type { Styles } from '../core/styles.js' +import * as warn from '../core/warn.js' export type Props = Except & { ref?: Ref diff --git a/src/ink/components/Button.tsx b/packages/@ant/ink/src/components/Button.tsx similarity index 90% rename from src/ink/components/Button.tsx rename to packages/@ant/ink/src/components/Button.tsx index 0095d9c59..487c38e13 100644 --- a/src/ink/components/Button.tsx +++ b/packages/@ant/ink/src/components/Button.tsx @@ -6,11 +6,11 @@ import React, { useState, } from 'react' import type { Except } from 'type-fest' -import type { DOMElement } from '../dom.js' -import type { ClickEvent } from '../events/click-event.js' -import type { FocusEvent } from '../events/focus-event.js' -import type { KeyboardEvent } from '../events/keyboard-event.js' -import type { Styles } from '../styles.js' +import type { DOMElement } from '../core/dom.js' +import type { ClickEvent } from '../core/events/click-event.js' +import type { FocusEvent } from '../core/events/focus-event.js' +import type { KeyboardEvent } from '../core/events/keyboard-event.js' +import type { Styles } from '../core/styles.js' import Box from './Box.js' type ButtonState = { diff --git a/src/ink/components/ClockContext.tsx b/packages/@ant/ink/src/components/ClockContext.tsx similarity index 98% rename from src/ink/components/ClockContext.tsx rename to packages/@ant/ink/src/components/ClockContext.tsx index 32a8b9a28..2822a84ba 100644 --- a/src/ink/components/ClockContext.tsx +++ b/packages/@ant/ink/src/components/ClockContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useEffect, useState } from 'react' -import { FRAME_INTERVAL_MS } from '../constants.js' +import { FRAME_INTERVAL_MS } from '../core/constants.js' import { useTerminalFocus } from '../hooks/use-terminal-focus.js' export type Clock = { diff --git a/src/ink/components/CursorDeclarationContext.ts b/packages/@ant/ink/src/components/CursorDeclarationContext.ts similarity index 95% rename from src/ink/components/CursorDeclarationContext.ts rename to packages/@ant/ink/src/components/CursorDeclarationContext.ts index 358c80409..eb5b4d0f9 100644 --- a/src/ink/components/CursorDeclarationContext.ts +++ b/packages/@ant/ink/src/components/CursorDeclarationContext.ts @@ -1,5 +1,5 @@ import { createContext } from 'react' -import type { DOMElement } from '../dom.js' +import type { DOMElement } from '../core/dom.js' export type CursorDeclaration = { /** Display column (terminal cell width) within the declared node */ diff --git a/src/ink/components/ErrorOverview.tsx b/packages/@ant/ink/src/components/ErrorOverview.tsx similarity index 100% rename from src/ink/components/ErrorOverview.tsx rename to packages/@ant/ink/src/components/ErrorOverview.tsx diff --git a/src/ink/components/Link.tsx b/packages/@ant/ink/src/components/Link.tsx similarity index 90% rename from src/ink/components/Link.tsx rename to packages/@ant/ink/src/components/Link.tsx index ee7f04d14..c3ad1e2f3 100644 --- a/src/ink/components/Link.tsx +++ b/packages/@ant/ink/src/components/Link.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react' import React from 'react' -import { supportsHyperlinks } from '../supports-hyperlinks.js' +import { supportsHyperlinks } from '../core/supports-hyperlinks.js' import Text from './Text.js' export type Props = { diff --git a/src/ink/components/Newline.tsx b/packages/@ant/ink/src/components/Newline.tsx similarity index 100% rename from src/ink/components/Newline.tsx rename to packages/@ant/ink/src/components/Newline.tsx diff --git a/src/ink/components/NoSelect.tsx b/packages/@ant/ink/src/components/NoSelect.tsx similarity index 100% rename from src/ink/components/NoSelect.tsx rename to packages/@ant/ink/src/components/NoSelect.tsx diff --git a/src/ink/components/RawAnsi.tsx b/packages/@ant/ink/src/components/RawAnsi.tsx similarity index 100% rename from src/ink/components/RawAnsi.tsx rename to packages/@ant/ink/src/components/RawAnsi.tsx diff --git a/src/ink/components/ScrollBox.tsx b/packages/@ant/ink/src/components/ScrollBox.tsx similarity index 97% rename from src/ink/components/ScrollBox.tsx rename to packages/@ant/ink/src/components/ScrollBox.tsx index c2d432be2..371ab0ab0 100644 --- a/src/ink/components/ScrollBox.tsx +++ b/packages/@ant/ink/src/components/ScrollBox.tsx @@ -6,11 +6,10 @@ import React, { useState, } from 'react' import type { Except } from 'type-fest' -import { markScrollActivity } from '../../bootstrap/state.js' -import type { DOMElement } from '../dom.js' -import { markDirty, scheduleRenderFrom } from '../dom.js' -import { markCommitStart } from '../reconciler.js' -import type { Styles } from '../styles.js' +import type { DOMElement } from '../core/dom.js' +import { markDirty, scheduleRenderFrom } from '../core/dom.js' +import { markCommitStart } from '../core/reconciler.js' +import type { Styles } from '../core/styles.js' import Box from './Box.js' export type ScrollBoxHandle = { @@ -116,7 +115,7 @@ function ScrollBox({ // Signal background intervals (IDE poll, LSP poll, GCS fetch, orphan // check) to skip their next tick — they compete for the event loop and // contributed to 1402ms max frame gaps during scroll drain. - markScrollActivity() + // noop — injected by business layer via onScrollActivity callback markDirty(el) markCommitStart() notify() diff --git a/src/ink/components/Spacer.tsx b/packages/@ant/ink/src/components/Spacer.tsx similarity index 100% rename from src/ink/components/Spacer.tsx rename to packages/@ant/ink/src/components/Spacer.tsx diff --git a/src/ink/components/StdinContext.ts b/packages/@ant/ink/src/components/StdinContext.ts similarity index 92% rename from src/ink/components/StdinContext.ts rename to packages/@ant/ink/src/components/StdinContext.ts index 0b1a49717..34de48e5e 100644 --- a/src/ink/components/StdinContext.ts +++ b/packages/@ant/ink/src/components/StdinContext.ts @@ -1,6 +1,6 @@ import { createContext } from 'react' -import { EventEmitter } from '../events/emitter.js' -import type { TerminalQuerier } from '../terminal-querier.js' +import { EventEmitter } from '../core/events/emitter.js' +import type { TerminalQuerier } from '../core/terminal-querier.js' export type Props = { /** diff --git a/src/ink/components/TerminalFocusContext.tsx b/packages/@ant/ink/src/components/TerminalFocusContext.tsx similarity index 97% rename from src/ink/components/TerminalFocusContext.tsx rename to packages/@ant/ink/src/components/TerminalFocusContext.tsx index 81dbaf60b..e1fca2563 100644 --- a/src/ink/components/TerminalFocusContext.tsx +++ b/packages/@ant/ink/src/components/TerminalFocusContext.tsx @@ -4,7 +4,7 @@ import { getTerminalFocusState, subscribeTerminalFocus, type TerminalFocusState, -} from '../terminal-focus-state.js' +} from '../core/terminal-focus-state.js' export type { TerminalFocusState } diff --git a/src/ink/components/TerminalSizeContext.tsx b/packages/@ant/ink/src/components/TerminalSizeContext.tsx similarity index 100% rename from src/ink/components/TerminalSizeContext.tsx rename to packages/@ant/ink/src/components/TerminalSizeContext.tsx diff --git a/src/ink/components/Text.tsx b/packages/@ant/ink/src/components/Text.tsx similarity index 97% rename from src/ink/components/Text.tsx rename to packages/@ant/ink/src/components/Text.tsx index f2e2bdb77..620881450 100644 --- a/src/ink/components/Text.tsx +++ b/packages/@ant/ink/src/components/Text.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react' import React from 'react' -import type { Color, Styles, TextStyles } from '../styles.js' +import type { Color, Styles, TextStyles } from '../core/styles.js' type BaseProps = { /** diff --git a/src/ink/Ansi.tsx b/packages/@ant/ink/src/core/Ansi.tsx similarity index 98% rename from src/ink/Ansi.tsx rename to packages/@ant/ink/src/core/Ansi.tsx index f6ff7f7de..a8a0999a0 100644 --- a/src/ink/Ansi.tsx +++ b/packages/@ant/ink/src/core/Ansi.tsx @@ -1,6 +1,6 @@ import React from 'react' -import Link from './components/Link.js' -import Text from './components/Text.js' +import Link from '../components/Link.js' +import Text from '../components/Text.js' import type { Color } from './styles.js' import { type NamedColor, diff --git a/src/ink/bidi.ts b/packages/@ant/ink/src/core/bidi.ts similarity index 100% rename from src/ink/bidi.ts rename to packages/@ant/ink/src/core/bidi.ts diff --git a/src/ink/clearTerminal.ts b/packages/@ant/ink/src/core/clearTerminal.ts similarity index 100% rename from src/ink/clearTerminal.ts rename to packages/@ant/ink/src/core/clearTerminal.ts diff --git a/src/ink/colorize.ts b/packages/@ant/ink/src/core/colorize.ts similarity index 100% rename from src/ink/colorize.ts rename to packages/@ant/ink/src/core/colorize.ts diff --git a/src/ink/constants.ts b/packages/@ant/ink/src/core/constants.ts similarity index 100% rename from src/ink/constants.ts rename to packages/@ant/ink/src/core/constants.ts diff --git a/src/ink/cursor.ts b/packages/@ant/ink/src/core/cursor.ts similarity index 100% rename from src/ink/cursor.ts rename to packages/@ant/ink/src/core/cursor.ts diff --git a/src/ink/devtools.ts b/packages/@ant/ink/src/core/devtools.ts similarity index 100% rename from src/ink/devtools.ts rename to packages/@ant/ink/src/core/devtools.ts diff --git a/src/ink/dom.ts b/packages/@ant/ink/src/core/dom.ts similarity index 100% rename from src/ink/dom.ts rename to packages/@ant/ink/src/core/dom.ts diff --git a/src/ink/events/click-event.ts b/packages/@ant/ink/src/core/events/click-event.ts similarity index 100% rename from src/ink/events/click-event.ts rename to packages/@ant/ink/src/core/events/click-event.ts diff --git a/src/ink/events/dispatcher.ts b/packages/@ant/ink/src/core/events/dispatcher.ts similarity index 97% rename from src/ink/events/dispatcher.ts rename to packages/@ant/ink/src/core/events/dispatcher.ts index a310d389e..46466e88f 100644 --- a/src/ink/events/dispatcher.ts +++ b/packages/@ant/ink/src/core/events/dispatcher.ts @@ -4,10 +4,14 @@ import { DiscreteEventPriority, NoEventPriority, } from 'react-reconciler/constants.js' -import { logError } from '../../utils/log.js' import { HANDLER_FOR_EVENT } from './event-handlers.js' import type { EventTarget, TerminalEvent } from './terminal-event.js' +// logError stub — replaced from business dependency; use injected logger in production +const logError = (error: unknown): void => { + console.error(error) +} + // -- type DispatchListener = { diff --git a/src/ink/events/emitter.ts b/packages/@ant/ink/src/core/events/emitter.ts similarity index 100% rename from src/ink/events/emitter.ts rename to packages/@ant/ink/src/core/events/emitter.ts diff --git a/src/ink/events/event-handlers.ts b/packages/@ant/ink/src/core/events/event-handlers.ts similarity index 100% rename from src/ink/events/event-handlers.ts rename to packages/@ant/ink/src/core/events/event-handlers.ts diff --git a/src/ink/events/event.ts b/packages/@ant/ink/src/core/events/event.ts similarity index 100% rename from src/ink/events/event.ts rename to packages/@ant/ink/src/core/events/event.ts diff --git a/src/ink/events/focus-event.ts b/packages/@ant/ink/src/core/events/focus-event.ts similarity index 100% rename from src/ink/events/focus-event.ts rename to packages/@ant/ink/src/core/events/focus-event.ts diff --git a/src/ink/events/input-event.ts b/packages/@ant/ink/src/core/events/input-event.ts similarity index 100% rename from src/ink/events/input-event.ts rename to packages/@ant/ink/src/core/events/input-event.ts diff --git a/src/ink/events/keyboard-event.ts b/packages/@ant/ink/src/core/events/keyboard-event.ts similarity index 100% rename from src/ink/events/keyboard-event.ts rename to packages/@ant/ink/src/core/events/keyboard-event.ts diff --git a/src/ink/events/paste-event.ts b/packages/@ant/ink/src/core/events/paste-event.ts similarity index 100% rename from src/ink/events/paste-event.ts rename to packages/@ant/ink/src/core/events/paste-event.ts diff --git a/src/ink/events/resize-event.ts b/packages/@ant/ink/src/core/events/resize-event.ts similarity index 100% rename from src/ink/events/resize-event.ts rename to packages/@ant/ink/src/core/events/resize-event.ts diff --git a/src/ink/events/terminal-event.ts b/packages/@ant/ink/src/core/events/terminal-event.ts similarity index 100% rename from src/ink/events/terminal-event.ts rename to packages/@ant/ink/src/core/events/terminal-event.ts diff --git a/src/ink/events/terminal-focus-event.ts b/packages/@ant/ink/src/core/events/terminal-focus-event.ts similarity index 100% rename from src/ink/events/terminal-focus-event.ts rename to packages/@ant/ink/src/core/events/terminal-focus-event.ts diff --git a/src/ink/focus.ts b/packages/@ant/ink/src/core/focus.ts similarity index 100% rename from src/ink/focus.ts rename to packages/@ant/ink/src/core/focus.ts diff --git a/src/ink/frame.ts b/packages/@ant/ink/src/core/frame.ts similarity index 100% rename from src/ink/frame.ts rename to packages/@ant/ink/src/core/frame.ts diff --git a/src/ink/get-max-width.ts b/packages/@ant/ink/src/core/get-max-width.ts similarity index 100% rename from src/ink/get-max-width.ts rename to packages/@ant/ink/src/core/get-max-width.ts diff --git a/src/ink/hit-test.ts b/packages/@ant/ink/src/core/hit-test.ts similarity index 100% rename from src/ink/hit-test.ts rename to packages/@ant/ink/src/core/hit-test.ts diff --git a/src/ink/ink.tsx b/packages/@ant/ink/src/core/ink.tsx similarity index 98% rename from src/ink/ink.tsx rename to packages/@ant/ink/src/core/ink.tsx index 65bf32bd3..f18f0ddec 100644 --- a/src/ink/ink.tsx +++ b/packages/@ant/ink/src/core/ink.tsx @@ -12,17 +12,14 @@ import React, { type ReactNode } from 'react' import type { FiberRoot } from 'react-reconciler' import { ConcurrentRoot } from 'react-reconciler/constants.js' import { onExit } from 'signal-exit' -import { flushInteractionTime } from 'src/bootstrap/state.js' -import { getYogaCounters } from 'src/native-ts/yoga-layout/index.js' -import { logForDebugging } from 'src/utils/debug.js' -import { logError } from 'src/utils/log.js' +import { getYogaCounters } from './yoga-layout/index.js' import { format } from 'util' import { colorize } from './colorize.js' -import App from './components/App.js' +import App from '../components/App.js' import type { CursorDeclaration, CursorDeclarationSetter, -} from './components/CursorDeclarationContext.js' +} from '../components/CursorDeclarationContext.js' import { FRAME_INTERVAL_MS } from './constants.js' import * as dom from './dom.js' import { KeyboardEvent } from './events/keyboard-event.js' @@ -116,7 +113,7 @@ import { supportsTabStatus, wrapForMultiplexer, } from './termio/osc.js' -import { TerminalWriteProvider } from './useTerminalNotification.js' +import { TerminalWriteProvider } from '../hooks/useTerminalNotification.js' // Alt-screen: renderer.ts sets cursor.visible = !isTTY || screen.height===0, // which is always false in alt-screen (TTY + content fills screen). @@ -140,6 +137,11 @@ function makeAltScreenParkPatch(terminalRows: number) { }) } +export type Logger = { + debug(message: string, options?: { level?: string }): void + error(error: Error | unknown): void +} + export type Options = { stdout: NodeJS.WriteStream stdin: NodeJS.ReadStream @@ -148,6 +150,16 @@ export type Options = { patchConsole: boolean waitUntilExit?: () => Promise onFrame?: (event: FrameEvent) => void + /** Called before each render cycle. Replaces flushInteractionTime(). */ + onBeforeRender?: () => void + /** Injected logger. Replaces logForDebugging / logError imports. */ + logger?: Logger +} + +/** No-op logger used when no logger is injected. */ +const noopLogger: Logger = { + debug() {}, + error() {}, } export default class Ink { @@ -240,9 +252,11 @@ export default class Ink { // for log-update's relative-move invariants). Alt-screen doesn't need // this — every frame begins with CSI H. null = no move emitted last frame. private displayCursor: { x: number; y: number } | null = null + private readonly logger: Logger constructor(private readonly options: Options) { autoBind(this) + this.logger = options.logger ?? noopLogger if (this.options.patchConsole) { this.restoreConsole = this.patchConsole() @@ -533,7 +547,7 @@ export default class Ink { // Date.now() at most once per frame instead of once per keypress. // Done before the render to avoid dirtying state that would trigger // an extra React re-render cycle. - flushInteractionTime() + this.options.onBeforeRender?.() const renderStart = performance.now() const terminalWidth = this.options.stdout.columns || 80 @@ -754,7 +768,7 @@ export default class Ink { this.rootNode, patch.debug.triggerY, ) - logForDebugging( + this.logger.debug( `[REPAINT] full reset · ${patch.reason} · row ${patch.debug.triggerY}\n` + ` prev: "${patch.debug.prevLine}"\n` + ` next: "${patch.debug.nextLine}"\n` + @@ -1274,7 +1288,7 @@ export default class Ink { // correctly. One extra paint of this message, but correct > fast. dom.markDirty(el) const positions = scanPositions(rendered, this.searchHighlightQuery) - logForDebugging( + this.logger.debug( `scanElementSubtree: q='${this.searchHighlightQuery}' ` + `el=${width}x${height}@(${elLeft},${elTop}) n=${positions.length} ` + `[${positions @@ -1580,7 +1594,7 @@ export default class Ink { // Store and remove all 'readable' event listeners temporarily // This prevents Ink from consuming stdin while the editor is active const readableListeners = stdin.listeners('readable') - logForDebugging( + this.logger.debug( `[stdin] suspendStdin: removing ${readableListeners.length} readable listener(s), wasRawMode=${(stdin as NodeJS.ReadStream & { isRaw?: boolean }).isRaw ?? false}`, ) readableListeners.forEach(listener => { @@ -1610,12 +1624,12 @@ export default class Ink { // Re-attach all the stored listeners if (this.stdinListeners.length === 0 && !this.wasRawMode) { - logForDebugging( + this.logger.debug( '[stdin] resumeStdin: called with no stored listeners and wasRawMode=false (possible desync)', { level: 'warn' }, ) } - logForDebugging( + this.logger.debug( `[stdin] resumeStdin: re-attaching ${this.stdinListeners.length} listener(s), wasRawMode=${this.wasRawMode}`, ) this.stdinListeners.forEach(({ event, listener }) => { @@ -1833,9 +1847,9 @@ export default class Ink { const con = console const originals: Partial> = {} const toDebug = (...args: unknown[]) => - logForDebugging(`console.log: ${format(...args)}`) + this.logger.debug(`console.log: ${format(...args)}`) const toError = (...args: unknown[]) => - logError(new Error(`console.error: ${format(...args)}`)) + this.logger.error(new Error(`console.error: ${format(...args)}`)) for (const m of CONSOLE_STDOUT_METHODS) { originals[m] = con[m] con[m] = toDebug @@ -1873,7 +1887,7 @@ export default class Ink { cb?: (err?: Error) => void, ): boolean => { const callback = typeof encodingOrCb === 'function' ? encodingOrCb : cb - // Reentrancy guard: logForDebugging → writeToStderr → here. Pass + // Reentrancy guard: logger.debug → writeToStderr → here. Pass // through to the original so --debug-to-stderr still works and we // don't stack-overflow. if (reentered) { @@ -1887,7 +1901,7 @@ export default class Ink { typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8') - logForDebugging(`[stderr] ${text}`, { level: 'warn' }) + this.logger.debug(`[stderr] ${text}`, { level: 'warn' }) if (this.altScreenActive && !this.isUnmounted && !this.isPaused) { this.prevFrameContaminated = true this.scheduleRender() diff --git a/src/ink/instances.ts b/packages/@ant/ink/src/core/instances.ts similarity index 100% rename from src/ink/instances.ts rename to packages/@ant/ink/src/core/instances.ts diff --git a/src/ink/layout/engine.ts b/packages/@ant/ink/src/core/layout/engine.ts similarity index 100% rename from src/ink/layout/engine.ts rename to packages/@ant/ink/src/core/layout/engine.ts diff --git a/src/ink/layout/geometry.ts b/packages/@ant/ink/src/core/layout/geometry.ts similarity index 100% rename from src/ink/layout/geometry.ts rename to packages/@ant/ink/src/core/layout/geometry.ts diff --git a/src/ink/layout/node.ts b/packages/@ant/ink/src/core/layout/node.ts similarity index 100% rename from src/ink/layout/node.ts rename to packages/@ant/ink/src/core/layout/node.ts diff --git a/src/ink/layout/yoga.ts b/packages/@ant/ink/src/core/layout/yoga.ts similarity index 99% rename from src/ink/layout/yoga.ts rename to packages/@ant/ink/src/core/layout/yoga.ts index 58f2646fb..1d5729e8c 100644 --- a/src/ink/layout/yoga.ts +++ b/packages/@ant/ink/src/core/layout/yoga.ts @@ -11,7 +11,7 @@ import Yoga, { PositionType, Wrap, type Node as YogaNode, -} from 'src/native-ts/yoga-layout/index.js' +} from '../yoga-layout/index.js' import { type LayoutAlign, LayoutDisplay, diff --git a/src/ink/line-width-cache.ts b/packages/@ant/ink/src/core/line-width-cache.ts similarity index 100% rename from src/ink/line-width-cache.ts rename to packages/@ant/ink/src/core/line-width-cache.ts diff --git a/src/ink/log-update.ts b/packages/@ant/ink/src/core/log-update.ts similarity index 99% rename from src/ink/log-update.ts rename to packages/@ant/ink/src/core/log-update.ts index 4434b9418..210b1e313 100644 --- a/src/ink/log-update.ts +++ b/packages/@ant/ink/src/core/log-update.ts @@ -3,7 +3,8 @@ import { ansiCodesToString, diffAnsiCodes, } from '@alcalzone/ansi-tokenize' -import { logForDebugging } from '../utils/debug.js' +/** Debug logger — no-op placeholder until proper logger injection is added */ +const logForDebugging = (_message: string) => {} import type { Diff, FlickerReason, Frame } from './frame.js' import type { Point } from './layout/geometry.js' import { diff --git a/src/ink/measure-element.ts b/packages/@ant/ink/src/core/measure-element.ts similarity index 100% rename from src/ink/measure-element.ts rename to packages/@ant/ink/src/core/measure-element.ts diff --git a/src/ink/measure-text.ts b/packages/@ant/ink/src/core/measure-text.ts similarity index 100% rename from src/ink/measure-text.ts rename to packages/@ant/ink/src/core/measure-text.ts diff --git a/src/ink/node-cache.ts b/packages/@ant/ink/src/core/node-cache.ts similarity index 100% rename from src/ink/node-cache.ts rename to packages/@ant/ink/src/core/node-cache.ts diff --git a/src/ink/optimizer.ts b/packages/@ant/ink/src/core/optimizer.ts similarity index 100% rename from src/ink/optimizer.ts rename to packages/@ant/ink/src/core/optimizer.ts diff --git a/src/ink/output.ts b/packages/@ant/ink/src/core/output.ts similarity index 99% rename from src/ink/output.ts rename to packages/@ant/ink/src/core/output.ts index 16b5ae27f..edee080c9 100644 --- a/src/ink/output.ts +++ b/packages/@ant/ink/src/core/output.ts @@ -4,9 +4,11 @@ import { styledCharsFromTokens, tokenize, } from '@alcalzone/ansi-tokenize' -import { logForDebugging } from '../utils/debug.js' -import { getGraphemeSegmenter } from '../utils/intl.js' -import sliceAnsi from '../utils/sliceAnsi.js' +import { getGraphemeSegmenter } from './utils/grapheme.js' +import sliceAnsi from './utils/sliceAnsi.js' + +/** Debug logger — no-op placeholder until proper logger injection is added */ +const logForDebugging = (_message: string) => {} import { reorderBidi } from './bidi.js' import { type Rectangle, unionRect } from './layout/geometry.js' import { diff --git a/src/ink/parse-keypress.ts b/packages/@ant/ink/src/core/parse-keypress.ts similarity index 100% rename from src/ink/parse-keypress.ts rename to packages/@ant/ink/src/core/parse-keypress.ts diff --git a/src/ink/reconciler.ts b/packages/@ant/ink/src/core/reconciler.ts similarity index 93% rename from src/ink/reconciler.ts rename to packages/@ant/ink/src/core/reconciler.ts index 831366f7a..4d75923b8 100644 --- a/src/ink/reconciler.ts +++ b/packages/@ant/ink/src/core/reconciler.ts @@ -1,9 +1,7 @@ /* eslint-disable custom-rules/no-top-level-side-effects */ -import { appendFileSync } from 'fs' import createReconciler from 'react-reconciler' -import { getYogaCounters } from 'src/native-ts/yoga-layout/index.js' -import { isEnvTruthy } from '../utils/envUtils.js' +import { getYogaCounters } from './yoga-layout/index.js' import { appendChildNode, clearYogaNodeReferences, @@ -179,15 +177,16 @@ export function getOwnerChain(fiber: unknown): string[] { let debugRepaints: boolean | undefined export function isDebugRepaintsEnabled(): boolean { if (debugRepaints === undefined) { - debugRepaints = isEnvTruthy(process.env.CLAUDE_CODE_DEBUG_REPAINTS) + debugRepaints = process.env.CLAUDE_CODE_DEBUG_REPAINTS === '1' } return debugRepaints } export const dispatcher = new Dispatcher() -// --- COMMIT INSTRUMENTATION (temp debugging) --- -// eslint-disable-next-line custom-rules/no-process-env-top-level -- debug instrumentation, read-once is fine +// --- COMMIT INSTRUMENTATION (debug logging) --- +// Uses console.warn instead of fs.appendFileSync to avoid filesystem dependencies. +// Set CLAUDE_CODE_COMMIT_LOG=1 to enable debug logging to stderr. const COMMIT_LOG = process.env.CLAUDE_CODE_COMMIT_LOG let _commits = 0 let _lastLog = 0 @@ -195,6 +194,12 @@ let _lastCommitAt = 0 let _maxGapMs = 0 let _createCount = 0 let _prepareAt = 0 + +/** Debug log helper — replaces fs.appendFileSync with console.warn. */ +function debugLog(message: string): void { + // biome-ignore lint/suspicious/noConsole: debug instrumentation + console.warn(`[ink-commit] ${message}`) +} // --- END --- // --- SCROLL PROFILING (bench/scroll-e2e.sh reads via getLastYogaMs) --- @@ -255,18 +260,14 @@ const reconciler = createReconciler< _lastCommitAt = now const reconcileMs = _prepareAt > 0 ? now - _prepareAt : 0 if (gap > 30 || reconcileMs > 20 || _createCount > 50) { - // eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation - appendFileSync( - COMMIT_LOG, - `${now.toFixed(1)} gap=${gap.toFixed(1)}ms reconcile=${reconcileMs.toFixed(1)}ms creates=${_createCount}\n`, + debugLog( + `${now.toFixed(1)} gap=${gap.toFixed(1)}ms reconcile=${reconcileMs.toFixed(1)}ms creates=${_createCount}`, ) } _createCount = 0 if (now - _lastLog > 1000) { - // eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation - appendFileSync( - COMMIT_LOG, - `${now.toFixed(1)} commits=${_commits}/s maxGap=${_maxGapMs.toFixed(1)}ms\n`, + debugLog( + `${now.toFixed(1)} commits=${_commits}/s maxGap=${_maxGapMs.toFixed(1)}ms`, ) _commits = 0 _maxGapMs = 0 @@ -281,10 +282,8 @@ const reconciler = createReconciler< const layoutMs = performance.now() - _t0 if (layoutMs > 20) { const c = getYogaCounters() - // eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation - appendFileSync( - COMMIT_LOG, - `${_t0.toFixed(1)} SLOW_YOGA ${layoutMs.toFixed(1)}ms visited=${c.visited} measured=${c.measured} hits=${c.cacheHits} live=${c.live}\n`, + debugLog( + `${_t0.toFixed(1)} SLOW_YOGA ${layoutMs.toFixed(1)}ms visited=${c.visited} measured=${c.measured} hits=${c.cacheHits} live=${c.live}`, ) } } @@ -305,10 +304,8 @@ const reconciler = createReconciler< if (COMMIT_LOG) { const renderMs = performance.now() - _tr if (renderMs > 10) { - // eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation - appendFileSync( - COMMIT_LOG, - `${_tr.toFixed(1)} SLOW_PAINT ${renderMs.toFixed(1)}ms\n`, + debugLog( + `${_tr.toFixed(1)} SLOW_PAINT ${renderMs.toFixed(1)}ms`, ) } } diff --git a/src/ink/render-border.ts b/packages/@ant/ink/src/core/render-border.ts similarity index 100% rename from src/ink/render-border.ts rename to packages/@ant/ink/src/core/render-border.ts diff --git a/src/ink/render-node-to-output.ts b/packages/@ant/ink/src/core/render-node-to-output.ts similarity index 100% rename from src/ink/render-node-to-output.ts rename to packages/@ant/ink/src/core/render-node-to-output.ts diff --git a/src/ink/render-to-screen.ts b/packages/@ant/ink/src/core/render-to-screen.ts similarity index 98% rename from src/ink/render-to-screen.ts rename to packages/@ant/ink/src/core/render-to-screen.ts index 0992dc946..dcf6407c6 100644 --- a/src/ink/render-to-screen.ts +++ b/packages/@ant/ink/src/core/render-to-screen.ts @@ -1,7 +1,8 @@ import noop from 'lodash-es/noop.js' import type { ReactElement } from 'react' import { LegacyRoot } from 'react-reconciler/constants.js' -import { logForDebugging } from '../utils/debug.js' +/** Debug logger — no-op placeholder until proper logger injection is added */ +const logForDebugging = (_message: string) => {} import { createNode, type DOMElement } from './dom.js' import { FocusManager } from './focus.js' import Output from './output.js' diff --git a/src/ink/renderer.ts b/packages/@ant/ink/src/core/renderer.ts similarity index 97% rename from src/ink/renderer.ts rename to packages/@ant/ink/src/core/renderer.ts index d87fb3db9..aa2cdeaa3 100644 --- a/src/ink/renderer.ts +++ b/packages/@ant/ink/src/core/renderer.ts @@ -1,4 +1,5 @@ -import { logForDebugging } from 'src/utils/debug.js' +/** Debug logger — no-op placeholder until proper logger injection is added */ +const logForDebugging = (_message: string, _opts?: { level?: string }) => {} import { type DOMElement, markDirty } from './dom.js' import type { Frame } from './frame.js' import { consumeAbsoluteRemovedFlag } from './node-cache.js' diff --git a/src/ink/root.ts b/packages/@ant/ink/src/core/root.ts similarity index 94% rename from src/ink/root.ts rename to packages/@ant/ink/src/core/root.ts index 067bbd496..36df98c70 100644 --- a/src/ink/root.ts +++ b/packages/@ant/ink/src/core/root.ts @@ -1,5 +1,4 @@ import type { ReactNode } from 'react' -import { logForDebugging } from 'src/utils/debug.js' import { Stream } from 'stream' import type { FrameEvent } from './frame.js' import Ink, { type Options as InkOptions } from './ink.js' @@ -114,9 +113,12 @@ const wrappedRender = async ( // write overwrites scrollback instead of appending below the logo. await Promise.resolve() const instance = renderSync(node, options) - logForDebugging( - `[render] first ink render: ${Math.round(process.uptime() * 1000)}ms since process start`, - ) + if (process.env.CLAUDE_CODE_DEBUG_REPAINTS === '1') { + // biome-ignore lint/suspicious/noConsole: debug instrumentation + console.warn( + `[render] first ink render: ${Math.round(process.uptime() * 1000)}ms since process start`, + ) + } return instance } diff --git a/src/ink/screen.ts b/packages/@ant/ink/src/core/screen.ts similarity index 100% rename from src/ink/screen.ts rename to packages/@ant/ink/src/core/screen.ts diff --git a/src/ink/searchHighlight.ts b/packages/@ant/ink/src/core/searchHighlight.ts similarity index 100% rename from src/ink/searchHighlight.ts rename to packages/@ant/ink/src/core/searchHighlight.ts diff --git a/src/ink/selection.ts b/packages/@ant/ink/src/core/selection.ts similarity index 100% rename from src/ink/selection.ts rename to packages/@ant/ink/src/core/selection.ts diff --git a/src/ink/squash-text-nodes.ts b/packages/@ant/ink/src/core/squash-text-nodes.ts similarity index 100% rename from src/ink/squash-text-nodes.ts rename to packages/@ant/ink/src/core/squash-text-nodes.ts diff --git a/src/ink/stringWidth.ts b/packages/@ant/ink/src/core/stringWidth.ts similarity index 99% rename from src/ink/stringWidth.ts rename to packages/@ant/ink/src/core/stringWidth.ts index 83f7bcb5e..2c3fa13b8 100644 --- a/src/ink/stringWidth.ts +++ b/packages/@ant/ink/src/core/stringWidth.ts @@ -1,7 +1,7 @@ import emojiRegex from 'emoji-regex' import { eastAsianWidth } from 'get-east-asian-width' import stripAnsi from 'strip-ansi' -import { getGraphemeSegmenter } from '../utils/intl.js' +import { getGraphemeSegmenter } from './utils/grapheme.js' const EMOJI_REGEX = emojiRegex() diff --git a/src/ink/styles.ts b/packages/@ant/ink/src/core/styles.ts similarity index 100% rename from src/ink/styles.ts rename to packages/@ant/ink/src/core/styles.ts diff --git a/src/ink/supports-hyperlinks.ts b/packages/@ant/ink/src/core/supports-hyperlinks.ts similarity index 100% rename from src/ink/supports-hyperlinks.ts rename to packages/@ant/ink/src/core/supports-hyperlinks.ts diff --git a/src/ink/tabstops.ts b/packages/@ant/ink/src/core/tabstops.ts similarity index 100% rename from src/ink/tabstops.ts rename to packages/@ant/ink/src/core/tabstops.ts diff --git a/src/ink/terminal-focus-state.ts b/packages/@ant/ink/src/core/terminal-focus-state.ts similarity index 100% rename from src/ink/terminal-focus-state.ts rename to packages/@ant/ink/src/core/terminal-focus-state.ts diff --git a/src/ink/terminal-querier.ts b/packages/@ant/ink/src/core/terminal-querier.ts similarity index 100% rename from src/ink/terminal-querier.ts rename to packages/@ant/ink/src/core/terminal-querier.ts diff --git a/src/ink/terminal.ts b/packages/@ant/ink/src/core/terminal.ts similarity index 97% rename from src/ink/terminal.ts rename to packages/@ant/ink/src/core/terminal.ts index 2aad947d7..1331376c3 100644 --- a/src/ink/terminal.ts +++ b/packages/@ant/ink/src/core/terminal.ts @@ -1,7 +1,5 @@ -import { coerce } from 'semver' +import { coerce, gte } from 'semver' import type { Writable } from 'stream' -import { env } from '../utils/env.js' -import { gte } from '../utils/semver.js' import { getClearTerminalSequence } from './clearTerminal.js' import type { Diff } from './frame.js' import { cursorMove, cursorTo, eraseLines } from './termio/csi.js' @@ -165,7 +163,7 @@ const EXTENDED_KEYS_TERMINALS = [ /** True if this terminal correctly handles extended key reporting * (Kitty keyboard protocol + xterm modifyOtherKeys). */ export function supportsExtendedKeys(): boolean { - return EXTENDED_KEYS_TERMINALS.includes(env.terminal ?? '') + return EXTENDED_KEYS_TERMINALS.includes(process.env.TERM_PROGRAM ?? '') } /** True if the terminal scrolls the viewport when it receives cursor-up diff --git a/src/ink/termio.ts b/packages/@ant/ink/src/core/termio.ts similarity index 100% rename from src/ink/termio.ts rename to packages/@ant/ink/src/core/termio.ts diff --git a/src/ink/termio/ansi.ts b/packages/@ant/ink/src/core/termio/ansi.ts similarity index 100% rename from src/ink/termio/ansi.ts rename to packages/@ant/ink/src/core/termio/ansi.ts diff --git a/src/ink/termio/csi.ts b/packages/@ant/ink/src/core/termio/csi.ts similarity index 100% rename from src/ink/termio/csi.ts rename to packages/@ant/ink/src/core/termio/csi.ts diff --git a/src/ink/termio/dec.ts b/packages/@ant/ink/src/core/termio/dec.ts similarity index 100% rename from src/ink/termio/dec.ts rename to packages/@ant/ink/src/core/termio/dec.ts diff --git a/src/ink/termio/esc.ts b/packages/@ant/ink/src/core/termio/esc.ts similarity index 100% rename from src/ink/termio/esc.ts rename to packages/@ant/ink/src/core/termio/esc.ts diff --git a/src/ink/termio/osc.ts b/packages/@ant/ink/src/core/termio/osc.ts similarity index 95% rename from src/ink/termio/osc.ts rename to packages/@ant/ink/src/core/termio/osc.ts index 9bef51532..f5e6f3712 100644 --- a/src/ink/termio/osc.ts +++ b/packages/@ant/ink/src/core/termio/osc.ts @@ -3,9 +3,26 @@ */ import { Buffer } from 'buffer' -import { env } from '../../utils/env.js' -import { execFileNoThrow } from '../../utils/execFileNoThrow.js' +import { execFile as nodeExecFile } from 'child_process' import { BEL, ESC, ESC_TYPE, SEP } from './ansi.js' + +/** Promise-based wrapper around child_process.execFile */ +function execFileNoThrow( + command: string, + args: string[], + options: { input?: string; useCwd?: boolean; timeout?: number } = {}, +): Promise<{ code: number; stdout: string; stderr: string }> { + return new Promise(resolve => { + const { input, timeout } = options + const proc = nodeExecFile(command, args, { timeout }, (error, stdout, stderr) => { + resolve({ code: error ? 1 : 0, stdout: stdout ?? '', stderr: stderr ?? '' }) + }) + if (input && proc.stdin) { + proc.stdin.write(input) + proc.stdin.end() + } + }) +} import type { Action, Color, TabStatusAction } from './types.js' export const OSC_PREFIX = ESC + String.fromCharCode(ESC_TYPE.OSC) @@ -16,7 +33,7 @@ export const ST = ESC + '\\' /** Generate an OSC sequence: ESC ] p1;p2;...;pN * Uses ST terminator for Kitty (avoids beeps), BEL for others */ export function osc(...parts: (string | number)[]): string { - const terminator = env.terminal === 'kitty' ? ST : BEL + const terminator = process.env.TERM_PROGRAM === 'kitty' ? ST : BEL return `${OSC_PREFIX}${parts.join(SEP)}${terminator}` } diff --git a/src/ink/termio/parser.ts b/packages/@ant/ink/src/core/termio/parser.ts similarity index 99% rename from src/ink/termio/parser.ts rename to packages/@ant/ink/src/core/termio/parser.ts index 301f14c5a..0ae9e7fa5 100644 --- a/src/ink/termio/parser.ts +++ b/packages/@ant/ink/src/core/termio/parser.ts @@ -11,7 +11,7 @@ * - Style tracking: maintains current text style state */ -import { getGraphemeSegmenter } from '../../utils/intl.js' +import { getGraphemeSegmenter } from '../utils/grapheme.js' import { C0 } from './ansi.js' import { CSI, CURSOR_STYLES, ERASE_DISPLAY, ERASE_LINE_REGION } from './csi.js' import { DEC } from './dec.js' diff --git a/src/ink/termio/sgr.ts b/packages/@ant/ink/src/core/termio/sgr.ts similarity index 100% rename from src/ink/termio/sgr.ts rename to packages/@ant/ink/src/core/termio/sgr.ts diff --git a/src/ink/termio/tokenize.ts b/packages/@ant/ink/src/core/termio/tokenize.ts similarity index 100% rename from src/ink/termio/tokenize.ts rename to packages/@ant/ink/src/core/termio/tokenize.ts diff --git a/src/ink/termio/types.ts b/packages/@ant/ink/src/core/termio/types.ts similarity index 100% rename from src/ink/termio/types.ts rename to packages/@ant/ink/src/core/termio/types.ts diff --git a/packages/@ant/ink/src/core/utils/grapheme.ts b/packages/@ant/ink/src/core/utils/grapheme.ts new file mode 100644 index 000000000..e010dc664 --- /dev/null +++ b/packages/@ant/ink/src/core/utils/grapheme.ts @@ -0,0 +1,53 @@ +/** + * Shared Intl object instances with lazy initialization. + * + * Intl constructors are expensive (~0.05-0.1ms each), so we cache instances + * for reuse across the codebase instead of creating new ones each time. + * Lazy initialization ensures we only pay the cost when actually needed. + * + * Vendored from src/utils/intl.ts for package independence. + */ + +// Segmenters for Unicode text processing (lazily initialized) +let graphemeSegmenter: Intl.Segmenter | null = null +let wordSegmenter: Intl.Segmenter | null = null + +export function getGraphemeSegmenter(): Intl.Segmenter { + if (!graphemeSegmenter) { + graphemeSegmenter = new Intl.Segmenter(undefined, { + granularity: 'grapheme', + }) + } + return graphemeSegmenter +} + +/** + * Extract the first grapheme cluster from a string. + * Returns '' for empty strings. + */ +export function firstGrapheme(text: string): string { + if (!text) return '' + const segments = getGraphemeSegmenter().segment(text) + const first = segments[Symbol.iterator]().next().value + return first?.segment ?? '' +} + +/** + * Extract the last grapheme cluster from a string. + * Returns '' for empty strings. + */ +export function lastGrapheme(text: string): string { + if (!text) return '' + let last = '' + for (const { segment } of getGraphemeSegmenter().segment(text)) { + last = segment + } + return last +} + +export function getWordSegmenter(): Intl.Segmenter { + if (!wordSegmenter) { + wordSegmenter = new Intl.Segmenter(undefined, { granularity: 'word' }) + } + return wordSegmenter +} diff --git a/packages/@ant/ink/src/core/utils/sliceAnsi.ts b/packages/@ant/ink/src/core/utils/sliceAnsi.ts new file mode 100644 index 000000000..42abe7ac7 --- /dev/null +++ b/packages/@ant/ink/src/core/utils/sliceAnsi.ts @@ -0,0 +1,97 @@ +/** + * Slice a string containing ANSI escape codes. + * + * Vendored from src/utils/sliceAnsi.ts for package independence. + * The only external dependency is stringWidth from the core package. + */ +import { + type AnsiCode, + ansiCodesToString, + reduceAnsiCodes, + tokenize, + undoAnsiCodes, +} from '@alcalzone/ansi-tokenize' +import { stringWidth } from '../stringWidth.js' + +// A code is an "end code" if its code equals its endCode (e.g., hyperlink close) +function isEndCode(code: AnsiCode): boolean { + return code.code === code.endCode +} + +// Filter to only include "start codes" (not end codes) +function filterStartCodes(codes: AnsiCode[]): AnsiCode[] { + return codes.filter(c => !isEndCode(c)) +} + +/** + * Slice a string containing ANSI escape codes. + * + * Unlike the slice-ansi package, this properly handles OSC 8 hyperlink + * sequences because @alcalzone/ansi-tokenize tokenizes them correctly. + */ +export default function sliceAnsi( + str: string, + start: number, + end?: number, +): string { + // Don't pass `end` to tokenize — it counts code units, not display cells, + // so it drops tokens early for text with zero-width combining marks. + const tokens = tokenize(str) + let activeCodes: AnsiCode[] = [] + let position = 0 + let result = '' + let include = false + + for (const token of tokens) { + // Advance by display width, not code units. Combining marks (Devanagari + // matras, virama, diacritics) are width 0 — counting them via .length + // advanced position past `end` early and truncated the slice. Callers + // pass start/end in display cells (via stringWidth), so position must + // track the same units. + const width = + token.type === 'ansi' ? 0 : token.type === 'char' ? (token.fullWidth ? 2 : stringWidth(token.value)) : 0 + + // Break AFTER trailing zero-width marks — a combining mark attaches to + // the preceding base char, so "भा" (भ + ा, 1 display cell) sliced at + // end=1 must include the ा. Breaking on position >= end BEFORE the + // zero-width check would drop it and render भ bare. ANSI codes are + // width 0 but must NOT be included past end (they open new style runs + // that leak into the undo sequence), so gate on char type too. The + // !include guard ensures empty slices (start===end) stay empty even + // when the string starts with a zero-width char (BOM, ZWJ). + if (end !== undefined && position >= end) { + if (token.type === 'ansi' || width > 0 || !include) break + } + + if (token.type === 'ansi') { + activeCodes.push(token) + if (include) { + // Emit all ANSI codes during the slice + result += token.code + } + } else { + if (!include && position >= start) { + // Skip leading zero-width marks at the start boundary — they belong + // to the preceding base char in the left half. Without this, the + // mark appears in BOTH halves: left+right ≠ original. Only applies + // when start > 0 (otherwise there's no preceding char to own it). + if (start > 0 && width === 0) continue + include = true + // Reduce and filter to only active start codes + activeCodes = filterStartCodes(reduceAnsiCodes(activeCodes)) + result = ansiCodesToString(activeCodes) + } + + if (include) { + result += (token as any).value + } + + position += width + } + } + + // Only undo start codes that are still active + const activeStartCodes = filterStartCodes(reduceAnsiCodes(activeCodes)) + result += ansiCodesToString(undoAnsiCodes(activeStartCodes)) + return result +} diff --git a/src/ink/warn.ts b/packages/@ant/ink/src/core/warn.ts similarity index 100% rename from src/ink/warn.ts rename to packages/@ant/ink/src/core/warn.ts diff --git a/src/ink/widest-line.ts b/packages/@ant/ink/src/core/widest-line.ts similarity index 100% rename from src/ink/widest-line.ts rename to packages/@ant/ink/src/core/widest-line.ts diff --git a/src/ink/wrap-text.ts b/packages/@ant/ink/src/core/wrap-text.ts similarity index 97% rename from src/ink/wrap-text.ts rename to packages/@ant/ink/src/core/wrap-text.ts index 434412cc9..b97f64235 100644 --- a/src/ink/wrap-text.ts +++ b/packages/@ant/ink/src/core/wrap-text.ts @@ -1,4 +1,4 @@ -import sliceAnsi from '../utils/sliceAnsi.js' +import sliceAnsi from './utils/sliceAnsi.js' import { stringWidth } from './stringWidth.js' import type { Styles } from './styles.js' import { wrapAnsi } from './wrapAnsi.js' diff --git a/src/ink/wrapAnsi.ts b/packages/@ant/ink/src/core/wrapAnsi.ts similarity index 100% rename from src/ink/wrapAnsi.ts rename to packages/@ant/ink/src/core/wrapAnsi.ts diff --git a/packages/@ant/ink/src/core/yoga-layout/enums.ts b/packages/@ant/ink/src/core/yoga-layout/enums.ts new file mode 100644 index 000000000..8cbb6ecff --- /dev/null +++ b/packages/@ant/ink/src/core/yoga-layout/enums.ts @@ -0,0 +1,134 @@ +/** + * Yoga enums — ported from yoga-layout/src/generated/YGEnums.ts + * Kept as `const` objects (not TS enums) per repo convention. + * Values match upstream exactly so callers don't change. + */ + +export const Align = { + Auto: 0, + FlexStart: 1, + Center: 2, + FlexEnd: 3, + Stretch: 4, + Baseline: 5, + SpaceBetween: 6, + SpaceAround: 7, + SpaceEvenly: 8, +} as const +export type Align = (typeof Align)[keyof typeof Align] + +export const BoxSizing = { + BorderBox: 0, + ContentBox: 1, +} as const +export type BoxSizing = (typeof BoxSizing)[keyof typeof BoxSizing] + +export const Dimension = { + Width: 0, + Height: 1, +} as const +export type Dimension = (typeof Dimension)[keyof typeof Dimension] + +export const Direction = { + Inherit: 0, + LTR: 1, + RTL: 2, +} as const +export type Direction = (typeof Direction)[keyof typeof Direction] + +export const Display = { + Flex: 0, + None: 1, + Contents: 2, +} as const +export type Display = (typeof Display)[keyof typeof Display] + +export const Edge = { + Left: 0, + Top: 1, + Right: 2, + Bottom: 3, + Start: 4, + End: 5, + Horizontal: 6, + Vertical: 7, + All: 8, +} as const +export type Edge = (typeof Edge)[keyof typeof Edge] + +export const Errata = { + None: 0, + StretchFlexBasis: 1, + AbsolutePositionWithoutInsetsExcludesPadding: 2, + AbsolutePercentAgainstInnerSize: 4, + All: 2147483647, + Classic: 2147483646, +} as const +export type Errata = (typeof Errata)[keyof typeof Errata] + +export const ExperimentalFeature = { + WebFlexBasis: 0, +} as const +export type ExperimentalFeature = + (typeof ExperimentalFeature)[keyof typeof ExperimentalFeature] + +export const FlexDirection = { + Column: 0, + ColumnReverse: 1, + Row: 2, + RowReverse: 3, +} as const +export type FlexDirection = (typeof FlexDirection)[keyof typeof FlexDirection] + +export const Gutter = { + Column: 0, + Row: 1, + All: 2, +} as const +export type Gutter = (typeof Gutter)[keyof typeof Gutter] + +export const Justify = { + FlexStart: 0, + Center: 1, + FlexEnd: 2, + SpaceBetween: 3, + SpaceAround: 4, + SpaceEvenly: 5, +} as const +export type Justify = (typeof Justify)[keyof typeof Justify] + +export const MeasureMode = { + Undefined: 0, + Exactly: 1, + AtMost: 2, +} as const +export type MeasureMode = (typeof MeasureMode)[keyof typeof MeasureMode] + +export const Overflow = { + Visible: 0, + Hidden: 1, + Scroll: 2, +} as const +export type Overflow = (typeof Overflow)[keyof typeof Overflow] + +export const PositionType = { + Static: 0, + Relative: 1, + Absolute: 2, +} as const +export type PositionType = (typeof PositionType)[keyof typeof PositionType] + +export const Unit = { + Undefined: 0, + Point: 1, + Percent: 2, + Auto: 3, +} as const +export type Unit = (typeof Unit)[keyof typeof Unit] + +export const Wrap = { + NoWrap: 0, + Wrap: 1, + WrapReverse: 2, +} as const +export type Wrap = (typeof Wrap)[keyof typeof Wrap] diff --git a/packages/@ant/ink/src/core/yoga-layout/index.ts b/packages/@ant/ink/src/core/yoga-layout/index.ts new file mode 100644 index 000000000..49b9602be --- /dev/null +++ b/packages/@ant/ink/src/core/yoga-layout/index.ts @@ -0,0 +1,2578 @@ +/** + * Pure-TypeScript port of yoga-layout (Meta's flexbox engine). + * + * This matches the `yoga-layout/load` API surface used by src/ink/layout/yoga.ts. + * The upstream C++ source is ~2500 lines in CalculateLayout.cpp alone; this port + * is a simplified single-pass flexbox implementation that covers the subset of + * features Ink actually uses: + * - flex-direction (row/column + reverse) + * - flex-grow / flex-shrink / flex-basis + * - align-items / align-self (stretch, flex-start, center, flex-end) + * - justify-content (all six values) + * - margin / padding / border / gap + * - width / height / min / max (point, percent, auto) + * - position: relative / absolute + * - display: flex / none + * - measure functions (for text nodes) + * + * Also implemented for spec parity (not used by Ink): + * - margin: auto (main + cross axis, overrides justify/align) + * - multi-pass flex clamping when children hit min/max constraints + * - flex-grow/shrink against container min/max when size is indefinite + * + * Also implemented for spec parity (not used by Ink): + * - flex-wrap: wrap / wrap-reverse (multi-line flex) + * - align-content (positions wrapped lines on cross axis) + * + * Also implemented for spec parity (not used by Ink): + * - display: contents (children lifted to grandparent, box removed) + * + * Also implemented for spec parity (not used by Ink): + * - baseline alignment (align-items/align-self: baseline) + * + * Not implemented (not used by Ink): + * - aspect-ratio + * - box-sizing: content-box + * - RTL direction (Ink always passes Direction.LTR) + * + * Upstream: https://github.com/facebook/yoga + */ + +import { + Align, + BoxSizing, + Dimension, + Direction, + Display, + Edge, + Errata, + ExperimentalFeature, + FlexDirection, + Gutter, + Justify, + MeasureMode, + Overflow, + PositionType, + Unit, + Wrap, +} from './enums.js' + +export { + Align, + BoxSizing, + Dimension, + Direction, + Display, + Edge, + Errata, + ExperimentalFeature, + FlexDirection, + Gutter, + Justify, + MeasureMode, + Overflow, + PositionType, + Unit, + Wrap, +} + +// -- +// Value types + +export type Value = { + unit: Unit + value: number +} + +const UNDEFINED_VALUE: Value = { unit: Unit.Undefined, value: NaN } +const AUTO_VALUE: Value = { unit: Unit.Auto, value: NaN } + +function pointValue(v: number): Value { + return { unit: Unit.Point, value: v } +} +function percentValue(v: number): Value { + return { unit: Unit.Percent, value: v } +} + +function resolveValue(v: Value, ownerSize: number): number { + switch (v.unit) { + case Unit.Point: + return v.value + case Unit.Percent: + return isNaN(ownerSize) ? NaN : (v.value * ownerSize) / 100 + default: + return NaN + } +} + +function isDefined(n: number): boolean { + return !isNaN(n) +} + +// NaN-safe equality for layout-cache input comparison +function sameFloat(a: number, b: number): boolean { + return a === b || (a !== a && b !== b) +} + +// -- +// Layout result (computed values) + +type Layout = { + left: number + top: number + width: number + height: number + // Computed per-edge values (resolved to physical edges) + border: [number, number, number, number] // left, top, right, bottom + padding: [number, number, number, number] + margin: [number, number, number, number] +} + +// -- +// Style (input values) + +type Style = { + direction: Direction + flexDirection: FlexDirection + justifyContent: Justify + alignItems: Align + alignSelf: Align + alignContent: Align + flexWrap: Wrap + overflow: Overflow + display: Display + positionType: PositionType + + flexGrow: number + flexShrink: number + flexBasis: Value + + // 9-edge arrays indexed by Edge enum + margin: Value[] + padding: Value[] + border: Value[] + position: Value[] + + // 3-gutter array indexed by Gutter enum + gap: Value[] + + width: Value + height: Value + minWidth: Value + minHeight: Value + maxWidth: Value + maxHeight: Value +} + +function defaultStyle(): Style { + return { + direction: Direction.Inherit, + flexDirection: FlexDirection.Column, + justifyContent: Justify.FlexStart, + alignItems: Align.Stretch, + alignSelf: Align.Auto, + alignContent: Align.FlexStart, + flexWrap: Wrap.NoWrap, + overflow: Overflow.Visible, + display: Display.Flex, + positionType: PositionType.Relative, + flexGrow: 0, + flexShrink: 0, + flexBasis: AUTO_VALUE, + margin: new Array(9).fill(UNDEFINED_VALUE), + padding: new Array(9).fill(UNDEFINED_VALUE), + border: new Array(9).fill(UNDEFINED_VALUE), + position: new Array(9).fill(UNDEFINED_VALUE), + gap: new Array(3).fill(UNDEFINED_VALUE), + width: AUTO_VALUE, + height: AUTO_VALUE, + minWidth: UNDEFINED_VALUE, + minHeight: UNDEFINED_VALUE, + maxWidth: UNDEFINED_VALUE, + maxHeight: UNDEFINED_VALUE, + } +} + +// -- +// Edge resolution — yoga's 9-edge model collapsed to 4 physical edges + +const EDGE_LEFT = 0 +const EDGE_TOP = 1 +const EDGE_RIGHT = 2 +const EDGE_BOTTOM = 3 + +function resolveEdge( + edges: Value[], + physicalEdge: number, + ownerSize: number, + // For margin/position we allow auto; for padding/border auto resolves to 0 + allowAuto = false, +): number { + // Precedence: specific edge > horizontal/vertical > all + let v = edges[physicalEdge]! + if (v.unit === Unit.Undefined) { + if (physicalEdge === EDGE_LEFT || physicalEdge === EDGE_RIGHT) { + v = edges[Edge.Horizontal]! + } else { + v = edges[Edge.Vertical]! + } + } + if (v.unit === Unit.Undefined) { + v = edges[Edge.All]! + } + // Start/End map to Left/Right for LTR (Ink is always LTR) + if (v.unit === Unit.Undefined) { + if (physicalEdge === EDGE_LEFT) v = edges[Edge.Start]! + if (physicalEdge === EDGE_RIGHT) v = edges[Edge.End]! + } + if (v.unit === Unit.Undefined) return 0 + if (v.unit === Unit.Auto) return allowAuto ? NaN : 0 + return resolveValue(v, ownerSize) +} + +function resolveEdgeRaw(edges: Value[], physicalEdge: number): Value { + let v = edges[physicalEdge]! + if (v.unit === Unit.Undefined) { + if (physicalEdge === EDGE_LEFT || physicalEdge === EDGE_RIGHT) { + v = edges[Edge.Horizontal]! + } else { + v = edges[Edge.Vertical]! + } + } + if (v.unit === Unit.Undefined) v = edges[Edge.All]! + if (v.unit === Unit.Undefined) { + if (physicalEdge === EDGE_LEFT) v = edges[Edge.Start]! + if (physicalEdge === EDGE_RIGHT) v = edges[Edge.End]! + } + return v +} + +function isMarginAuto(edges: Value[], physicalEdge: number): boolean { + return resolveEdgeRaw(edges, physicalEdge).unit === Unit.Auto +} + +// Setter helpers for the _hasAutoMargin / _hasPosition fast-path flags. +// Unit.Undefined = 0, Unit.Auto = 3. +function hasAnyAutoEdge(edges: Value[]): boolean { + for (let i = 0; i < 9; i++) if (edges[i]!.unit === 3) return true + return false +} +function hasAnyDefinedEdge(edges: Value[]): boolean { + for (let i = 0; i < 9; i++) if (edges[i]!.unit !== 0) return true + return false +} + +// Hot path: resolve all 4 physical edges in one pass, writing into `out`. +// Equivalent to calling resolveEdge() 4× with allowAuto=false, but hoists the +// shared fallback lookups (Horizontal/Vertical/All/Start/End) and avoids +// allocating a fresh 4-array on every layoutNode() call. +function resolveEdges4Into( + edges: Value[], + ownerSize: number, + out: [number, number, number, number], +): void { + // Hoist fallbacks once — the 4 per-edge chains share these reads. + const eH = edges[6]! // Edge.Horizontal + const eV = edges[7]! // Edge.Vertical + const eA = edges[8]! // Edge.All + const eS = edges[4]! // Edge.Start + const eE = edges[5]! // Edge.End + const pctDenom = isNaN(ownerSize) ? NaN : ownerSize / 100 + + // Left: edges[0] → Horizontal → All → Start + let v = edges[0]! + if (v.unit === 0) v = eH + if (v.unit === 0) v = eA + if (v.unit === 0) v = eS + out[0] = v.unit === 1 ? v.value : v.unit === 2 ? v.value * pctDenom : 0 + + // Top: edges[1] → Vertical → All + v = edges[1]! + if (v.unit === 0) v = eV + if (v.unit === 0) v = eA + out[1] = v.unit === 1 ? v.value : v.unit === 2 ? v.value * pctDenom : 0 + + // Right: edges[2] → Horizontal → All → End + v = edges[2]! + if (v.unit === 0) v = eH + if (v.unit === 0) v = eA + if (v.unit === 0) v = eE + out[2] = v.unit === 1 ? v.value : v.unit === 2 ? v.value * pctDenom : 0 + + // Bottom: edges[3] → Vertical → All + v = edges[3]! + if (v.unit === 0) v = eV + if (v.unit === 0) v = eA + out[3] = v.unit === 1 ? v.value : v.unit === 2 ? v.value * pctDenom : 0 +} + +// -- +// Axis helpers + +function isRow(dir: FlexDirection): boolean { + return dir === FlexDirection.Row || dir === FlexDirection.RowReverse +} +function isReverse(dir: FlexDirection): boolean { + return dir === FlexDirection.RowReverse || dir === FlexDirection.ColumnReverse +} +function crossAxis(dir: FlexDirection): FlexDirection { + return isRow(dir) ? FlexDirection.Column : FlexDirection.Row +} +function leadingEdge(dir: FlexDirection): number { + switch (dir) { + case FlexDirection.Row: + return EDGE_LEFT + case FlexDirection.RowReverse: + return EDGE_RIGHT + case FlexDirection.Column: + return EDGE_TOP + case FlexDirection.ColumnReverse: + return EDGE_BOTTOM + } +} +function trailingEdge(dir: FlexDirection): number { + switch (dir) { + case FlexDirection.Row: + return EDGE_RIGHT + case FlexDirection.RowReverse: + return EDGE_LEFT + case FlexDirection.Column: + return EDGE_BOTTOM + case FlexDirection.ColumnReverse: + return EDGE_TOP + } +} + +// -- +// Public types + +export type MeasureFunction = ( + width: number, + widthMode: MeasureMode, + height: number, + heightMode: MeasureMode, +) => { width: number; height: number } + +export type Size = { width: number; height: number } + +// -- +// Config + +export type Config = { + pointScaleFactor: number + errata: Errata + useWebDefaults: boolean + free(): void + isExperimentalFeatureEnabled(_: ExperimentalFeature): boolean + setExperimentalFeatureEnabled(_: ExperimentalFeature, __: boolean): void + setPointScaleFactor(factor: number): void + getErrata(): Errata + setErrata(errata: Errata): void + setUseWebDefaults(v: boolean): void +} + +function createConfig(): Config { + const config: Config = { + pointScaleFactor: 1, + errata: Errata.None, + useWebDefaults: false, + free() {}, + isExperimentalFeatureEnabled() { + return false + }, + setExperimentalFeatureEnabled() {}, + setPointScaleFactor(f) { + config.pointScaleFactor = f + }, + getErrata() { + return config.errata + }, + setErrata(e) { + config.errata = e + }, + setUseWebDefaults(v) { + config.useWebDefaults = v + }, + } + return config +} + +// -- +// Node implementation + +export class Node { + style: Style + layout: Layout + parent: Node | null + children: Node[] + measureFunc: MeasureFunction | null + config: Config + isDirty_: boolean + isReferenceBaseline_: boolean + + // Per-layout scratch (not public API) + _flexBasis = 0 + _mainSize = 0 + _crossSize = 0 + _lineIndex = 0 + // Fast-path flags maintained by style setters. Per CPU profile, the + // positioning loop calls isMarginAuto 6× and resolveEdgeRaw(position) 4× + // per child per layout pass — ~11k calls for the 1000-node bench, nearly + // all of which return false/undefined since most nodes have no auto + // margins and no position insets. These flags let us skip straight to + // the common case with a single branch. + _hasAutoMargin = false + _hasPosition = false + // Same pattern for the 3× resolveEdges4Into calls at the top of every + // layoutNode(). In the 1000-node bench ~67% of those calls operate on + // all-undefined edge arrays (most nodes have no border; only cols have + // padding; only leaf cells have margin) — a single-branch skip beats + // ~20 property reads + ~15 compares + 4 writes of zeros. + _hasPadding = false + _hasBorder = false + _hasMargin = false + // -- Dirty-flag layout cache. Mirrors upstream CalculateLayout.cpp's + // layoutNodeInternal: skip a subtree entirely when it's clean and we're + // asking the same question we cached the answer to. Two slots since + // each node typically sees a measure call (performLayout=false, from + // computeFlexBasis) followed by a layout call (performLayout=true) with + // different inputs per parent pass — a single slot thrashes. Re-layout + // bench (dirty one leaf, recompute root) went 2.7x→1.1x with this: + // clean siblings skip straight through, only the dirty chain recomputes. + _lW = NaN + _lH = NaN + _lWM: MeasureMode = 0 + _lHM: MeasureMode = 0 + _lOW = NaN + _lOH = NaN + _lFW = false + _lFH = false + // _hasL stores INPUTS early (before compute) but layout.width/height are + // mutated by the multi-entry cache and by subsequent compute calls with + // different inputs. Without storing OUTPUTS, a _hasL hit returns whatever + // layout.width/height happened to be left by the last call — the scrollbox + // vpH=33→2624 bug. Store + restore outputs like the multi-entry cache does. + _lOutW = NaN + _lOutH = NaN + _hasL = false + _mW = NaN + _mH = NaN + _mWM: MeasureMode = 0 + _mHM: MeasureMode = 0 + _mOW = NaN + _mOH = NaN + _mOutW = NaN + _mOutH = NaN + _hasM = false + // Cached computeFlexBasis result. For clean children, basis only depends + // on the container's inner dimensions — if those haven't changed, skip the + // layoutNode(performLayout=false) recursion entirely. This is the hot path + // for scroll: 500-message content container is dirty, its 499 clean + // children each get measured ~20× as the dirty chain's measure/layout + // passes cascade. Basis cache short-circuits at the child boundary. + _fbBasis = NaN + _fbOwnerW = NaN + _fbOwnerH = NaN + _fbAvailMain = NaN + _fbAvailCross = NaN + _fbCrossMode: MeasureMode = 0 + // Generation at which _fbBasis was written. Dirty nodes from a PREVIOUS + // generation have stale cache (subtree changed), but within the SAME + // generation the cache is fresh — the dirty chain's measure→layout + // cascade invokes computeFlexBasis ≥2^depth times per calculateLayout on + // fresh-mounted items, and the subtree doesn't change between calls. + // Gating on generation instead of isDirty_ lets fresh mounts (virtual + // scroll) cache-hit after first compute: 105k visits → ~10k. + _fbGen = -1 + // Multi-entry layout cache — stores (inputs → computed w,h) so hits with + // different inputs than _hasL can restore the right dimensions. Upstream + // yoga uses 16; 4 covers Ink's dirty-chain depth. Packed as flat arrays + // to avoid per-entry object allocs. Slot i uses indices [i*8, i*8+8) in + // _cIn (aW,aH,wM,hM,oW,oH,fW,fH) and [i*2, i*2+2) in _cOut (w,h). + _cIn: Float64Array | null = null + _cOut: Float64Array | null = null + _cGen = -1 + _cN = 0 + _cWr = 0 + + constructor(config?: Config) { + this.style = defaultStyle() + this.layout = { + left: 0, + top: 0, + width: 0, + height: 0, + border: [0, 0, 0, 0], + padding: [0, 0, 0, 0], + margin: [0, 0, 0, 0], + } + this.parent = null + this.children = [] + this.measureFunc = null + this.config = config ?? DEFAULT_CONFIG + this.isDirty_ = true + this.isReferenceBaseline_ = false + _yogaLiveNodes++ + } + + // -- Tree + + insertChild(child: Node, index: number): void { + child.parent = this + this.children.splice(index, 0, child) + this.markDirty() + } + removeChild(child: Node): void { + const idx = this.children.indexOf(child) + if (idx >= 0) { + this.children.splice(idx, 1) + child.parent = null + this.markDirty() + } + } + getChild(index: number): Node { + return this.children[index]! + } + getChildCount(): number { + return this.children.length + } + getParent(): Node | null { + return this.parent + } + + // -- Lifecycle + + free(): void { + this.parent = null + this.children = [] + this.measureFunc = null + this._cIn = null + this._cOut = null + _yogaLiveNodes-- + } + freeRecursive(): void { + for (const c of this.children) c.freeRecursive() + this.free() + } + reset(): void { + this.style = defaultStyle() + this.children = [] + this.parent = null + this.measureFunc = null + this.isDirty_ = true + this._hasAutoMargin = false + this._hasPosition = false + this._hasPadding = false + this._hasBorder = false + this._hasMargin = false + this._hasL = false + this._hasM = false + this._cN = 0 + this._cWr = 0 + this._fbBasis = NaN + } + + // -- Dirty tracking + + markDirty(): void { + this.isDirty_ = true + if (this.parent && !this.parent.isDirty_) this.parent.markDirty() + } + isDirty(): boolean { + return this.isDirty_ + } + hasNewLayout(): boolean { + return true + } + markLayoutSeen(): void {} + + // -- Measure function + + setMeasureFunc(fn: MeasureFunction | null): void { + this.measureFunc = fn + this.markDirty() + } + unsetMeasureFunc(): void { + this.measureFunc = null + this.markDirty() + } + + // -- Computed layout getters + + getComputedLeft(): number { + return this.layout.left + } + getComputedTop(): number { + return this.layout.top + } + getComputedWidth(): number { + return this.layout.width + } + getComputedHeight(): number { + return this.layout.height + } + getComputedRight(): number { + const p = this.parent + return p ? p.layout.width - this.layout.left - this.layout.width : 0 + } + getComputedBottom(): number { + const p = this.parent + return p ? p.layout.height - this.layout.top - this.layout.height : 0 + } + getComputedLayout(): { + left: number + top: number + right: number + bottom: number + width: number + height: number + } { + return { + left: this.layout.left, + top: this.layout.top, + right: this.getComputedRight(), + bottom: this.getComputedBottom(), + width: this.layout.width, + height: this.layout.height, + } + } + getComputedBorder(edge: Edge): number { + return this.layout.border[physicalEdge(edge)]! + } + getComputedPadding(edge: Edge): number { + return this.layout.padding[physicalEdge(edge)]! + } + getComputedMargin(edge: Edge): number { + return this.layout.margin[physicalEdge(edge)]! + } + + // -- Style setters: dimensions + + setWidth(v: number | 'auto' | string | undefined): void { + this.style.width = parseDimension(v) + this.markDirty() + } + setWidthPercent(v: number): void { + this.style.width = percentValue(v) + this.markDirty() + } + setWidthAuto(): void { + this.style.width = AUTO_VALUE + this.markDirty() + } + setHeight(v: number | 'auto' | string | undefined): void { + this.style.height = parseDimension(v) + this.markDirty() + } + setHeightPercent(v: number): void { + this.style.height = percentValue(v) + this.markDirty() + } + setHeightAuto(): void { + this.style.height = AUTO_VALUE + this.markDirty() + } + setMinWidth(v: number | string | undefined): void { + this.style.minWidth = parseDimension(v) + this.markDirty() + } + setMinWidthPercent(v: number): void { + this.style.minWidth = percentValue(v) + this.markDirty() + } + setMinHeight(v: number | string | undefined): void { + this.style.minHeight = parseDimension(v) + this.markDirty() + } + setMinHeightPercent(v: number): void { + this.style.minHeight = percentValue(v) + this.markDirty() + } + setMaxWidth(v: number | string | undefined): void { + this.style.maxWidth = parseDimension(v) + this.markDirty() + } + setMaxWidthPercent(v: number): void { + this.style.maxWidth = percentValue(v) + this.markDirty() + } + setMaxHeight(v: number | string | undefined): void { + this.style.maxHeight = parseDimension(v) + this.markDirty() + } + setMaxHeightPercent(v: number): void { + this.style.maxHeight = percentValue(v) + this.markDirty() + } + + // -- Style setters: flex + + setFlexDirection(dir: FlexDirection): void { + this.style.flexDirection = dir + this.markDirty() + } + setFlexGrow(v: number | undefined): void { + this.style.flexGrow = v ?? 0 + this.markDirty() + } + setFlexShrink(v: number | undefined): void { + this.style.flexShrink = v ?? 0 + this.markDirty() + } + setFlex(v: number | undefined): void { + if (v === undefined || isNaN(v)) { + this.style.flexGrow = 0 + this.style.flexShrink = 0 + } else if (v > 0) { + this.style.flexGrow = v + this.style.flexShrink = 1 + this.style.flexBasis = pointValue(0) + } else if (v < 0) { + this.style.flexGrow = 0 + this.style.flexShrink = -v + } else { + this.style.flexGrow = 0 + this.style.flexShrink = 0 + } + this.markDirty() + } + setFlexBasis(v: number | 'auto' | string | undefined): void { + this.style.flexBasis = parseDimension(v) + this.markDirty() + } + setFlexBasisPercent(v: number): void { + this.style.flexBasis = percentValue(v) + this.markDirty() + } + setFlexBasisAuto(): void { + this.style.flexBasis = AUTO_VALUE + this.markDirty() + } + setFlexWrap(wrap: Wrap): void { + this.style.flexWrap = wrap + this.markDirty() + } + + // -- Style setters: alignment + + setAlignItems(a: Align): void { + this.style.alignItems = a + this.markDirty() + } + setAlignSelf(a: Align): void { + this.style.alignSelf = a + this.markDirty() + } + setAlignContent(a: Align): void { + this.style.alignContent = a + this.markDirty() + } + setJustifyContent(j: Justify): void { + this.style.justifyContent = j + this.markDirty() + } + + // -- Style setters: display / position / overflow + + setDisplay(d: Display): void { + this.style.display = d + this.markDirty() + } + getDisplay(): Display { + return this.style.display + } + setPositionType(t: PositionType): void { + this.style.positionType = t + this.markDirty() + } + setPosition(edge: Edge, v: number | string | undefined): void { + this.style.position[edge] = parseDimension(v) + this._hasPosition = hasAnyDefinedEdge(this.style.position) + this.markDirty() + } + setPositionPercent(edge: Edge, v: number): void { + this.style.position[edge] = percentValue(v) + this._hasPosition = true + this.markDirty() + } + setPositionAuto(edge: Edge): void { + this.style.position[edge] = AUTO_VALUE + this._hasPosition = true + this.markDirty() + } + setOverflow(o: Overflow): void { + this.style.overflow = o + this.markDirty() + } + setDirection(d: Direction): void { + this.style.direction = d + this.markDirty() + } + setBoxSizing(_: BoxSizing): void { + // Not implemented — Ink doesn't use content-box + } + + // -- Style setters: spacing + + setMargin(edge: Edge, v: number | 'auto' | string | undefined): void { + const val = parseDimension(v) + this.style.margin[edge] = val + if (val.unit === Unit.Auto) this._hasAutoMargin = true + else this._hasAutoMargin = hasAnyAutoEdge(this.style.margin) + this._hasMargin = + this._hasAutoMargin || hasAnyDefinedEdge(this.style.margin) + this.markDirty() + } + setMarginPercent(edge: Edge, v: number): void { + this.style.margin[edge] = percentValue(v) + this._hasAutoMargin = hasAnyAutoEdge(this.style.margin) + this._hasMargin = true + this.markDirty() + } + setMarginAuto(edge: Edge): void { + this.style.margin[edge] = AUTO_VALUE + this._hasAutoMargin = true + this._hasMargin = true + this.markDirty() + } + setPadding(edge: Edge, v: number | string | undefined): void { + this.style.padding[edge] = parseDimension(v) + this._hasPadding = hasAnyDefinedEdge(this.style.padding) + this.markDirty() + } + setPaddingPercent(edge: Edge, v: number): void { + this.style.padding[edge] = percentValue(v) + this._hasPadding = true + this.markDirty() + } + setBorder(edge: Edge, v: number | undefined): void { + this.style.border[edge] = v === undefined ? UNDEFINED_VALUE : pointValue(v) + this._hasBorder = hasAnyDefinedEdge(this.style.border) + this.markDirty() + } + setGap(gutter: Gutter, v: number | string | undefined): void { + this.style.gap[gutter] = parseDimension(v) + this.markDirty() + } + setGapPercent(gutter: Gutter, v: number): void { + this.style.gap[gutter] = percentValue(v) + this.markDirty() + } + + // -- Style getters (partial — only what tests need) + + getFlexDirection(): FlexDirection { + return this.style.flexDirection + } + getJustifyContent(): Justify { + return this.style.justifyContent + } + getAlignItems(): Align { + return this.style.alignItems + } + getAlignSelf(): Align { + return this.style.alignSelf + } + getAlignContent(): Align { + return this.style.alignContent + } + getFlexGrow(): number { + return this.style.flexGrow + } + getFlexShrink(): number { + return this.style.flexShrink + } + getFlexBasis(): Value { + return this.style.flexBasis + } + getFlexWrap(): Wrap { + return this.style.flexWrap + } + getWidth(): Value { + return this.style.width + } + getHeight(): Value { + return this.style.height + } + getOverflow(): Overflow { + return this.style.overflow + } + getPositionType(): PositionType { + return this.style.positionType + } + getDirection(): Direction { + return this.style.direction + } + + // -- Unused API stubs (present for API parity) + + copyStyle(_: Node): void {} + setDirtiedFunc(_: unknown): void {} + unsetDirtiedFunc(): void {} + setIsReferenceBaseline(v: boolean): void { + this.isReferenceBaseline_ = v + this.markDirty() + } + isReferenceBaseline(): boolean { + return this.isReferenceBaseline_ + } + setAspectRatio(_: number | undefined): void {} + getAspectRatio(): number { + return NaN + } + setAlwaysFormsContainingBlock(_: boolean): void {} + + // -- Layout entry point + + calculateLayout( + ownerWidth: number | undefined, + ownerHeight: number | undefined, + _direction?: Direction, + ): void { + _yogaNodesVisited = 0 + _yogaMeasureCalls = 0 + _yogaCacheHits = 0 + _generation++ + const w = ownerWidth === undefined ? NaN : ownerWidth + const h = ownerHeight === undefined ? NaN : ownerHeight + layoutNode( + this, + w, + h, + isDefined(w) ? MeasureMode.Exactly : MeasureMode.Undefined, + isDefined(h) ? MeasureMode.Exactly : MeasureMode.Undefined, + w, + h, + true, + ) + // Root's own position = margin + position insets (yoga applies position + // to the root even without a parent container; this matters for rounding + // since the root's abs top/left seeds the pixel-grid walk). + const mar = this.layout.margin + const posL = resolveValue( + resolveEdgeRaw(this.style.position, EDGE_LEFT), + isDefined(w) ? w : 0, + ) + const posT = resolveValue( + resolveEdgeRaw(this.style.position, EDGE_TOP), + isDefined(w) ? w : 0, + ) + this.layout.left = mar[EDGE_LEFT] + (isDefined(posL) ? posL : 0) + this.layout.top = mar[EDGE_TOP] + (isDefined(posT) ? posT : 0) + roundLayout(this, this.config.pointScaleFactor, 0, 0) + } +} + +const DEFAULT_CONFIG = createConfig() + +const CACHE_SLOTS = 4 +function cacheWrite( + node: Node, + aW: number, + aH: number, + wM: MeasureMode, + hM: MeasureMode, + oW: number, + oH: number, + fW: boolean, + fH: boolean, + wasDirty: boolean, +): void { + if (!node._cIn) { + node._cIn = new Float64Array(CACHE_SLOTS * 8) + node._cOut = new Float64Array(CACHE_SLOTS * 2) + } + // First write after a dirty clears stale entries from before the dirty. + // _cGen < _generation means entries are from a previous calculateLayout; + // if wasDirty, the subtree changed since then → old dimensions invalid. + // Clean nodes' old entries stay — same subtree → same result for same + // inputs, so cross-generation caching works (the scroll hot path where + // 499 clean messages cache-hit while one dirty leaf recomputes). + if (wasDirty && node._cGen !== _generation) { + node._cN = 0 + node._cWr = 0 + } + // LRU write index wraps; _cN stays at CACHE_SLOTS so the read scan always + // checks all populated slots (not just those since last wrap). + const i = node._cWr++ % CACHE_SLOTS + if (node._cN < CACHE_SLOTS) node._cN = node._cWr + const o = i * 8 + const cIn = node._cIn + cIn[o] = aW + cIn[o + 1] = aH + cIn[o + 2] = wM + cIn[o + 3] = hM + cIn[o + 4] = oW + cIn[o + 5] = oH + cIn[o + 6] = fW ? 1 : 0 + cIn[o + 7] = fH ? 1 : 0 + node._cOut![i * 2] = node.layout.width + node._cOut![i * 2 + 1] = node.layout.height + node._cGen = _generation +} + +// Store computed layout.width/height into the single-slot cache output fields. +// _hasL/_hasM inputs are committed at the TOP of layoutNode (before compute); +// outputs must be committed HERE (after compute) so a cache hit can restore +// the correct dimensions. Without this, a _hasL hit returns whatever +// layout.width/height was left by the last call — which may be the intrinsic +// content height from a heightMode=Undefined measure pass rather than the +// constrained viewport height from the layout pass. That's the scrollbox +// vpH=33→2624 bug: scrollTop clamps to 0, viewport goes blank. +function commitCacheOutputs(node: Node, performLayout: boolean): void { + if (performLayout) { + node._lOutW = node.layout.width + node._lOutH = node.layout.height + } else { + node._mOutW = node.layout.width + node._mOutH = node.layout.height + } +} + +// -- +// Core flexbox algorithm + +// Profiling counters — reset per calculateLayout, read via getYogaCounters. +// Incremented on each calculateLayout(). Nodes stamp _fbGen/_cGen when +// their cache is written; a cache entry with gen === _generation was +// computed THIS pass and is fresh regardless of isDirty_ state. +let _generation = 0 +let _yogaNodesVisited = 0 +let _yogaMeasureCalls = 0 +let _yogaCacheHits = 0 +let _yogaLiveNodes = 0 +export function getYogaCounters(): { + visited: number + measured: number + cacheHits: number + live: number +} { + return { + visited: _yogaNodesVisited, + measured: _yogaMeasureCalls, + cacheHits: _yogaCacheHits, + live: _yogaLiveNodes, + } +} + +function layoutNode( + node: Node, + availableWidth: number, + availableHeight: number, + widthMode: MeasureMode, + heightMode: MeasureMode, + ownerWidth: number, + ownerHeight: number, + performLayout: boolean, + // When true, ignore style dimension on this axis — the flex container + // has already determined the main size (flex-basis + grow/shrink result). + forceWidth = false, + forceHeight = false, +): void { + _yogaNodesVisited++ + const style = node.style + const layout = node.layout + + // Dirty-flag skip: clean subtree + matching inputs → layout object already + // holds the answer. A cached layout result also satisfies a measure request + // (positions are a superset of dimensions); the reverse does not hold. + // Same-generation entries are fresh regardless of isDirty_ — they were + // computed THIS calculateLayout, the subtree hasn't changed since. + // Previous-generation entries need !isDirty_ (a dirty node's cache from + // before the dirty is stale). + // sameGen bypass only for MEASURE calls — a layout-pass cache hit would + // skip the child-positioning recursion (STEP 5), leaving children at + // stale positions. Measure calls only need w/h which the cache stores. + const sameGen = node._cGen === _generation && !performLayout + if (!node.isDirty_ || sameGen) { + if ( + !node.isDirty_ && + node._hasL && + node._lWM === widthMode && + node._lHM === heightMode && + node._lFW === forceWidth && + node._lFH === forceHeight && + sameFloat(node._lW, availableWidth) && + sameFloat(node._lH, availableHeight) && + sameFloat(node._lOW, ownerWidth) && + sameFloat(node._lOH, ownerHeight) + ) { + _yogaCacheHits++ + layout.width = node._lOutW + layout.height = node._lOutH + return + } + // Multi-entry cache: scan for matching inputs, restore cached w/h on hit. + // Covers the scroll case where a dirty ancestor's measure→layout cascade + // produces N>1 distinct input combos per clean child — the single _hasL + // slot thrashed, forcing full subtree recursion. With 500-message + // scrollbox and one dirty leaf, this took dirty-leaf relayout from + // 76k layoutNode calls (21.7×nodes) to 4k (1.2×nodes), 6.86ms → 550µs. + // Same-generation check covers fresh-mounted (dirty) nodes during + // virtual scroll — the dirty chain invokes them ≥2^depth times, first + // call writes cache, rest hit: 105k visits → ~10k for 1593-node tree. + if (node._cN > 0 && (sameGen || !node.isDirty_)) { + const cIn = node._cIn! + for (let i = 0; i < node._cN; i++) { + const o = i * 8 + if ( + cIn[o + 2] === widthMode && + cIn[o + 3] === heightMode && + cIn[o + 6] === (forceWidth ? 1 : 0) && + cIn[o + 7] === (forceHeight ? 1 : 0) && + sameFloat(cIn[o]!, availableWidth) && + sameFloat(cIn[o + 1]!, availableHeight) && + sameFloat(cIn[o + 4]!, ownerWidth) && + sameFloat(cIn[o + 5]!, ownerHeight) + ) { + layout.width = node._cOut![i * 2]! + layout.height = node._cOut![i * 2 + 1]! + _yogaCacheHits++ + return + } + } + } + if ( + !node.isDirty_ && + !performLayout && + node._hasM && + node._mWM === widthMode && + node._mHM === heightMode && + sameFloat(node._mW, availableWidth) && + sameFloat(node._mH, availableHeight) && + sameFloat(node._mOW, ownerWidth) && + sameFloat(node._mOH, ownerHeight) + ) { + layout.width = node._mOutW + layout.height = node._mOutH + _yogaCacheHits++ + return + } + } + // Commit cache inputs up front so every return path leaves a valid entry. + // Only clear isDirty_ on the LAYOUT pass — the measure pass (computeFlexBasis + // → layoutNode(performLayout=false)) runs before the layout pass in the same + // calculateLayout call. Clearing dirty during measure lets the subsequent + // layout pass hit the STALE _hasL cache from the previous calculateLayout + // (before children were inserted), so ScrollBox content height never grows + // and sticky-scroll never follows new content. A dirty node's _hasL entry is + // stale by definition — invalidate it so the layout pass recomputes. + const wasDirty = node.isDirty_ + if (performLayout) { + node._lW = availableWidth + node._lH = availableHeight + node._lWM = widthMode + node._lHM = heightMode + node._lOW = ownerWidth + node._lOH = ownerHeight + node._lFW = forceWidth + node._lFH = forceHeight + node._hasL = true + node.isDirty_ = false + // Previous approach cleared _cN here to prevent stale pre-dirty entries + // from hitting (long-continuous blank-screen bug). Now replaced by + // generation stamping: the cache check requires sameGen || !isDirty_, so + // previous-generation entries from a dirty node can't hit. Clearing here + // would wipe fresh same-generation entries from an earlier measure call, + // forcing recompute on the layout call. + if (wasDirty) node._hasM = false + } else { + node._mW = availableWidth + node._mH = availableHeight + node._mWM = widthMode + node._mHM = heightMode + node._mOW = ownerWidth + node._mOH = ownerHeight + node._hasM = true + // Don't clear isDirty_. For DIRTY nodes, invalidate _hasL so the upcoming + // performLayout=true call recomputes with the new child set (otherwise + // sticky-scroll never follows new content — the bug from 4557bc9f9c). + // Clean nodes keep _hasL: their layout from the previous generation is + // still valid, they're only here because an ancestor is dirty and called + // with different inputs than cached. + if (wasDirty) node._hasL = false + } + + // Resolve padding/border/margin against ownerWidth (yoga uses ownerWidth for %) + // Write directly into the pre-allocated layout arrays — avoids 3 allocs per + // layoutNode call and 12 resolveEdge calls (was the #1 hotspot per CPU profile). + // Skip entirely when no edges are set — the 4-write zero is cheaper than + // the ~20 reads + ~15 compares resolveEdges4Into does to produce zeros. + const pad = layout.padding + const bor = layout.border + const mar = layout.margin + if (node._hasPadding) resolveEdges4Into(style.padding, ownerWidth, pad) + else pad[0] = pad[1] = pad[2] = pad[3] = 0 + if (node._hasBorder) resolveEdges4Into(style.border, ownerWidth, bor) + else bor[0] = bor[1] = bor[2] = bor[3] = 0 + if (node._hasMargin) resolveEdges4Into(style.margin, ownerWidth, mar) + else mar[0] = mar[1] = mar[2] = mar[3] = 0 + + const paddingBorderWidth = pad[0] + pad[2] + bor[0] + bor[2] + const paddingBorderHeight = pad[1] + pad[3] + bor[1] + bor[3] + + // Resolve style dimensions + const styleWidth = forceWidth ? NaN : resolveValue(style.width, ownerWidth) + const styleHeight = forceHeight + ? NaN + : resolveValue(style.height, ownerHeight) + + // If style dimension is defined, it overrides the available size + let width = availableWidth + let height = availableHeight + let wMode = widthMode + let hMode = heightMode + if (isDefined(styleWidth)) { + width = styleWidth + wMode = MeasureMode.Exactly + } + if (isDefined(styleHeight)) { + height = styleHeight + hMode = MeasureMode.Exactly + } + + // Apply min/max constraints to the node's own dimensions + width = boundAxis(style, true, width, ownerWidth, ownerHeight) + height = boundAxis(style, false, height, ownerWidth, ownerHeight) + + // Measure-func leaf node + if (node.measureFunc && node.children.length === 0) { + const innerW = + wMode === MeasureMode.Undefined + ? NaN + : Math.max(0, width - paddingBorderWidth) + const innerH = + hMode === MeasureMode.Undefined + ? NaN + : Math.max(0, height - paddingBorderHeight) + _yogaMeasureCalls++ + const measured = node.measureFunc(innerW, wMode, innerH, hMode) + node.layout.width = + wMode === MeasureMode.Exactly + ? width + : boundAxis( + style, + true, + (measured.width ?? 0) + paddingBorderWidth, + ownerWidth, + ownerHeight, + ) + node.layout.height = + hMode === MeasureMode.Exactly + ? height + : boundAxis( + style, + false, + (measured.height ?? 0) + paddingBorderHeight, + ownerWidth, + ownerHeight, + ) + commitCacheOutputs(node, performLayout) + // Write cache even for dirty nodes — fresh-mounted items during virtual + // scroll are dirty on first layout, but the dirty chain's measure→layout + // cascade invokes them ≥2^depth times per calculateLayout. Writing here + // lets the 2nd+ calls hit cache (isDirty_ was cleared in the layout pass + // above). Measured: 105k visits → 10k for a 1593-node fresh-mount tree. + cacheWrite( + node, + availableWidth, + availableHeight, + widthMode, + heightMode, + ownerWidth, + ownerHeight, + forceWidth, + forceHeight, + wasDirty, + ) + return + } + + // Leaf node with no children and no measure func + if (node.children.length === 0) { + node.layout.width = + wMode === MeasureMode.Exactly + ? width + : boundAxis(style, true, paddingBorderWidth, ownerWidth, ownerHeight) + node.layout.height = + hMode === MeasureMode.Exactly + ? height + : boundAxis(style, false, paddingBorderHeight, ownerWidth, ownerHeight) + commitCacheOutputs(node, performLayout) + // Write cache even for dirty nodes — fresh-mounted items during virtual + // scroll are dirty on first layout, but the dirty chain's measure→layout + // cascade invokes them ≥2^depth times per calculateLayout. Writing here + // lets the 2nd+ calls hit cache (isDirty_ was cleared in the layout pass + // above). Measured: 105k visits → 10k for a 1593-node fresh-mount tree. + cacheWrite( + node, + availableWidth, + availableHeight, + widthMode, + heightMode, + ownerWidth, + ownerHeight, + forceWidth, + forceHeight, + wasDirty, + ) + return + } + + // Container with children — run flexbox algorithm + const mainAxis = style.flexDirection + const crossAx = crossAxis(mainAxis) + const isMainRow = isRow(mainAxis) + + const mainSize = isMainRow ? width : height + const crossSize = isMainRow ? height : width + const mainMode = isMainRow ? wMode : hMode + const crossMode = isMainRow ? hMode : wMode + const mainPadBorder = isMainRow ? paddingBorderWidth : paddingBorderHeight + const crossPadBorder = isMainRow ? paddingBorderHeight : paddingBorderWidth + + const innerMainSize = isDefined(mainSize) + ? Math.max(0, mainSize - mainPadBorder) + : NaN + const innerCrossSize = isDefined(crossSize) + ? Math.max(0, crossSize - crossPadBorder) + : NaN + + // Resolve gap + const gapMain = resolveGap( + style, + isMainRow ? Gutter.Column : Gutter.Row, + innerMainSize, + ) + + // Partition children into flow vs absolute. display:contents nodes are + // transparent — their children are lifted into the grandparent's child list + // (recursively), and the contents node itself gets zero layout. + const flowChildren: Node[] = [] + const absChildren: Node[] = [] + collectLayoutChildren(node, flowChildren, absChildren) + + // ownerW/H are the reference sizes for resolving children's percentage + // values. Per CSS, a % width resolves against the parent's content-box + // width. If this node's width is indefinite, children's % widths are also + // indefinite — do NOT fall through to the grandparent's size. + const ownerW = isDefined(width) ? width : NaN + const ownerH = isDefined(height) ? height : NaN + const isWrap = style.flexWrap !== Wrap.NoWrap + const gapCross = resolveGap( + style, + isMainRow ? Gutter.Row : Gutter.Column, + innerCrossSize, + ) + + // STEP 1: Compute flex-basis for each flow child and break into lines. + // Single-line (NoWrap) containers always get one line; multi-line containers + // break when accumulated basis+margin+gap exceeds innerMainSize. + for (const c of flowChildren) { + c._flexBasis = computeFlexBasis( + c, + mainAxis, + innerMainSize, + innerCrossSize, + crossMode, + ownerW, + ownerH, + ) + } + const lines: Node[][] = [] + if (!isWrap || !isDefined(innerMainSize) || flowChildren.length === 0) { + for (const c of flowChildren) c._lineIndex = 0 + lines.push(flowChildren) + } else { + // Line-break decisions use the min/max-clamped basis (flexbox spec §9.3.5: + // "hypothetical main size"), not the raw flex-basis. + let lineStart = 0 + let lineLen = 0 + for (let i = 0; i < flowChildren.length; i++) { + const c = flowChildren[i]! + const hypo = boundAxis(c.style, isMainRow, c._flexBasis, ownerW, ownerH) + const outer = Math.max(0, hypo) + childMarginForAxis(c, mainAxis, ownerW) + const withGap = i > lineStart ? gapMain : 0 + if (i > lineStart && lineLen + withGap + outer > innerMainSize) { + lines.push(flowChildren.slice(lineStart, i)) + lineStart = i + lineLen = outer + } else { + lineLen += withGap + outer + } + c._lineIndex = lines.length + } + lines.push(flowChildren.slice(lineStart)) + } + const lineCount = lines.length + const isBaseline = isBaselineLayout(node, flowChildren) + + // STEP 2+3: For each line, resolve flexible lengths and lay out children to + // measure cross sizes. Track per-line consumed main and max cross. + const lineConsumedMain: number[] = new Array(lineCount) + const lineCrossSizes: number[] = new Array(lineCount) + // Baseline layout tracks max ascent (baseline + leading margin) per line so + // baseline-aligned items can be positioned at maxAscent - childBaseline. + const lineMaxAscent: number[] = isBaseline ? new Array(lineCount).fill(0) : [] + let maxLineMain = 0 + let totalLinesCross = 0 + for (let li = 0; li < lineCount; li++) { + const line = lines[li]! + const lineGap = line.length > 1 ? gapMain * (line.length - 1) : 0 + let lineBasis = lineGap + for (const c of line) { + lineBasis += c._flexBasis + childMarginForAxis(c, mainAxis, ownerW) + } + // Resolve flexible lengths against available inner main. For indefinite + // containers with min/max, flex against the clamped size. + let availMain = innerMainSize + if (!isDefined(availMain)) { + const mainOwner = isMainRow ? ownerWidth : ownerHeight + const minM = resolveValue( + isMainRow ? style.minWidth : style.minHeight, + mainOwner, + ) + const maxM = resolveValue( + isMainRow ? style.maxWidth : style.maxHeight, + mainOwner, + ) + if (isDefined(maxM) && lineBasis > maxM - mainPadBorder) { + availMain = Math.max(0, maxM - mainPadBorder) + } else if (isDefined(minM) && lineBasis < minM - mainPadBorder) { + availMain = Math.max(0, minM - mainPadBorder) + } + } + resolveFlexibleLengths( + line, + availMain, + lineBasis, + isMainRow, + ownerW, + ownerH, + ) + + // Lay out each child in this line to measure cross + let lineCross = 0 + for (const c of line) { + const cStyle = c.style + const childAlign = + cStyle.alignSelf === Align.Auto ? style.alignItems : cStyle.alignSelf + const cMarginCross = childMarginForAxis(c, crossAx, ownerW) + let childCrossSize = NaN + let childCrossMode: MeasureMode = MeasureMode.Undefined + const resolvedCrossStyle = resolveValue( + isMainRow ? cStyle.height : cStyle.width, + isMainRow ? ownerH : ownerW, + ) + const crossLeadE = isMainRow ? EDGE_TOP : EDGE_LEFT + const crossTrailE = isMainRow ? EDGE_BOTTOM : EDGE_RIGHT + const hasCrossAutoMargin = + c._hasAutoMargin && + (isMarginAuto(cStyle.margin, crossLeadE) || + isMarginAuto(cStyle.margin, crossTrailE)) + // Single-line stretch goes directly to the container cross size. + // Multi-line wrap measures intrinsic cross (Undefined mode) so + // flex-grow grandchildren don't expand to the container — the line + // cross size is determined first, then items are re-stretched. + if (isDefined(resolvedCrossStyle)) { + childCrossSize = resolvedCrossStyle + childCrossMode = MeasureMode.Exactly + } else if ( + childAlign === Align.Stretch && + !hasCrossAutoMargin && + !isWrap && + isDefined(innerCrossSize) && + crossMode === MeasureMode.Exactly + ) { + childCrossSize = Math.max(0, innerCrossSize - cMarginCross) + childCrossMode = MeasureMode.Exactly + } else if (!isWrap && isDefined(innerCrossSize)) { + childCrossSize = Math.max(0, innerCrossSize - cMarginCross) + childCrossMode = MeasureMode.AtMost + } + const cw = isMainRow ? c._mainSize : childCrossSize + const ch = isMainRow ? childCrossSize : c._mainSize + layoutNode( + c, + cw, + ch, + isMainRow ? MeasureMode.Exactly : childCrossMode, + isMainRow ? childCrossMode : MeasureMode.Exactly, + ownerW, + ownerH, + performLayout, + isMainRow, + !isMainRow, + ) + c._crossSize = isMainRow ? c.layout.height : c.layout.width + lineCross = Math.max(lineCross, c._crossSize + cMarginCross) + } + // Baseline layout: line cross size must fit maxAscent + maxDescent of + // baseline-aligned children (yoga STEP 8). Only applies to row direction. + if (isBaseline) { + let maxAscent = 0 + let maxDescent = 0 + for (const c of line) { + if (resolveChildAlign(node, c) !== Align.Baseline) continue + const mTop = resolveEdge(c.style.margin, EDGE_TOP, ownerW) + const mBot = resolveEdge(c.style.margin, EDGE_BOTTOM, ownerW) + const ascent = calculateBaseline(c) + mTop + const descent = c.layout.height + mTop + mBot - ascent + if (ascent > maxAscent) maxAscent = ascent + if (descent > maxDescent) maxDescent = descent + } + lineMaxAscent[li] = maxAscent + if (maxAscent + maxDescent > lineCross) { + lineCross = maxAscent + maxDescent + } + } + // layoutNode(c) at line ~1117 above already resolved c.layout.margin[] via + // resolveEdges4Into with the same ownerW — read directly instead of + // re-resolving through childMarginForAxis → 2× resolveEdge. + const mainLead = leadingEdge(mainAxis) + const mainTrail = trailingEdge(mainAxis) + let consumed = lineGap + for (const c of line) { + const cm = c.layout.margin + consumed += c._mainSize + cm[mainLead]! + cm[mainTrail]! + } + lineConsumedMain[li] = consumed + lineCrossSizes[li] = lineCross + maxLineMain = Math.max(maxLineMain, consumed) + totalLinesCross += lineCross + } + const totalCrossGap = lineCount > 1 ? gapCross * (lineCount - 1) : 0 + totalLinesCross += totalCrossGap + + // STEP 4: Determine container dimensions. Per yoga's STEP 9, for both + // AtMost (FitContent) and Undefined (MaxContent) the node sizes to its + // content — AtMost is NOT a hard clamp, items may overflow the available + // space (CSS "fit-content" behavior). Only Scroll overflow clamps to the + // available size. Wrap containers that broke into multiple lines under + // AtMost fill the available main size since they wrapped at that boundary. + const isScroll = style.overflow === Overflow.Scroll + const contentMain = maxLineMain + mainPadBorder + const finalMainSize = + mainMode === MeasureMode.Exactly + ? mainSize + : mainMode === MeasureMode.AtMost && isScroll + ? Math.max(Math.min(mainSize, contentMain), mainPadBorder) + : isWrap && lineCount > 1 && mainMode === MeasureMode.AtMost + ? mainSize + : contentMain + const contentCross = totalLinesCross + crossPadBorder + const finalCrossSize = + crossMode === MeasureMode.Exactly + ? crossSize + : crossMode === MeasureMode.AtMost && isScroll + ? Math.max(Math.min(crossSize, contentCross), crossPadBorder) + : contentCross + node.layout.width = boundAxis( + style, + true, + isMainRow ? finalMainSize : finalCrossSize, + ownerWidth, + ownerHeight, + ) + node.layout.height = boundAxis( + style, + false, + isMainRow ? finalCrossSize : finalMainSize, + ownerWidth, + ownerHeight, + ) + commitCacheOutputs(node, performLayout) + // Write cache even for dirty nodes — fresh-mounted items during virtual scroll + cacheWrite( + node, + availableWidth, + availableHeight, + widthMode, + heightMode, + ownerWidth, + ownerHeight, + forceWidth, + forceHeight, + wasDirty, + ) + + if (!performLayout) return + + // STEP 5: Position lines (align-content) and children (justify-content + + // align-items + auto margins). + const actualInnerMain = + (isMainRow ? node.layout.width : node.layout.height) - mainPadBorder + const actualInnerCross = + (isMainRow ? node.layout.height : node.layout.width) - crossPadBorder + const mainLeadEdgePhys = leadingEdge(mainAxis) + const mainTrailEdgePhys = trailingEdge(mainAxis) + const crossLeadEdgePhys = isMainRow ? EDGE_TOP : EDGE_LEFT + const crossTrailEdgePhys = isMainRow ? EDGE_BOTTOM : EDGE_RIGHT + const reversed = isReverse(mainAxis) + const mainContainerSize = isMainRow ? node.layout.width : node.layout.height + const crossLead = pad[crossLeadEdgePhys]! + bor[crossLeadEdgePhys]! + + // Align-content: distribute free cross space among lines. Single-line + // containers use the full cross size for the one line (align-items handles + // positioning within it). + let lineCrossOffset = crossLead + let betweenLines = gapCross + const freeCross = actualInnerCross - totalLinesCross + if (lineCount === 1 && !isWrap && !isBaseline) { + lineCrossSizes[0] = actualInnerCross + } else { + const remCross = Math.max(0, freeCross) + switch (style.alignContent) { + case Align.FlexStart: + break + case Align.Center: + lineCrossOffset += freeCross / 2 + break + case Align.FlexEnd: + lineCrossOffset += freeCross + break + case Align.Stretch: + if (lineCount > 0 && remCross > 0) { + const add = remCross / lineCount + for (let i = 0; i < lineCount; i++) lineCrossSizes[i]! += add + } + break + case Align.SpaceBetween: + if (lineCount > 1) betweenLines += remCross / (lineCount - 1) + break + case Align.SpaceAround: + if (lineCount > 0) { + betweenLines += remCross / lineCount + lineCrossOffset += remCross / lineCount / 2 + } + break + case Align.SpaceEvenly: + if (lineCount > 0) { + betweenLines += remCross / (lineCount + 1) + lineCrossOffset += remCross / (lineCount + 1) + } + break + default: + break + } + } + + // For wrap-reverse, lines stack from the trailing cross edge. Walk lines in + // order but flip the cross position within the container. + const wrapReverse = style.flexWrap === Wrap.WrapReverse + const crossContainerSize = isMainRow ? node.layout.height : node.layout.width + let lineCrossPos = lineCrossOffset + for (let li = 0; li < lineCount; li++) { + const line = lines[li]! + const lineCross = lineCrossSizes[li]! + const consumedMain = lineConsumedMain[li]! + const n = line.length + + // Re-stretch children whose cross is auto and align is stretch, now that + // the line cross size is known. Needed for multi-line wrap (line cross + // wasn't known during initial measure) AND single-line when the container + // cross was not Exactly (initial stretch at ~line 1250 was skipped because + // innerCrossSize wasn't defined — the container sized to max child cross). + if (isWrap || crossMode !== MeasureMode.Exactly) { + for (const c of line) { + const cStyle = c.style + const childAlign = + cStyle.alignSelf === Align.Auto ? style.alignItems : cStyle.alignSelf + const crossStyleDef = isDefined( + resolveValue( + isMainRow ? cStyle.height : cStyle.width, + isMainRow ? ownerH : ownerW, + ), + ) + const hasCrossAutoMargin = + c._hasAutoMargin && + (isMarginAuto(cStyle.margin, crossLeadEdgePhys) || + isMarginAuto(cStyle.margin, crossTrailEdgePhys)) + if ( + childAlign === Align.Stretch && + !crossStyleDef && + !hasCrossAutoMargin + ) { + const cMarginCross = childMarginForAxis(c, crossAx, ownerW) + const target = Math.max(0, lineCross - cMarginCross) + if (c._crossSize !== target) { + const cw = isMainRow ? c._mainSize : target + const ch = isMainRow ? target : c._mainSize + layoutNode( + c, + cw, + ch, + MeasureMode.Exactly, + MeasureMode.Exactly, + ownerW, + ownerH, + performLayout, + isMainRow, + !isMainRow, + ) + c._crossSize = target + } + } + } + } + + // Justify-content + auto margins for this line + let mainOffset = pad[mainLeadEdgePhys]! + bor[mainLeadEdgePhys]! + let betweenMain = gapMain + let numAutoMarginsMain = 0 + for (const c of line) { + if (!c._hasAutoMargin) continue + if (isMarginAuto(c.style.margin, mainLeadEdgePhys)) numAutoMarginsMain++ + if (isMarginAuto(c.style.margin, mainTrailEdgePhys)) numAutoMarginsMain++ + } + const freeMain = actualInnerMain - consumedMain + const remainingMain = Math.max(0, freeMain) + const autoMarginMainSize = + numAutoMarginsMain > 0 && remainingMain > 0 + ? remainingMain / numAutoMarginsMain + : 0 + if (numAutoMarginsMain === 0) { + switch (style.justifyContent) { + case Justify.FlexStart: + break + case Justify.Center: + mainOffset += freeMain / 2 + break + case Justify.FlexEnd: + mainOffset += freeMain + break + case Justify.SpaceBetween: + if (n > 1) betweenMain += remainingMain / (n - 1) + break + case Justify.SpaceAround: + if (n > 0) { + betweenMain += remainingMain / n + mainOffset += remainingMain / n / 2 + } + break + case Justify.SpaceEvenly: + if (n > 0) { + betweenMain += remainingMain / (n + 1) + mainOffset += remainingMain / (n + 1) + } + break + } + } + + const effectiveLineCrossPos = wrapReverse + ? crossContainerSize - lineCrossPos - lineCross + : lineCrossPos + + let pos = mainOffset + for (const c of line) { + const cMargin = c.style.margin + // c.layout.margin[] was populated by resolveEdges4Into inside the + // layoutNode(c) call above (same ownerW). Read resolved values directly + // instead of re-running the edge fallback chain 4× via resolveEdge. + // Auto margins resolve to 0 in layout.margin, so autoMarginMainSize + // substitution still uses the isMarginAuto check against style. + const cLayoutMargin = c.layout.margin + let autoMainLead = false + let autoMainTrail = false + let autoCrossLead = false + let autoCrossTrail = false + let mMainLead: number + let mMainTrail: number + let mCrossLead: number + let mCrossTrail: number + if (c._hasAutoMargin) { + autoMainLead = isMarginAuto(cMargin, mainLeadEdgePhys) + autoMainTrail = isMarginAuto(cMargin, mainTrailEdgePhys) + autoCrossLead = isMarginAuto(cMargin, crossLeadEdgePhys) + autoCrossTrail = isMarginAuto(cMargin, crossTrailEdgePhys) + mMainLead = autoMainLead + ? autoMarginMainSize + : cLayoutMargin[mainLeadEdgePhys]! + mMainTrail = autoMainTrail + ? autoMarginMainSize + : cLayoutMargin[mainTrailEdgePhys]! + mCrossLead = autoCrossLead ? 0 : cLayoutMargin[crossLeadEdgePhys]! + mCrossTrail = autoCrossTrail ? 0 : cLayoutMargin[crossTrailEdgePhys]! + } else { + // Fast path: no auto margins — read resolved values directly. + mMainLead = cLayoutMargin[mainLeadEdgePhys]! + mMainTrail = cLayoutMargin[mainTrailEdgePhys]! + mCrossLead = cLayoutMargin[crossLeadEdgePhys]! + mCrossTrail = cLayoutMargin[crossTrailEdgePhys]! + } + + const mainPos = reversed + ? mainContainerSize - (pos + mMainLead) - c._mainSize + : pos + mMainLead + + const childAlign = + c.style.alignSelf === Align.Auto ? style.alignItems : c.style.alignSelf + let crossPos = effectiveLineCrossPos + mCrossLead + const crossFree = lineCross - c._crossSize - mCrossLead - mCrossTrail + if (autoCrossLead && autoCrossTrail) { + crossPos += Math.max(0, crossFree) / 2 + } else if (autoCrossLead) { + crossPos += Math.max(0, crossFree) + } else if (autoCrossTrail) { + // stays at leading + } else { + switch (childAlign) { + case Align.FlexStart: + case Align.Stretch: + if (wrapReverse) crossPos += crossFree + break + case Align.Center: + crossPos += crossFree / 2 + break + case Align.FlexEnd: + if (!wrapReverse) crossPos += crossFree + break + case Align.Baseline: + // Row direction only (isBaselineLayout checked this). Position so + // the child's baseline aligns with the line's max ascent. Per + // yoga: top = currentLead + maxAscent - childBaseline + leadingPosition. + if (isBaseline) { + crossPos = + effectiveLineCrossPos + + lineMaxAscent[li]! - + calculateBaseline(c) + } + break + default: + break + } + } + + // Relative position offsets. Fast path: no position insets set → + // skip 4× resolveEdgeRaw + 4× resolveValue + 4× isDefined. + let relX = 0 + let relY = 0 + if (c._hasPosition) { + const relLeft = resolveValue( + resolveEdgeRaw(c.style.position, EDGE_LEFT), + ownerW, + ) + const relRight = resolveValue( + resolveEdgeRaw(c.style.position, EDGE_RIGHT), + ownerW, + ) + const relTop = resolveValue( + resolveEdgeRaw(c.style.position, EDGE_TOP), + ownerW, + ) + const relBottom = resolveValue( + resolveEdgeRaw(c.style.position, EDGE_BOTTOM), + ownerW, + ) + relX = isDefined(relLeft) + ? relLeft + : isDefined(relRight) + ? -relRight + : 0 + relY = isDefined(relTop) + ? relTop + : isDefined(relBottom) + ? -relBottom + : 0 + } + + if (isMainRow) { + c.layout.left = mainPos + relX + c.layout.top = crossPos + relY + } else { + c.layout.left = crossPos + relX + c.layout.top = mainPos + relY + } + pos += c._mainSize + mMainLead + mMainTrail + betweenMain + } + lineCrossPos += lineCross + betweenLines + } + + // STEP 6: Absolute-positioned children + for (const c of absChildren) { + layoutAbsoluteChild( + node, + c, + node.layout.width, + node.layout.height, + pad, + bor, + ) + } +} + +function layoutAbsoluteChild( + parent: Node, + child: Node, + parentWidth: number, + parentHeight: number, + pad: [number, number, number, number], + bor: [number, number, number, number], +): void { + const cs = child.style + const posLeft = resolveEdgeRaw(cs.position, EDGE_LEFT) + const posRight = resolveEdgeRaw(cs.position, EDGE_RIGHT) + const posTop = resolveEdgeRaw(cs.position, EDGE_TOP) + const posBottom = resolveEdgeRaw(cs.position, EDGE_BOTTOM) + + const rLeft = resolveValue(posLeft, parentWidth) + const rRight = resolveValue(posRight, parentWidth) + const rTop = resolveValue(posTop, parentHeight) + const rBottom = resolveValue(posBottom, parentHeight) + + // Absolute children's percentage dimensions resolve against the containing + // block's padding-box (parent size minus border), per CSS §10.1. + const paddingBoxW = parentWidth - bor[0] - bor[2] + const paddingBoxH = parentHeight - bor[1] - bor[3] + let cw = resolveValue(cs.width, paddingBoxW) + let ch = resolveValue(cs.height, paddingBoxH) + + // If both left+right defined and width not, derive width + if (!isDefined(cw) && isDefined(rLeft) && isDefined(rRight)) { + cw = paddingBoxW - rLeft - rRight + } + if (!isDefined(ch) && isDefined(rTop) && isDefined(rBottom)) { + ch = paddingBoxH - rTop - rBottom + } + + layoutNode( + child, + cw, + ch, + isDefined(cw) ? MeasureMode.Exactly : MeasureMode.Undefined, + isDefined(ch) ? MeasureMode.Exactly : MeasureMode.Undefined, + paddingBoxW, + paddingBoxH, + true, + ) + + // Margin of absolute child (applied in addition to insets) + const mL = resolveEdge(cs.margin, EDGE_LEFT, parentWidth) + const mT = resolveEdge(cs.margin, EDGE_TOP, parentWidth) + const mR = resolveEdge(cs.margin, EDGE_RIGHT, parentWidth) + const mB = resolveEdge(cs.margin, EDGE_BOTTOM, parentWidth) + + const mainAxis = parent.style.flexDirection + const reversed = isReverse(mainAxis) + const mainRow = isRow(mainAxis) + const wrapReverse = parent.style.flexWrap === Wrap.WrapReverse + // alignSelf overrides alignItems for absolute children (same as flow items) + const alignment = + cs.alignSelf === Align.Auto ? parent.style.alignItems : cs.alignSelf + + // Position + let left: number + if (isDefined(rLeft)) { + left = bor[0] + rLeft + mL + } else if (isDefined(rRight)) { + left = parentWidth - bor[2] - rRight - child.layout.width - mR + } else if (mainRow) { + // Main axis — justify-content, flipped for reversed + const lead = pad[0] + bor[0] + const trail = parentWidth - pad[2] - bor[2] + left = reversed + ? trail - child.layout.width - mR + : justifyAbsolute( + parent.style.justifyContent, + lead, + trail, + child.layout.width, + ) + mL + } else { + left = + alignAbsolute( + alignment, + pad[0] + bor[0], + parentWidth - pad[2] - bor[2], + child.layout.width, + wrapReverse, + ) + mL + } + + let top: number + if (isDefined(rTop)) { + top = bor[1] + rTop + mT + } else if (isDefined(rBottom)) { + top = parentHeight - bor[3] - rBottom - child.layout.height - mB + } else if (mainRow) { + top = + alignAbsolute( + alignment, + pad[1] + bor[1], + parentHeight - pad[3] - bor[3], + child.layout.height, + wrapReverse, + ) + mT + } else { + const lead = pad[1] + bor[1] + const trail = parentHeight - pad[3] - bor[3] + top = reversed + ? trail - child.layout.height - mB + : justifyAbsolute( + parent.style.justifyContent, + lead, + trail, + child.layout.height, + ) + mT + } + + child.layout.left = left + child.layout.top = top +} + +function justifyAbsolute( + justify: Justify, + leadEdge: number, + trailEdge: number, + childSize: number, +): number { + switch (justify) { + case Justify.Center: + return leadEdge + (trailEdge - leadEdge - childSize) / 2 + case Justify.FlexEnd: + return trailEdge - childSize + default: + return leadEdge + } +} + +function alignAbsolute( + align: Align, + leadEdge: number, + trailEdge: number, + childSize: number, + wrapReverse: boolean, +): number { + // Wrap-reverse flips the cross axis: flex-start/stretch go to trailing, + // flex-end goes to leading (yoga's absoluteLayoutChild flips the align value + // when the containing block has wrap-reverse). + switch (align) { + case Align.Center: + return leadEdge + (trailEdge - leadEdge - childSize) / 2 + case Align.FlexEnd: + return wrapReverse ? leadEdge : trailEdge - childSize + default: + return wrapReverse ? trailEdge - childSize : leadEdge + } +} + +function computeFlexBasis( + child: Node, + mainAxis: FlexDirection, + availableMain: number, + availableCross: number, + crossMode: MeasureMode, + ownerWidth: number, + ownerHeight: number, +): number { + // Same-generation cache hit: basis was computed THIS calculateLayout, so + // it's fresh regardless of isDirty_. Covers both clean children (scrolling + // past unchanged messages) AND fresh-mounted dirty children (virtual + // scroll mounts new items — the dirty chain's measure→layout cascade + // invokes this ≥2^depth times, but the child's subtree doesn't change + // between calls within one calculateLayout). For clean children with + // cache from a PREVIOUS generation, also hit if inputs match — isDirty_ + // gates since a dirty child's previous-gen cache is stale. + const sameGen = child._fbGen === _generation + if ( + (sameGen || !child.isDirty_) && + child._fbCrossMode === crossMode && + sameFloat(child._fbOwnerW, ownerWidth) && + sameFloat(child._fbOwnerH, ownerHeight) && + sameFloat(child._fbAvailMain, availableMain) && + sameFloat(child._fbAvailCross, availableCross) + ) { + return child._fbBasis + } + const cs = child.style + const isMainRow = isRow(mainAxis) + + // Explicit flex-basis + const basis = resolveValue(cs.flexBasis, availableMain) + if (isDefined(basis)) { + const b = Math.max(0, basis) + child._fbBasis = b + child._fbOwnerW = ownerWidth + child._fbOwnerH = ownerHeight + child._fbAvailMain = availableMain + child._fbAvailCross = availableCross + child._fbCrossMode = crossMode + child._fbGen = _generation + return b + } + + // Style dimension on main axis + const mainStyleDim = isMainRow ? cs.width : cs.height + const mainOwner = isMainRow ? ownerWidth : ownerHeight + const resolved = resolveValue(mainStyleDim, mainOwner) + if (isDefined(resolved)) { + const b = Math.max(0, resolved) + child._fbBasis = b + child._fbOwnerW = ownerWidth + child._fbOwnerH = ownerHeight + child._fbAvailMain = availableMain + child._fbAvailCross = availableCross + child._fbCrossMode = crossMode + child._fbGen = _generation + return b + } + + // Need to measure the child to get its natural size + const crossStyleDim = isMainRow ? cs.height : cs.width + const crossOwner = isMainRow ? ownerHeight : ownerWidth + let crossConstraint = resolveValue(crossStyleDim, crossOwner) + let crossConstraintMode: MeasureMode = isDefined(crossConstraint) + ? MeasureMode.Exactly + : MeasureMode.Undefined + if (!isDefined(crossConstraint) && isDefined(availableCross)) { + crossConstraint = availableCross + crossConstraintMode = + crossMode === MeasureMode.Exactly && isStretchAlign(child) + ? MeasureMode.Exactly + : MeasureMode.AtMost + } + + // Upstream yoga (YGNodeComputeFlexBasisForChild) passes the available inner + // width with mode AtMost when the subtree will call a measure-func — so text + // nodes don't report unconstrained intrinsic width as flex-basis, which + // would force siblings to shrink and the text to wrap at the wrong width. + // Passing Undefined here made Ink's inside get + // width = intrinsic instead of available, dropping chars at wrap boundaries. + // + // Two constraints on when this applies: + // - Width only. Height is never constrained during basis measurement — + // column containers must measure children at natural height so + // scrollable content can overflow (constraining height clips ScrollBox). + // - Subtree has a measure-func. Pure layout subtrees (no measure-func) + // with flex-grow children would grow into the AtMost constraint, + // inflating the basis (breaks YGMinMaxDimensionTest flex_grow_in_at_most + // where a flexGrow:1 child should stay at basis 0, not grow to 100). + let mainConstraint = NaN + let mainConstraintMode: MeasureMode = MeasureMode.Undefined + if (isMainRow && isDefined(availableMain) && hasMeasureFuncInSubtree(child)) { + mainConstraint = availableMain + mainConstraintMode = MeasureMode.AtMost + } + + const mw = isMainRow ? mainConstraint : crossConstraint + const mh = isMainRow ? crossConstraint : mainConstraint + const mwMode = isMainRow ? mainConstraintMode : crossConstraintMode + const mhMode = isMainRow ? crossConstraintMode : mainConstraintMode + + layoutNode(child, mw, mh, mwMode, mhMode, ownerWidth, ownerHeight, false) + const b = isMainRow ? child.layout.width : child.layout.height + child._fbBasis = b + child._fbOwnerW = ownerWidth + child._fbOwnerH = ownerHeight + child._fbAvailMain = availableMain + child._fbAvailCross = availableCross + child._fbCrossMode = crossMode + child._fbGen = _generation + return b +} + +function hasMeasureFuncInSubtree(node: Node): boolean { + if (node.measureFunc) return true + for (const c of node.children) { + if (hasMeasureFuncInSubtree(c)) return true + } + return false +} + +function resolveFlexibleLengths( + children: Node[], + availableInnerMain: number, + totalFlexBasis: number, + isMainRow: boolean, + ownerW: number, + ownerH: number, +): void { + // Multi-pass flex distribution per CSS flexbox spec §9.7 "Resolving Flexible + // Lengths": distribute free space, detect min/max violations, freeze all + // violators, redistribute among unfrozen children. Repeat until stable. + const n = children.length + const frozen: boolean[] = new Array(n).fill(false) + const initialFree = isDefined(availableInnerMain) + ? availableInnerMain - totalFlexBasis + : 0 + // Freeze inflexible items at their clamped basis + for (let i = 0; i < n; i++) { + const c = children[i]! + const clamped = boundAxis(c.style, isMainRow, c._flexBasis, ownerW, ownerH) + const inflexible = + !isDefined(availableInnerMain) || + (initialFree >= 0 ? c.style.flexGrow === 0 : c.style.flexShrink === 0) + if (inflexible) { + c._mainSize = Math.max(0, clamped) + frozen[i] = true + } else { + c._mainSize = c._flexBasis + } + } + // Iteratively distribute until no violations. Free space is recomputed each + // pass: initial free space minus the delta frozen children consumed beyond + // (or below) their basis. + const unclamped: number[] = new Array(n) + for (let iter = 0; iter <= n; iter++) { + let frozenDelta = 0 + let totalGrow = 0 + let totalShrinkScaled = 0 + let unfrozenCount = 0 + for (let i = 0; i < n; i++) { + const c = children[i]! + if (frozen[i]) { + frozenDelta += c._mainSize - c._flexBasis + } else { + totalGrow += c.style.flexGrow + totalShrinkScaled += c.style.flexShrink * c._flexBasis + unfrozenCount++ + } + } + if (unfrozenCount === 0) break + let remaining = initialFree - frozenDelta + // Spec §9.7 step 4c: if sum of flex factors < 1, only distribute + // initialFree × sum, not the full remaining space (partial flex). + if (remaining > 0 && totalGrow > 0 && totalGrow < 1) { + const scaled = initialFree * totalGrow + if (scaled < remaining) remaining = scaled + } else if (remaining < 0 && totalShrinkScaled > 0) { + let totalShrink = 0 + for (let i = 0; i < n; i++) { + if (!frozen[i]) totalShrink += children[i]!.style.flexShrink + } + if (totalShrink < 1) { + const scaled = initialFree * totalShrink + if (scaled > remaining) remaining = scaled + } + } + // Compute targets + violations for all unfrozen children + let totalViolation = 0 + for (let i = 0; i < n; i++) { + if (frozen[i]) continue + const c = children[i]! + let t = c._flexBasis + if (remaining > 0 && totalGrow > 0) { + t += (remaining * c.style.flexGrow) / totalGrow + } else if (remaining < 0 && totalShrinkScaled > 0) { + t += + (remaining * (c.style.flexShrink * c._flexBasis)) / totalShrinkScaled + } + unclamped[i] = t + const clamped = Math.max( + 0, + boundAxis(c.style, isMainRow, t, ownerW, ownerH), + ) + c._mainSize = clamped + totalViolation += clamped - t + } + // Freeze per spec §9.7 step 5: if totalViolation is zero freeze all; if + // positive freeze min-violators; if negative freeze max-violators. + if (totalViolation === 0) break + let anyFrozen = false + for (let i = 0; i < n; i++) { + if (frozen[i]) continue + const v = children[i]!._mainSize - unclamped[i]! + if ((totalViolation > 0 && v > 0) || (totalViolation < 0 && v < 0)) { + frozen[i] = true + anyFrozen = true + } + } + if (!anyFrozen) break + } +} + +function isStretchAlign(child: Node): boolean { + const p = child.parent + if (!p) return false + const align = + child.style.alignSelf === Align.Auto + ? p.style.alignItems + : child.style.alignSelf + return align === Align.Stretch +} + +function resolveChildAlign(parent: Node, child: Node): Align { + return child.style.alignSelf === Align.Auto + ? parent.style.alignItems + : child.style.alignSelf +} + +// Baseline of a node per CSS Flexbox §8.5 / yoga's YGBaseline. Leaf nodes +// (no children) use their own height. Containers recurse into the first +// baseline-aligned child on the first line (or the first flow child if none +// are baseline-aligned), returning that child's baseline + its top offset. +function calculateBaseline(node: Node): number { + let baselineChild: Node | null = null + for (const c of node.children) { + if (c._lineIndex > 0) break + if (c.style.positionType === PositionType.Absolute) continue + if (c.style.display === Display.None) continue + if ( + resolveChildAlign(node, c) === Align.Baseline || + c.isReferenceBaseline_ + ) { + baselineChild = c + break + } + if (baselineChild === null) baselineChild = c + } + if (baselineChild === null) return node.layout.height + return calculateBaseline(baselineChild) + baselineChild.layout.top +} + +// A container uses baseline layout only for row direction, when either +// align-items is baseline or any flow child has align-self: baseline. +function isBaselineLayout(node: Node, flowChildren: Node[]): boolean { + if (!isRow(node.style.flexDirection)) return false + if (node.style.alignItems === Align.Baseline) return true + for (const c of flowChildren) { + if (c.style.alignSelf === Align.Baseline) return true + } + return false +} + +function childMarginForAxis( + child: Node, + axis: FlexDirection, + ownerWidth: number, +): number { + if (!child._hasMargin) return 0 + const lead = resolveEdge(child.style.margin, leadingEdge(axis), ownerWidth) + const trail = resolveEdge(child.style.margin, trailingEdge(axis), ownerWidth) + return lead + trail +} + +function resolveGap(style: Style, gutter: Gutter, ownerSize: number): number { + let v = style.gap[gutter]! + if (v.unit === Unit.Undefined) v = style.gap[Gutter.All]! + const r = resolveValue(v, ownerSize) + return isDefined(r) ? Math.max(0, r) : 0 +} + +function boundAxis( + style: Style, + isWidth: boolean, + value: number, + ownerWidth: number, + ownerHeight: number, +): number { + const minV = isWidth ? style.minWidth : style.minHeight + const maxV = isWidth ? style.maxWidth : style.maxHeight + const minU = minV.unit + const maxU = maxV.unit + // Fast path: no min/max constraints set. Per CPU profile this is the + // overwhelmingly common case (~32k calls/layout on the 1000-node bench, + // nearly all with undefined min/max) — skipping 2× resolveValue + 2× isNaN + // that always no-op. Unit.Undefined = 0. + if (minU === 0 && maxU === 0) return value + const owner = isWidth ? ownerWidth : ownerHeight + let v = value + // Inlined resolveValue: Unit.Point=1, Unit.Percent=2. `m === m` is !isNaN. + if (maxU === 1) { + if (v > maxV.value) v = maxV.value + } else if (maxU === 2) { + const m = (maxV.value * owner) / 100 + if (m === m && v > m) v = m + } + if (minU === 1) { + if (v < minV.value) v = minV.value + } else if (minU === 2) { + const m = (minV.value * owner) / 100 + if (m === m && v < m) v = m + } + return v +} + +function zeroLayoutRecursive(node: Node): void { + for (const c of node.children) { + c.layout.left = 0 + c.layout.top = 0 + c.layout.width = 0 + c.layout.height = 0 + // Invalidate layout cache — without this, unhide → calculateLayout finds + // the child clean (!isDirty_) with _hasL intact, hits the cache at line + // ~1086, restores stale _lOutW/_lOutH, and returns early — skipping the + // child-positioning recursion. Grandchildren stay at (0,0,0,0) from the + // zeroing above and render invisible. isDirty_=true also gates _cN and + // _fbBasis via their (sameGen || !isDirty_) checks — _cGen/_fbGen freeze + // during hide so sameGen is false on unhide. + c.isDirty_ = true + c._hasL = false + c._hasM = false + zeroLayoutRecursive(c) + } +} + +function collectLayoutChildren(node: Node, flow: Node[], abs: Node[]): void { + // Partition a node's children into flow and absolute lists, flattening + // display:contents subtrees so their children are laid out as direct + // children of this node (per CSS display:contents spec — the box is removed + // from the layout tree but its children remain, lifted to the grandparent). + for (const c of node.children) { + const disp = c.style.display + if (disp === Display.None) { + c.layout.left = 0 + c.layout.top = 0 + c.layout.width = 0 + c.layout.height = 0 + zeroLayoutRecursive(c) + } else if (disp === Display.Contents) { + c.layout.left = 0 + c.layout.top = 0 + c.layout.width = 0 + c.layout.height = 0 + // Recurse — nested display:contents lifts all the way up. The contents + // node's own margin/padding/position/dimensions are ignored. + collectLayoutChildren(c, flow, abs) + } else if (c.style.positionType === PositionType.Absolute) { + abs.push(c) + } else { + flow.push(c) + } + } +} + +function roundLayout( + node: Node, + scale: number, + absLeft: number, + absTop: number, +): void { + if (scale === 0) return + const l = node.layout + const nodeLeft = l.left + const nodeTop = l.top + const nodeWidth = l.width + const nodeHeight = l.height + + const absNodeLeft = absLeft + nodeLeft + const absNodeTop = absTop + nodeTop + + // Upstream YGRoundValueToPixelGrid: text nodes (has measureFunc) floor their + // positions so wrapped text never starts past its allocated column. Width + // uses ceil-if-fractional to avoid clipping the last glyph. Non-text nodes + // use standard round. Matches yoga's PixelGrid.cpp — without this, justify + // center/space-evenly positions are off-by-one vs WASM and flex-shrink + // overflow places siblings at the wrong column. + const isText = node.measureFunc !== null + l.left = roundValue(nodeLeft, scale, false, isText) + l.top = roundValue(nodeTop, scale, false, isText) + + // Width/height rounded via absolute edges to avoid cumulative drift + const absRight = absNodeLeft + nodeWidth + const absBottom = absNodeTop + nodeHeight + const hasFracW = !isWholeNumber(nodeWidth * scale) + const hasFracH = !isWholeNumber(nodeHeight * scale) + l.width = + roundValue(absRight, scale, isText && hasFracW, isText && !hasFracW) - + roundValue(absNodeLeft, scale, false, isText) + l.height = + roundValue(absBottom, scale, isText && hasFracH, isText && !hasFracH) - + roundValue(absNodeTop, scale, false, isText) + + for (const c of node.children) { + roundLayout(c, scale, absNodeLeft, absNodeTop) + } +} + +function isWholeNumber(v: number): boolean { + const frac = v - Math.floor(v) + return frac < 0.0001 || frac > 0.9999 +} + +function roundValue( + v: number, + scale: number, + forceCeil: boolean, + forceFloor: boolean, +): number { + let scaled = v * scale + let frac = scaled - Math.floor(scaled) + if (frac < 0) frac += 1 + // Float-epsilon tolerance matches upstream YGDoubleEqual (1e-4) + if (frac < 0.0001) { + scaled = Math.floor(scaled) + } else if (frac > 0.9999) { + scaled = Math.ceil(scaled) + } else if (forceCeil) { + scaled = Math.ceil(scaled) + } else if (forceFloor) { + scaled = Math.floor(scaled) + } else { + // Round half-up (>= 0.5 goes up), per upstream + scaled = Math.floor(scaled) + (frac >= 0.4999 ? 1 : 0) + } + return scaled / scale +} + +// -- +// Helpers + +function parseDimension(v: number | string | undefined): Value { + if (v === undefined) return UNDEFINED_VALUE + if (v === 'auto') return AUTO_VALUE + if (typeof v === 'number') { + // WASM yoga's YGFloatIsUndefined treats NaN and ±Infinity as undefined. + // Ink passes height={Infinity} (e.g. LogSelector maxHeight default) and + // expects it to mean "unconstrained" — storing it as a literal point value + // makes the node height Infinity and breaks all downstream layout. + return Number.isFinite(v) ? pointValue(v) : UNDEFINED_VALUE + } + if (typeof v === 'string' && v.endsWith('%')) { + return percentValue(parseFloat(v)) + } + const n = parseFloat(v) + return isNaN(n) ? UNDEFINED_VALUE : pointValue(n) +} + +function physicalEdge(edge: Edge): number { + switch (edge) { + case Edge.Left: + case Edge.Start: + return EDGE_LEFT + case Edge.Top: + return EDGE_TOP + case Edge.Right: + case Edge.End: + return EDGE_RIGHT + case Edge.Bottom: + return EDGE_BOTTOM + default: + return EDGE_LEFT + } +} + +// -- +// Module API matching yoga-layout/load + +export type Yoga = { + Config: { + create(): Config + destroy(config: Config): void + } + Node: { + create(config?: Config): Node + createDefault(): Node + createWithConfig(config: Config): Node + destroy(node: Node): void + } +} + +const YOGA_INSTANCE: Yoga = { + Config: { + create: createConfig, + destroy() {}, + }, + Node: { + create: (config?: Config) => new Node(config), + createDefault: () => new Node(), + createWithConfig: (config: Config) => new Node(config), + destroy() {}, + }, +} + +export function loadYoga(): Promise { + return Promise.resolve(YOGA_INSTANCE) +} + +export default YOGA_INSTANCE diff --git a/src/ink/hooks/use-animation-frame.ts b/packages/@ant/ink/src/hooks/use-animation-frame.ts similarity index 97% rename from src/ink/hooks/use-animation-frame.ts rename to packages/@ant/ink/src/hooks/use-animation-frame.ts index d4dd38a16..a6ef6b4e2 100644 --- a/src/ink/hooks/use-animation-frame.ts +++ b/packages/@ant/ink/src/hooks/use-animation-frame.ts @@ -1,6 +1,6 @@ import { useContext, useEffect, useState } from 'react' import { ClockContext } from '../components/ClockContext.js' -import type { DOMElement } from '../dom.js' +import type { DOMElement } from '../core/dom.js' import { useTerminalViewport } from './use-terminal-viewport.js' /** diff --git a/src/ink/hooks/use-app.ts b/packages/@ant/ink/src/hooks/use-app.ts similarity index 100% rename from src/ink/hooks/use-app.ts rename to packages/@ant/ink/src/hooks/use-app.ts diff --git a/src/ink/hooks/use-declared-cursor.ts b/packages/@ant/ink/src/hooks/use-declared-cursor.ts similarity index 98% rename from src/ink/hooks/use-declared-cursor.ts rename to packages/@ant/ink/src/hooks/use-declared-cursor.ts index e49668b38..d1aea014c 100644 --- a/src/ink/hooks/use-declared-cursor.ts +++ b/packages/@ant/ink/src/hooks/use-declared-cursor.ts @@ -1,6 +1,6 @@ import { useCallback, useContext, useLayoutEffect, useRef } from 'react' import CursorDeclarationContext from '../components/CursorDeclarationContext.js' -import type { DOMElement } from '../dom.js' +import type { DOMElement } from '../core/dom.js' /** * Declares where the terminal cursor should be parked after each frame. diff --git a/src/ink/hooks/use-input.ts b/packages/@ant/ink/src/hooks/use-input.ts similarity index 97% rename from src/ink/hooks/use-input.ts rename to packages/@ant/ink/src/hooks/use-input.ts index 7cf75b311..0d5cd55b7 100644 --- a/src/ink/hooks/use-input.ts +++ b/packages/@ant/ink/src/hooks/use-input.ts @@ -1,6 +1,6 @@ import { useEffect, useLayoutEffect } from 'react' import { useEventCallback } from 'usehooks-ts' -import type { InputEvent, Key } from '../events/input-event.js' +import type { InputEvent, Key } from '../core/events/input-event.js' import useStdin from './use-stdin.js' type Handler = (input: string, key: Key, event: InputEvent) => void diff --git a/src/ink/hooks/use-interval.ts b/packages/@ant/ink/src/hooks/use-interval.ts similarity index 100% rename from src/ink/hooks/use-interval.ts rename to packages/@ant/ink/src/hooks/use-interval.ts diff --git a/src/ink/hooks/use-search-highlight.ts b/packages/@ant/ink/src/hooks/use-search-highlight.ts similarity index 92% rename from src/ink/hooks/use-search-highlight.ts rename to packages/@ant/ink/src/hooks/use-search-highlight.ts index ce9fc364d..6c79d5ebe 100644 --- a/src/ink/hooks/use-search-highlight.ts +++ b/packages/@ant/ink/src/hooks/use-search-highlight.ts @@ -1,8 +1,8 @@ import { useContext, useMemo } from 'react' import StdinContext from '../components/StdinContext.js' -import type { DOMElement } from '../dom.js' -import instances from '../instances.js' -import type { MatchPosition } from '../render-to-screen.js' +import type { DOMElement } from '../core/dom.js' +import instances from '../core/instances.js' +import type { MatchPosition } from '../core/render-to-screen.js' /** * Set the search highlight query on the Ink instance. Non-empty → all diff --git a/src/ink/hooks/use-selection.ts b/packages/@ant/ink/src/hooks/use-selection.ts similarity index 98% rename from src/ink/hooks/use-selection.ts rename to packages/@ant/ink/src/hooks/use-selection.ts index f7e1d4571..1a29e384d 100644 --- a/src/ink/hooks/use-selection.ts +++ b/packages/@ant/ink/src/hooks/use-selection.ts @@ -1,11 +1,11 @@ import { useContext, useMemo, useSyncExternalStore } from 'react' import StdinContext from '../components/StdinContext.js' -import instances from '../instances.js' +import instances from '../core/instances.js' import { type FocusMove, type SelectionState, shiftAnchor, -} from '../selection.js' +} from '../core/selection.js' /** * Access to text selection operations on the Ink instance (fullscreen only). diff --git a/src/ink/hooks/use-stdin.ts b/packages/@ant/ink/src/hooks/use-stdin.ts similarity index 100% rename from src/ink/hooks/use-stdin.ts rename to packages/@ant/ink/src/hooks/use-stdin.ts diff --git a/src/ink/hooks/use-tab-status.ts b/packages/@ant/ink/src/hooks/use-tab-status.ts similarity index 93% rename from src/ink/hooks/use-tab-status.ts rename to packages/@ant/ink/src/hooks/use-tab-status.ts index be6014204..cfd32e253 100644 --- a/src/ink/hooks/use-tab-status.ts +++ b/packages/@ant/ink/src/hooks/use-tab-status.ts @@ -4,9 +4,9 @@ import { supportsTabStatus, tabStatus, wrapForMultiplexer, -} from '../termio/osc.js' -import type { Color } from '../termio/types.js' -import { TerminalWriteContext } from '../useTerminalNotification.js' +} from '../core/termio/osc.js' +import type { Color } from '../core/termio/types.js' +import { TerminalWriteContext } from './useTerminalNotification.js' export type TabStatusKind = 'idle' | 'busy' | 'waiting' diff --git a/src/ink/hooks/use-terminal-focus.ts b/packages/@ant/ink/src/hooks/use-terminal-focus.ts similarity index 100% rename from src/ink/hooks/use-terminal-focus.ts rename to packages/@ant/ink/src/hooks/use-terminal-focus.ts diff --git a/src/ink/hooks/use-terminal-title.ts b/packages/@ant/ink/src/hooks/use-terminal-title.ts similarity index 88% rename from src/ink/hooks/use-terminal-title.ts rename to packages/@ant/ink/src/hooks/use-terminal-title.ts index d820cd7ae..4645179a7 100644 --- a/src/ink/hooks/use-terminal-title.ts +++ b/packages/@ant/ink/src/hooks/use-terminal-title.ts @@ -1,7 +1,7 @@ import { useContext, useEffect } from 'react' import stripAnsi from 'strip-ansi' -import { OSC, osc } from '../termio/osc.js' -import { TerminalWriteContext } from '../useTerminalNotification.js' +import { OSC, osc } from '../core/termio/osc.js' +import { TerminalWriteContext } from './useTerminalNotification.js' /** * Declaratively set the terminal tab/window title. diff --git a/src/ink/hooks/use-terminal-viewport.ts b/packages/@ant/ink/src/hooks/use-terminal-viewport.ts similarity index 98% rename from src/ink/hooks/use-terminal-viewport.ts rename to packages/@ant/ink/src/hooks/use-terminal-viewport.ts index 91193bf73..82d96f341 100644 --- a/src/ink/hooks/use-terminal-viewport.ts +++ b/packages/@ant/ink/src/hooks/use-terminal-viewport.ts @@ -1,6 +1,6 @@ import { useCallback, useContext, useLayoutEffect, useRef } from 'react' import { TerminalSizeContext } from '../components/TerminalSizeContext.js' -import type { DOMElement } from '../dom.js' +import type { DOMElement } from '../core/dom.js' type ViewportEntry = { /** diff --git a/packages/@ant/ink/src/hooks/useExitOnCtrlCD.ts b/packages/@ant/ink/src/hooks/useExitOnCtrlCD.ts new file mode 100644 index 000000000..442d4fad3 --- /dev/null +++ b/packages/@ant/ink/src/hooks/useExitOnCtrlCD.ts @@ -0,0 +1,95 @@ +/** + * Minimal stub of useExitOnCtrlCD + useExitOnCtrlCDWithKeybindings. + * + * The original hooks depend on the keybinding system and useApp() exit. + * This stub provides the same interface with simplified Ctrl+C/D handling + * via useInput, suitable for the standalone @anthropic/ink package. + */ + +import { useCallback, useState } from 'react' +import useInput from './use-input.js' + +export type ExitState = { + pending: boolean + keyName: 'Ctrl-C' | 'Ctrl-D' | null +} + +/** + * Minimal double-press exit handler. + * First Ctrl+C/D shows pending state, second press within timeout fires onExit. + */ +const DOUBLE_PRESS_TIMEOUT_MS = 800 + +function useDoublePress( + setPending: (pending: boolean) => void, + onDoublePress: () => void, +): () => void { + let lastPress = 0 + let timeout: ReturnType | undefined + + return () => { + const now = Date.now() + const timeSince = now - lastPress + const isDouble = + timeSince <= DOUBLE_PRESS_TIMEOUT_MS && timeout !== undefined + + if (isDouble) { + clearTimeout(timeout) + timeout = undefined + setPending(false) + onDoublePress() + } else { + setPending(true) + clearTimeout(timeout) + timeout = setTimeout(() => { + setPending(false) + timeout = undefined + }, DOUBLE_PRESS_TIMEOUT_MS) + } + lastPress = now + } +} + +/** + * Stub that provides ExitState for Ctrl+C/D double-press UI. + * In the standalone package, this uses useInput directly rather than the + * keybinding system. + */ +export function useExitOnCtrlCDWithKeybindings( + _onExit?: () => void, + _onInterrupt?: () => boolean, + isActive: boolean = true, +): ExitState { + const [exitState, setExitState] = useState({ + pending: false, + keyName: null, + }) + + const handleCtrlC = useDoublePress( + (pending: boolean) => + setExitState({ pending, keyName: pending ? 'Ctrl-C' : null }), + () => process.exit(0), + ) + + const handleCtrlD = useDoublePress( + (pending: boolean) => + setExitState({ pending, keyName: pending ? 'Ctrl-D' : null }), + () => process.exit(0), + ) + + const handleInput = useCallback( + (_input: string, key: { ctrl?: boolean; name?: string }) => { + if (!isActive) return + if (key.ctrl && key.name === 'c') { + handleCtrlC() + } else if (key.ctrl && key.name === 'd') { + handleCtrlD() + } + }, + [isActive, handleCtrlC, handleCtrlD], + ) + + useInput(handleInput, { isActive }) + + return exitState +} diff --git a/packages/@ant/ink/src/hooks/useSearchInput.ts b/packages/@ant/ink/src/hooks/useSearchInput.ts new file mode 100644 index 000000000..9935256c7 --- /dev/null +++ b/packages/@ant/ink/src/hooks/useSearchInput.ts @@ -0,0 +1,222 @@ +/** + * Minimal stub of useSearchInput for the standalone @anthropic/ink package. + * + * Provides the same interface as the full implementation but without + * kill-ring / yank support. Suitable for FuzzyPicker and other theme + * components that need text input handling. + */ + +import { useCallback, useState } from 'react' +import type { KeyboardEvent } from '../core/events/keyboard-event.js' +import useInput from './use-input.js' +import { useTerminalSize } from '../hooks/useTerminalSize.js' + +type UseSearchInputOptions = { + isActive: boolean + onExit: () => void + onCancel?: () => void + onExitUp?: () => void + columns?: number + passthroughCtrlKeys?: string[] + initialQuery?: string + backspaceExitsOnEmpty?: boolean +} + +type UseSearchInputReturn = { + query: string + setQuery: (q: string) => void + cursorOffset: number + handleKeyDown: (e: KeyboardEvent) => void +} + +const UNHANDLED_SPECIAL_KEYS = new Set([ + 'pageup', + 'pagedown', + 'insert', + 'wheelup', + 'wheeldown', + 'mouse', + 'f1', + 'f2', + 'f3', + 'f4', + 'f5', + 'f6', + 'f7', + 'f8', + 'f9', + 'f10', + 'f11', + 'f12', +]) + +export function useSearchInput({ + isActive, + onExit, + onCancel, + onExitUp, + columns, + initialQuery = '', + backspaceExitsOnEmpty = true, +}: UseSearchInputOptions): UseSearchInputReturn { + const { columns: terminalColumns } = useTerminalSize() + const _effectiveColumns = columns ?? terminalColumns + const [query, setQueryState] = useState(initialQuery) + const [cursorOffset, setCursorOffset] = useState(initialQuery.length) + + const setQuery = useCallback((q: string) => { + setQueryState(q) + setCursorOffset(q.length) + }, []) + + const handleKeyDown = (e: KeyboardEvent): void => { + if (!isActive) return + + if (e.key === 'return' || e.key === 'down') { + e.preventDefault() + onExit() + return + } + if (e.key === 'up') { + e.preventDefault() + onExitUp?.() + return + } + if (e.key === 'escape') { + e.preventDefault() + if (onCancel) { + onCancel() + } else if (query.length > 0) { + setQueryState('') + setCursorOffset(0) + } else { + onExit() + } + return + } + if (e.key === 'backspace') { + e.preventDefault() + if (query.length === 0) { + if (backspaceExitsOnEmpty) (onCancel ?? onExit)() + return + } + const newOffset = Math.max(0, cursorOffset - 1) + setQueryState(query.slice(0, newOffset) + query.slice(cursorOffset)) + setCursorOffset(newOffset) + return + } + if (e.key === 'delete') { + e.preventDefault() + if (cursorOffset < query.length) { + setQueryState(query.slice(0, cursorOffset) + query.slice(cursorOffset + 1)) + } + return + } + if (e.key === 'left') { + e.preventDefault() + setCursorOffset(Math.max(0, cursorOffset - 1)) + return + } + if (e.key === 'right') { + e.preventDefault() + setCursorOffset(Math.min(query.length, cursorOffset + 1)) + return + } + if (e.key === 'home') { + e.preventDefault() + setCursorOffset(0) + return + } + if (e.key === 'end') { + e.preventDefault() + setCursorOffset(query.length) + return + } + if (e.ctrl) { + switch (e.key.toLowerCase()) { + case 'a': + e.preventDefault() + setCursorOffset(0) + return + case 'e': + e.preventDefault() + setCursorOffset(query.length) + return + case 'b': + e.preventDefault() + setCursorOffset(Math.max(0, cursorOffset - 1)) + return + case 'f': + e.preventDefault() + setCursorOffset(Math.min(query.length, cursorOffset + 1)) + return + case 'd': { + e.preventDefault() + if (query.length === 0) { + ;(onCancel ?? onExit)() + return + } + if (cursorOffset < query.length) { + setQueryState(query.slice(0, cursorOffset) + query.slice(cursorOffset + 1)) + } + return + } + case 'h': { + e.preventDefault() + if (query.length === 0) { + if (backspaceExitsOnEmpty) (onCancel ?? onExit)() + return + } + const newOffset = Math.max(0, cursorOffset - 1) + setQueryState(query.slice(0, newOffset) + query.slice(cursorOffset)) + setCursorOffset(newOffset) + return + } + case 'c': + e.preventDefault() + onCancel?.() + return + case 'u': + e.preventDefault() + setQueryState(query.slice(cursorOffset)) + setCursorOffset(0) + return + case 'k': + e.preventDefault() + setQueryState(query.slice(0, cursorOffset)) + return + case 'w': { + e.preventDefault() + // Delete word before cursor + const before = query.slice(0, cursorOffset) + const after = query.slice(cursorOffset) + const trimmed = before.replace(/\S+\s*$/, '') + setQueryState(trimmed + after) + setCursorOffset(trimmed.length) + return + } + } + return + } + if (e.key === 'tab') { + return + } + + // Regular character input + if (e.key.length >= 1 && !UNHANDLED_SPECIAL_KEYS.has(e.key)) { + e.preventDefault() + setQueryState(query.slice(0, cursorOffset) + e.key + query.slice(cursorOffset)) + setCursorOffset(cursorOffset + 1) + } + } + + // Bridge: subscribe via useInput and adapt to KeyboardEvent + useInput( + (_input: string, _key: unknown, event: { keypress: string }) => { + handleKeyDown(new KeyboardEvent(event.keypress)) + }, + { isActive }, + ) + + return { query, setQuery, cursorOffset, handleKeyDown } +} diff --git a/src/ink/useTerminalNotification.ts b/packages/@ant/ink/src/hooks/useTerminalNotification.ts similarity index 96% rename from src/ink/useTerminalNotification.ts rename to packages/@ant/ink/src/hooks/useTerminalNotification.ts index 90e53eb63..083a94ad0 100644 --- a/src/ink/useTerminalNotification.ts +++ b/packages/@ant/ink/src/hooks/useTerminalNotification.ts @@ -1,7 +1,7 @@ import { createContext, useCallback, useContext, useMemo } from 'react' -import { isProgressReportingAvailable, type Progress } from './terminal.js' -import { BEL } from './termio/ansi.js' -import { ITERM2, OSC, osc, PROGRESS, wrapForMultiplexer } from './termio/osc.js' +import { isProgressReportingAvailable, type Progress } from '../core/terminal.js' +import { BEL } from '../core/termio/ansi.js' +import { ITERM2, OSC, osc, PROGRESS, wrapForMultiplexer } from '../core/termio/osc.js' type WriteRaw = (data: string) => void diff --git a/packages/@ant/ink/src/hooks/useTerminalSize.ts b/packages/@ant/ink/src/hooks/useTerminalSize.ts new file mode 100644 index 000000000..bccc5bef6 --- /dev/null +++ b/packages/@ant/ink/src/hooks/useTerminalSize.ts @@ -0,0 +1,15 @@ +import { useContext } from 'react' +import { + type TerminalSize, + TerminalSizeContext, +} from '../components/TerminalSizeContext.js' + +export function useTerminalSize(): TerminalSize { + const size = useContext(TerminalSizeContext) + + if (!size) { + throw new Error('useTerminalSize must be used within an Ink App component') + } + + return size +} diff --git a/packages/@ant/ink/src/index.ts b/packages/@ant/ink/src/index.ts new file mode 100644 index 000000000..ac92daabc --- /dev/null +++ b/packages/@ant/ink/src/index.ts @@ -0,0 +1,126 @@ +/** + * @anthropic/ink — Terminal React rendering framework + * + * Three-layer architecture: + * core/ — Rendering engine (reconciler, layout, terminal I/O, screen buffer) + * components/ — UI primitives (Box, Text, ScrollBox, App, hooks) + * theme/ — Theme system (ThemeProvider, ThemedBox, ThemedText, design-system) + */ + +// ============================================================ +// Core API (render/createRoot) +// ============================================================ +export { default as wrappedRender, renderSync, createRoot } from './core/root.js' +export type { RenderOptions, Instance, Root } from './core/root.js' + +// InkCore class +export { default as Ink } from './core/ink.js' + +// ============================================================ +// Core types +// ============================================================ +export type { DOMElement, TextNode, ElementNames, DOMNodeAttribute } from './core/dom.js' +export type { Styles, TextStyles, Color, RGBColor, HexColor, Ansi256Color, AnsiColor } from './core/styles.js' +export type { Key } from './core/events/input-event.js' +export type { FlickerReason, FrameEvent } from './core/frame.js' +export type { MatchPosition } from './core/render-to-screen.js' +export type { SelectionState, FocusMove } from './core/selection.js' +export type { Progress } from './core/terminal.js' + +// ============================================================ +// Core modules +// ============================================================ +export { ClickEvent } from './core/events/click-event.js' +export { EventEmitter } from './core/events/emitter.js' +export { Event } from './core/events/event.js' +export { InputEvent } from './core/events/input-event.js' +export { TerminalFocusEvent, type TerminalFocusEventType } from './core/events/terminal-focus-event.js' +export { KeyboardEvent } from './core/events/keyboard-event.js' +export { FocusEvent } from './core/events/focus-event.js' +export { FocusManager } from './core/focus.js' +export { Ansi } from './core/Ansi.js' +export { stringWidth } from './core/stringWidth.js' +export { default as wrapText } from './core/wrap-text.js' +export { default as measureElement } from './core/measure-element.js' +export { supportsTabStatus } from './core/termio/osc.js' +export { setClipboard, getClipboardPath, CLEAR_ITERM2_PROGRESS, CLEAR_TAB_STATUS, CLEAR_TERMINAL_TITLE, wrapForMultiplexer } from './core/termio/osc.js' +export { DISABLE_KITTY_KEYBOARD, DISABLE_MODIFY_OTHER_KEYS } from './core/termio/csi.js' +export { SHOW_CURSOR, DBP, DFE, DISABLE_MOUSE_TRACKING, EXIT_ALT_SCREEN, HIDE_CURSOR, ENTER_ALT_SCREEN, ENABLE_MOUSE_TRACKING } from './core/termio/dec.js' +export { default as instances } from './core/instances.js' +export { default as renderBorder, type BorderTextOptions } from './core/render-border.js' +export { isSynchronizedOutputSupported, isXtermJs, hasCursorUpViewportYankBug, writeDiffToTerminal } from './core/terminal.js' +export { colorize, applyColor, applyTextStyles, type ColorType } from './core/colorize.js' +export { wrapAnsi } from './core/wrapAnsi.js' +export { default as styles } from './core/styles.js' +export { clamp } from './core/layout/geometry.js' +export { getTerminalFocusState, getTerminalFocused, subscribeTerminalFocus } from './core/terminal-focus-state.js' +export { supportsHyperlinks } from './core/supports-hyperlinks.js' + +// ============================================================ +// Components (Layer 2) +// ============================================================ +export { default as BaseBox } from './components/Box.js' +export type { Props as BaseBoxProps } from './components/Box.js' +export { default as BaseText } from './components/Text.js' +export type { Props as BaseTextProps } from './components/Text.js' +export { default as Button, type ButtonState, type Props as ButtonProps } from './components/Button.js' +export { default as Link } from './components/Link.js' +export type { Props as LinkProps } from './components/Link.js' +export { default as Newline } from './components/Newline.js' +export type { Props as NewlineProps } from './components/Newline.js' +export { default as Spacer } from './components/Spacer.js' +export { NoSelect } from './components/NoSelect.js' +export { RawAnsi } from './components/RawAnsi.js' +export { default as ScrollBox, type ScrollBoxHandle } from './components/ScrollBox.js' +export { AlternateScreen } from './components/AlternateScreen.js' + +// App types +export type { Props as AppProps } from './components/AppContext.js' +export type { Props as StdinProps } from './components/StdinContext.js' +export { TerminalSizeContext, type TerminalSize } from './components/TerminalSizeContext.js' + +// ============================================================ +// Hooks +// ============================================================ +export { default as useApp } from './hooks/use-app.js' +export { default as useInput } from './hooks/use-input.js' +export { useAnimationFrame } from './hooks/use-animation-frame.js' +export { useAnimationTimer, useInterval } from './hooks/use-interval.js' +export { useSelection, useHasSelection } from './hooks/use-selection.js' +export { default as useStdin } from './hooks/use-stdin.js' +export { useTabStatus, type TabStatusKind } from './hooks/use-tab-status.js' +export { useTerminalFocus } from './hooks/use-terminal-focus.js' +export { useTerminalTitle } from './hooks/use-terminal-title.js' +export { useTerminalViewport } from './hooks/use-terminal-viewport.js' +export { useSearchHighlight } from './hooks/use-search-highlight.js' +export { useDeclaredCursor } from './hooks/use-declared-cursor.js' +export { TerminalWriteProvider, useTerminalNotification, type TerminalNotification } from './hooks/useTerminalNotification.js' + +// ============================================================ +// Theme (Layer 3) +// ============================================================ +export { + ThemeProvider, + usePreviewTheme, + useTheme, + useThemeSetting, +} from './theme/ThemeProvider.js' +export { default as Box } from './theme/ThemedBox.js' +export type { Props as BoxProps } from './theme/ThemedBox.js' +export { default as Text } from './theme/ThemedText.js' +export type { Props as TextProps } from './theme/ThemedText.js' +export { color } from './theme/color.js' + +// Theme sub-components +export { Dialog } from './theme/Dialog.js' +export { Divider } from './theme/Divider.js' +export { FuzzyPicker } from './theme/FuzzyPicker.js' +export { ListItem } from './theme/ListItem.js' +export { LoadingState } from './theme/LoadingState.js' +export { Pane } from './theme/Pane.js' +export { ProgressBar } from './theme/ProgressBar.js' +export { Ratchet } from './theme/Ratchet.js' +export { StatusIcon } from './theme/StatusIcon.js' +export { Tabs } from './theme/Tabs.js' +export { Byline } from './theme/Byline.js' +export { KeyboardShortcutHint } from './theme/KeyboardShortcutHint.js' diff --git a/packages/@ant/ink/src/theme/Byline.tsx b/packages/@ant/ink/src/theme/Byline.tsx new file mode 100644 index 000000000..bb34e52fc --- /dev/null +++ b/packages/@ant/ink/src/theme/Byline.tsx @@ -0,0 +1,57 @@ +import React, { Children, isValidElement } from 'react' +import { Text } from '../index.js' + +type Props = { + /** The items to join with a middot separator */ + children: React.ReactNode +} + +/** + * Joins children with a middot separator (" · ") for inline metadata display. + * + * Named after the publishing term "byline" - the line of metadata typically + * shown below a title (e.g., "John Doe · 5 min read · Mar 12"). + * + * Automatically filters out null/undefined/false children and only renders + * separators between valid elements. + * + * @example + * // Basic usage: "Enter to confirm · Esc to cancel" + * + * + * + * + * + * + * + * @example + * // With conditional children: "Esc to cancel" (only one item shown) + * + * + * {showEnter && } + * + * + * + * + */ +export function Byline({ children }: Props): React.ReactNode { + // Children.toArray already filters out null, undefined, and booleans + const validChildren = Children.toArray(children) + + if (validChildren.length === 0) { + return null + } + + return ( + <> + {validChildren.map((child, index) => ( + + {index > 0 && · } + {child} + + ))} + + ) +} diff --git a/packages/@ant/ink/src/theme/ConfigurableShortcutHint.tsx b/packages/@ant/ink/src/theme/ConfigurableShortcutHint.tsx new file mode 100644 index 000000000..85911d387 --- /dev/null +++ b/packages/@ant/ink/src/theme/ConfigurableShortcutHint.tsx @@ -0,0 +1,35 @@ +/** + * Simplified ConfigurableShortcutHint for the standalone @anthropic/ink package. + * + * The full version reads user-configured keybindings via useShortcutDisplay. + * This stub just renders the fallback shortcut — sufficient for the package's + * internal theme components. + */ + +import React from 'react' +import { KeyboardShortcutHint } from './KeyboardShortcutHint.js' + +type Props = { + action: string + context: string + fallback: string + description: string + parens?: boolean + bold?: boolean +} + +export function ConfigurableShortcutHint({ + fallback, + description, + parens, + bold, +}: Props): React.ReactNode { + return ( + + ) +} diff --git a/packages/@ant/ink/src/theme/Dialog.tsx b/packages/@ant/ink/src/theme/Dialog.tsx new file mode 100644 index 000000000..3b60b3587 --- /dev/null +++ b/packages/@ant/ink/src/theme/Dialog.tsx @@ -0,0 +1,100 @@ +import React from 'react' +import { + type ExitState, + useExitOnCtrlCDWithKeybindings, +} from '../hooks/useExitOnCtrlCD.js' +import { Box, Text } from '../index.js' +import { useKeybinding } from './keybindings.js' +import type { Theme } from './theme-types.js' +import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' +import { Byline } from './Byline.js' +import { KeyboardShortcutHint } from './KeyboardShortcutHint.js' +import { Pane } from './Pane.js' + +type DialogProps = { + title: React.ReactNode + subtitle?: React.ReactNode + children: React.ReactNode + onCancel: () => void + color?: keyof Theme + hideInputGuide?: boolean + hideBorder?: boolean + /** Custom input guide content. Receives exitState for Ctrl+C/D pending display. */ + inputGuide?: (exitState: ExitState) => React.ReactNode + /** + * Controls whether Dialog's built-in confirm:no (Esc/n) and app:exit/interrupt + * (Ctrl-C/D) keybindings are active. Set to `false` while an embedded text + * field is being edited so those keys reach the field instead of being + * consumed by Dialog. TextInput has its own ctrl+c/d handlers (cancel on + * press, delete-forward on ctrl+d with text). Defaults to `true`. + */ + isCancelActive?: boolean +} + +export function Dialog({ + title, + subtitle, + children, + onCancel, + color = 'permission', + hideInputGuide, + hideBorder, + inputGuide, + isCancelActive = true, +}: DialogProps): React.ReactNode { + const exitState = useExitOnCtrlCDWithKeybindings( + undefined, + undefined, + isCancelActive, + ) + + // Use configurable keybinding for ESC to cancel. + // isCancelActive lets consumers (e.g. ElicitationDialog) disable this while + // an embedded TextInput is focused, so that keys like 'n' reach the field + // instead of being consumed here. + useKeybinding('confirm:no', onCancel, { + context: 'Confirmation', + isActive: isCancelActive, + }) + + const defaultInputGuide = exitState.pending ? ( + Press {exitState.keyName} again to exit + ) : ( + + + + + ) + + const content = ( + <> + + + + {title} + + {subtitle && {subtitle}} + + {children} + + {!hideInputGuide && ( + + + {inputGuide ? inputGuide(exitState) : defaultInputGuide} + + + )} + + ) + + if (hideBorder) { + return content + } + + return {content} +} diff --git a/packages/@ant/ink/src/theme/Divider.tsx b/packages/@ant/ink/src/theme/Divider.tsx new file mode 100644 index 000000000..077546a75 --- /dev/null +++ b/packages/@ant/ink/src/theme/Divider.tsx @@ -0,0 +1,97 @@ +import React from 'react' +import { useTerminalSize } from '../hooks/useTerminalSize.js' +import { stringWidth } from '../core/stringWidth.js' +import { Ansi, Text } from '../index.js' +import type { Theme } from './theme-types.js' + +type DividerProps = { + /** + * Width of the divider in characters. + * Defaults to terminal width. + */ + width?: number + + /** + * Theme color for the divider. + * If not provided, dimColor is used. + */ + color?: keyof Theme + + /** + * Character to use for the divider line. + * @default '─' + */ + char?: string + + /** + * Padding to subtract from the width (e.g., for indentation). + * @default 0 + */ + padding?: number + + /** + * Title shown in the middle of the divider. + * May contain ANSI codes (e.g., chalk-styled text). + * + * @example + * // ─────────── Title ─────────── + * + */ + title?: string +} + +/** + * A horizontal divider line. + * + * @example + * // Full-width dimmed divider + * + * + * @example + * // Colored divider + * + * + * @example + * // Fixed width + * + * + * @example + * // Full width minus padding (for indented content) + * + * + * @example + * // With centered title + * + */ +export function Divider({ + width, + color, + char = '─', + padding = 0, + title, +}: DividerProps): React.ReactNode { + const { columns: terminalWidth } = useTerminalSize() + const effectiveWidth = Math.max(0, (width ?? terminalWidth) - padding) + + if (title) { + const titleWidth = stringWidth(title) + 2 // +2 for spaces around title + const sideWidth = Math.max(0, effectiveWidth - titleWidth) + const leftWidth = Math.floor(sideWidth / 2) + const rightWidth = sideWidth - leftWidth + return ( + + {char.repeat(leftWidth)}{' '} + + {title} + {' '} + {char.repeat(rightWidth)} + + ) + } + + return ( + + {char.repeat(effectiveWidth)} + + ) +} diff --git a/packages/@ant/ink/src/theme/FuzzyPicker.tsx b/packages/@ant/ink/src/theme/FuzzyPicker.tsx new file mode 100644 index 000000000..642c09e7d --- /dev/null +++ b/packages/@ant/ink/src/theme/FuzzyPicker.tsx @@ -0,0 +1,350 @@ +import * as React from 'react' +import { useEffect, useState } from 'react' +import { useSearchInput } from '../hooks/useSearchInput.js' +import { useTerminalSize } from '../hooks/useTerminalSize.js' +import type { KeyboardEvent } from '../core/events/keyboard-event.js' +import { clamp } from '../core/layout/geometry.js' +import { Box, Text, useTerminalFocus } from '../index.js' +import { SearchBox } from './SearchBox.js' +import { Byline } from './Byline.js' +import { KeyboardShortcutHint } from './KeyboardShortcutHint.js' +import { ListItem } from './ListItem.js' +import { Pane } from './Pane.js' + +type PickerAction = { + /** Hint label shown in the byline, e.g. "mention" → "Tab to mention". */ + action: string + handler: (item: T) => void +} + +type Props = { + title: string + placeholder?: string + initialQuery?: string + items: readonly T[] + getKey: (item: T) => string + /** Keep to one line — preview handles overflow. */ + renderItem: (item: T, isFocused: boolean) => React.ReactNode + renderPreview?: (item: T) => React.ReactNode + /** 'right' keeps hints stable (no bounce), but needs width. */ + previewPosition?: 'bottom' | 'right' + visibleCount?: number + /** + * 'up' puts items[0] at the bottom next to the input (atuin-style). Arrows + * always match screen direction — ↑ walks visually up regardless. + */ + direction?: 'down' | 'up' + /** Caller owns filtering: re-filter on each call and pass new items. */ + onQueryChange: (query: string) => void + /** Enter key. Primary action. */ + onSelect: (item: T) => void + /** + * Tab key. If provided, Tab no longer aliases Enter — it gets its own + * handler and hint. Shift+Tab falls through to this if onShiftTab is unset. + */ + onTab?: PickerAction + /** Shift+Tab key. Gets its own hint. */ + onShiftTab?: PickerAction + /** + * Fires when the focused item changes (via arrows or when items reset). + * Useful for async preview loading — keeps I/O out of renderPreview. + */ + onFocus?: (item: T | undefined) => void + onCancel: () => void + /** Shown when items is empty. Caller bakes loading/searching state into this. */ + emptyMessage?: string | ((query: string) => string) + /** + * Status line below the list, e.g. "500+ matches" or "42 matches…". + * Caller decides when to show it — pass undefined to hide. + */ + matchLabel?: string + selectAction?: string + extraHints?: React.ReactNode +} + +const DEFAULT_VISIBLE = 8 +// Pane (paddingTop + Divider) + title + 3 gaps + SearchBox (rounded border = 3 +// rows) + hints. matchLabel adds +1 when present, accounted for separately. +const CHROME_ROWS = 10 +const MIN_VISIBLE = 2 + +export function FuzzyPicker({ + title, + placeholder = 'Type to search…', + initialQuery, + items, + getKey, + renderItem, + renderPreview, + previewPosition = 'bottom', + visibleCount: requestedVisible = DEFAULT_VISIBLE, + direction = 'down', + onQueryChange, + onSelect, + onTab, + onShiftTab, + onFocus, + onCancel, + emptyMessage = 'No results', + matchLabel, + selectAction = 'select', + extraHints, +}: Props): React.ReactNode { + const isTerminalFocused = useTerminalFocus() + const { rows, columns } = useTerminalSize() + const [focusedIndex, setFocusedIndex] = useState(0) + + // Cap visibleCount so the picker never exceeds the terminal height. When it + // overflows, each re-render (arrow key, ctrl+p) mis-positions the cursor-up + // by the overflow amount and a previously-drawn line flashes blank. + const visibleCount = Math.max( + MIN_VISIBLE, + Math.min(requestedVisible, rows - CHROME_ROWS - (matchLabel ? 1 : 0)), + ) + + // Full hint row with onTab+onShiftTab is ~100 chars and wraps inconsistently + // below that. Compact mode drops shift+tab and shortens labels. + const compact = columns < 120 + + const step = (delta: 1 | -1) => { + setFocusedIndex(i => clamp(i + delta, 0, items.length - 1)) + } + + // onKeyDown fires after useSearchInput's useInput, so onExit must be a + // no-op — return/downArrow are handled by handleKeyDown below. onCancel + // still covers escape/ctrl+c/ctrl+d. Backspace-on-empty is disabled so + // a held backspace doesn't eject the user from the dialog. + const { query, cursorOffset } = useSearchInput({ + isActive: true, + onExit: () => {}, + onCancel, + initialQuery, + backspaceExitsOnEmpty: false, + }) + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'up' || (e.ctrl && e.key === 'p')) { + e.preventDefault() + e.stopImmediatePropagation() + step(direction === 'up' ? 1 : -1) + return + } + if (e.key === 'down' || (e.ctrl && e.key === 'n')) { + e.preventDefault() + e.stopImmediatePropagation() + step(direction === 'up' ? -1 : 1) + return + } + if (e.key === 'return') { + e.preventDefault() + e.stopImmediatePropagation() + const selected = items[focusedIndex] + if (selected) onSelect(selected) + return + } + if (e.key === 'tab') { + e.preventDefault() + e.stopImmediatePropagation() + const selected = items[focusedIndex] + if (!selected) return + const tabAction = e.shift ? (onShiftTab ?? onTab) : onTab + if (tabAction) { + tabAction.handler(selected) + } else { + onSelect(selected) + } + } + } + + useEffect(() => { + onQueryChange(query) + setFocusedIndex(0) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [query]) + + useEffect(() => { + setFocusedIndex(i => clamp(i, 0, items.length - 1)) + }, [items.length]) + + const focused = items[focusedIndex] + useEffect(() => { + onFocus?.(focused) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [focused]) + + const windowStart = clamp( + focusedIndex - visibleCount + 1, + 0, + items.length - visibleCount, + ) + const visible = items.slice(windowStart, windowStart + visibleCount) + + const emptyText = + typeof emptyMessage === 'function' ? emptyMessage(query) : emptyMessage + + const searchBox = ( + + ) + + const listBlock = ( + + ) + + const preview = + renderPreview && focused ? ( + + {renderPreview(focused)} + + ) : null + + // Structure must not depend on preview truthiness — when focused goes + // undefined (e.g. delete clears matches), switching row→fragment would + // change both layout AND gap count, bouncing the searchBox below. + const listGroup = + renderPreview && previewPosition === 'right' ? ( + + + {listBlock} + {matchLabel && {matchLabel}} + + {preview ?? } + + ) : ( + // Box (not fragment) so the outer gap={1} doesn't insert a blank line + // between list/matchLabel/preview — that read as extra space above the + // prompt in direction='up'. + + {listBlock} + {matchLabel && {matchLabel}} + {preview} + + ) + + const inputAbove = direction !== 'up' + return ( + + + + {title} + + {inputAbove && searchBox} + {listGroup} + {!inputAbove && searchBox} + + + + + {onTab && ( + + )} + {onShiftTab && !compact && ( + + )} + + {extraHints} + + + + + ) +} + +type ListProps = Pick< + Props, + 'visibleCount' | 'direction' | 'getKey' | 'renderItem' +> & { + visible: readonly T[] + windowStart: number + total: number + focusedIndex: number + emptyText: string +} + +function List({ + visible, + windowStart, + visibleCount, + total, + focusedIndex, + direction, + getKey, + renderItem, + emptyText, +}: ListProps): React.ReactNode { + if (visible.length === 0) { + return ( + + {emptyText} + + ) + } + + const rows = visible.map((item, i) => { + const actualIndex = windowStart + i + const isFocused = actualIndex === focusedIndex + const atLowEdge = i === 0 && windowStart > 0 + const atHighEdge = + i === visible.length - 1 && windowStart + visibleCount! < total + return ( + + {renderItem(item, isFocused)} + + ) + }) + + return ( + + {rows} + + ) +} + +function firstWord(s: string): string { + const i = s.indexOf(' ') + return i === -1 ? s : s.slice(0, i) +} diff --git a/packages/@ant/ink/src/theme/KeyboardShortcutHint.tsx b/packages/@ant/ink/src/theme/KeyboardShortcutHint.tsx new file mode 100644 index 000000000..30d4d2ccc --- /dev/null +++ b/packages/@ant/ink/src/theme/KeyboardShortcutHint.tsx @@ -0,0 +1,58 @@ +import React from 'react' +import Text from '../components/Text.js' + +type Props = { + /** The key or chord to display (e.g., "ctrl+o", "Enter", "↑/↓") */ + shortcut: string + /** The action the key performs (e.g., "expand", "select", "navigate") */ + action: string + /** Whether to wrap the hint in parentheses. Default: false */ + parens?: boolean + /** Whether to render the shortcut in bold. Default: false */ + bold?: boolean +} + +/** + * Renders a keyboard shortcut hint like "ctrl+o to expand" or "(tab to toggle)" + * + * Wrap in for the common dim styling. + * + * @example + * // Simple hint wrapped in dim Text + * + * + * // With parentheses: "(ctrl+o to expand)" + * + * + * // With bold shortcut: "Enter to confirm" (Enter is bold) + * + * + * // Multiple hints with middot separator - use Byline + * + * + * + * + * + * + */ +export function KeyboardShortcutHint({ + shortcut, + action, + parens = false, + bold = false, +}: Props): React.ReactNode { + const shortcutText = bold ? {shortcut} : shortcut + + if (parens) { + return ( + + ({shortcutText} to {action}) + + ) + } + return ( + + {shortcutText} to {action} + + ) +} diff --git a/packages/@ant/ink/src/theme/ListItem.tsx b/packages/@ant/ink/src/theme/ListItem.tsx new file mode 100644 index 000000000..b74619de1 --- /dev/null +++ b/packages/@ant/ink/src/theme/ListItem.tsx @@ -0,0 +1,188 @@ +import figures from 'figures' +import type { ReactNode } from 'react' +import React from 'react' +import { useDeclaredCursor } from '../hooks/use-declared-cursor.js' +import { Box, Text } from '../index.js' + +type ListItemProps = { + /** + * Whether this item is currently focused (keyboard selection). + * Shows the pointer indicator (❯) when true. + */ + isFocused: boolean + + /** + * Whether this item is selected (chosen/checked). + * Shows the checkmark indicator (✓) when true. + * @default false + */ + isSelected?: boolean + + /** + * The content to display for this item. + */ + children: ReactNode + + /** + * Optional description text displayed below the main content. + */ + description?: string + + /** + * Show a down arrow indicator instead of pointer (for scroll hints). + * Only applies when not focused. + */ + showScrollDown?: boolean + + /** + * Show an up arrow indicator instead of pointer (for scroll hints). + * Only applies when not focused. + */ + showScrollUp?: boolean + + /** + * Whether to apply automatic styling to the children based on focus/selection state. + * - When true (default): children are wrapped in Text with state-based colors + * - When false: children are rendered as-is, allowing custom styling + * @default true + */ + styled?: boolean + + /** + * Whether this item is disabled. Disabled items show dimmed text and no indicators. + * @default false + */ + disabled?: boolean + + /** + * Whether this ListItem should declare the terminal cursor position. + * Set false when a child (e.g. BaseTextInput) declares its own cursor. + * @default true + */ + declareCursor?: boolean +} + +/** + * A list item component for selection UIs (dropdowns, multi-selects, menus). + * + * Handles the common pattern of: + * - Pointer indicator (❯) for focused items + * - Checkmark indicator (✓) for selected items + * - Scroll indicators (↓↑) for truncated lists + * - Color states for focus/selection + * + * @example + * // Basic usage in a selection list + * {options.map((option, i) => ( + * + * {option.label} + * + * ))} + * + * @example + * // With scroll indicators + * First visible item + * ... + * Last visible item + * + * @example + * // With description + * + * Primary text + * + * + * @example + * // Custom children styling (styled=false) + * + * Custom styled content + * + */ +export function ListItem({ + isFocused, + isSelected = false, + children, + description, + showScrollDown, + showScrollUp, + styled = true, + disabled = false, + declareCursor, +}: ListItemProps): React.ReactNode { + // Determine which indicator to show + function renderIndicator(): ReactNode { + if (disabled) { + return + } + + if (isFocused) { + return {figures.pointer} + } + + if (showScrollDown) { + return {figures.arrowDown} + } + + if (showScrollUp) { + return {figures.arrowUp} + } + + return + } + + // Determine text color based on state + function getTextColor(): 'success' | 'suggestion' | 'inactive' | undefined { + if (disabled) { + return 'inactive' + } + + if (!styled) { + return undefined + } + + if (isSelected) { + return 'success' + } + + if (isFocused) { + return 'suggestion' + } + + return undefined + } + + const textColor = getTextColor() + + // Park the native terminal cursor on the pointer indicator so screen + // readers / magnifiers track the focused item. (0,0) is the top-left of + // this Box, where the pointer renders. + const cursorRef = useDeclaredCursor({ + line: 0, + column: 0, + active: isFocused && !disabled && declareCursor !== false, + }) + + return ( + + + {renderIndicator()} + {styled ? ( + + {children} + + ) : ( + children + )} + {isSelected && !disabled && {figures.tick}} + + {description && ( + + {description} + + )} + + ) +} diff --git a/packages/@ant/ink/src/theme/LoadingState.tsx b/packages/@ant/ink/src/theme/LoadingState.tsx new file mode 100644 index 000000000..ec1459cee --- /dev/null +++ b/packages/@ant/ink/src/theme/LoadingState.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import { Box, Text } from '../index.js' +import { Spinner } from './Spinner.js' + +type LoadingStateProps = { + /** + * The loading message to display next to the spinner. + */ + message: string + + /** + * Display the message in bold. + * @default false + */ + bold?: boolean + + /** + * Display the message in dimmed color. + * @default false + */ + dimColor?: boolean + + /** + * Optional subtitle displayed below the main message. + */ + subtitle?: string +} + +/** + * A spinner with loading message for async operations. + * + * @example + * // Basic loading + * + * + * @example + * // Bold loading message + * + * + * @example + * // With subtitle + * + */ +export function LoadingState({ + message, + bold = false, + dimColor = false, + subtitle, +}: LoadingStateProps): React.ReactNode { + return ( + + + + + {' '} + {message} + + + {subtitle && {subtitle}} + + ) +} diff --git a/packages/@ant/ink/src/theme/Pane.tsx b/packages/@ant/ink/src/theme/Pane.tsx new file mode 100644 index 000000000..d6868faec --- /dev/null +++ b/packages/@ant/ink/src/theme/Pane.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import { useIsInsideModal } from './modalContext.js' +import { Box } from '../index.js' +import type { Theme } from './theme-types.js' +import { Divider } from './Divider.js' + +type PaneProps = { + children: React.ReactNode + /** + * Theme color for the top border line. + */ + color?: keyof Theme +} + +/** + * A pane — a region of the terminal that appears below the REPL prompt, + * bounded by a colored top line with a one-row gap above and horizontal + * padding. Used by all slash-command screens: /config, /help, /plugins, + * /sandbox, /stats, /permissions. + * + * For confirm/cancel dialogs (Esc to dismiss, Enter to confirm), use + * `` instead — it registers its own keybindings. For a full + * rounded-border card, use ``. + * + * Submenus rendered inside a Pane should use `hideBorder` on their Dialog + * so the Pane's border remains the single frame. + * + * @example + * + * ... + * + */ +export function Pane({ children, color }: PaneProps): React.ReactNode { + // When rendered inside FullscreenLayout's modal slot, its ▔ divider IS + // the frame. Skip our own Divider (would double-frame) and the extra top + // padding. This lets slash-command screens that wrap in Pane (e.g. + // /model → ModelPicker) route through the modal slot unchanged. + if (useIsInsideModal()) { + // flexShrink=0: the modal slot's absolute Box has no explicit height + // (grows to fit, maxHeight cap). With flexGrow=1, re-renders cause + // yoga to resolve this Box's height to 0 against the undetermined + // parent — /permissions body blanks on Down arrow. See #23592. + return ( + + {children} + + ) + } + return ( + + + + {children} + + + ) +} diff --git a/packages/@ant/ink/src/theme/ProgressBar.tsx b/packages/@ant/ink/src/theme/ProgressBar.tsx new file mode 100644 index 000000000..1d5c1f674 --- /dev/null +++ b/packages/@ant/ink/src/theme/ProgressBar.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import { Text } from '../index.js' +import type { Theme } from './theme-types.js' + +type Props = { + /** + * How much progress to display, between 0 and 1 inclusive + */ + ratio: number // [0, 1] + + /** + * How many characters wide to draw the progress bar + */ + width: number // how many characters wide + + /** + * Optional color for the filled portion of the bar + */ + fillColor?: keyof Theme + + /** + * Optional color for the empty portion of the bar + */ + emptyColor?: keyof Theme +} + +const BLOCKS = [' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'] + +export function ProgressBar({ + ratio: inputRatio, + width, + fillColor, + emptyColor, +}: Props): React.ReactNode { + const ratio = Math.min(1, Math.max(0, inputRatio)) + const whole = Math.floor(ratio * width) + const segments = [BLOCKS[BLOCKS.length - 1]!.repeat(whole)] + if (whole < width) { + const remainder = ratio * width - whole + const middle = Math.floor(remainder * BLOCKS.length) + segments.push(BLOCKS[middle]!) + + const empty = width - whole - 1 + if (empty > 0) { + segments.push(BLOCKS[0]!.repeat(empty)) + } + } + + return ( + + {segments.join('')} + + ) +} diff --git a/packages/@ant/ink/src/theme/Ratchet.tsx b/packages/@ant/ink/src/theme/Ratchet.tsx new file mode 100644 index 000000000..dbb80c3f4 --- /dev/null +++ b/packages/@ant/ink/src/theme/Ratchet.tsx @@ -0,0 +1,45 @@ +import React, { useCallback, useLayoutEffect, useRef, useState } from 'react' +import { useTerminalSize } from '../hooks/useTerminalSize.js' +import { useTerminalViewport } from '../hooks/use-terminal-viewport.js' +import { Box, type DOMElement, measureElement } from '../index.js' + +type Props = { + children: React.ReactNode + lock?: 'always' | 'offscreen' +} + +export function Ratchet({ children, lock = 'always' }: Props): React.ReactNode { + const [viewportRef, { isVisible }] = useTerminalViewport() + const { rows } = useTerminalSize() + const innerRef = useRef(null) + const maxHeight = useRef(0) + const [minHeight, setMinHeight] = useState(0) + + const outerRef = useCallback( + (el: DOMElement | null) => { + viewportRef(el) + }, + [viewportRef], + ) + + const engaged = lock === 'always' || !isVisible + + useLayoutEffect(() => { + if (!innerRef.current) { + return + } + const { height } = measureElement(innerRef.current) + if (height > maxHeight.current) { + maxHeight.current = Math.min(height, rows) + setMinHeight(maxHeight.current) + } + }) + + return ( + + + {children} + + + ) +} diff --git a/packages/@ant/ink/src/theme/SearchBox.tsx b/packages/@ant/ink/src/theme/SearchBox.tsx new file mode 100644 index 000000000..bf716be9e --- /dev/null +++ b/packages/@ant/ink/src/theme/SearchBox.tsx @@ -0,0 +1,71 @@ +import React from 'react' +import { Box, Text } from '../index.js' + +type Props = { + query: string + placeholder?: string + isFocused: boolean + isTerminalFocused: boolean + prefix?: string + width?: number | string + cursorOffset?: number + borderless?: boolean +} + +export function SearchBox({ + query, + placeholder = 'Search…', + isFocused, + isTerminalFocused, + prefix = '\u2315', + width, + cursorOffset, + borderless = false, +}: Props): React.ReactNode { + const offset = cursorOffset ?? query.length + + return ( + + + {prefix}{' '} + {isFocused ? ( + <> + {query ? ( + isTerminalFocused ? ( + <> + {query.slice(0, offset)} + + {offset < query.length ? query[offset] : ' '} + + {offset < query.length && ( + {query.slice(offset + 1)} + )} + + ) : ( + {query} + ) + ) : isTerminalFocused ? ( + <> + {placeholder.charAt(0)} + {placeholder.slice(1)} + + ) : ( + {placeholder} + )} + + ) : query ? ( + {query} + ) : ( + {placeholder} + )} + + + ) +} diff --git a/packages/@ant/ink/src/theme/Spinner.tsx b/packages/@ant/ink/src/theme/Spinner.tsx new file mode 100644 index 000000000..2d85ec08c --- /dev/null +++ b/packages/@ant/ink/src/theme/Spinner.tsx @@ -0,0 +1,20 @@ +import React, { useState, useEffect } from 'react' +import { Text } from '../index.js' + +const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] + +/** + * A simple animated spinner for loading states. + */ +export function Spinner(): React.ReactNode { + const [frame, setFrame] = useState(0) + + useEffect(() => { + const timer = setInterval(() => { + setFrame(f => (f + 1) % FRAMES.length) + }, 80) + return () => clearInterval(timer) + }, []) + + return {FRAMES[frame]} +} diff --git a/packages/@ant/ink/src/theme/StatusIcon.tsx b/packages/@ant/ink/src/theme/StatusIcon.tsx new file mode 100644 index 000000000..943d6be25 --- /dev/null +++ b/packages/@ant/ink/src/theme/StatusIcon.tsx @@ -0,0 +1,71 @@ +import figures from 'figures' +import React from 'react' +import { Text } from '../index.js' + +type Status = 'success' | 'error' | 'warning' | 'info' | 'pending' | 'loading' + +type Props = { + /** + * The status to display. Determines both the icon and color. + * + * - `success`: Green checkmark (✓) + * - `error`: Red cross (✗) + * - `warning`: Yellow warning symbol (⚠) + * - `info`: Blue info symbol (ℹ) + * - `pending`: Dimmed circle (○) + * - `loading`: Dimmed ellipsis (…) + */ + status: Status + /** + * Include a trailing space after the icon. Useful when followed by text. + * @default false + */ + withSpace?: boolean +} + +const STATUS_CONFIG: Record< + Status, + { + icon: string + color: 'success' | 'error' | 'warning' | 'suggestion' | undefined + } +> = { + success: { icon: figures.tick, color: 'success' }, + error: { icon: figures.cross, color: 'error' }, + warning: { icon: figures.warning, color: 'warning' }, + info: { icon: figures.info, color: 'suggestion' }, + pending: { icon: figures.circle, color: undefined }, + loading: { icon: '…', color: undefined }, +} + +/** + * Renders a status indicator icon with appropriate color. + * + * @example + * // Success indicator + * + * + * @example + * // Error with trailing space for text + * Failed to connect + * + * @example + * // Status line pattern + * + * + * Waiting for response + * + */ +export function StatusIcon({ + status, + withSpace = false, +}: Props): React.ReactNode { + const config = STATUS_CONFIG[status] + + return ( + + {config.icon} + {withSpace && ' '} + + ) +} diff --git a/packages/@ant/ink/src/theme/Tabs.tsx b/packages/@ant/ink/src/theme/Tabs.tsx new file mode 100644 index 000000000..cec38cf7e --- /dev/null +++ b/packages/@ant/ink/src/theme/Tabs.tsx @@ -0,0 +1,339 @@ +import React, { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from 'react' +import { + useIsInsideModal, + useModalScrollRef, +} from './modalContext.js' +import { useTerminalSize } from '../hooks/useTerminalSize.js' +import ScrollBox from '../components/ScrollBox.js' +import type { KeyboardEvent } from '../core/events/keyboard-event.js' +import { stringWidth } from '../core/stringWidth.js' +import { Box, Text } from '../index.js' +import { useKeybindings } from './keybindings.js' +import type { Theme } from './theme-types.js' + +type TabsProps = { + children: Array> + title?: string + color?: keyof Theme + defaultTab?: string + hidden?: boolean + useFullWidth?: boolean + /** Controlled mode: current selected tab id/title */ + selectedTab?: string + /** Controlled mode: callback when tab changes */ + onTabChange?: (tabId: string) => void + /** Optional banner to display below tabs header */ + banner?: React.ReactNode + /** Disable keyboard navigation (e.g. when a child component handles arrow keys) */ + disableNavigation?: boolean + /** + * Initial focus state for the tab header row. Defaults to true (header + * focused, nav always works). Keep the default for Select/list content — + * those only use up/down so there's no conflict; pass + * isDisabled={headerFocused} to the Select instead. Only set false when + * content actually binds left/right/tab (e.g. enum cycling), and show a + * "↑ tabs" footer hint — without it tabs look broken. + */ + initialHeaderFocused?: boolean + /** + * Fixed height for the content area. When set, all tabs render within the + * same height (overflow hidden) so switching tabs doesn't cause layout + * shifts. Shorter tabs get whitespace; taller tabs are clipped. + */ + contentHeight?: number + /** + * Let Tab/←/→ switch tabs from focused content. Opt-in since some + * content uses those keys; pass a reactive boolean to cede them when + * needed. Switching from content focuses the header. + */ + navFromContent?: boolean +} + +type TabsContextValue = { + selectedTab: string | undefined + width: number | undefined + headerFocused: boolean + focusHeader: () => void + blurHeader: () => void + registerOptIn: () => () => void +} + +const TabsContext = createContext({ + selectedTab: undefined, + width: undefined, + // Default for components rendered outside a Tabs (tests, standalone): + // content has focus, focusHeader is a no-op. + headerFocused: false, + focusHeader: () => {}, + blurHeader: () => {}, + registerOptIn: () => () => {}, +}) + +export function Tabs({ + title, + color, + defaultTab, + children, + hidden, + useFullWidth, + selectedTab: controlledSelectedTab, + onTabChange, + banner, + disableNavigation, + initialHeaderFocused = true, + contentHeight, + navFromContent = false, +}: TabsProps): React.ReactNode { + const { columns: terminalWidth } = useTerminalSize() + const tabs = children.map(child => [ + child.props.id ?? child.props.title, + child.props.title, + ]) + const defaultTabIndex = defaultTab + ? tabs.findIndex(tab => defaultTab === tab[0]) + : 0 + + // Support both controlled and uncontrolled modes + const isControlled = controlledSelectedTab !== undefined + const [internalSelectedTab, setInternalSelectedTab] = useState( + defaultTabIndex !== -1 ? defaultTabIndex : 0, + ) + + // In controlled mode, find the index of the controlled tab + const controlledTabIndex = isControlled + ? tabs.findIndex(tab => tab[0] === controlledSelectedTab) + : -1 + const selectedTabIndex = isControlled + ? controlledTabIndex !== -1 + ? controlledTabIndex + : 0 + : internalSelectedTab + + const modalScrollRef = useModalScrollRef() + + // Header focus: left/right/tab only switch tabs when the header row is + // focused. Children with interactive content call focusHeader() (via + // useTabHeaderFocus) on up-arrow to hand focus back here; down-arrow + // returns it. Tabs that never call the hook see no behavior change — + // initialHeaderFocused defaults to true so nav always works. + const [headerFocused, setHeaderFocused] = useState(initialHeaderFocused) + const focusHeader = useCallback(() => setHeaderFocused(true), []) + const blurHeader = useCallback(() => setHeaderFocused(false), []) + // Count of mounted children using useTabHeaderFocus(). Down-arrow blur and + // the ↓ hint only engage when at least one child has opted in — otherwise + // pressing down on a legacy tab would strand the user with nav disabled. + const [optInCount, setOptInCount] = useState(0) + const registerOptIn = useCallback(() => { + setOptInCount(n => n + 1) + return () => setOptInCount(n => n - 1) + }, []) + const optedIn = optInCount > 0 + + const handleTabChange = (offset: number) => { + const newIndex = (selectedTabIndex + tabs.length + offset) % tabs.length + const newTabId = tabs[newIndex]?.[0] + + if (isControlled && onTabChange && newTabId) { + onTabChange(newTabId) + } else { + setInternalSelectedTab(newIndex) + } + // Tab switching is a header action — stay focused so the user can keep + // cycling. The newly mounted tab can blur via its own interaction. + setHeaderFocused(true) + } + + useKeybindings( + { + 'tabs:next': () => handleTabChange(1), + 'tabs:previous': () => handleTabChange(-1), + }, + { + context: 'Tabs', + isActive: !hidden && !disableNavigation && headerFocused, + }, + ) + + // When the header is focused, down-arrow returns focus to content. Only + // active when the selected tab has opted in via useTabHeaderFocus() — + // legacy tabs have nowhere to return focus to. + const handleKeyDown = (e: KeyboardEvent) => { + if (!headerFocused || !optedIn || hidden) return + if (e.key === 'down') { + e.preventDefault() + setHeaderFocused(false) + } + } + + // Opt-in: same tabs:next/previous actions, active from content. Focuses + // the header so subsequent presses cycle via the handler above. + useKeybindings( + { + 'tabs:next': () => { + handleTabChange(1) + setHeaderFocused(true) + }, + 'tabs:previous': () => { + handleTabChange(-1) + setHeaderFocused(true) + }, + }, + { + context: 'Tabs', + isActive: + navFromContent && + !headerFocused && + optedIn && + !hidden && + !disableNavigation, + }, + ) + + // Calculate spacing to fill the available width. No keyboard hint in the + // header row — content footers own hints (see useTabHeaderFocus docs). + const titleWidth = title ? stringWidth(title) + 1 : 0 // +1 for gap + const tabsWidth = tabs.reduce( + (sum, [, tabTitle]) => sum + (tabTitle ? stringWidth(tabTitle) : 0) + 2 + 1, // +2 for padding, +1 for gap + 0, + ) + const usedWidth = titleWidth + tabsWidth + const spacerWidth = useFullWidth ? Math.max(0, terminalWidth - usedWidth) : 0 + + const contentWidth = useFullWidth ? terminalWidth : undefined + + return ( + + + {!hidden && ( + + {title !== undefined && ( + + {title} + + )} + {tabs.map(([id, title], i) => { + const isCurrent = selectedTabIndex === i + const hasColorCursor = color && isCurrent && headerFocused + return ( + + {' '} + {title}{' '} + + ) + })} + {spacerWidth > 0 && {' '.repeat(spacerWidth)}} + + )} + {banner} + {modalScrollRef ? ( + // Inside the modal slot: own the ScrollBox here so the tabs + // header row above sits OUTSIDE the scroll area — it can never + // scroll off. The ref reaches REPL's ScrollKeybindingHandler via + // ModalContext. Keyed by selectedTabIndex → remounts on tab + // switch, resetting scrollTop to 0 without scrollTo() timing games. + + + {children} + + + ) : ( + + {children} + + )} + + + ) +} + +type TabProps = { + title: string + id?: string + children: React.ReactNode +} + +export function Tab({ title, id, children }: TabProps): React.ReactNode { + const { selectedTab, width } = useContext(TabsContext) + const insideModal = useIsInsideModal() + if (selectedTab !== (id ?? title)) { + return null + } + + return ( + + {children} + + ) +} + +export function useTabsWidth(): number | undefined { + const { width } = useContext(TabsContext) + return width +} + +/** + * Opt into header-focus gating. Returns the current header focus state and a + * callback to hand focus back to the tab row. For a Select, pass + * `isDisabled={headerFocused}` and `onUpFromFirstItem={focusHeader}`; keep the + * parent Tabs' initialHeaderFocused at its default so tab/←/→ work on mount. + * + * Calling this hook registers a ↓-blurs-header opt-in on mount. Don't call it + * above an early return that renders static text — ↓ will blur the header with + * no onUpFromFirstItem to recover. Split the component so the hook only runs + * when the Select renders. + */ +export function useTabHeaderFocus(): { + headerFocused: boolean + focusHeader: () => void + blurHeader: () => void +} { + const { headerFocused, focusHeader, blurHeader, registerOptIn } = + useContext(TabsContext) + useEffect(registerOptIn, [registerOptIn]) + return { headerFocused, focusHeader, blurHeader } +} diff --git a/packages/@ant/ink/src/theme/ThemeProvider.tsx b/packages/@ant/ink/src/theme/ThemeProvider.tsx new file mode 100644 index 000000000..bfd6a4307 --- /dev/null +++ b/packages/@ant/ink/src/theme/ThemeProvider.tsx @@ -0,0 +1,172 @@ +import { feature } from 'bun:bundle' +import React, { + createContext, + useContext, + useEffect, + useMemo, + useState, +} from 'react' +import useStdin from '../hooks/use-stdin.js' +import { getSystemThemeName, type SystemTheme } from './systemTheme.js' +import type { ThemeName, ThemeSetting } from './theme-types.js' + +// -- Config persistence injection -- +// Business layer provides these via setThemeConfigCallbacks(). +// Defaults read/write from a simple module-level store. + +let _loadTheme: () => ThemeSetting = () => 'dark' +let _saveTheme: (setting: ThemeSetting) => void = () => {} + +/** Inject config persistence from the business layer. Call once at startup. */ +export function setThemeConfigCallbacks(opts: { + loadTheme: () => ThemeSetting + saveTheme: (setting: ThemeSetting) => void +}): void { + _loadTheme = opts.loadTheme + _saveTheme = opts.saveTheme +} + +type ThemeContextValue = { + /** The saved user preference. May be 'auto'. */ + themeSetting: ThemeSetting + setThemeSetting: (setting: ThemeSetting) => void + setPreviewTheme: (setting: ThemeSetting) => void + savePreview: () => void + cancelPreview: () => void + /** The resolved theme to render with. Never 'auto'. */ + currentTheme: ThemeName +} + +// Non-'auto' default so useTheme() works without a provider (tests, tooling). +const DEFAULT_THEME: ThemeName = 'dark' + +const ThemeContext = createContext({ + themeSetting: DEFAULT_THEME, + setThemeSetting: () => {}, + setPreviewTheme: () => {}, + savePreview: () => {}, + cancelPreview: () => {}, + currentTheme: DEFAULT_THEME, +}) + +type Props = { + children: React.ReactNode + initialState?: ThemeSetting + onThemeSave?: (setting: ThemeSetting) => void +} + +function defaultInitialTheme(): ThemeSetting { + return _loadTheme() +} + +function defaultSaveTheme(setting: ThemeSetting): void { + _saveTheme(setting) +} + +export function ThemeProvider({ + children, + initialState, + onThemeSave = defaultSaveTheme, +}: Props) { + const [themeSetting, setThemeSetting] = useState( + initialState ?? defaultInitialTheme, + ) + const [previewTheme, setPreviewTheme] = useState(null) + + // Track terminal theme for 'auto' resolution. Seeds from $COLORFGBG (or + // 'dark' if unset); the OSC 11 watcher corrects it on first poll. + const [systemTheme, setSystemTheme] = useState(() => + (initialState ?? themeSetting) === 'auto' ? getSystemThemeName() : 'dark', + ) + + // The setting currently in effect (preview wins while picker is open) + const activeSetting = previewTheme ?? themeSetting + + const { internal_querier } = useStdin() + + // Watch for live terminal theme changes while 'auto' is active. + // Positive feature() pattern so the watcher import is dead-code-eliminated + // in external builds. + useEffect(() => { + if (feature('AUTO_THEME')) { + if (activeSetting !== 'auto' || !internal_querier) return + let cleanup: (() => void) | undefined + let cancelled = false + void import('../../utils/systemThemeWatcher.js').then( + ({ watchSystemTheme }) => { + if (cancelled) return + cleanup = watchSystemTheme(internal_querier, setSystemTheme) + }, + ) + return () => { + cancelled = true + cleanup?.() + } + } + }, [activeSetting, internal_querier]) + + const currentTheme: ThemeName = + activeSetting === 'auto' ? systemTheme : activeSetting + + const value = useMemo( + () => ({ + themeSetting, + setThemeSetting: (newSetting: ThemeSetting) => { + setThemeSetting(newSetting) + setPreviewTheme(null) + // Switching to 'auto' restarts the watcher (activeSetting dep), whose + // first poll fires immediately. Seed from the cache so the OSC + // round-trip doesn't flash the wrong palette. + if (newSetting === 'auto') { + setSystemTheme(getSystemThemeName()) + } + onThemeSave?.(newSetting) + }, + setPreviewTheme: (newSetting: ThemeSetting) => { + setPreviewTheme(newSetting) + if (newSetting === 'auto') { + setSystemTheme(getSystemThemeName()) + } + }, + savePreview: () => { + if (previewTheme !== null) { + setThemeSetting(previewTheme) + setPreviewTheme(null) + onThemeSave?.(previewTheme) + } + }, + cancelPreview: () => { + if (previewTheme !== null) { + setPreviewTheme(null) + } + }, + currentTheme, + }), + [themeSetting, previewTheme, currentTheme, onThemeSave], + ) + + return {children} +} + +/** + * Returns the resolved theme for rendering (never 'auto') and a setter that + * accepts any ThemeSetting (including 'auto'). + */ +export function useTheme(): [ThemeName, (setting: ThemeSetting) => void] { + const { currentTheme, setThemeSetting } = useContext(ThemeContext) + return [currentTheme, setThemeSetting] +} + +/** + * Returns the raw theme setting as stored in config. Use this in UI that + * needs to show 'auto' as a distinct choice (e.g., ThemePicker). + */ +export function useThemeSetting(): ThemeSetting { + return useContext(ThemeContext).themeSetting +} + +export function usePreviewTheme() { + const { setPreviewTheme, savePreview, cancelPreview } = + useContext(ThemeContext) + return { setPreviewTheme, savePreview, cancelPreview } +} diff --git a/packages/@ant/ink/src/theme/ThemedBox.tsx b/packages/@ant/ink/src/theme/ThemedBox.tsx new file mode 100644 index 000000000..46aadeab9 --- /dev/null +++ b/packages/@ant/ink/src/theme/ThemedBox.tsx @@ -0,0 +1,112 @@ +import React, { type PropsWithChildren, type Ref } from 'react' +import Box from '../components/Box.js' +import type { DOMElement } from '../core/dom.js' +import type { ClickEvent } from '../core/events/click-event.js' +import type { FocusEvent } from '../core/events/focus-event.js' +import type { KeyboardEvent } from '../core/events/keyboard-event.js' +import type { Color, Styles } from '../core/styles.js' +import { getTheme, type Theme } from './theme-types.js' +import { useTheme } from './ThemeProvider.js' + +// Color props that accept theme keys +type ThemedColorProps = { + readonly borderColor?: keyof Theme | Color + readonly borderTopColor?: keyof Theme | Color + readonly borderBottomColor?: keyof Theme | Color + readonly borderLeftColor?: keyof Theme | Color + readonly borderRightColor?: keyof Theme | Color + readonly backgroundColor?: keyof Theme | Color +} + +// Base Styles without color props (they'll be overridden) +type BaseStylesWithoutColors = Omit< + Styles, + | 'textWrap' + | 'borderColor' + | 'borderTopColor' + | 'borderBottomColor' + | 'borderLeftColor' + | 'borderRightColor' + | 'backgroundColor' +> + +export type Props = BaseStylesWithoutColors & + ThemedColorProps & { + ref?: Ref + tabIndex?: number + autoFocus?: boolean + onClick?: (event: ClickEvent) => void + onFocus?: (event: FocusEvent) => void + onFocusCapture?: (event: FocusEvent) => void + onBlur?: (event: FocusEvent) => void + onBlurCapture?: (event: FocusEvent) => void + onKeyDown?: (event: KeyboardEvent) => void + onKeyDownCapture?: (event: KeyboardEvent) => void + onMouseEnter?: () => void + onMouseLeave?: () => void + } + +/** + * Resolves a color value that may be a theme key to a raw Color. + */ +function resolveColor( + color: keyof Theme | Color | undefined, + theme: Theme, +): Color | undefined { + if (!color) return undefined + // Check if it's a raw color (starts with rgb(, #, ansi256(, or ansi:) + if ( + color.startsWith('rgb(') || + color.startsWith('#') || + color.startsWith('ansi256(') || + color.startsWith('ansi:') + ) { + return color as Color + } + // It's a theme key - resolve it + return theme[color as keyof Theme] as Color +} + +/** + * Theme-aware Box component that resolves theme color keys to raw colors. + * This wraps the base Box component with theme resolution for border colors. + */ +function ThemedBox({ + borderColor, + borderTopColor, + borderBottomColor, + borderLeftColor, + borderRightColor, + backgroundColor, + children, + ref, + ...rest +}: PropsWithChildren): React.ReactNode { + const [themeName] = useTheme() + const theme = getTheme(themeName) + + // Resolve theme keys to raw colors + const resolvedBorderColor = resolveColor(borderColor, theme) + const resolvedBorderTopColor = resolveColor(borderTopColor, theme) + const resolvedBorderBottomColor = resolveColor(borderBottomColor, theme) + const resolvedBorderLeftColor = resolveColor(borderLeftColor, theme) + const resolvedBorderRightColor = resolveColor(borderRightColor, theme) + const resolvedBackgroundColor = resolveColor(backgroundColor, theme) + + return ( + + {children} + + ) +} + +export default ThemedBox diff --git a/packages/@ant/ink/src/theme/ThemedText.tsx b/packages/@ant/ink/src/theme/ThemedText.tsx new file mode 100644 index 000000000..35e884ca9 --- /dev/null +++ b/packages/@ant/ink/src/theme/ThemedText.tsx @@ -0,0 +1,132 @@ +import type { ReactNode } from 'react' +import React, { useContext } from 'react' +import Text from '../components/Text.js' +import type { Color, Styles } from '../core/styles.js' +import { getTheme, type Theme } from './theme-types.js' +import { useTheme } from './ThemeProvider.js' + +/** Colors uncolored ThemedText in the subtree. Precedence: explicit `color` > + * this > dimColor. Crosses Box boundaries (Ink's style cascade doesn't). */ +export const TextHoverColorContext = React.createContext< + keyof Theme | undefined +>(undefined) + +export type Props = { + /** + * Change text color. Accepts a theme key or raw color value. + */ + readonly color?: keyof Theme | Color + + /** + * Same as `color`, but for background. Must be a theme key. + */ + readonly backgroundColor?: keyof Theme + + /** + * Dim the color using the theme's inactive color. + * This is compatible with bold (unlike ANSI dim). + */ + readonly dimColor?: boolean + + /** + * Make the text bold. + */ + readonly bold?: boolean + + /** + * Make the text italic. + */ + readonly italic?: boolean + + /** + * Make the text underlined. + */ + readonly underline?: boolean + + /** + * Make the text crossed with a line. + */ + readonly strikethrough?: boolean + + /** + * Inverse background and foreground colors. + */ + readonly inverse?: boolean + + /** + * This property tells Ink to wrap or truncate text if its width is larger than container. + * If `wrap` is passed (by default), Ink will wrap text and split it into multiple lines. + * If `truncate-*` is passed, Ink will truncate text instead, which will result in one line of text with the rest cut off. + */ + readonly wrap?: Styles['textWrap'] + + readonly children?: ReactNode +} + +/** + * Resolves a color value that may be a theme key to a raw Color. + */ +function resolveColor( + color: keyof Theme | Color | undefined, + theme: Theme, +): Color | undefined { + if (!color) return undefined + // Check if it's a raw color (starts with rgb(, #, ansi256(, or ansi:) + if ( + color.startsWith('rgb(') || + color.startsWith('#') || + color.startsWith('ansi256(') || + color.startsWith('ansi:') + ) { + return color as Color + } + // It's a theme key - resolve it + return theme[color as keyof Theme] as Color +} + +/** + * Theme-aware Text component that resolves theme color keys to raw colors. + * This wraps the base Text component with theme resolution. + */ +export default function ThemedText({ + color, + backgroundColor, + dimColor = false, + bold = false, + italic = false, + underline = false, + strikethrough = false, + inverse = false, + wrap = 'wrap', + children, +}: Props): React.ReactNode { + const [themeName] = useTheme() + const theme = getTheme(themeName) + const hoverColor = useContext(TextHoverColorContext) + + // Resolve theme keys to raw colors + const resolvedColor = + !color && hoverColor + ? resolveColor(hoverColor, theme) + : dimColor + ? (theme.inactive as Color) + : resolveColor(color, theme) + const resolvedBackgroundColor = backgroundColor + ? (theme[backgroundColor] as Color) + : undefined + + return ( + + {children} + + ) +} diff --git a/packages/@ant/ink/src/theme/color.ts b/packages/@ant/ink/src/theme/color.ts new file mode 100644 index 000000000..f8b567d4d --- /dev/null +++ b/packages/@ant/ink/src/theme/color.ts @@ -0,0 +1,30 @@ +import { type ColorType, colorize } from '../core/colorize.js' +import type { Color } from '../core/styles.js' +import { getTheme, type Theme, type ThemeName } from './theme-types.js' + +/** + * Curried theme-aware color function. Resolves theme keys to raw color + * values before delegating to the ink renderer's colorize. + */ +export function color( + c: keyof Theme | Color | undefined, + theme: ThemeName, + type: ColorType = 'foreground', +): (text: string) => string { + return text => { + if (!c) { + return text + } + // Raw color values bypass theme lookup + if ( + c.startsWith('rgb(') || + c.startsWith('#') || + c.startsWith('ansi256(') || + c.startsWith('ansi:') + ) { + return colorize(text, c, type) + } + // Theme key lookup + return colorize(text, getTheme(theme)[c as keyof Theme], type) + } +} diff --git a/packages/@ant/ink/src/theme/keybindings.ts b/packages/@ant/ink/src/theme/keybindings.ts new file mode 100644 index 000000000..a2ce31c50 --- /dev/null +++ b/packages/@ant/ink/src/theme/keybindings.ts @@ -0,0 +1,87 @@ +/** + * Minimal stub of the keybinding system for the standalone @anthropic/ink package. + * + * The full keybinding system (src/keybindings/) depends on KeybindingContext, + * KeybindingRegistry, and chord handling. This stub provides the same hook + * interfaces (useKeybinding / useKeybindings) but routes directly through + * useInput, matching common key sequences to action names. + * + * Only the keybindings used by theme components are mapped: + * - confirm:no → Escape + * - tabs:next → Tab / Right arrow + * - tabs:previous → Shift+Tab / Left arrow + */ + +import { useCallback } from 'react' +import useInput from '../hooks/use-input.js' +import type { Key } from '../core/events/input-event.js' + +type Options = { + context?: string + isActive?: boolean +} + +/** Maps action names to key matching logic. */ +const ACTION_MATCHERS: Record< + string, + (input: string, key: Key) => boolean +> = { + 'confirm:no': (_input, key) => key.escape === true, + 'tabs:next': (input, key) => + (key.tab && !key.shift) || (key.rightArrow && !key.shift), + 'tabs:previous': (_input, key) => + (key.tab && key.shift) || (key.leftArrow && !key.shift), +} + +/** + * Register a single keybinding action handler. + */ +export function useKeybinding( + action: string, + handler: () => void | false | Promise, + options: Options = {}, +): void { + const { isActive = true } = options + + const handleInput = useCallback( + (input: string, key: Key) => { + if (!isActive) return + const matcher = ACTION_MATCHERS[action] + if (matcher && matcher(input, key)) { + if (handler() !== false) { + // consumed + } + } + }, + [action, handler, isActive], + ) + + useInput(handleInput, { isActive }) +} + +/** + * Register multiple keybinding action handlers in one hook. + */ +export function useKeybindings( + handlers: Record void | false | Promise>, + options: Options = {}, +): void { + const { isActive = true } = options + + const handleInput = useCallback( + (input: string, key: Key) => { + if (!isActive) return + for (const [action, handler] of Object.entries(handlers)) { + const matcher = ACTION_MATCHERS[action] + if (matcher && matcher(input, key)) { + if (handler() !== false) { + break // consumed, stop checking other handlers + } + } + } + }, + [handlers, isActive], + ) + + useInput(handleInput, { isActive }) +} diff --git a/packages/@ant/ink/src/theme/modalContext.ts b/packages/@ant/ink/src/theme/modalContext.ts new file mode 100644 index 000000000..919abe9d6 --- /dev/null +++ b/packages/@ant/ink/src/theme/modalContext.ts @@ -0,0 +1,25 @@ +/** + * Minimal modal context for the standalone @anthropic/ink package. + * + * Provides useIsInsideModal() and useModalScrollRef() used by Pane and Tabs + * to adjust rendering when inside a FullscreenLayout modal slot. + */ + +import { createContext, type RefObject, useContext } from 'react' +import type { ScrollBoxHandle } from '../components/ScrollBox.js' + +type ModalCtx = { + rows: number + columns: number + scrollRef: RefObject | null +} + +export const ModalContext = createContext(null) + +export function useIsInsideModal(): boolean { + return useContext(ModalContext) !== null +} + +export function useModalScrollRef(): RefObject | null { + return useContext(ModalContext)?.scrollRef ?? null +} diff --git a/packages/@ant/ink/src/theme/systemTheme.ts b/packages/@ant/ink/src/theme/systemTheme.ts new file mode 100644 index 000000000..2ac3da5b6 --- /dev/null +++ b/packages/@ant/ink/src/theme/systemTheme.ts @@ -0,0 +1,40 @@ +/** + * Terminal dark/light mode detection. + * + * Detection is based on the terminal's actual background color (queried via + * OSC 11) rather than the OS appearance setting. + * + * Vendored from src/utils/systemTheme.ts for package independence. + */ + +export type SystemTheme = 'dark' | 'light' + +let cachedSystemTheme: SystemTheme | undefined + +/** + * Detect theme from $COLORFGBG environment variable (set by some terminals). + */ +function detectFromColorFgBg(): SystemTheme | undefined { + const colorFgBg = process.env.COLORFGBG + if (!colorFgBg) return undefined + const parts = colorFgBg.split(';') + if (parts.length < 2) return undefined + const bg = parseInt(parts[parts.length - 1]!, 10) + // Standard ANSI color indices: 0-7 are dark, 8-15 are bright/light + if (isNaN(bg)) return undefined + return bg >= 8 ? 'light' : 'dark' +} + +/** + * Get the current terminal theme. Cached after first detection. + */ +export function getSystemThemeName(): SystemTheme { + if (cachedSystemTheme === undefined) { + cachedSystemTheme = detectFromColorFgBg() ?? 'dark' + } + return cachedSystemTheme +} + +export function setCachedSystemTheme(theme: SystemTheme): void { + cachedSystemTheme = theme +} diff --git a/packages/@ant/ink/src/theme/theme-types.ts b/packages/@ant/ink/src/theme/theme-types.ts new file mode 100644 index 000000000..7e59f4059 --- /dev/null +++ b/packages/@ant/ink/src/theme/theme-types.ts @@ -0,0 +1,639 @@ +import chalk, { Chalk } from 'chalk' +// env import replaced with process.env + +export type Theme = { + autoAccept: string + bashBorder: string + claude: string + claudeShimmer: string // Lighter version of claude color for shimmer effect + claudeBlue_FOR_SYSTEM_SPINNER: string + claudeBlueShimmer_FOR_SYSTEM_SPINNER: string + permission: string + permissionShimmer: string // Lighter version of permission color for shimmer effect + planMode: string + ide: string + promptBorder: string + promptBorderShimmer: string // Lighter version of promptBorder color for shimmer effect + text: string + inverseText: string + inactive: string + inactiveShimmer: string // Lighter version of inactive color for shimmer effect + subtle: string + suggestion: string + remember: string + background: string + // Semantic colors + success: string + error: string + warning: string + merged: string + warningShimmer: string // Lighter version of warning color for shimmer effect + // Diff colors + diffAdded: string + diffRemoved: string + diffAddedDimmed: string + diffRemovedDimmed: string + // Word-level diff highlighting + diffAddedWord: string + diffRemovedWord: string + // Agent colors + red_FOR_SUBAGENTS_ONLY: string + blue_FOR_SUBAGENTS_ONLY: string + green_FOR_SUBAGENTS_ONLY: string + yellow_FOR_SUBAGENTS_ONLY: string + purple_FOR_SUBAGENTS_ONLY: string + orange_FOR_SUBAGENTS_ONLY: string + pink_FOR_SUBAGENTS_ONLY: string + cyan_FOR_SUBAGENTS_ONLY: string + // Grove colors + professionalBlue: string + // Chrome colors + chromeYellow: string + // TUI V2 colors + clawd_body: string + clawd_background: string + userMessageBackground: string + userMessageBackgroundHover: string + /** Message-actions selection. Cool shift toward `suggestion` blue; distinct from default AND userMessageBackground. */ + messageActionsBackground: string + /** Text-selection highlight background (alt-screen mouse selection). Solid + * bg that REPLACES the cell's bg while preserving its fg — matches native + * terminal selection. Previously SGR-7 inverse (swapped fg/bg per cell), + * which fragmented badly over syntax highlighting. */ + selectionBg: string + bashMessageBackgroundColor: string + + memoryBackgroundColor: string + rate_limit_fill: string + rate_limit_empty: string + fastMode: string + fastModeShimmer: string + // Brief/assistant mode label colors + briefLabelYou: string + briefLabelClaude: string + // Rainbow colors for ultrathink keyword highlighting + rainbow_red: string + rainbow_orange: string + rainbow_yellow: string + rainbow_green: string + rainbow_blue: string + rainbow_indigo: string + rainbow_violet: string + rainbow_red_shimmer: string + rainbow_orange_shimmer: string + rainbow_yellow_shimmer: string + rainbow_green_shimmer: string + rainbow_blue_shimmer: string + rainbow_indigo_shimmer: string + rainbow_violet_shimmer: string +} + +export const THEME_NAMES = [ + 'dark', + 'light', + 'light-daltonized', + 'dark-daltonized', + 'light-ansi', + 'dark-ansi', +] as const + +/** A renderable theme. Always resolvable to a concrete color palette. */ +export type ThemeName = (typeof THEME_NAMES)[number] + +export const THEME_SETTINGS = ['auto', ...THEME_NAMES] as const + +/** + * A theme preference as stored in user config. `'auto'` follows the system + * dark/light mode and is resolved to a ThemeName at runtime. + */ +export type ThemeSetting = (typeof THEME_SETTINGS)[number] + +/** + * Light theme using explicit RGB values to avoid inconsistencies + * from users' custom terminal ANSI color definitions + */ +const lightTheme: Theme = { + autoAccept: 'rgb(135,0,255)', // Electric violet + bashBorder: 'rgb(255,0,135)', // Vibrant pink + claude: 'rgb(215,119,87)', // Claude orange + claudeShimmer: 'rgb(245,149,117)', // Lighter claude orange for shimmer effect + claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(87,105,247)', // Medium blue for system spinner + claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(117,135,255)', // Lighter blue for system spinner shimmer + permission: 'rgb(87,105,247)', // Medium blue + permissionShimmer: 'rgb(137,155,255)', // Lighter blue for shimmer effect + planMode: 'rgb(0,102,102)', // Muted teal + ide: 'rgb(71,130,200)', // Muted blue + promptBorder: 'rgb(153,153,153)', // Medium gray + promptBorderShimmer: 'rgb(183,183,183)', // Lighter gray for shimmer effect + text: 'rgb(0,0,0)', // Black + inverseText: 'rgb(255,255,255)', // White + inactive: 'rgb(102,102,102)', // Dark gray + inactiveShimmer: 'rgb(142,142,142)', // Lighter gray for shimmer effect + subtle: 'rgb(175,175,175)', // Light gray + suggestion: 'rgb(87,105,247)', // Medium blue + remember: 'rgb(0,0,255)', // Blue + background: 'rgb(0,153,153)', // Cyan + success: 'rgb(44,122,57)', // Green + error: 'rgb(171,43,63)', // Red + warning: 'rgb(150,108,30)', // Amber + merged: 'rgb(135,0,255)', // Electric violet (matches autoAccept) + warningShimmer: 'rgb(200,158,80)', // Lighter amber for shimmer effect + diffAdded: 'rgb(105,219,124)', // Light green + diffRemoved: 'rgb(255,168,180)', // Light red + diffAddedDimmed: 'rgb(199,225,203)', // Very light green + diffRemovedDimmed: 'rgb(253,210,216)', // Very light red + diffAddedWord: 'rgb(47,157,68)', // Medium green + diffRemovedWord: 'rgb(209,69,75)', // Medium red + // Agent colors + red_FOR_SUBAGENTS_ONLY: 'rgb(220,38,38)', // Red 600 + blue_FOR_SUBAGENTS_ONLY: 'rgb(37,99,235)', // Blue 600 + green_FOR_SUBAGENTS_ONLY: 'rgb(22,163,74)', // Green 600 + yellow_FOR_SUBAGENTS_ONLY: 'rgb(202,138,4)', // Yellow 600 + purple_FOR_SUBAGENTS_ONLY: 'rgb(147,51,234)', // Purple 600 + orange_FOR_SUBAGENTS_ONLY: 'rgb(234,88,12)', // Orange 600 + pink_FOR_SUBAGENTS_ONLY: 'rgb(219,39,119)', // Pink 600 + cyan_FOR_SUBAGENTS_ONLY: 'rgb(8,145,178)', // Cyan 600 + // Grove colors + professionalBlue: 'rgb(106,155,204)', + // Chrome colors + chromeYellow: 'rgb(251,188,4)', // Chrome yellow + // TUI V2 colors + clawd_body: 'rgb(215,119,87)', + clawd_background: 'rgb(0,0,0)', + userMessageBackground: 'rgb(240, 240, 240)', // Slightly darker grey for optimal contrast + userMessageBackgroundHover: 'rgb(252, 252, 252)', // ≥250 to quantize distinct from base at 256-color level + messageActionsBackground: 'rgb(232, 236, 244)', // cool gray — darker than userMsg 240 (visible on white), slight blue toward `suggestion` + selectionBg: 'rgb(180, 213, 255)', // classic light-mode selection blue (macOS/VS Code-ish); dark fgs stay readable + bashMessageBackgroundColor: 'rgb(250, 245, 250)', + + memoryBackgroundColor: 'rgb(230, 245, 250)', + rate_limit_fill: 'rgb(87,105,247)', // Medium blue + rate_limit_empty: 'rgb(39,47,111)', // Dark blue + fastMode: 'rgb(255,106,0)', // Electric orange + fastModeShimmer: 'rgb(255,150,50)', // Lighter orange for shimmer + // Brief/assistant mode + briefLabelYou: 'rgb(37,99,235)', // Blue + briefLabelClaude: 'rgb(215,119,87)', // Brand orange + rainbow_red: 'rgb(235,95,87)', + rainbow_orange: 'rgb(245,139,87)', + rainbow_yellow: 'rgb(250,195,95)', + rainbow_green: 'rgb(145,200,130)', + rainbow_blue: 'rgb(130,170,220)', + rainbow_indigo: 'rgb(155,130,200)', + rainbow_violet: 'rgb(200,130,180)', + rainbow_red_shimmer: 'rgb(250,155,147)', + rainbow_orange_shimmer: 'rgb(255,185,137)', + rainbow_yellow_shimmer: 'rgb(255,225,155)', + rainbow_green_shimmer: 'rgb(185,230,180)', + rainbow_blue_shimmer: 'rgb(180,205,240)', + rainbow_indigo_shimmer: 'rgb(195,180,230)', + rainbow_violet_shimmer: 'rgb(230,180,210)', +} + +/** + * Light ANSI theme using only the 16 standard ANSI colors + * for terminals without true color support + */ +const lightAnsiTheme: Theme = { + autoAccept: 'ansi:magenta', + bashBorder: 'ansi:magenta', + claude: 'ansi:redBright', + claudeShimmer: 'ansi:yellowBright', + claudeBlue_FOR_SYSTEM_SPINNER: 'ansi:blue', + claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'ansi:blueBright', + permission: 'ansi:blue', + permissionShimmer: 'ansi:blueBright', + planMode: 'ansi:cyan', + ide: 'ansi:blueBright', + promptBorder: 'ansi:white', + promptBorderShimmer: 'ansi:whiteBright', + text: 'ansi:black', + inverseText: 'ansi:white', + inactive: 'ansi:blackBright', + inactiveShimmer: 'ansi:white', + subtle: 'ansi:blackBright', + suggestion: 'ansi:blue', + remember: 'ansi:blue', + background: 'ansi:cyan', + success: 'ansi:green', + error: 'ansi:red', + warning: 'ansi:yellow', + merged: 'ansi:magenta', + warningShimmer: 'ansi:yellowBright', + diffAdded: 'ansi:green', + diffRemoved: 'ansi:red', + diffAddedDimmed: 'ansi:green', + diffRemovedDimmed: 'ansi:red', + diffAddedWord: 'ansi:greenBright', + diffRemovedWord: 'ansi:redBright', + // Agent colors + red_FOR_SUBAGENTS_ONLY: 'ansi:red', + blue_FOR_SUBAGENTS_ONLY: 'ansi:blue', + green_FOR_SUBAGENTS_ONLY: 'ansi:green', + yellow_FOR_SUBAGENTS_ONLY: 'ansi:yellow', + purple_FOR_SUBAGENTS_ONLY: 'ansi:magenta', + orange_FOR_SUBAGENTS_ONLY: 'ansi:redBright', + pink_FOR_SUBAGENTS_ONLY: 'ansi:magentaBright', + cyan_FOR_SUBAGENTS_ONLY: 'ansi:cyan', + // Grove colors + professionalBlue: 'ansi:blueBright', + // Chrome colors + chromeYellow: 'ansi:yellow', // Chrome yellow + // TUI V2 colors + clawd_body: 'ansi:redBright', + clawd_background: 'ansi:black', + userMessageBackground: 'ansi:white', + userMessageBackgroundHover: 'ansi:whiteBright', + messageActionsBackground: 'ansi:white', + selectionBg: 'ansi:cyan', // lighter named bg for light-ansi; dark fgs stay readable + bashMessageBackgroundColor: 'ansi:whiteBright', + + memoryBackgroundColor: 'ansi:white', + rate_limit_fill: 'ansi:yellow', + rate_limit_empty: 'ansi:black', + fastMode: 'ansi:red', + fastModeShimmer: 'ansi:redBright', + briefLabelYou: 'ansi:blue', + briefLabelClaude: 'ansi:redBright', + rainbow_red: 'ansi:red', + rainbow_orange: 'ansi:redBright', + rainbow_yellow: 'ansi:yellow', + rainbow_green: 'ansi:green', + rainbow_blue: 'ansi:cyan', + rainbow_indigo: 'ansi:blue', + rainbow_violet: 'ansi:magenta', + rainbow_red_shimmer: 'ansi:redBright', + rainbow_orange_shimmer: 'ansi:yellow', + rainbow_yellow_shimmer: 'ansi:yellowBright', + rainbow_green_shimmer: 'ansi:greenBright', + rainbow_blue_shimmer: 'ansi:cyanBright', + rainbow_indigo_shimmer: 'ansi:blueBright', + rainbow_violet_shimmer: 'ansi:magentaBright', +} + +/** + * Dark ANSI theme using only the 16 standard ANSI colors + * for terminals without true color support + */ +const darkAnsiTheme: Theme = { + autoAccept: 'ansi:magentaBright', + bashBorder: 'ansi:magentaBright', + claude: 'ansi:redBright', + claudeShimmer: 'ansi:yellowBright', + claudeBlue_FOR_SYSTEM_SPINNER: 'ansi:blueBright', + claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'ansi:blueBright', + permission: 'ansi:blueBright', + permissionShimmer: 'ansi:blueBright', + planMode: 'ansi:cyanBright', + ide: 'ansi:blue', + promptBorder: 'ansi:white', + promptBorderShimmer: 'ansi:whiteBright', + text: 'ansi:whiteBright', + inverseText: 'ansi:black', + inactive: 'ansi:white', + inactiveShimmer: 'ansi:whiteBright', + subtle: 'ansi:white', + suggestion: 'ansi:blueBright', + remember: 'ansi:blueBright', + background: 'ansi:cyanBright', + success: 'ansi:greenBright', + error: 'ansi:redBright', + warning: 'ansi:yellowBright', + merged: 'ansi:magentaBright', + warningShimmer: 'ansi:yellowBright', + diffAdded: 'ansi:green', + diffRemoved: 'ansi:red', + diffAddedDimmed: 'ansi:green', + diffRemovedDimmed: 'ansi:red', + diffAddedWord: 'ansi:greenBright', + diffRemovedWord: 'ansi:redBright', + // Agent colors + red_FOR_SUBAGENTS_ONLY: 'ansi:redBright', + blue_FOR_SUBAGENTS_ONLY: 'ansi:blueBright', + green_FOR_SUBAGENTS_ONLY: 'ansi:greenBright', + yellow_FOR_SUBAGENTS_ONLY: 'ansi:yellowBright', + purple_FOR_SUBAGENTS_ONLY: 'ansi:magentaBright', + orange_FOR_SUBAGENTS_ONLY: 'ansi:redBright', + pink_FOR_SUBAGENTS_ONLY: 'ansi:magentaBright', + cyan_FOR_SUBAGENTS_ONLY: 'ansi:cyanBright', + // Grove colors + professionalBlue: 'rgb(106,155,204)', + // Chrome colors + chromeYellow: 'ansi:yellowBright', // Chrome yellow + // TUI V2 colors + clawd_body: 'ansi:redBright', + clawd_background: 'ansi:black', + userMessageBackground: 'ansi:blackBright', + userMessageBackgroundHover: 'ansi:white', + messageActionsBackground: 'ansi:blackBright', + selectionBg: 'ansi:blue', // darker named bg for dark-ansi; bright fgs stay readable + bashMessageBackgroundColor: 'ansi:black', + + memoryBackgroundColor: 'ansi:blackBright', + rate_limit_fill: 'ansi:yellow', + rate_limit_empty: 'ansi:white', + fastMode: 'ansi:redBright', + fastModeShimmer: 'ansi:redBright', + briefLabelYou: 'ansi:blueBright', + briefLabelClaude: 'ansi:redBright', + rainbow_red: 'ansi:red', + rainbow_orange: 'ansi:redBright', + rainbow_yellow: 'ansi:yellow', + rainbow_green: 'ansi:green', + rainbow_blue: 'ansi:cyan', + rainbow_indigo: 'ansi:blue', + rainbow_violet: 'ansi:magenta', + rainbow_red_shimmer: 'ansi:redBright', + rainbow_orange_shimmer: 'ansi:yellow', + rainbow_yellow_shimmer: 'ansi:yellowBright', + rainbow_green_shimmer: 'ansi:greenBright', + rainbow_blue_shimmer: 'ansi:cyanBright', + rainbow_indigo_shimmer: 'ansi:blueBright', + rainbow_violet_shimmer: 'ansi:magentaBright', +} + +/** + * Light daltonized theme (color-blind friendly) using explicit RGB values + * to avoid inconsistencies from users' custom terminal ANSI color definitions + */ +const lightDaltonizedTheme: Theme = { + autoAccept: 'rgb(135,0,255)', // Electric violet + bashBorder: 'rgb(0,102,204)', // Blue instead of pink + claude: 'rgb(255,153,51)', // Orange adjusted for deuteranopia + claudeShimmer: 'rgb(255,183,101)', // Lighter orange for shimmer effect + claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(51,102,255)', // Bright blue for system spinner + claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(101,152,255)', // Lighter bright blue for system spinner shimmer + permission: 'rgb(51,102,255)', // Bright blue + permissionShimmer: 'rgb(101,152,255)', // Lighter bright blue for shimmer + planMode: 'rgb(51,102,102)', // Muted blue-gray (works for color-blind) + ide: 'rgb(71,130,200)', // Muted blue + promptBorder: 'rgb(153,153,153)', // Medium gray + promptBorderShimmer: 'rgb(183,183,183)', // Lighter gray for shimmer + text: 'rgb(0,0,0)', // Black + inverseText: 'rgb(255,255,255)', // White + inactive: 'rgb(102,102,102)', // Dark gray + inactiveShimmer: 'rgb(142,142,142)', // Lighter gray for shimmer effect + subtle: 'rgb(175,175,175)', // Light gray + suggestion: 'rgb(51,102,255)', // Bright blue + remember: 'rgb(51,102,255)', // Bright blue + background: 'rgb(0,153,153)', // Cyan (color-blind friendly) + success: 'rgb(0,102,153)', // Blue instead of green for deuteranopia + error: 'rgb(204,0,0)', // Pure red for better distinction + warning: 'rgb(255,153,0)', // Orange adjusted for deuteranopia + merged: 'rgb(135,0,255)', // Electric violet (matches autoAccept) + warningShimmer: 'rgb(255,183,50)', // Lighter orange for shimmer + diffAdded: 'rgb(153,204,255)', // Light blue instead of green + diffRemoved: 'rgb(255,204,204)', // Light red + diffAddedDimmed: 'rgb(209,231,253)', // Very light blue + diffRemovedDimmed: 'rgb(255,233,233)', // Very light red + diffAddedWord: 'rgb(51,102,204)', // Medium blue (less intense than deep blue) + diffRemovedWord: 'rgb(153,51,51)', // Softer red (less intense than deep red) + // Agent colors (daltonism-friendly) + red_FOR_SUBAGENTS_ONLY: 'rgb(204,0,0)', // Pure red + blue_FOR_SUBAGENTS_ONLY: 'rgb(0,102,204)', // Pure blue + green_FOR_SUBAGENTS_ONLY: 'rgb(0,204,0)', // Pure green + yellow_FOR_SUBAGENTS_ONLY: 'rgb(255,204,0)', // Golden yellow + purple_FOR_SUBAGENTS_ONLY: 'rgb(128,0,128)', // True purple + orange_FOR_SUBAGENTS_ONLY: 'rgb(255,128,0)', // True orange + pink_FOR_SUBAGENTS_ONLY: 'rgb(255,102,178)', // Adjusted pink + cyan_FOR_SUBAGENTS_ONLY: 'rgb(0,178,178)', // Adjusted cyan + // Grove colors + professionalBlue: 'rgb(106,155,204)', + // Chrome colors + chromeYellow: 'rgb(251,188,4)', // Chrome yellow + // TUI V2 colors + clawd_body: 'rgb(215,119,87)', + clawd_background: 'rgb(0,0,0)', + userMessageBackground: 'rgb(220, 220, 220)', // Slightly darker grey for optimal contrast + userMessageBackgroundHover: 'rgb(232, 232, 232)', // ≥230 to quantize distinct from base at 256-color level + messageActionsBackground: 'rgb(210, 216, 226)', // cool gray — darker than userMsg 220, slight blue + selectionBg: 'rgb(180, 213, 255)', // light selection blue; daltonized fgs are yellows/blues, both readable on light blue + bashMessageBackgroundColor: 'rgb(250, 245, 250)', + + memoryBackgroundColor: 'rgb(230, 245, 250)', + rate_limit_fill: 'rgb(51,102,255)', // Bright blue + rate_limit_empty: 'rgb(23,46,114)', // Dark blue + fastMode: 'rgb(255,106,0)', // Electric orange (color-blind safe) + fastModeShimmer: 'rgb(255,150,50)', // Lighter orange for shimmer + briefLabelYou: 'rgb(37,99,235)', // Blue + briefLabelClaude: 'rgb(255,153,51)', // Orange adjusted for deuteranopia (matches claude) + rainbow_red: 'rgb(235,95,87)', + rainbow_orange: 'rgb(245,139,87)', + rainbow_yellow: 'rgb(250,195,95)', + rainbow_green: 'rgb(145,200,130)', + rainbow_blue: 'rgb(130,170,220)', + rainbow_indigo: 'rgb(155,130,200)', + rainbow_violet: 'rgb(200,130,180)', + rainbow_red_shimmer: 'rgb(250,155,147)', + rainbow_orange_shimmer: 'rgb(255,185,137)', + rainbow_yellow_shimmer: 'rgb(255,225,155)', + rainbow_green_shimmer: 'rgb(185,230,180)', + rainbow_blue_shimmer: 'rgb(180,205,240)', + rainbow_indigo_shimmer: 'rgb(195,180,230)', + rainbow_violet_shimmer: 'rgb(230,180,210)', +} + +/** + * Dark theme using explicit RGB values to avoid inconsistencies + * from users' custom terminal ANSI color definitions + */ +const darkTheme: Theme = { + autoAccept: 'rgb(175,135,255)', // Electric violet + bashBorder: 'rgb(253,93,177)', // Bright pink + claude: 'rgb(215,119,87)', // Claude orange + claudeShimmer: 'rgb(235,159,127)', // Lighter claude orange for shimmer effect + claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(147,165,255)', // Blue for system spinner + claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(177,195,255)', // Lighter blue for system spinner shimmer + permission: 'rgb(177,185,249)', // Light blue-purple + permissionShimmer: 'rgb(207,215,255)', // Lighter blue-purple for shimmer + planMode: 'rgb(72,150,140)', // Muted sage green + ide: 'rgb(71,130,200)', // Muted blue + promptBorder: 'rgb(136,136,136)', // Medium gray + promptBorderShimmer: 'rgb(166,166,166)', // Lighter gray for shimmer + text: 'rgb(255,255,255)', // White + inverseText: 'rgb(0,0,0)', // Black + inactive: 'rgb(153,153,153)', // Light gray + inactiveShimmer: 'rgb(193,193,193)', // Lighter gray for shimmer effect + subtle: 'rgb(80,80,80)', // Dark gray + suggestion: 'rgb(177,185,249)', // Light blue-purple + remember: 'rgb(177,185,249)', // Light blue-purple + background: 'rgb(0,204,204)', // Bright cyan + success: 'rgb(78,186,101)', // Bright green + error: 'rgb(255,107,128)', // Bright red + warning: 'rgb(255,193,7)', // Bright amber + merged: 'rgb(175,135,255)', // Electric violet (matches autoAccept) + warningShimmer: 'rgb(255,223,57)', // Lighter amber for shimmer + diffAdded: 'rgb(34,92,43)', // Dark green + diffRemoved: 'rgb(122,41,54)', // Dark red + diffAddedDimmed: 'rgb(71,88,74)', // Very dark green + diffRemovedDimmed: 'rgb(105,72,77)', // Very dark red + diffAddedWord: 'rgb(56,166,96)', // Medium green + diffRemovedWord: 'rgb(179,89,107)', // Softer red (less intense than bright red) + // Agent colors + red_FOR_SUBAGENTS_ONLY: 'rgb(220,38,38)', // Red 600 + blue_FOR_SUBAGENTS_ONLY: 'rgb(37,99,235)', // Blue 600 + green_FOR_SUBAGENTS_ONLY: 'rgb(22,163,74)', // Green 600 + yellow_FOR_SUBAGENTS_ONLY: 'rgb(202,138,4)', // Yellow 600 + purple_FOR_SUBAGENTS_ONLY: 'rgb(147,51,234)', // Purple 600 + orange_FOR_SUBAGENTS_ONLY: 'rgb(234,88,12)', // Orange 600 + pink_FOR_SUBAGENTS_ONLY: 'rgb(219,39,119)', // Pink 600 + cyan_FOR_SUBAGENTS_ONLY: 'rgb(8,145,178)', // Cyan 600 + // Grove colors + professionalBlue: 'rgb(106,155,204)', + // Chrome colors + chromeYellow: 'rgb(251,188,4)', // Chrome yellow + // TUI V2 colors + clawd_body: 'rgb(215,119,87)', + clawd_background: 'rgb(0,0,0)', + userMessageBackground: 'rgb(55, 55, 55)', // Lighter grey for better visual contrast + userMessageBackgroundHover: 'rgb(70, 70, 70)', + messageActionsBackground: 'rgb(44, 50, 62)', // cool gray, slight blue + selectionBg: 'rgb(38, 79, 120)', // classic dark-mode selection blue (VS Code dark default); light fgs stay readable + bashMessageBackgroundColor: 'rgb(65, 60, 65)', + + memoryBackgroundColor: 'rgb(55, 65, 70)', + rate_limit_fill: 'rgb(177,185,249)', // Light blue-purple + rate_limit_empty: 'rgb(80,83,112)', // Medium blue-purple + fastMode: 'rgb(255,120,20)', // Electric orange for dark bg + fastModeShimmer: 'rgb(255,165,70)', // Lighter orange for shimmer + briefLabelYou: 'rgb(122,180,232)', // Light blue + briefLabelClaude: 'rgb(215,119,87)', // Brand orange + rainbow_red: 'rgb(235,95,87)', + rainbow_orange: 'rgb(245,139,87)', + rainbow_yellow: 'rgb(250,195,95)', + rainbow_green: 'rgb(145,200,130)', + rainbow_blue: 'rgb(130,170,220)', + rainbow_indigo: 'rgb(155,130,200)', + rainbow_violet: 'rgb(200,130,180)', + rainbow_red_shimmer: 'rgb(250,155,147)', + rainbow_orange_shimmer: 'rgb(255,185,137)', + rainbow_yellow_shimmer: 'rgb(255,225,155)', + rainbow_green_shimmer: 'rgb(185,230,180)', + rainbow_blue_shimmer: 'rgb(180,205,240)', + rainbow_indigo_shimmer: 'rgb(195,180,230)', + rainbow_violet_shimmer: 'rgb(230,180,210)', +} + +/** + * Dark daltonized theme (color-blind friendly) using explicit RGB values + * to avoid inconsistencies from users' custom terminal ANSI color definitions + */ +const darkDaltonizedTheme: Theme = { + autoAccept: 'rgb(175,135,255)', // Electric violet + bashBorder: 'rgb(51,153,255)', // Bright blue + claude: 'rgb(255,153,51)', // Orange adjusted for deuteranopia + claudeShimmer: 'rgb(255,183,101)', // Lighter orange for shimmer effect + claudeBlue_FOR_SYSTEM_SPINNER: 'rgb(153,204,255)', // Light blue for system spinner + claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'rgb(183,224,255)', // Lighter blue for system spinner shimmer + permission: 'rgb(153,204,255)', // Light blue + permissionShimmer: 'rgb(183,224,255)', // Lighter blue for shimmer + planMode: 'rgb(102,153,153)', // Muted gray-teal (works for color-blind) + ide: 'rgb(71,130,200)', // Muted blue + promptBorder: 'rgb(136,136,136)', // Medium gray + promptBorderShimmer: 'rgb(166,166,166)', // Lighter gray for shimmer + text: 'rgb(255,255,255)', // White + inverseText: 'rgb(0,0,0)', // Black + inactive: 'rgb(153,153,153)', // Light gray + inactiveShimmer: 'rgb(193,193,193)', // Lighter gray for shimmer effect + subtle: 'rgb(80,80,80)', // Dark gray + suggestion: 'rgb(153,204,255)', // Light blue + remember: 'rgb(153,204,255)', // Light blue + background: 'rgb(0,204,204)', // Bright cyan (color-blind friendly) + success: 'rgb(51,153,255)', // Blue instead of green + error: 'rgb(255,102,102)', // Bright red + warning: 'rgb(255,204,0)', // Yellow-orange for deuteranopia + merged: 'rgb(175,135,255)', // Electric violet (matches autoAccept) + warningShimmer: 'rgb(255,234,50)', // Lighter yellow-orange for shimmer + diffAdded: 'rgb(0,68,102)', // Dark blue + diffRemoved: 'rgb(102,0,0)', // Dark red + diffAddedDimmed: 'rgb(62,81,91)', // Dimmed blue + diffRemovedDimmed: 'rgb(62,44,44)', // Dimmed red + diffAddedWord: 'rgb(0,119,179)', // Medium blue + diffRemovedWord: 'rgb(179,0,0)', // Medium red + // Agent colors (daltonism-friendly, dark mode) + red_FOR_SUBAGENTS_ONLY: 'rgb(255,102,102)', // Bright red + blue_FOR_SUBAGENTS_ONLY: 'rgb(102,178,255)', // Bright blue + green_FOR_SUBAGENTS_ONLY: 'rgb(102,255,102)', // Bright green + yellow_FOR_SUBAGENTS_ONLY: 'rgb(255,255,102)', // Bright yellow + purple_FOR_SUBAGENTS_ONLY: 'rgb(178,102,255)', // Bright purple + orange_FOR_SUBAGENTS_ONLY: 'rgb(255,178,102)', // Bright orange + pink_FOR_SUBAGENTS_ONLY: 'rgb(255,153,204)', // Bright pink + cyan_FOR_SUBAGENTS_ONLY: 'rgb(102,204,204)', // Bright cyan + // Grove colors + professionalBlue: 'rgb(106,155,204)', + // Chrome colors + chromeYellow: 'rgb(251,188,4)', // Chrome yellow + // TUI V2 colors + clawd_body: 'rgb(215,119,87)', + clawd_background: 'rgb(0,0,0)', + userMessageBackground: 'rgb(55, 55, 55)', // Lighter grey for better visual contrast + userMessageBackgroundHover: 'rgb(70, 70, 70)', + messageActionsBackground: 'rgb(44, 50, 62)', // cool gray, slight blue + selectionBg: 'rgb(38, 79, 120)', // classic dark-mode selection blue (VS Code dark default); light fgs stay readable + bashMessageBackgroundColor: 'rgb(65, 60, 65)', + + memoryBackgroundColor: 'rgb(55, 65, 70)', + rate_limit_fill: 'rgb(153,204,255)', // Light blue + rate_limit_empty: 'rgb(69,92,115)', // Dark blue + fastMode: 'rgb(255,120,20)', // Electric orange for dark bg (color-blind safe) + fastModeShimmer: 'rgb(255,165,70)', // Lighter orange for shimmer + briefLabelYou: 'rgb(122,180,232)', // Light blue + briefLabelClaude: 'rgb(255,153,51)', // Orange adjusted for deuteranopia (matches claude) + rainbow_red: 'rgb(235,95,87)', + rainbow_orange: 'rgb(245,139,87)', + rainbow_yellow: 'rgb(250,195,95)', + rainbow_green: 'rgb(145,200,130)', + rainbow_blue: 'rgb(130,170,220)', + rainbow_indigo: 'rgb(155,130,200)', + rainbow_violet: 'rgb(200,130,180)', + rainbow_red_shimmer: 'rgb(250,155,147)', + rainbow_orange_shimmer: 'rgb(255,185,137)', + rainbow_yellow_shimmer: 'rgb(255,225,155)', + rainbow_green_shimmer: 'rgb(185,230,180)', + rainbow_blue_shimmer: 'rgb(180,205,240)', + rainbow_indigo_shimmer: 'rgb(195,180,230)', + rainbow_violet_shimmer: 'rgb(230,180,210)', +} + +export function getTheme(themeName: ThemeName): Theme { + switch (themeName) { + case 'light': + return lightTheme + case 'light-ansi': + return lightAnsiTheme + case 'dark-ansi': + return darkAnsiTheme + case 'light-daltonized': + return lightDaltonizedTheme + case 'dark-daltonized': + return darkDaltonizedTheme + default: + return darkTheme + } +} + +// Create a chalk instance with 256-color level for Apple Terminal +// Apple Terminal doesn't handle 24-bit color escape sequences well +const chalkForChart = + process.env.TERM_PROGRAM === 'Apple_Terminal' + ? new Chalk({ level: 2 }) // 256 colors + : chalk + +/** + * Converts a theme color to an ANSI escape sequence for use with asciichart. + * Uses chalk to generate the escape codes, with 256-color mode for Apple Terminal. + */ +export function themeColorToAnsi(themeColor: string): string { + const rgbMatch = themeColor.match(/rgb\(\s?(\d+),\s?(\d+),\s?(\d+)\s?\)/) + if (rgbMatch) { + const r = parseInt(rgbMatch[1]!, 10) + const g = parseInt(rgbMatch[2]!, 10) + const b = parseInt(rgbMatch[3]!, 10) + // Use chalk.rgb which auto-converts to 256 colors when level is 2 + // Extract just the opening escape sequence by using a marker + const colored = chalkForChart.rgb(r, g, b)('X') + return colored.slice(0, colored.indexOf('X')) + } + // Fallback to magenta if parsing fails + return '\x1b[35m' +} diff --git a/packages/@ant/ink/src/theme/types.ts b/packages/@ant/ink/src/theme/types.ts new file mode 100644 index 000000000..561da3b2e --- /dev/null +++ b/packages/@ant/ink/src/theme/types.ts @@ -0,0 +1,11 @@ +/** + * Theme type re-exports. + * + * ThemeName and ThemeSetting are business-level concepts stored in config; + * they live in theme-types.ts and are re-exported here for convenient + * consumption by theme-layer components. + */ +export type { Theme, ThemeName, ThemeSetting } from './theme-types.js' +export { getTheme } from './theme-types.js' +export type { ColorType } from '../core/colorize.js' +export { colorize } from '../core/colorize.js' diff --git a/packages/@ant/ink/src/types/ink-elements.d.ts b/packages/@ant/ink/src/types/ink-elements.d.ts new file mode 100644 index 000000000..e439ae56b --- /dev/null +++ b/packages/@ant/ink/src/types/ink-elements.d.ts @@ -0,0 +1,49 @@ +// Type declarations for custom Ink JSX elements +// Note: The detailed prop types are defined in ink-jsx.d.ts via React module augmentation. +// This file provides the global JSX namespace fallback declarations. +import type { ReactNode, Ref } from 'react'; +import type { ClickEvent } from '../core/events/click-event.js'; +import type { FocusEvent } from '../core/events/focus-event.js'; +import type { KeyboardEvent } from '../core/events/keyboard-event.js'; +import type { Styles, TextStyles } from '../core/styles.js'; +import type { DOMElement } from '../core/dom.js'; + +declare global { + namespace JSX { + interface IntrinsicElements { + 'ink-box': { + ref?: Ref; + tabIndex?: number; + autoFocus?: boolean; + onClick?: (event: ClickEvent) => void; + onFocus?: (event: FocusEvent) => void; + onFocusCapture?: (event: FocusEvent) => void; + onBlur?: (event: FocusEvent) => void; + onBlurCapture?: (event: FocusEvent) => void; + onMouseEnter?: () => void; + onMouseLeave?: () => void; + onKeyDown?: (event: KeyboardEvent) => void; + onKeyDownCapture?: (event: KeyboardEvent) => void; + style?: Styles; + stickyScroll?: boolean; + children?: ReactNode; + }; + 'ink-text': { + style?: Styles; + textStyles?: TextStyles; + children?: ReactNode; + }; + 'ink-link': { + href?: string; + children?: ReactNode; + }; + 'ink-raw-ansi': { + rawText?: string; + rawWidth?: number; + rawHeight?: number; + }; + } + } +} + +export {}; diff --git a/packages/@ant/ink/src/types/ink-jsx.d.ts b/packages/@ant/ink/src/types/ink-jsx.d.ts new file mode 100644 index 000000000..456be9399 --- /dev/null +++ b/packages/@ant/ink/src/types/ink-jsx.d.ts @@ -0,0 +1,54 @@ +/** + * Ink custom JSX intrinsic elements. + * + * With "jsx": "react-jsx", TypeScript resolves JSX types from react/jsx-runtime + * whose IntrinsicElements extends React.JSX.IntrinsicElements. We augment the + * 'react' module to inject our custom elements into React.JSX.IntrinsicElements. + * + * This file must be a module (have an import/export) for `declare module` + * augmentation to work correctly. + */ +import type { ReactNode, Ref } from 'react'; +import type { ClickEvent } from '../core/events/click-event.js'; +import type { FocusEvent } from '../core/events/focus-event.js'; +import type { KeyboardEvent } from '../core/events/keyboard-event.js'; +import type { Styles, TextStyles } from '../core/styles.js'; +import type { DOMElement } from '../core/dom.js'; + +declare module 'react' { + namespace JSX { + interface IntrinsicElements { + 'ink-box': { + ref?: Ref; + tabIndex?: number; + autoFocus?: boolean; + onClick?: (event: ClickEvent) => void; + onFocus?: (event: FocusEvent) => void; + onFocusCapture?: (event: FocusEvent) => void; + onBlur?: (event: FocusEvent) => void; + onBlurCapture?: (event: FocusEvent) => void; + onMouseEnter?: () => void; + onMouseLeave?: () => void; + onKeyDown?: (event: KeyboardEvent) => void; + onKeyDownCapture?: (event: KeyboardEvent) => void; + style?: Styles; + stickyScroll?: boolean; + children?: ReactNode; + }; + 'ink-text': { + style?: Styles; + textStyles?: TextStyles; + children?: ReactNode; + }; + 'ink-link': { + href?: string; + children?: ReactNode; + }; + 'ink-raw-ansi': { + rawText?: string; + rawWidth?: number; + rawHeight?: number; + }; + } + } +} diff --git a/packages/@ant/ink/src/utils/debug.ts b/packages/@ant/ink/src/utils/debug.ts new file mode 100644 index 000000000..2d3ae198e --- /dev/null +++ b/packages/@ant/ink/src/utils/debug.ts @@ -0,0 +1,2 @@ +// Stub debug logger for package independence +export function logForDebugging(..._args: unknown[]): void {} diff --git a/src/bridge/bridgeStatusUtil.ts b/src/bridge/bridgeStatusUtil.ts index 90de462e8..d9c285d70 100644 --- a/src/bridge/bridgeStatusUtil.ts +++ b/src/bridge/bridgeStatusUtil.ts @@ -2,7 +2,7 @@ import { getClaudeAiBaseUrl, getRemoteSessionUrl, } from '../constants/product.js' -import { stringWidth } from '../ink/stringWidth.js' +import { stringWidth } from '@anthropic/ink' import { formatDuration, truncateToWidth } from '../utils/format.js' import { getGraphemeSegmenter } from '../utils/intl.js' diff --git a/src/bridge/bridgeUI.ts b/src/bridge/bridgeUI.ts index 5149839ea..67a5132d2 100644 --- a/src/bridge/bridgeUI.ts +++ b/src/bridge/bridgeUI.ts @@ -5,7 +5,7 @@ import { BRIDGE_READY_INDICATOR, BRIDGE_SPINNER_FRAMES, } from '../constants/figures.js' -import { stringWidth } from '../ink/stringWidth.js' +import { stringWidth } from '@anthropic/ink' import { logForDebugging } from '../utils/debug.js' import { buildActiveFooterText, diff --git a/src/buddy/CompanionCard.tsx b/src/buddy/CompanionCard.tsx index f9264acf3..1f571605c 100644 --- a/src/buddy/CompanionCard.tsx +++ b/src/buddy/CompanionCard.tsx @@ -3,8 +3,8 @@ * Mirrors official vc8 component: bordered box with sprite, stats, last reaction. */ import React from 'react'; -import { Box, Text } from '../ink.js'; -import { useInput } from '../ink.js'; +import { Box, Text } from '@anthropic/ink'; +import { useInput } from '@anthropic/ink'; import { renderSprite } from './sprites.js'; import { RARITY_COLORS, RARITY_STARS, STAT_NAMES, type Companion } from './types.js'; diff --git a/src/buddy/CompanionSprite.tsx b/src/buddy/CompanionSprite.tsx index d8c7ae473..22dbeb643 100644 --- a/src/buddy/CompanionSprite.tsx +++ b/src/buddy/CompanionSprite.tsx @@ -2,8 +2,7 @@ import { feature } from 'bun:bundle' import figures from 'figures' import React, { useEffect, useRef, useState } from 'react' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { stringWidth } from '../ink/stringWidth.js' -import { Box, Text } from '../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import { useAppState, useSetAppState } from '../state/AppState.js' import type { AppState } from '../state/AppStateStore.js' import { getGlobalConfig } from '../utils/config.js' diff --git a/src/buddy/useBuddyNotification.tsx b/src/buddy/useBuddyNotification.tsx index 62d61f4cf..2df078e79 100644 --- a/src/buddy/useBuddyNotification.tsx +++ b/src/buddy/useBuddyNotification.tsx @@ -1,7 +1,7 @@ import { feature } from 'bun:bundle' import React, { useEffect } from 'react' import { useNotifications } from '../context/notifications.js' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { getGlobalConfig } from '../utils/config.js' import { getRainbowColor } from '../utils/thinking.js' diff --git a/src/cli/handlers/mcp.tsx b/src/cli/handlers/mcp.tsx index 134918c75..b9c030da9 100644 --- a/src/cli/handlers/mcp.tsx +++ b/src/cli/handlers/mcp.tsx @@ -8,7 +8,7 @@ import pMap from 'p-map' import { cwd } from 'process' import React from 'react' import { MCPServerDesktopImportDialog } from '../../components/MCPServerDesktopImportDialog.js' -import { render } from '../../ink.js' +import { wrappedRender as render } from '@anthropic/ink' import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, diff --git a/src/cli/handlers/util.tsx b/src/cli/handlers/util.tsx index c86b31737..b1a8cc0b9 100644 --- a/src/cli/handlers/util.tsx +++ b/src/cli/handlers/util.tsx @@ -8,8 +8,8 @@ import { cwd } from 'process' import React from 'react' import { WelcomeV2 } from '../../components/LogoV2/WelcomeV2.js' import { useManagePlugins } from '../../hooks/useManagePlugins.js' -import type { Root } from '../../ink.js' -import { Box, Text } from '../../ink.js' +import type { Root } from '@anthropic/ink' +import { Box, Text } from '@anthropic/ink' import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js' import { logEvent } from '../../services/analytics/index.js' import { MCPConnectionManager } from '../../services/mcp/MCPConnectionManager.js' diff --git a/src/commands/add-dir/add-dir.tsx b/src/commands/add-dir/add-dir.tsx index cfb6c6687..91180cd73 100644 --- a/src/commands/add-dir/add-dir.tsx +++ b/src/commands/add-dir/add-dir.tsx @@ -8,7 +8,7 @@ import { 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 { Box, Text } from '@anthropic/ink' import type { LocalJSXCommandOnDone } from '../../types/command.js' import { applyPermissionUpdate, diff --git a/src/commands/bridge/bridge.tsx b/src/commands/bridge/bridge.tsx index 33a681202..78b0341f1 100644 --- a/src/commands/bridge/bridge.tsx +++ b/src/commands/bridge/bridge.tsx @@ -13,11 +13,10 @@ 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 { Dialog, ListItem } from '@anthropic/ink' import { shouldShowRemoteCallout } from '../../components/RemoteCallout.js' import { useRegisterOverlay } from '../../context/overlayContext.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, diff --git a/src/commands/btw/btw.tsx b/src/commands/btw/btw.tsx index 28a83946b..753b5821e 100644 --- a/src/commands/btw/btw.tsx +++ b/src/commands/btw/btw.tsx @@ -9,11 +9,8 @@ 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 KeyboardEvent, type ScrollBoxHandle, ScrollBox } from '@anthropic/ink' +import { Box, Text } from '@anthropic/ink' import type { LocalJSXCommandOnDone } from '../../types/command.js' import type { Message } from '../../types/message.js' import { createAbortController } from '../../utils/abortController.js' diff --git a/src/commands/chrome/chrome.tsx b/src/commands/chrome/chrome.tsx index 3fd0dbca3..1fe1b1470 100644 --- a/src/commands/chrome/chrome.tsx +++ b/src/commands/chrome/chrome.tsx @@ -3,8 +3,8 @@ import { type OptionWithDescription, Select, } from '../../components/CustomSelect/select.js' -import { Dialog } from '../../components/design-system/Dialog.js' -import { Box, Text } from '../../ink.js' +import { Dialog } from '@anthropic/ink' +import { Box, Text } from '@anthropic/ink' import { useAppState } from '../../state/AppState.js' import { isClaudeAISubscriber } from '../../utils/auth.js' import { openBrowser } from '../../utils/browser.js' diff --git a/src/commands/copy/copy.tsx b/src/commands/copy/copy.tsx index d5196de20..e54171a63 100644 --- a/src/commands/copy/copy.tsx +++ b/src/commands/copy/copy.tsx @@ -6,13 +6,8 @@ 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 { Byline, KeyboardShortcutHint, Pane } from '@anthropic/ink' +import { Box, setClipboard, Text, stringWidth, type KeyboardEvent } from '@anthropic/ink' import { logEvent } from '../../services/analytics/index.js' import type { LocalJSXCommandCall } from '../../types/command.js' import type { AssistantMessage, Message } from '../../types/message.js' diff --git a/src/commands/fast/fast.tsx b/src/commands/fast/fast.tsx index a959a909a..2ab17db78 100644 --- a/src/commands/fast/fast.tsx +++ b/src/commands/fast/fast.tsx @@ -4,9 +4,9 @@ import type { CommandResultDisplay, LocalJSXCommandContext, } from '../../commands.js' -import { Dialog } from '../../components/design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { FastIcon, getFastIconString } from '../../components/FastIcon.js' -import { Box, Link, Text } from '../../ink.js' +import { Box, Link, Text } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, diff --git a/src/commands/ide/ide.tsx b/src/commands/ide/ide.tsx index f22c16e6a..d5944636d 100644 --- a/src/commands/ide/ide.tsx +++ b/src/commands/ide/ide.tsx @@ -7,14 +7,14 @@ import type { LocalJSXCommandContext, } from '../../commands.js' import { Select } from '../../components/CustomSelect/index.js' -import { Dialog } from '../../components/design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { IdeAutoConnectDialog, IdeDisableAutoConnectDialog, shouldShowAutoConnectDialog, shouldShowDisableAutoConnectDialog, } from '../../components/IdeAutoConnectDialog.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { clearServerCache } from '../../services/mcp/client.js' import type { ScopedMcpServerConfig } from '../../services/mcp/types.js' import { useAppState, useSetAppState } from '../../state/AppState.js' diff --git a/src/commands/install-github-app/ApiKeyStep.tsx b/src/commands/install-github-app/ApiKeyStep.tsx index 3b88a94f3..942bc662a 100644 --- a/src/commands/install-github-app/ApiKeyStep.tsx +++ b/src/commands/install-github-app/ApiKeyStep.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useState } from 'react' import TextInput from '../../components/TextInput.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, color, Text, useTheme } from '../../ink.js' +import { Box, color, Text, useTheme } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' interface ApiKeyStepProps { diff --git a/src/commands/install-github-app/CheckExistingSecretStep.tsx b/src/commands/install-github-app/CheckExistingSecretStep.tsx index b00b682c2..de7f4b9a7 100644 --- a/src/commands/install-github-app/CheckExistingSecretStep.tsx +++ b/src/commands/install-github-app/CheckExistingSecretStep.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useState } from 'react' import TextInput from '../../components/TextInput.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, color, Text, useTheme } from '../../ink.js' +import { Box, color, Text, useTheme } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' interface CheckExistingSecretStepProps { diff --git a/src/commands/install-github-app/CheckGitHubStep.tsx b/src/commands/install-github-app/CheckGitHubStep.tsx index 16f4d7e8a..a43be6c6c 100644 --- a/src/commands/install-github-app/CheckGitHubStep.tsx +++ b/src/commands/install-github-app/CheckGitHubStep.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' export function CheckGitHubStep() { return Checking GitHub CLI installation… diff --git a/src/commands/install-github-app/ChooseRepoStep.tsx b/src/commands/install-github-app/ChooseRepoStep.tsx index b0d4c63b0..67e921834 100644 --- a/src/commands/install-github-app/ChooseRepoStep.tsx +++ b/src/commands/install-github-app/ChooseRepoStep.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useState } from 'react' import TextInput from '../../components/TextInput.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' interface ChooseRepoStepProps { diff --git a/src/commands/install-github-app/CreatingStep.tsx b/src/commands/install-github-app/CreatingStep.tsx index 1861571ed..c021d0bcb 100644 --- a/src/commands/install-github-app/CreatingStep.tsx +++ b/src/commands/install-github-app/CreatingStep.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { Workflow } from './types.js' interface CreatingStepProps { diff --git a/src/commands/install-github-app/ErrorStep.tsx b/src/commands/install-github-app/ErrorStep.tsx index a8333f395..5864a0659 100644 --- a/src/commands/install-github-app/ErrorStep.tsx +++ b/src/commands/install-github-app/ErrorStep.tsx @@ -1,6 +1,6 @@ import React from 'react' import { GITHUB_ACTION_SETUP_DOCS_URL } from '../../constants/github-app.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' interface ErrorStepProps { error: string | undefined diff --git a/src/commands/install-github-app/ExistingWorkflowStep.tsx b/src/commands/install-github-app/ExistingWorkflowStep.tsx index 645edb742..11b0a1bb2 100644 --- a/src/commands/install-github-app/ExistingWorkflowStep.tsx +++ b/src/commands/install-github-app/ExistingWorkflowStep.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Select } from 'src/components/CustomSelect/index.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' interface ExistingWorkflowStepProps { repoName: string diff --git a/src/commands/install-github-app/InstallAppStep.tsx b/src/commands/install-github-app/InstallAppStep.tsx index 98a699945..e966578c1 100644 --- a/src/commands/install-github-app/InstallAppStep.tsx +++ b/src/commands/install-github-app/InstallAppStep.tsx @@ -1,7 +1,7 @@ import figures from 'figures' import React from 'react' import { GITHUB_ACTION_SETUP_DOCS_URL } from '../../constants/github-app.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' interface InstallAppStepProps { diff --git a/src/commands/install-github-app/OAuthFlowStep.tsx b/src/commands/install-github-app/OAuthFlowStep.tsx index b8fd96a49..b13493f18 100644 --- a/src/commands/install-github-app/OAuthFlowStep.tsx +++ b/src/commands/install-github-app/OAuthFlowStep.tsx @@ -3,13 +3,11 @@ import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, } from 'src/services/analytics/index.js' -import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js' +import { KeyboardShortcutHint } from '@anthropic/ink' import { Spinner } from '../../components/Spinner.js' import TextInput from '../../components/TextInput.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { setClipboard } from '../../ink/termio/osc.js' -import { Box, Link, Text } from '../../ink.js' +import { type KeyboardEvent, setClipboard, Box, Link, Text } from '@anthropic/ink' import { OAuthService } from '../../services/oauth/index.js' import { saveOAuthTokensIfNeeded } from '../../utils/auth.js' import { logError } from '../../utils/log.js' diff --git a/src/commands/install-github-app/SuccessStep.tsx b/src/commands/install-github-app/SuccessStep.tsx index a04b98ac7..2080f3dc7 100644 --- a/src/commands/install-github-app/SuccessStep.tsx +++ b/src/commands/install-github-app/SuccessStep.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' type SuccessStepProps = { secretExists: boolean diff --git a/src/commands/install-github-app/WarningsStep.tsx b/src/commands/install-github-app/WarningsStep.tsx index 122cdeac3..c3d347798 100644 --- a/src/commands/install-github-app/WarningsStep.tsx +++ b/src/commands/install-github-app/WarningsStep.tsx @@ -1,7 +1,7 @@ import figures from 'figures' import React from 'react' import { GITHUB_ACTION_SETUP_DOCS_URL } from '../../constants/github-app.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import type { Warning } from './types.js' diff --git a/src/commands/install-github-app/install-github-app.tsx b/src/commands/install-github-app/install-github-app.tsx index 3a78ae106..c57be920c 100644 --- a/src/commands/install-github-app/install-github-app.tsx +++ b/src/commands/install-github-app/install-github-app.tsx @@ -7,8 +7,7 @@ import { import { WorkflowMultiselectDialog } from '../../components/WorkflowMultiselectDialog.js' import { GITHUB_ACTION_SETUP_DOCS_URL } from '../../constants/github-app.js' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box } from '../../ink.js' +import { type KeyboardEvent, Box } from '@anthropic/ink' import type { LocalJSXCommandOnDone } from '../../types/command.js' import { getAnthropicApiKey, isAnthropicAuthEnabled } from '../../utils/auth.js' import { openBrowser } from '../../utils/browser.js' diff --git a/src/commands/install.tsx b/src/commands/install.tsx index 15eddd575..bb72ad4a8 100644 --- a/src/commands/install.tsx +++ b/src/commands/install.tsx @@ -3,8 +3,8 @@ import { join } from 'node:path' import React, { useEffect, useState } from 'react' import type { CommandResultDisplay } from 'src/commands.js' import { logEvent } from 'src/services/analytics/index.js' -import { StatusIcon } from '../components/design-system/StatusIcon.js' -import { Box, render, Text } from '../ink.js' +import { StatusIcon } from '@anthropic/ink' +import { Box, wrappedRender as render, Text } from '@anthropic/ink' import { logForDebugging } from '../utils/debug.js' import { env } from '../utils/env.js' import { errorMessage } from '../utils/errors.js' diff --git a/src/commands/login/login.tsx b/src/commands/login/login.tsx index 912ad61f9..b4329fe62 100644 --- a/src/commands/login/login.tsx +++ b/src/commands/login/login.tsx @@ -8,9 +8,9 @@ import { import type { LocalJSXCommandContext } from '../../commands.js' import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js' import { ConsoleOAuthFlow } from '../../components/ConsoleOAuthFlow.js' -import { Dialog } from '../../components/design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { refreshGrowthBookAfterAuthChange } from '../../services/analytics/growthbook.js' import { refreshPolicyLimits } from '../../services/policyLimits/index.js' import { refreshRemoteManagedSettings } from '../../services/remoteManagedSettings/index.js' diff --git a/src/commands/logout/logout.tsx b/src/commands/logout/logout.tsx index b8eb13b87..4223feff4 100644 --- a/src/commands/logout/logout.tsx +++ b/src/commands/logout/logout.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { clearTrustedDeviceTokenCache } from '../../bridge/trustedDevice.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { refreshGrowthBookAfterAuthChange } from '../../services/analytics/growthbook.js' import { getGroveNoticeConfig, diff --git a/src/commands/memory/memory.tsx b/src/commands/memory/memory.tsx index 945b34a60..885ab57dd 100644 --- a/src/commands/memory/memory.tsx +++ b/src/commands/memory/memory.tsx @@ -1,10 +1,10 @@ import { mkdir, writeFile } from 'fs/promises' import * as React from 'react' import type { CommandResultDisplay } from '../../commands.js' -import { Dialog } from '../../components/design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { MemoryFileSelector } from '../../components/memory/MemoryFileSelector.js' import { getRelativeMemoryPath } from '../../components/memory/MemoryUpdateNotification.js' -import { Box, Link, Text } from '../../ink.js' +import { Box, Link, Text } from '@anthropic/ink' import type { LocalJSXCommandCall } from '../../types/command.js' import { clearMemoryFileCaches, getMemoryFiles } from '../../utils/claudemd.js' import { getClaudeConfigHomeDir } from '../../utils/envUtils.js' diff --git a/src/commands/mobile/mobile.tsx b/src/commands/mobile/mobile.tsx index 0467919bb..6c1d8f828 100644 --- a/src/commands/mobile/mobile.tsx +++ b/src/commands/mobile/mobile.tsx @@ -1,9 +1,8 @@ import { toString as qrToString } from 'qrcode' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' -import { Pane } from '../../components/design-system/Pane.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text } from '../../ink.js' +import { Pane } from '@anthropic/ink' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import type { LocalJSXCommandOnDone } from '../../types/command.js' diff --git a/src/commands/plan/plan.tsx b/src/commands/plan/plan.tsx index 8c3f328a7..d7694182c 100644 --- a/src/commands/plan/plan.tsx +++ b/src/commands/plan/plan.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { handlePlanModeTransition } from '../../bootstrap/state.js' import type { LocalJSXCommandContext } from '../../commands.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { LocalJSXCommandOnDone } from '../../types/command.js' import { getExternalEditor } from '../../utils/editor.js' import { toIDEDisplayName } from '../../utils/ide.js' diff --git a/src/commands/plugin/AddMarketplace.tsx b/src/commands/plugin/AddMarketplace.tsx index e0a1d4d16..7a9138333 100644 --- a/src/commands/plugin/AddMarketplace.tsx +++ b/src/commands/plugin/AddMarketplace.tsx @@ -5,11 +5,10 @@ import { logEvent, } from 'src/services/analytics/index.js' import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js' -import { Byline } from '../../components/design-system/Byline.js' -import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { Spinner } from '../../components/Spinner.js' import TextInput from '../../components/TextInput.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { toError } from '../../utils/errors.js' import { logError } from '../../utils/log.js' import { clearAllCaches } from '../../utils/plugins/cacheUtils.js' diff --git a/src/commands/plugin/BrowseMarketplace.tsx b/src/commands/plugin/BrowseMarketplace.tsx index e8733052e..8e3205f5e 100644 --- a/src/commands/plugin/BrowseMarketplace.tsx +++ b/src/commands/plugin/BrowseMarketplace.tsx @@ -2,8 +2,7 @@ import figures from 'figures' import * as React from 'react' import { useEffect, useState } from 'react' import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js' -import { Byline } from '../../components/design-system/Byline.js' -import { Box, Text } from '../../ink.js' +import { Box, Byline, Text } from '@anthropic/ink' import { useKeybinding, useKeybindings, diff --git a/src/commands/plugin/DiscoverPlugins.tsx b/src/commands/plugin/DiscoverPlugins.tsx index 442e2686f..4c3435de1 100644 --- a/src/commands/plugin/DiscoverPlugins.tsx +++ b/src/commands/plugin/DiscoverPlugins.tsx @@ -2,12 +2,12 @@ import figures from 'figures' import * as React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js' -import { Byline } from '../../components/design-system/Byline.js' import { SearchBox } from '../../components/SearchBox.js' +import { Byline } from '@anthropic/ink' import { useSearchInput } from '../../hooks/useSearchInput.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- useInput needed for raw search mode text input -import { Box, Text, useInput, useTerminalFocus } from '../../ink.js' +import { Box, Text, useInput, useTerminalFocus } from '@anthropic/ink' import { useKeybinding, useKeybindings, diff --git a/src/commands/plugin/ManageMarketplaces.tsx b/src/commands/plugin/ManageMarketplaces.tsx index 868f26e32..5ec3dbe80 100644 --- a/src/commands/plugin/ManageMarketplaces.tsx +++ b/src/commands/plugin/ManageMarketplaces.tsx @@ -6,10 +6,9 @@ import { logEvent, } from 'src/services/analytics/index.js' import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js' -import { Byline } from '../../components/design-system/Byline.js' -import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- useInput needed for marketplace-specific u/r shortcuts and y/n confirmation not in keybinding schema -import { Box, Text, useInput } from '../../ink.js' +import { Box, Text, useInput } from '@anthropic/ink' import { useKeybinding, useKeybindings, diff --git a/src/commands/plugin/ManagePlugins.tsx b/src/commands/plugin/ManagePlugins.tsx index e1ec554ac..a3524724c 100644 --- a/src/commands/plugin/ManagePlugins.tsx +++ b/src/commands/plugin/ManagePlugins.tsx @@ -5,7 +5,7 @@ import * as path from 'path' import * as React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js' -import { Byline } from '../../components/design-system/Byline.js' +import { Byline } from '@anthropic/ink' import { MCPRemoteServerMenu } from '../../components/mcp/MCPRemoteServerMenu.js' import { MCPStdioServerMenu } from '../../components/mcp/MCPStdioServerMenu.js' import { MCPToolDetailView } from '../../components/mcp/MCPToolDetailView.js' @@ -20,7 +20,7 @@ import { SearchBox } from '../../components/SearchBox.js' import { useSearchInput } from '../../hooks/useSearchInput.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- useInput needed for raw search mode text input -import { Box, Text, useInput, useTerminalFocus } from '../../ink.js' +import { Box, Text, useInput, useTerminalFocus } from '@anthropic/ink' import { useKeybinding, useKeybindings, diff --git a/src/commands/plugin/PluginOptionsDialog.tsx b/src/commands/plugin/PluginOptionsDialog.tsx index 8ef1f6809..cb09352d3 100644 --- a/src/commands/plugin/PluginOptionsDialog.tsx +++ b/src/commands/plugin/PluginOptionsDialog.tsx @@ -1,9 +1,8 @@ import figures from 'figures' import React, { useCallback, useState } from 'react' -import { Dialog } from '../../components/design-system/Dialog.js' -import { stringWidth } from '../../ink/stringWidth.js' +import { Dialog } from '@anthropic/ink' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw text input for config dialog -import { Box, Text, useInput } from '../../ink.js' +import { Box, Text, useInput, stringWidth } from '@anthropic/ink' import { useKeybinding, useKeybindings, diff --git a/src/commands/plugin/PluginSettings.tsx b/src/commands/plugin/PluginSettings.tsx index e0c3d54da..4d544ef47 100644 --- a/src/commands/plugin/PluginSettings.tsx +++ b/src/commands/plugin/PluginSettings.tsx @@ -2,11 +2,10 @@ import figures from 'figures' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js' -import { Byline } from '../../components/design-system/Byline.js' -import { Pane } from '../../components/design-system/Pane.js' -import { Tab, Tabs } from '../../components/design-system/Tabs.js' +import { Byline, Pane, Tabs } from '@anthropic/ink' +import { Tab } from '../../components/design-system/Tabs.js' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding, useKeybindings, diff --git a/src/commands/plugin/PluginTrustWarning.tsx b/src/commands/plugin/PluginTrustWarning.tsx index 2295db691..3be3dd79f 100644 --- a/src/commands/plugin/PluginTrustWarning.tsx +++ b/src/commands/plugin/PluginTrustWarning.tsx @@ -1,6 +1,6 @@ import figures from 'figures' import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getPluginTrustMessage } from '../../utils/plugins/marketplaceHelpers.js' export function PluginTrustWarning(): React.ReactNode { diff --git a/src/commands/plugin/UnifiedInstalledCell.tsx b/src/commands/plugin/UnifiedInstalledCell.tsx index 5ce8783de..05e44821f 100644 --- a/src/commands/plugin/UnifiedInstalledCell.tsx +++ b/src/commands/plugin/UnifiedInstalledCell.tsx @@ -1,6 +1,6 @@ import figures from 'figures' import * as React from 'react' -import { Box, color, Text, useTheme } from '../../ink.js' +import { Box, color, Text, useTheme } from '@anthropic/ink' import { plural } from '../../utils/stringUtils.js' import type { UnifiedInstalledItem } from './unifiedTypes.js' diff --git a/src/commands/plugin/ValidatePlugin.tsx b/src/commands/plugin/ValidatePlugin.tsx index 4d8afd8b6..9eb52d4b6 100644 --- a/src/commands/plugin/ValidatePlugin.tsx +++ b/src/commands/plugin/ValidatePlugin.tsx @@ -1,7 +1,7 @@ import figures from 'figures' import * as React from 'react' import { useEffect } from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { errorMessage } from '../../utils/errors.js' import { logError } from '../../utils/log.js' import { validateManifest } from '../../utils/plugins/validatePlugin.js' diff --git a/src/commands/plugin/pluginDetailsHelpers.tsx b/src/commands/plugin/pluginDetailsHelpers.tsx index caec86b46..2a9909e1a 100644 --- a/src/commands/plugin/pluginDetailsHelpers.tsx +++ b/src/commands/plugin/pluginDetailsHelpers.tsx @@ -6,8 +6,7 @@ import * as React from 'react' import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js' -import { Byline } from '../../components/design-system/Byline.js' -import { Box, Text } from '../../ink.js' +import { Box, Byline, Text } from '@anthropic/ink' import type { PluginMarketplaceEntry } from '../../utils/plugins/schemas.js' /** diff --git a/src/commands/rate-limit-options/rate-limit-options.tsx b/src/commands/rate-limit-options/rate-limit-options.tsx index e86eb040d..50198f561 100644 --- a/src/commands/rate-limit-options/rate-limit-options.tsx +++ b/src/commands/rate-limit-options/rate-limit-options.tsx @@ -7,7 +7,7 @@ import { type OptionWithDescription, Select, } from '../../components/CustomSelect/select.js' -import { Dialog } from '../../components/design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' import { logEvent } from '../../services/analytics/index.js' import { useClaudeAiLimits } from '../../services/claudeAiLimitsHook.js' diff --git a/src/commands/remote-setup/remote-setup.tsx b/src/commands/remote-setup/remote-setup.tsx index 05813453d..e511f064b 100644 --- a/src/commands/remote-setup/remote-setup.tsx +++ b/src/commands/remote-setup/remote-setup.tsx @@ -2,9 +2,7 @@ import { execa } from 'execa' import * as React from 'react' import { useEffect, useState } from 'react' import { Select } from '../../components/CustomSelect/index.js' -import { Dialog } from '../../components/design-system/Dialog.js' -import { LoadingState } from '../../components/design-system/LoadingState.js' -import { Box, Text } from '../../ink.js' +import { Box, Dialog, LoadingState, Text } from '@anthropic/ink' import { logEvent, type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS as SafeString, diff --git a/src/commands/resume/resume.tsx b/src/commands/resume/resume.tsx index f66d654c6..795c05077 100644 --- a/src/commands/resume/resume.tsx +++ b/src/commands/resume/resume.tsx @@ -9,8 +9,8 @@ import { MessageResponse } from '../../components/MessageResponse.js' import { Spinner } from '../../components/Spinner.js' import { useIsInsideModal } from '../../context/modalContext.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { setClipboard } from '../../ink/termio/osc.js' -import { Box, Text } from '../../ink.js' +import { setClipboard } from '@anthropic/ink' +import { Box, Text } from '@anthropic/ink' import type { LocalJSXCommandCall } from '../../types/command.js' import type { LogOption } from '../../types/logs.js' import { agenticSessionSearch } from '../../utils/agenticSessionSearch.js' diff --git a/src/commands/review/UltrareviewOverageDialog.tsx b/src/commands/review/UltrareviewOverageDialog.tsx index 020db57f8..12ea10429 100644 --- a/src/commands/review/UltrareviewOverageDialog.tsx +++ b/src/commands/review/UltrareviewOverageDialog.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useRef, useState } from 'react' import { Select } from '../../components/CustomSelect/select.js' -import { Dialog } from '../../components/design-system/Dialog.js' -import { Box, Text } from '../../ink.js' +import { Box, Dialog, Text } from '@anthropic/ink' type Props = { onProceed: (signal: AbortSignal) => Promise diff --git a/src/commands/sandbox-toggle/sandbox-toggle.tsx b/src/commands/sandbox-toggle/sandbox-toggle.tsx index 157961ad5..5f8d46097 100644 --- a/src/commands/sandbox-toggle/sandbox-toggle.tsx +++ b/src/commands/sandbox-toggle/sandbox-toggle.tsx @@ -2,7 +2,7 @@ import { relative } from 'path' import React from 'react' import { getCwdState } from '../../bootstrap/state.js' import { SandboxSettings } from '../../components/sandbox/SandboxSettings.js' -import { color } from '../../ink.js' +import { color } from '@anthropic/ink' import { getPlatform } from '../../utils/platform.js' import { addToExcludedCommands, diff --git a/src/commands/session/session.tsx b/src/commands/session/session.tsx index 82135a3fa..18312e183 100644 --- a/src/commands/session/session.tsx +++ b/src/commands/session/session.tsx @@ -1,8 +1,7 @@ import { toString as qrToString } from 'qrcode' import * as React from 'react' import { useEffect, useState } from 'react' -import { Pane } from '../../components/design-system/Pane.js' -import { Box, Text } from '../../ink.js' +import { Box, Pane, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { useAppState } from '../../state/AppState.js' import type { LocalJSXCommandCall } from '../../types/command.js' diff --git a/src/commands/tag/tag.tsx b/src/commands/tag/tag.tsx index c9d0c6524..ea5813d59 100644 --- a/src/commands/tag/tag.tsx +++ b/src/commands/tag/tag.tsx @@ -4,9 +4,9 @@ import * as React from 'react' import { getSessionId } from '../../bootstrap/state.js' import type { CommandResultDisplay } from '../../commands.js' import { Select } from '../../components/CustomSelect/select.js' -import { Dialog } from '../../components/design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { COMMON_HELP_ARGS, COMMON_INFO_ARGS } from '../../constants/xml.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { logEvent } from '../../services/analytics/index.js' import type { LocalJSXCommandOnDone } from '../../types/command.js' import { recursivelySanitizeUnicode } from '../../utils/sanitization.js' diff --git a/src/commands/terminalSetup/terminalSetup.tsx b/src/commands/terminalSetup/terminalSetup.tsx index 52b8cdea2..c7776da46 100644 --- a/src/commands/terminalSetup/terminalSetup.tsx +++ b/src/commands/terminalSetup/terminalSetup.tsx @@ -5,8 +5,8 @@ import { homedir, platform } from 'os' import { dirname, join } from 'path' import type { ThemeName } from 'src/utils/theme.js' import { pathToFileURL } from 'url' -import { supportsHyperlinks } from '../../ink/supports-hyperlinks.js' -import { color } from '../../ink.js' +import { supportsHyperlinks } from '@anthropic/ink' +import { color } from '@anthropic/ink' import { maybeMarkProjectOnboardingComplete } from '../../projectOnboardingState.js' import type { ToolUseContext } from '../../Tool.js' import type { diff --git a/src/commands/theme/theme.tsx b/src/commands/theme/theme.tsx index 03ad2fd13..26d4bd1aa 100644 --- a/src/commands/theme/theme.tsx +++ b/src/commands/theme/theme.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import type { CommandResultDisplay } from '../../commands.js' -import { Pane } from '../../components/design-system/Pane.js' +import { Pane } from '@anthropic/ink' import { ThemePicker } from '../../components/ThemePicker.js' -import { useTheme } from '../../ink.js' +import { useTheme } from '@anthropic/ink' import type { LocalJSXCommandCall } from '../../types/command.js' type Props = { diff --git a/src/commands/thinkback/thinkback.tsx b/src/commands/thinkback/thinkback.tsx index feea58a66..1986cd4ed 100644 --- a/src/commands/thinkback/thinkback.tsx +++ b/src/commands/thinkback/thinkback.tsx @@ -5,10 +5,9 @@ import * as React from 'react' import { useCallback, useEffect, useState } from 'react' import type { CommandResultDisplay } from '../../commands.js' import { Select } from '../../components/CustomSelect/select.js' -import { Dialog } from '../../components/design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { Spinner } from '../../components/Spinner.js' -import instances from '../../ink/instances.js' -import { Box, Text } from '../../ink.js' +import { Box, Text, instances } from '@anthropic/ink' import { enablePluginOp } from '../../services/plugins/pluginOperations.js' import { logForDebugging } from '../../utils/debug.js' import { isENOENT, toError } from '../../utils/errors.js' diff --git a/src/components/AgentProgressLine.tsx b/src/components/AgentProgressLine.tsx index 7580160e7..c15a765a4 100644 --- a/src/components/AgentProgressLine.tsx +++ b/src/components/AgentProgressLine.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { formatNumber } from '../utils/format.js' import type { Theme } from '../utils/theme.js' diff --git a/src/components/ApproveApiKey.tsx b/src/components/ApproveApiKey.tsx index 990f0c14e..545e78f6f 100644 --- a/src/components/ApproveApiKey.tsx +++ b/src/components/ApproveApiKey.tsx @@ -1,8 +1,7 @@ import React from 'react' -import { Text } from '../ink.js' +import { Text, Dialog } from '@anthropic/ink' import { saveGlobalConfig } from '../utils/config.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' type Props = { customApiKeyTruncated: string diff --git a/src/components/AutoModeOptInDialog.tsx b/src/components/AutoModeOptInDialog.tsx index 4aeee28e6..a6d7f6a12 100644 --- a/src/components/AutoModeOptInDialog.tsx +++ b/src/components/AutoModeOptInDialog.tsx @@ -1,9 +1,8 @@ import React from 'react' import { logEvent } from 'src/services/analytics/index.js' -import { Box, Link, Text } from '../ink.js' +import { Box, Dialog, Link, Text } from '@anthropic/ink' import { updateSettingsForSource } from '../utils/settings/settings.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' // NOTE: This copy is legally reviewed — do not modify without Legal team approval. export const AUTO_MODE_DESCRIPTION = diff --git a/src/components/AutoUpdater.tsx b/src/components/AutoUpdater.tsx index 09b523fc4..cf41e7d84 100644 --- a/src/components/AutoUpdater.tsx +++ b/src/components/AutoUpdater.tsx @@ -6,7 +6,7 @@ import { } from 'src/services/analytics/index.js' import { useInterval } from 'usehooks-ts' import { useUpdateNotification } from '../hooks/useUpdateNotification.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { type AutoUpdaterResult, getLatestVersion, diff --git a/src/components/AwsAuthStatusBox.tsx b/src/components/AwsAuthStatusBox.tsx index ea2d1a5d3..fbe0216d7 100644 --- a/src/components/AwsAuthStatusBox.tsx +++ b/src/components/AwsAuthStatusBox.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { Box, Link, Text } from '../ink.js' +import { Box, Link, Text } from '@anthropic/ink' import { type AwsAuthStatus, AwsAuthStatusManager, diff --git a/src/components/BaseTextInput.tsx b/src/components/BaseTextInput.tsx index 07d12974b..054e6e2e0 100644 --- a/src/components/BaseTextInput.tsx +++ b/src/components/BaseTextInput.tsx @@ -1,8 +1,8 @@ import React from 'react' import { renderPlaceholder } from '../hooks/renderPlaceholder.js' import { usePasteHandler } from '../hooks/usePasteHandler.js' -import { useDeclaredCursor } from '../ink/hooks/use-declared-cursor.js' -import { Ansi, Box, Text, useInput } from '../ink.js' +import { useDeclaredCursor } from '@anthropic/ink' +import { Ansi, Box, Text, useInput } from '@anthropic/ink' import type { BaseInputState, BaseTextInputProps, diff --git a/src/components/BashModeProgress.tsx b/src/components/BashModeProgress.tsx index 0b6d4b408..c36dd1b32 100644 --- a/src/components/BashModeProgress.tsx +++ b/src/components/BashModeProgress.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box } from '../ink.js' +import { Box } from '@anthropic/ink' import { BashTool } from '../tools/BashTool/BashTool.js' import type { ShellProgress } from '../types/tools.js' import { UserBashInputMessage } from './messages/UserBashInputMessage.js' diff --git a/src/components/BridgeDialog.tsx b/src/components/BridgeDialog.tsx index 9a23311fb..27707875d 100644 --- a/src/components/BridgeDialog.tsx +++ b/src/components/BridgeDialog.tsx @@ -15,12 +15,12 @@ import { } from '../constants/figures.js' import { useRegisterOverlay } from '../context/overlayContext.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw 'd' key for disconnect, not a configurable keybinding action -import { Box, Text, useInput } from '../ink.js' +import { Box, Text, useInput } from '@anthropic/ink' import { useKeybindings } from '../keybindings/useKeybinding.js' import { useAppState, useSetAppState } from '../state/AppState.js' import { saveGlobalConfig } from '../utils/config.js' import { getBranch } from '../utils/git.js' -import { Dialog } from './design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' type Props = { onDone: () => void diff --git a/src/components/BuiltinStatusLine.tsx b/src/components/BuiltinStatusLine.tsx index 14b7ec594..fdbf2969d 100644 --- a/src/components/BuiltinStatusLine.tsx +++ b/src/components/BuiltinStatusLine.tsx @@ -1,8 +1,7 @@ import React, { useEffect, useState } from 'react'; import { formatCost } from '../cost-tracker.js'; -import { Box, Text } from '../ink.js'; +import { Box, Text, ProgressBar } from '@anthropic/ink'; import { formatTokens } from '../utils/format.js'; -import { ProgressBar } from './design-system/ProgressBar.js'; import { useTerminalSize } from '../hooks/useTerminalSize.js'; type RateLimitBucket = { diff --git a/src/components/BypassPermissionsModeDialog.tsx b/src/components/BypassPermissionsModeDialog.tsx index adc708c77..8e8e40d88 100644 --- a/src/components/BypassPermissionsModeDialog.tsx +++ b/src/components/BypassPermissionsModeDialog.tsx @@ -1,10 +1,10 @@ import React, { useCallback } from 'react' import { logEvent } from 'src/services/analytics/index.js' -import { Box, Link, Newline, Text } from '../ink.js' +import { Box, Link, Newline, Text } from '@anthropic/ink' import { gracefulShutdownSync } from '../utils/gracefulShutdown.js' import { updateSettingsForSource } from '../utils/settings/settings.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' type Props = { onAccept(): void diff --git a/src/components/ChannelDowngradeDialog.tsx b/src/components/ChannelDowngradeDialog.tsx index 54db87690..5122dda7b 100644 --- a/src/components/ChannelDowngradeDialog.tsx +++ b/src/components/ChannelDowngradeDialog.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' export type ChannelDowngradeChoice = 'downgrade' | 'stay' | 'cancel' diff --git a/src/components/ClaudeCodeHint/PluginHintMenu.tsx b/src/components/ClaudeCodeHint/PluginHintMenu.tsx index 8afd7cda8..e461afb77 100644 --- a/src/components/ClaudeCodeHint/PluginHintMenu.tsx +++ b/src/components/ClaudeCodeHint/PluginHintMenu.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { Select } from '../CustomSelect/select.js' import { PermissionDialog } from '../permissions/PermissionDialog.js' diff --git a/src/components/ClaudeInChromeOnboarding.tsx b/src/components/ClaudeInChromeOnboarding.tsx index 7c420ad43..ad2ca096c 100644 --- a/src/components/ClaudeInChromeOnboarding.tsx +++ b/src/components/ClaudeInChromeOnboarding.tsx @@ -1,10 +1,9 @@ import React from 'react' import { logEvent } from 'src/services/analytics/index.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- enter to continue -import { Box, Link, Newline, Text, useInput } from '../ink.js' +import { Box, Dialog, Link, Newline, Text, useInput } from '@anthropic/ink' import { isChromeExtensionInstalled } from '../utils/claudeInChrome/setup.js' import { saveGlobalConfig } from '../utils/config.js' -import { Dialog } from './design-system/Dialog.js' const CHROME_EXTENSION_URL = 'https://claude.ai/chrome' const CHROME_PERMISSIONS_URL = 'https://clau.de/chrome/permissions' diff --git a/src/components/ClaudeMdExternalIncludesDialog.tsx b/src/components/ClaudeMdExternalIncludesDialog.tsx index 1ca6fcd12..2614010ca 100644 --- a/src/components/ClaudeMdExternalIncludesDialog.tsx +++ b/src/components/ClaudeMdExternalIncludesDialog.tsx @@ -1,10 +1,9 @@ import React, { useCallback } from 'react' import { logEvent } from 'src/services/analytics/index.js' -import { Box, Link, Text } from '../ink.js' +import { Box, Dialog, Link, Text } from '@anthropic/ink' import type { ExternalClaudeMdInclude } from '../utils/claudemd.js' import { saveCurrentProjectConfig } from '../utils/config.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' type Props = { onDone(): void diff --git a/src/components/ClickableImageRef.tsx b/src/components/ClickableImageRef.tsx index 51144a720..2aa6485b6 100644 --- a/src/components/ClickableImageRef.tsx +++ b/src/components/ClickableImageRef.tsx @@ -1,8 +1,6 @@ import * as React from 'react' import { pathToFileURL } from 'url' -import Link from '../ink/components/Link.js' -import { supportsHyperlinks } from '../ink/supports-hyperlinks.js' -import { Text } from '../ink.js' +import { Link, supportsHyperlinks, Text } from '@anthropic/ink' import { getStoredImagePath } from '../utils/imageStore.js' import type { Theme } from '../utils/theme.js' diff --git a/src/components/CompactSummary.tsx b/src/components/CompactSummary.tsx index 1cd1687fa..e343a6591 100644 --- a/src/components/CompactSummary.tsx +++ b/src/components/CompactSummary.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { BLACK_CIRCLE } from '../constants/figures.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import type { Screen } from '../screens/REPL.js' import type { NormalizedUserMessage } from '../types/message.js' import { getUserMessageText } from '../utils/messages.js' diff --git a/src/components/ConfigurableShortcutHint.tsx b/src/components/ConfigurableShortcutHint.tsx index 82aea15fa..36ec0acd9 100644 --- a/src/components/ConfigurableShortcutHint.tsx +++ b/src/components/ConfigurableShortcutHint.tsx @@ -4,7 +4,7 @@ import type { KeybindingContextName, } from '../keybindings/types.js' import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' +import { KeyboardShortcutHint } from '@anthropic/ink' type Props = { /** The keybinding action (e.g., 'app:toggleTranscript') */ diff --git a/src/components/ConsoleOAuthFlow.tsx b/src/components/ConsoleOAuthFlow.tsx index 7330be828..5264b32b3 100644 --- a/src/components/ConsoleOAuthFlow.tsx +++ b/src/components/ConsoleOAuthFlow.tsx @@ -5,9 +5,7 @@ import { } from 'src/services/analytics/index.js' import { installOAuthTokens } from '../cli/handlers/auth.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { setClipboard } from '../ink/termio/osc.js' -import { useTerminalNotification } from '../ink/useTerminalNotification.js' -import { Box, Link, Text } from '../ink.js' +import { setClipboard, useTerminalNotification, Box, Link, Text, KeyboardShortcutHint } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { getSSLErrorHint } from '../services/api/errorUtils.js' import { sendNotification } from '../services/notifier.js' @@ -16,7 +14,6 @@ import { getOauthAccountInfo, validateForceLoginOrg } from '../utils/auth.js' import { logError } from '../utils/log.js' import { getSettings_DEPRECATED, updateSettingsForSource } from '../utils/settings/settings.js' import { Select } from './CustomSelect/select.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' import { Spinner } from './Spinner.js' import TextInput from './TextInput.js' import { fi } from 'zod/v4/locales' diff --git a/src/components/ContextSuggestions.tsx b/src/components/ContextSuggestions.tsx index 2eeafafe3..2017d7ab9 100644 --- a/src/components/ContextSuggestions.tsx +++ b/src/components/ContextSuggestions.tsx @@ -1,9 +1,8 @@ import figures from 'figures' import * as React from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text, StatusIcon } from '@anthropic/ink' import type { ContextSuggestion } from '../utils/contextSuggestions.js' import { formatTokens } from '../utils/format.js' -import { StatusIcon } from './design-system/StatusIcon.js' type Props = { suggestions: ContextSuggestion[] diff --git a/src/components/ContextVisualization.tsx b/src/components/ContextVisualization.tsx index e6fb57493..3502db5f8 100644 --- a/src/components/ContextVisualization.tsx +++ b/src/components/ContextVisualization.tsx @@ -1,6 +1,6 @@ import { feature } from 'bun:bundle' import * as React from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ContextData } from '../utils/analyzeContext.js' import { generateContextSuggestions } from '../utils/contextSuggestions.js' import { getDisplayPath } from '../utils/file.js' diff --git a/src/components/CoordinatorAgentStatus.tsx b/src/components/CoordinatorAgentStatus.tsx index 2aacb7d6a..9609a0662 100644 --- a/src/components/CoordinatorAgentStatus.tsx +++ b/src/components/CoordinatorAgentStatus.tsx @@ -10,8 +10,7 @@ import figures from 'figures' import * as React from 'react' import { BLACK_CIRCLE, PAUSE_ICON, PLAY_ICON } from '../constants/figures.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { stringWidth } from '../ink/stringWidth.js' -import { Box, Text, wrapText } from '../ink.js' +import { Box, Text, stringWidth, wrapText } from '@anthropic/ink' import { type AppState, useAppState, diff --git a/src/components/CostThresholdDialog.tsx b/src/components/CostThresholdDialog.tsx index 584d864a3..283e76c44 100644 --- a/src/components/CostThresholdDialog.tsx +++ b/src/components/CostThresholdDialog.tsx @@ -1,7 +1,6 @@ import React from 'react' -import { Box, Link, Text } from '../ink.js' +import { Box, Dialog, Link, Text } from '@anthropic/ink' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' type Props = { onDone: () => void diff --git a/src/components/CtrlOToExpand.tsx b/src/components/CtrlOToExpand.tsx index 24b4add81..01ac3b2fb 100644 --- a/src/components/CtrlOToExpand.tsx +++ b/src/components/CtrlOToExpand.tsx @@ -1,9 +1,9 @@ import chalk from 'chalk' import React, { useContext } from 'react' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { getShortcutDisplay } from '../keybindings/shortcutFormat.js' import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' +import { KeyboardShortcutHint } from '@anthropic/ink' import { InVirtualListContext } from './messageActions.js' // Context to track if we're inside a sub agent diff --git a/src/components/CustomSelect/SelectMulti.tsx b/src/components/CustomSelect/SelectMulti.tsx index bb43e9e1e..198917487 100644 --- a/src/components/CustomSelect/SelectMulti.tsx +++ b/src/components/CustomSelect/SelectMulti.tsx @@ -1,6 +1,6 @@ import figures from 'figures' import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { PastedContent } from '../../utils/config.js' import type { ImageDimensions } from '../../utils/imageResizer.js' import type { OptionWithDescription } from './select.js' diff --git a/src/components/CustomSelect/select-input-option.tsx b/src/components/CustomSelect/select-input-option.tsx index 0f3f9483f..ccb8a3989 100644 --- a/src/components/CustomSelect/select-input-option.tsx +++ b/src/components/CustomSelect/select-input-option.tsx @@ -1,6 +1,6 @@ import React, { type ReactNode, useEffect, useRef, useState } from 'react' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- UP arrow exit not in Attachments bindings -import { Box, Text, useInput } from '../../ink.js' +import { Box, Text, useInput } from '@anthropic/ink' import { useKeybinding, useKeybindings, @@ -10,7 +10,7 @@ import { getImageFromClipboard } from '../../utils/imagePaste.js' import type { ImageDimensions } from '../../utils/imageResizer.js' import { ClickableImageRef } from '../ClickableImageRef.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' -import { Byline } from '../design-system/Byline.js' +import { Byline } from '@anthropic/ink' import TextInput from '../TextInput.js' import type { OptionWithDescription } from './select.js' import { SelectOption } from './select-option.js' diff --git a/src/components/CustomSelect/select-option.tsx b/src/components/CustomSelect/select-option.tsx index 2f84affd9..a4c5a386d 100644 --- a/src/components/CustomSelect/select-option.tsx +++ b/src/components/CustomSelect/select-option.tsx @@ -1,5 +1,5 @@ import React, { type ReactNode } from 'react' -import { ListItem } from '../design-system/ListItem.js' +import { ListItem } from '@anthropic/ink' export type SelectOptionProps = { /** diff --git a/src/components/CustomSelect/select.tsx b/src/components/CustomSelect/select.tsx index d3a144772..7d368c2c3 100644 --- a/src/components/CustomSelect/select.tsx +++ b/src/components/CustomSelect/select.tsx @@ -1,8 +1,6 @@ import figures from 'figures' import React, { type ReactNode, useEffect, useRef, useState } from 'react' -import { useDeclaredCursor } from '../../ink/hooks/use-declared-cursor.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Ansi, Box, Text } from '../../ink.js' +import { Ansi, Box, Text, stringWidth, useDeclaredCursor } from '@anthropic/ink' import { count } from '../../utils/array.js' import type { PastedContent } from '../../utils/config.js' import type { ImageDimensions } from '../../utils/imageResizer.js' diff --git a/src/components/CustomSelect/use-multi-select-state.ts b/src/components/CustomSelect/use-multi-select-state.ts index 16b6c01b2..a089a20d4 100644 --- a/src/components/CustomSelect/use-multi-select-state.ts +++ b/src/components/CustomSelect/use-multi-select-state.ts @@ -1,9 +1,7 @@ import { useCallback, useState } from 'react' import { isDeepStrictEqual } from 'util' import { useRegisterOverlay } from '../../context/overlayContext.js' -import type { InputEvent } from '../../ink/events/input-event.js' -// eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw space/arrow multiselect input -import { useInput } from '../../ink.js' +import { type InputEvent, useInput } from '@anthropic/ink' import { normalizeFullWidthDigits, normalizeFullWidthSpace, diff --git a/src/components/CustomSelect/use-select-input.ts b/src/components/CustomSelect/use-select-input.ts index dcafeb4f9..b289056ee 100644 --- a/src/components/CustomSelect/use-select-input.ts +++ b/src/components/CustomSelect/use-select-input.ts @@ -1,7 +1,6 @@ import { useMemo } from 'react' import { useRegisterOverlay } from '../../context/overlayContext.js' -import type { InputEvent } from '../../ink/events/input-event.js' -import { useInput } from '../../ink.js' +import { type InputEvent, useInput } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { normalizeFullWidthDigits, diff --git a/src/components/DesktopHandoff.tsx b/src/components/DesktopHandoff.tsx index 8e0632fd4..8dfe65156 100644 --- a/src/components/DesktopHandoff.tsx +++ b/src/components/DesktopHandoff.tsx @@ -1,16 +1,13 @@ import React, { useEffect, useState } from 'react' import type { CommandResultDisplay } from '../commands.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw input for "any key" dismiss and y/n prompt -import { Box, Text, useInput } from '../ink.js' +import { Box, Text, useInput, LoadingState } from '@anthropic/ink' +import { getDesktopInstallStatus, openCurrentSessionInDesktop } from '../utils/desktopDeepLink.js' import { openBrowser } from '../utils/browser.js' -import { - getDesktopInstallStatus, - openCurrentSessionInDesktop, -} from '../utils/desktopDeepLink.js' + import { errorMessage } from '../utils/errors.js' import { gracefulShutdown } from '../utils/gracefulShutdown.js' import { flushSessionStorage } from '../utils/sessionStorage.js' -import { LoadingState } from './design-system/LoadingState.js' const DESKTOP_DOCS_URL = 'https://clau.de/desktop' diff --git a/src/components/DesktopUpsell/DesktopUpsellStartup.tsx b/src/components/DesktopUpsell/DesktopUpsellStartup.tsx index 9f5f233a4..f2ca4d46e 100644 --- a/src/components/DesktopUpsell/DesktopUpsellStartup.tsx +++ b/src/components/DesktopUpsell/DesktopUpsellStartup.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useEffect, useState } from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getDynamicConfig_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' import { logEvent } from '../../services/analytics/index.js' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' diff --git a/src/components/DevBar.tsx b/src/components/DevBar.tsx index 95ff6b983..9695637ad 100644 --- a/src/components/DevBar.tsx +++ b/src/components/DevBar.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useState } from 'react' import { getSlowOperations } from '../bootstrap/state.js' -import { Text, useInterval } from '../ink.js' +import { Text, useInterval } from '@anthropic/ink' // Show DevBar for dev builds or all ants function shouldShowDevBar(): boolean { diff --git a/src/components/DevChannelsDialog.tsx b/src/components/DevChannelsDialog.tsx index 7dfc674cc..f055e4ae6 100644 --- a/src/components/DevChannelsDialog.tsx +++ b/src/components/DevChannelsDialog.tsx @@ -1,9 +1,8 @@ import React, { useCallback } from 'react' import type { ChannelEntry } from '../bootstrap/state.js' -import { Box, Text } from '../ink.js' +import { Box, Text, Dialog } from '@anthropic/ink' import { gracefulShutdownSync } from '../utils/gracefulShutdown.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' type Props = { channels: ChannelEntry[] diff --git a/src/components/DiagnosticsDisplay.tsx b/src/components/DiagnosticsDisplay.tsx index ad01c7756..5a92190b5 100644 --- a/src/components/DiagnosticsDisplay.tsx +++ b/src/components/DiagnosticsDisplay.tsx @@ -1,6 +1,6 @@ import { relative } from 'path' import React from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { DiagnosticTrackingService } from '../services/diagnosticTracking.js' import type { Attachment } from '../utils/attachments.js' import { getCwd } from '../utils/cwd.js' diff --git a/src/components/EffortCallout.tsx b/src/components/EffortCallout.tsx index 00feda439..a9acc10a8 100644 --- a/src/components/EffortCallout.tsx +++ b/src/components/EffortCallout.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useRef } from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { isMaxSubscriber, isProSubscriber, diff --git a/src/components/ExportDialog.tsx b/src/components/ExportDialog.tsx index f4f1560a4..b217d4c15 100644 --- a/src/components/ExportDialog.tsx +++ b/src/components/ExportDialog.tsx @@ -2,16 +2,12 @@ import { join } from 'path' import React, { useCallback, useState } from 'react' import type { ExitState } from '../hooks/useExitOnCtrlCDWithKeybindings.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { setClipboard } from '../ink/termio/osc.js' -import { Box, Text } from '../ink.js' +import { setClipboard, Box, Text, Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { getCwd } from '../utils/cwd.js' import { writeFileSync_DEPRECATED } from '../utils/slowOperations.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { Select } from './CustomSelect/select.js' -import { Byline } from './design-system/Byline.js' -import { Dialog } from './design-system/Dialog.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' import TextInput from './TextInput.js' type ExportDialogProps = { diff --git a/src/components/FallbackToolUseErrorMessage.tsx b/src/components/FallbackToolUseErrorMessage.tsx index d86ac2b7c..a60ad65b9 100644 --- a/src/components/FallbackToolUseErrorMessage.tsx +++ b/src/components/FallbackToolUseErrorMessage.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import { stripUnderlineAnsi } from 'src/components/shell/OutputLine.js' import { extractTag } from 'src/utils/messages.js' import { removeSandboxViolationTags } from 'src/utils/sandbox/sandbox-ui-utils.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js' import { countCharInString } from '../utils/stringUtils.js' import { MessageResponse } from './MessageResponse.js' diff --git a/src/components/FastIcon.tsx b/src/components/FastIcon.tsx index 956c6290a..b89283145 100644 --- a/src/components/FastIcon.tsx +++ b/src/components/FastIcon.tsx @@ -1,10 +1,9 @@ import chalk from 'chalk' import * as React from 'react' import { LIGHTNING_BOLT } from '../constants/figures.js' -import { Text } from '../ink.js' +import { Text, color } from '@anthropic/ink' import { getGlobalConfig } from '../utils/config.js' import { resolveThemeSetting } from '../utils/systemTheme.js' -import { color } from './design-system/color.js' type Props = { cooldown?: boolean diff --git a/src/components/Feedback.tsx b/src/components/Feedback.tsx index 5d3a3678f..6298b61b9 100644 --- a/src/components/Feedback.tsx +++ b/src/components/Feedback.tsx @@ -14,7 +14,7 @@ import { } from 'src/utils/messages.js' import type { CommandResultDisplay } from '../commands.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { Box, Text, useInput } from '../ink.js' +import { Box, Text, useInput } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { queryHaiku } from '../services/api/claude.js' import { startsWithApiErrorPrefix } from '../services/api/errors.js' @@ -36,9 +36,7 @@ import { import { jsonStringify } from '../utils/slowOperations.js' import { asSystemPrompt } from '../utils/systemPromptType.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' -import { Byline } from './design-system/Byline.js' -import { Dialog } from './design-system/Dialog.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import TextInput from './TextInput.js' // This value was determined experimentally by testing the URL length limit diff --git a/src/components/FeedbackSurvey/FeedbackSurvey.tsx b/src/components/FeedbackSurvey/FeedbackSurvey.tsx index 2f9c8e47d..a92213441 100644 --- a/src/components/FeedbackSurvey/FeedbackSurvey.tsx +++ b/src/components/FeedbackSurvey/FeedbackSurvey.tsx @@ -3,7 +3,7 @@ import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, } from 'src/services/analytics/index.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { FeedbackSurveyView, isValidResponseInput, diff --git a/src/components/FeedbackSurvey/FeedbackSurveyView.tsx b/src/components/FeedbackSurvey/FeedbackSurveyView.tsx index a8eadf3ba..5a8721e12 100644 --- a/src/components/FeedbackSurvey/FeedbackSurveyView.tsx +++ b/src/components/FeedbackSurvey/FeedbackSurveyView.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useDebouncedDigitInput } from './useDebouncedDigitInput.js' import type { FeedbackSurveyResponse } from './utils.js' diff --git a/src/components/FeedbackSurvey/TranscriptSharePrompt.tsx b/src/components/FeedbackSurvey/TranscriptSharePrompt.tsx index ec7a974f5..d3f39d25e 100644 --- a/src/components/FeedbackSurvey/TranscriptSharePrompt.tsx +++ b/src/components/FeedbackSurvey/TranscriptSharePrompt.tsx @@ -1,6 +1,6 @@ import React from 'react' import { BLACK_CIRCLE } from '../../constants/figures.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useDebouncedDigitInput } from './useDebouncedDigitInput.js' export type TranscriptShareResponse = 'yes' | 'no' | 'dont_ask_again' diff --git a/src/components/FileEditToolDiff.tsx b/src/components/FileEditToolDiff.tsx index a5146d3c8..75efda0f5 100644 --- a/src/components/FileEditToolDiff.tsx +++ b/src/components/FileEditToolDiff.tsx @@ -2,7 +2,7 @@ import type { StructuredPatchHunk } from 'diff' import * as React from 'react' import { Suspense, use, useState } from 'react' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import type { FileEdit } from '../tools/FileEditTool/types.js' import { findActualString, diff --git a/src/components/FileEditToolUpdatedMessage.tsx b/src/components/FileEditToolUpdatedMessage.tsx index 0248583af..5fbc68005 100644 --- a/src/components/FileEditToolUpdatedMessage.tsx +++ b/src/components/FileEditToolUpdatedMessage.tsx @@ -1,7 +1,7 @@ import type { StructuredPatchHunk } from 'diff' import * as React from 'react' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { count } from '../utils/array.js' import { MessageResponse } from './MessageResponse.js' import { StructuredDiffList } from './StructuredDiffList.js' diff --git a/src/components/FileEditToolUseRejectedMessage.tsx b/src/components/FileEditToolUseRejectedMessage.tsx index 6171b0f65..2d53117d2 100644 --- a/src/components/FileEditToolUseRejectedMessage.tsx +++ b/src/components/FileEditToolUseRejectedMessage.tsx @@ -3,7 +3,7 @@ import { relative } from 'path' import * as React from 'react' import { useTerminalSize } from 'src/hooks/useTerminalSize.js' import { getCwd } from 'src/utils/cwd.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { HighlightedCode } from './HighlightedCode.js' import { MessageResponse } from './MessageResponse.js' import { StructuredDiffList } from './StructuredDiffList.js' diff --git a/src/components/FilePathLink.tsx b/src/components/FilePathLink.tsx index 05a6167a2..9ab7f3cd1 100644 --- a/src/components/FilePathLink.tsx +++ b/src/components/FilePathLink.tsx @@ -1,6 +1,6 @@ import React from 'react' import { pathToFileURL } from 'url' -import Link from '../ink/components/Link.js' +import { Link } from '@anthropic/ink' type Props = { /** The absolute file path */ diff --git a/src/components/FullscreenLayout.tsx b/src/components/FullscreenLayout.tsx index 8502e46de..608c7b1c9 100644 --- a/src/components/FullscreenLayout.tsx +++ b/src/components/FullscreenLayout.tsx @@ -19,9 +19,7 @@ import { usePromptOverlayDialog, } from '../context/promptOverlayContext.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import ScrollBox, { type ScrollBoxHandle } from '../ink/components/ScrollBox.js' -import instances from '../ink/instances.js' -import { Box, Text } from '../ink.js' +import { Box, ScrollBox, type ScrollBoxHandle, Text, instances } from '@anthropic/ink' import type { Message } from '../types/message.js' import { openBrowser, openPath } from '../utils/browser.js' import { isFullscreenEnvEnabled } from '../utils/fullscreen.js' diff --git a/src/components/GlobalSearchDialog.tsx b/src/components/GlobalSearchDialog.tsx index 0df3231ce..ee1d2fdf5 100644 --- a/src/components/GlobalSearchDialog.tsx +++ b/src/components/GlobalSearchDialog.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import { useEffect, useRef, useState } from 'react' import { useRegisterOverlay } from '../context/overlayContext.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { logEvent } from '../services/analytics/index.js' import { getCwd } from '../utils/cwd.js' import { openFileInExternalEditor } from '../utils/editor.js' @@ -12,8 +12,7 @@ import { highlightMatch } from '../utils/highlightMatch.js' import { relativePath } from '../utils/permissions/filesystem.js' import { readFileInRange } from '../utils/readFileInRange.js' import { ripGrepStream } from '../utils/ripgrep.js' -import { FuzzyPicker } from './design-system/FuzzyPicker.js' -import { LoadingState } from './design-system/LoadingState.js' +import { FuzzyPicker, LoadingState } from '@anthropic/ink' type Props = { onDone: () => void diff --git a/src/components/HelpV2/Commands.tsx b/src/components/HelpV2/Commands.tsx index 9e34e5539..dd5eda75e 100644 --- a/src/components/HelpV2/Commands.tsx +++ b/src/components/HelpV2/Commands.tsx @@ -1,9 +1,7 @@ import * as React from 'react' import { useMemo } from 'react' import { type Command, formatDescriptionWithSource } from '../../commands.js' -import { Box, Text } from '../../ink.js' -import { truncate } from '../../utils/format.js' -import { Select } from '../CustomSelect/select.js' +import { Box, Text } from '@anthropic/ink' import { useTabHeaderFocus } from '../design-system/Tabs.js' type Props = { diff --git a/src/components/HelpV2/General.tsx b/src/components/HelpV2/General.tsx index 69b5c6509..4117d9568 100644 --- a/src/components/HelpV2/General.tsx +++ b/src/components/HelpV2/General.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { PromptInputHelpMenu } from '../PromptInput/PromptInputHelpMenu.js' export function General(): React.ReactNode { diff --git a/src/components/HelpV2/HelpV2.tsx b/src/components/HelpV2/HelpV2.tsx index 9e2b0ce27..aa077f649 100644 --- a/src/components/HelpV2/HelpV2.tsx +++ b/src/components/HelpV2/HelpV2.tsx @@ -9,10 +9,10 @@ import { } from '../../commands.js' import { useIsInsideModal } from '../../context/modalContext.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, Link, Text } from '../../ink.js' +import { Box, Link, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' -import { Pane } from '../design-system/Pane.js' -import { Tab, Tabs } from '../design-system/Tabs.js' +import { Pane, Tabs } from '@anthropic/ink' +import { Tab } from '../design-system/Tabs.js' import { Commands } from './Commands.js' import { General } from './General.js' diff --git a/src/components/HighlightedCode.tsx b/src/components/HighlightedCode.tsx index 47f7271bc..3cfe9a736 100644 --- a/src/components/HighlightedCode.tsx +++ b/src/components/HighlightedCode.tsx @@ -1,15 +1,7 @@ import * as React from 'react' import { memo, useEffect, useMemo, useRef, useState } from 'react' import { useSettings } from '../hooks/useSettings.js' -import { - Ansi, - Box, - type DOMElement, - measureElement, - NoSelect, - Text, - useTheme, -} from '../ink.js' +import { Ansi, Box, type DOMElement, measureElement, NoSelect, Text, useTheme } from '@anthropic/ink' import { isFullscreenEnvEnabled } from '../utils/fullscreen.js' import sliceAnsi from '../utils/sliceAnsi.js' import { countCharInString } from '../utils/stringUtils.js' diff --git a/src/components/HighlightedCode/Fallback.tsx b/src/components/HighlightedCode/Fallback.tsx index 3d1f70112..e81d44f3d 100644 --- a/src/components/HighlightedCode/Fallback.tsx +++ b/src/components/HighlightedCode/Fallback.tsx @@ -1,6 +1,6 @@ import { extname } from 'path' import React, { Suspense, use, useMemo } from 'react' -import { Ansi, Text } from '../../ink.js' +import { Ansi, Text } from '@anthropic/ink' import { getCliHighlightPromise } from '../../utils/cliHighlight.js' import { logForDebugging } from '../../utils/debug.js' import { convertLeadingTabsToSpaces } from '../../utils/file.js' diff --git a/src/components/HistorySearchDialog.tsx b/src/components/HistorySearchDialog.tsx index dd2e02da5..54bf98372 100644 --- a/src/components/HistorySearchDialog.tsx +++ b/src/components/HistorySearchDialog.tsx @@ -6,13 +6,11 @@ import { type TimestampedHistoryEntry, } from '../history.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { stringWidth } from '../ink/stringWidth.js' -import { wrapAnsi } from '../ink/wrapAnsi.js' -import { Box, Text } from '../ink.js' +import { Box, Text, stringWidth, wrapAnsi } from '@anthropic/ink' import { logEvent } from '../services/analytics/index.js' import type { HistoryEntry } from '../utils/config.js' import { formatRelativeTimeAgo, truncateToWidth } from '../utils/format.js' -import { FuzzyPicker } from './design-system/FuzzyPicker.js' +import { FuzzyPicker } from '@anthropic/ink' type Props = { initialQuery?: string diff --git a/src/components/IdeAutoConnectDialog.tsx b/src/components/IdeAutoConnectDialog.tsx index 2377cfb3a..f262cc465 100644 --- a/src/components/IdeAutoConnectDialog.tsx +++ b/src/components/IdeAutoConnectDialog.tsx @@ -1,9 +1,8 @@ import React, { useCallback } from 'react' -import { Text } from '../ink.js' +import { Text, Dialog } from '@anthropic/ink' import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js' import { isSupportedTerminal } from '../utils/ide.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' type IdeAutoConnectDialogProps = { onComplete: () => void diff --git a/src/components/IdeOnboardingDialog.tsx b/src/components/IdeOnboardingDialog.tsx index 86f03018e..aae0b1742 100644 --- a/src/components/IdeOnboardingDialog.tsx +++ b/src/components/IdeOnboardingDialog.tsx @@ -1,6 +1,6 @@ import React from 'react' import { envDynamic } from 'src/utils/envDynamic.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybindings } from '../keybindings/useKeybinding.js' import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js' import { env } from '../utils/env.js' @@ -10,7 +10,7 @@ import { isJetBrainsIde, toIDEDisplayName, } from '../utils/ide.js' -import { Dialog } from './design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' interface Props { onDone: () => void diff --git a/src/components/IdeStatusIndicator.tsx b/src/components/IdeStatusIndicator.tsx index 13c1846c0..13096b120 100644 --- a/src/components/IdeStatusIndicator.tsx +++ b/src/components/IdeStatusIndicator.tsx @@ -2,7 +2,7 @@ import { basename } from 'path' import * as React from 'react' import { useIdeConnectionStatus } from '../hooks/useIdeConnectionStatus.js' import type { IDESelection } from '../hooks/useIdeSelection.js' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import type { MCPServerConnection } from '../services/mcp/types.js' type IdeStatusIndicatorProps = { diff --git a/src/components/IdleReturnDialog.tsx b/src/components/IdleReturnDialog.tsx index d651cfe38..9ffc8893a 100644 --- a/src/components/IdleReturnDialog.tsx +++ b/src/components/IdleReturnDialog.tsx @@ -1,8 +1,8 @@ import React from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { formatTokens } from '../utils/format.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' type IdleReturnAction = 'continue' | 'clear' | 'dismiss' | 'never' diff --git a/src/components/InterruptedByUser.tsx b/src/components/InterruptedByUser.tsx index 0a77c7153..13c641f0b 100644 --- a/src/components/InterruptedByUser.tsx +++ b/src/components/InterruptedByUser.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' export function InterruptedByUser(): React.ReactNode { return ( diff --git a/src/components/InvalidConfigDialog.tsx b/src/components/InvalidConfigDialog.tsx index 8fa3bba97..7bbc04b14 100644 --- a/src/components/InvalidConfigDialog.tsx +++ b/src/components/InvalidConfigDialog.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, render, Text } from '../ink.js' +import { Box, Dialog, wrappedRender as render, Text } from '@anthropic/ink' import { KeybindingSetup } from '../keybindings/KeybindingProviderSetup.js' import { AppStateProvider } from '../state/AppState.js' import type { ConfigParseError } from '../utils/errors.js' @@ -10,7 +10,6 @@ import { } from '../utils/slowOperations.js' import type { ThemeName } from '../utils/theme.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' interface InvalidConfigHandlerProps { error: ConfigParseError diff --git a/src/components/InvalidSettingsDialog.tsx b/src/components/InvalidSettingsDialog.tsx index c1fddf96a..f3b0db018 100644 --- a/src/components/InvalidSettingsDialog.tsx +++ b/src/components/InvalidSettingsDialog.tsx @@ -1,8 +1,7 @@ import React from 'react' -import { Text } from '../ink.js' +import { Text, Dialog } from '@anthropic/ink' import type { ValidationError } from '../utils/settings/validation.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' import { ValidationErrorsList } from './ValidationErrorsList.js' type Props = { diff --git a/src/components/KeybindingWarnings.tsx b/src/components/KeybindingWarnings.tsx index 8f6957c3e..dc1b5a74a 100644 --- a/src/components/KeybindingWarnings.tsx +++ b/src/components/KeybindingWarnings.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { getCachedKeybindingWarnings, getKeybindingsPath, diff --git a/src/components/LanguagePicker.tsx b/src/components/LanguagePicker.tsx index 53be69d48..ae357ff8e 100644 --- a/src/components/LanguagePicker.tsx +++ b/src/components/LanguagePicker.tsx @@ -1,6 +1,6 @@ import figures from 'figures' import React, { useState } from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import TextInput from './TextInput.js' diff --git a/src/components/LogSelector.tsx b/src/components/LogSelector.tsx index d1fb9f607..806a0082d 100644 --- a/src/components/LogSelector.tsx +++ b/src/components/LogSelector.tsx @@ -6,9 +6,7 @@ import { getOriginalCwd, getSessionId } from '../bootstrap/state.js' import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js' import { useSearchInput } from '../hooks/useSearchInput.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { applyColor } from '../ink/colorize.js' -import type { Color } from '../ink/styles.js' -import { Box, Text, useInput, useTerminalFocus, useTheme } from '../ink.js' +import { applyColor, Box, Text, useInput, useTerminalFocus, useTheme, type Color, Byline, Divider, KeyboardShortcutHint } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { logEvent } from '../services/analytics/index.js' import type { LogOption, SerializedMessage } from '../types/logs.js' @@ -25,9 +23,6 @@ import { import { getTheme } from '../utils/theme.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { Select } from './CustomSelect/select.js' -import { Byline } from './design-system/Byline.js' -import { Divider } from './design-system/Divider.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' import { SearchBox } from './SearchBox.js' import { SessionPreview } from './SessionPreview.js' import { Spinner } from './Spinner.js' diff --git a/src/components/LogoV2/AnimatedAsterisk.tsx b/src/components/LogoV2/AnimatedAsterisk.tsx index 1c5adcf06..9e72baaa2 100644 --- a/src/components/LogoV2/AnimatedAsterisk.tsx +++ b/src/components/LogoV2/AnimatedAsterisk.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useEffect, useRef, useState } from 'react' import { TEARDROP_ASTERISK } from '../../constants/figures.js' -import { Box, Text, useAnimationFrame } from '../../ink.js' +import { Box, Text, useAnimationFrame } from '@anthropic/ink' import { getInitialSettings } from '../../utils/settings/settings.js' import { hueToRgb, toRGBColor } from '../Spinner/utils.js' diff --git a/src/components/LogoV2/AnimatedClawd.tsx b/src/components/LogoV2/AnimatedClawd.tsx index ed3060066..5ad68babb 100644 --- a/src/components/LogoV2/AnimatedClawd.tsx +++ b/src/components/LogoV2/AnimatedClawd.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useEffect, useRef, useState } from 'react' -import { Box } from '../../ink.js' +import { Box } from '@anthropic/ink' import { getInitialSettings } from '../../utils/settings/settings.js' import { Clawd, type ClawdPose } from './Clawd.js' diff --git a/src/components/LogoV2/ChannelsNotice.tsx b/src/components/LogoV2/ChannelsNotice.tsx index 66f41b303..c58f400c1 100644 --- a/src/components/LogoV2/ChannelsNotice.tsx +++ b/src/components/LogoV2/ChannelsNotice.tsx @@ -11,7 +11,7 @@ import { getAllowedChannels, getHasDevChannels, } from '../../bootstrap/state.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { isChannelsEnabled } from '../../services/mcp/channelAllowlist.js' import { getEffectiveChannelAllowlist } from '../../services/mcp/channelNotification.js' import { getMcpConfigsByScope } from '../../services/mcp/config.js' diff --git a/src/components/LogoV2/Clawd.tsx b/src/components/LogoV2/Clawd.tsx index 8ddc1bf8e..6969466bc 100644 --- a/src/components/LogoV2/Clawd.tsx +++ b/src/components/LogoV2/Clawd.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { env } from '../../utils/env.js' export type ClawdPose = diff --git a/src/components/LogoV2/CondensedLogo.tsx b/src/components/LogoV2/CondensedLogo.tsx index be587bf83..eb048ec2d 100644 --- a/src/components/LogoV2/CondensedLogo.tsx +++ b/src/components/LogoV2/CondensedLogo.tsx @@ -2,8 +2,7 @@ import * as React from 'react' import { type ReactNode, useEffect } from 'react' import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, Text } from '../../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import { useAppState } from '../../state/AppState.js' import { getEffortSuffix } from '../../utils/effort.js' import { truncate } from '../../utils/format.js' diff --git a/src/components/LogoV2/EmergencyTip.tsx b/src/components/LogoV2/EmergencyTip.tsx index c0a8235ba..33280bcf7 100644 --- a/src/components/LogoV2/EmergencyTip.tsx +++ b/src/components/LogoV2/EmergencyTip.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useEffect, useMemo } from 'react' -import { Box, Text } from 'src/ink.js' +import { Box, Text } from '@anthropic/ink' import { getDynamicConfig_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js' import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js' diff --git a/src/components/LogoV2/Feed.tsx b/src/components/LogoV2/Feed.tsx index 15a7d84d6..be849bfd7 100644 --- a/src/components/LogoV2/Feed.tsx +++ b/src/components/LogoV2/Feed.tsx @@ -1,6 +1,5 @@ import * as React from 'react' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, Text } from '../../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import { truncate } from '../../utils/format.js' export type FeedLine = { diff --git a/src/components/LogoV2/FeedColumn.tsx b/src/components/LogoV2/FeedColumn.tsx index 0b08ec84a..4c6ae84f5 100644 --- a/src/components/LogoV2/FeedColumn.tsx +++ b/src/components/LogoV2/FeedColumn.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import { Box } from '../../ink.js' -import { Divider } from '../design-system/Divider.js' +import { Box } from '@anthropic/ink' +import { Divider } from '@anthropic/ink' import type { FeedConfig } from './Feed.js' import { calculateFeedWidth, Feed } from './Feed.js' diff --git a/src/components/LogoV2/GuestPassesUpsell.tsx b/src/components/LogoV2/GuestPassesUpsell.tsx index 12796e43b..c1bae01b4 100644 --- a/src/components/LogoV2/GuestPassesUpsell.tsx +++ b/src/components/LogoV2/GuestPassesUpsell.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useState } from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { logEvent } from '../../services/analytics/index.js' import { checkCachedPassesEligibility, diff --git a/src/components/LogoV2/LogoV2.tsx b/src/components/LogoV2/LogoV2.tsx index d65c24fe3..e457f1084 100644 --- a/src/components/LogoV2/LogoV2.tsx +++ b/src/components/LogoV2/LogoV2.tsx @@ -1,8 +1,7 @@ // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered import * as React from 'react' -import { Box, Text, color } from '../../ink.js' +import { Box, Text, color, stringWidth } from '@anthropic/ink' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { stringWidth } from '../../ink/stringWidth.js' import { getLayoutMode, calculateLayoutDimensions, diff --git a/src/components/LogoV2/Opus1mMergeNotice.tsx b/src/components/LogoV2/Opus1mMergeNotice.tsx index 63c42ab66..9bbf84752 100644 --- a/src/components/LogoV2/Opus1mMergeNotice.tsx +++ b/src/components/LogoV2/Opus1mMergeNotice.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useEffect, useState } from 'react' import { UP_ARROW } from '../../constants/figures.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' import { isOpus1mMergeEnabled } from '../../utils/model/model.js' import { AnimatedAsterisk } from './AnimatedAsterisk.js' diff --git a/src/components/LogoV2/OverageCreditUpsell.tsx b/src/components/LogoV2/OverageCreditUpsell.tsx index ce006e0d0..c08140645 100644 --- a/src/components/LogoV2/OverageCreditUpsell.tsx +++ b/src/components/LogoV2/OverageCreditUpsell.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useState } from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { logEvent } from '../../services/analytics/index.js' import { formatGrantAmount, diff --git a/src/components/LogoV2/VoiceModeNotice.tsx b/src/components/LogoV2/VoiceModeNotice.tsx index 531460533..b8e74e3c6 100644 --- a/src/components/LogoV2/VoiceModeNotice.tsx +++ b/src/components/LogoV2/VoiceModeNotice.tsx @@ -1,7 +1,7 @@ import { feature } from 'bun:bundle' import * as React from 'react' import { useEffect, useState } from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' import { getInitialSettings } from '../../utils/settings/settings.js' import { isVoiceModeEnabled } from '../../voice/voiceModeEnabled.js' diff --git a/src/components/LogoV2/WelcomeV2.tsx b/src/components/LogoV2/WelcomeV2.tsx index 354e1182b..ccbbcbf44 100644 --- a/src/components/LogoV2/WelcomeV2.tsx +++ b/src/components/LogoV2/WelcomeV2.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text, useTheme } from 'src/ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import { env } from '../../utils/env.js' const WELCOME_V2_WIDTH = 58 diff --git a/src/components/LogoV2/feedConfigs.tsx b/src/components/LogoV2/feedConfigs.tsx index 50ec4575c..8f6652f3c 100644 --- a/src/components/LogoV2/feedConfigs.tsx +++ b/src/components/LogoV2/feedConfigs.tsx @@ -1,7 +1,7 @@ import figures from 'figures' import { homedir } from 'os' import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { Step } from '../../projectOnboardingState.js' import { formatCreditAmount, diff --git a/src/components/LspRecommendation/LspRecommendationMenu.tsx b/src/components/LspRecommendation/LspRecommendationMenu.tsx index 7dc41ac39..3ea815069 100644 --- a/src/components/LspRecommendation/LspRecommendationMenu.tsx +++ b/src/components/LspRecommendation/LspRecommendationMenu.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { Select } from '../CustomSelect/select.js' import { PermissionDialog } from '../permissions/PermissionDialog.js' diff --git a/src/components/MCPServerApprovalDialog.tsx b/src/components/MCPServerApprovalDialog.tsx index 5d5e00898..f80d42008 100644 --- a/src/components/MCPServerApprovalDialog.tsx +++ b/src/components/MCPServerApprovalDialog.tsx @@ -8,7 +8,7 @@ import { updateSettingsForSource, } from '../utils/settings/settings.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { MCPServerDialogCopy } from './MCPServerDialogCopy.js' type Props = { diff --git a/src/components/MCPServerDesktopImportDialog.tsx b/src/components/MCPServerDesktopImportDialog.tsx index 50b9ef6d6..aaea61894 100644 --- a/src/components/MCPServerDesktopImportDialog.tsx +++ b/src/components/MCPServerDesktopImportDialog.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useState } from 'react' import { gracefulShutdown } from 'src/utils/gracefulShutdown.js' import { writeToStdout } from 'src/utils/process.js' -import { Box, color, Text, useTheme } from '../ink.js' +import { Box, color, Text, useTheme, Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { addMcpConfig, getAllMcpConfigs } from '../services/mcp/config.js' import type { ConfigScope, @@ -11,9 +11,6 @@ import type { import { plural } from '../utils/stringUtils.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { SelectMulti } from './CustomSelect/SelectMulti.js' -import { Byline } from './design-system/Byline.js' -import { Dialog } from './design-system/Dialog.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' type Props = { servers: Record diff --git a/src/components/MCPServerDialogCopy.tsx b/src/components/MCPServerDialogCopy.tsx index 93dce3655..f6a87674f 100644 --- a/src/components/MCPServerDialogCopy.tsx +++ b/src/components/MCPServerDialogCopy.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Link, Text } from '../ink.js' +import { Link, Text } from '@anthropic/ink' export function MCPServerDialogCopy(): React.ReactNode { return ( diff --git a/src/components/MCPServerMultiselectDialog.tsx b/src/components/MCPServerMultiselectDialog.tsx index e14c46d46..fa41da3a7 100644 --- a/src/components/MCPServerMultiselectDialog.tsx +++ b/src/components/MCPServerMultiselectDialog.tsx @@ -1,16 +1,14 @@ import partition from 'lodash-es/partition.js' import React, { useCallback } from 'react' import { logEvent } from 'src/services/analytics/index.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { getSettings_DEPRECATED, updateSettingsForSource, } from '../utils/settings/settings.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { SelectMulti } from './CustomSelect/SelectMulti.js' -import { Byline } from './design-system/Byline.js' -import { Dialog } from './design-system/Dialog.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { MCPServerDialogCopy } from './MCPServerDialogCopy.js' type Props = { diff --git a/src/components/ManagedSettingsSecurityDialog/ManagedSettingsSecurityDialog.tsx b/src/components/ManagedSettingsSecurityDialog/ManagedSettingsSecurityDialog.tsx index 392979770..2d7de2ec7 100644 --- a/src/components/ManagedSettingsSecurityDialog/ManagedSettingsSecurityDialog.tsx +++ b/src/components/ManagedSettingsSecurityDialog/ManagedSettingsSecurityDialog.tsx @@ -1,6 +1,6 @@ import React from 'react' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import type { SettingsJson } from '../../utils/settings/types.js' import { Select } from '../CustomSelect/index.js' diff --git a/src/components/Markdown.tsx b/src/components/Markdown.tsx index 4616f46c2..063404baf 100644 --- a/src/components/Markdown.tsx +++ b/src/components/Markdown.tsx @@ -1,7 +1,7 @@ import { marked, type Token, type Tokens } from 'marked' import React, { Suspense, use, useMemo, useRef } from 'react' import { useSettings } from '../hooks/useSettings.js' -import { Ansi, Box, useTheme } from '../ink.js' +import { Ansi, Box, useTheme } from '@anthropic/ink' import { type CliHighlight, getCliHighlightPromise, diff --git a/src/components/MarkdownTable.tsx b/src/components/MarkdownTable.tsx index c8997d9a1..0b3473d43 100644 --- a/src/components/MarkdownTable.tsx +++ b/src/components/MarkdownTable.tsx @@ -2,9 +2,7 @@ import type { Token, Tokens } from 'marked' import React from 'react' import stripAnsi from 'strip-ansi' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { stringWidth } from '../ink/stringWidth.js' -import { wrapAnsi } from '../ink/wrapAnsi.js' -import { Ansi, useTheme } from '../ink.js' +import { Ansi, stringWidth, useTheme, wrapAnsi } from '@anthropic/ink' import type { CliHighlight } from '../utils/cliHighlight.js' import { formatToken, padAligned } from '../utils/markdown.js' diff --git a/src/components/MemoryUsageIndicator.tsx b/src/components/MemoryUsageIndicator.tsx index 37c91e778..4004133c2 100644 --- a/src/components/MemoryUsageIndicator.tsx +++ b/src/components/MemoryUsageIndicator.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useMemoryUsage } from '../hooks/useMemoryUsage.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { formatFileSize } from '../utils/format.js' export function MemoryUsageIndicator(): React.ReactNode { diff --git a/src/components/Message.tsx b/src/components/Message.tsx index 79a152682..7f5055465 100644 --- a/src/components/Message.tsx +++ b/src/components/Message.tsx @@ -10,7 +10,7 @@ import type { import * as React from 'react' import type { Command } from '../commands.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { Box } from '../ink.js' +import { Box } from '@anthropic/ink' import type { Tools } from '../Tool.js' import { type ConnectorTextBlock, diff --git a/src/components/MessageModel.tsx b/src/components/MessageModel.tsx index a99f861e6..4afa5bc33 100644 --- a/src/components/MessageModel.tsx +++ b/src/components/MessageModel.tsx @@ -1,6 +1,5 @@ import React from 'react' -import { stringWidth } from '../ink/stringWidth.js' -import { Box, Text } from '../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import type { NormalizedMessage } from '../types/message.js' type Props = { diff --git a/src/components/MessageResponse.tsx b/src/components/MessageResponse.tsx index f71d40ce8..a8dd8613c 100644 --- a/src/components/MessageResponse.tsx +++ b/src/components/MessageResponse.tsx @@ -1,7 +1,6 @@ import * as React from 'react' import { useContext } from 'react' -import { Box, NoSelect, Text } from '../ink.js' -import { Ratchet } from './design-system/Ratchet.js' +import { Box, NoSelect, Text, Ratchet } from '@anthropic/ink' type Props = { children: React.ReactNode diff --git a/src/components/MessageRow.tsx b/src/components/MessageRow.tsx index e42fb9d96..e02a2708f 100644 --- a/src/components/MessageRow.tsx +++ b/src/components/MessageRow.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import type { Command } from '../commands.js' -import { Box } from '../ink.js' +import { Box } from '@anthropic/ink' import type { Screen } from '../screens/REPL.js' import type { Tools } from '../Tool.js' import type { RenderableMessage } from '../types/message.js' diff --git a/src/components/MessageSelector.tsx b/src/components/MessageSelector.tsx index b372a4b5d..ab4e12292 100644 --- a/src/components/MessageSelector.tsx +++ b/src/components/MessageSelector.tsx @@ -19,7 +19,7 @@ import { } from 'src/utils/fileHistory.js' import { logError } from 'src/utils/log.js' import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Text } from '../ink.js' +import { Box, Text, Divider } from '@anthropic/ink' import { useKeybinding, useKeybindings } from '../keybindings/useKeybinding.js' import type { Message, @@ -58,8 +58,6 @@ import { import { count } from '../utils/array.js' import { formatRelativeTimeAgo, truncate } from '../utils/format.js' import type { Theme } from '../utils/theme.js' -import { Divider } from './design-system/Divider.js' - type RestoreOption = | 'both' | 'conversation' diff --git a/src/components/MessageTimestamp.tsx b/src/components/MessageTimestamp.tsx index 8eac935e5..3b0e07322 100644 --- a/src/components/MessageTimestamp.tsx +++ b/src/components/MessageTimestamp.tsx @@ -1,6 +1,5 @@ import React from 'react' -import { stringWidth } from '../ink/stringWidth.js' -import { Box, Text } from '../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import type { NormalizedMessage } from '../types/message.js' type Props = { diff --git a/src/components/Messages.tsx b/src/components/Messages.tsx index c946a9fd7..bd165b535 100644 --- a/src/components/Messages.tsx +++ b/src/components/Messages.tsx @@ -9,9 +9,9 @@ import { getIsRemoteMode } from '../bootstrap/state.js' import type { Command } from '../commands.js' import { BLACK_CIRCLE } from '../constants/figures.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import type { ScrollBoxHandle } from '../ink/components/ScrollBox.js' -import { useTerminalNotification } from '../ink/useTerminalNotification.js' -import { Box, Text } from '../ink.js' +import type { ScrollBoxHandle } from '@anthropic/ink' +import { useTerminalNotification } from '@anthropic/ink' +import { Box, Text } from '@anthropic/ink' import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js' import type { Screen } from '../screens/REPL.js' import type { Tools } from '../Tool.js' @@ -49,7 +49,7 @@ import { } from '../utils/messages.js' import { plural } from '../utils/stringUtils.js' import { renderableSearchText } from '../utils/transcriptSearch.js' -import { Divider } from './design-system/Divider.js' +import { Divider } from '@anthropic/ink' import type { UnseenDivider } from './FullscreenLayout.js' import { LogoV2 } from './LogoV2/LogoV2.js' import { StreamingMarkdown } from './Markdown.js' @@ -291,13 +291,13 @@ type Props = { /** Paint an existing DOM subtree to fresh Screen, scan. Element comes * from the main tree (all real providers). Message-relative positions. */ scanElement?: ( - el: import('../ink/dom.js').DOMElement, - ) => import('../ink/render-to-screen.js').MatchPosition[] + el: import('@anthropic/ink').DOMElement, + ) => import('@anthropic/ink').MatchPosition[] /** Position-based CURRENT highlight. positions stable (msg-relative), * rowOffset tracks scroll. null clears. */ setPositions?: ( state: { - positions: import('../ink/render-to-screen.js').MatchPosition[] + positions: import('@anthropic/ink').MatchPosition[] rowOffset: number currentIdx: number } | null, diff --git a/src/components/ModelPicker.tsx b/src/components/ModelPicker.tsx index 6658ad62d..4977dbbcb 100644 --- a/src/components/ModelPicker.tsx +++ b/src/components/ModelPicker.tsx @@ -12,7 +12,7 @@ import { isFastModeCooldown, isFastModeEnabled, } from 'src/utils/fastMode.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybindings } from '../keybindings/useKeybinding.js' import { useAppState, useSetAppState } from '../state/AppState.js' import { @@ -37,9 +37,7 @@ import { } from '../utils/settings/settings.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { Select } from './CustomSelect/index.js' -import { Byline } from './design-system/Byline.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' -import { Pane } from './design-system/Pane.js' +import { Byline, KeyboardShortcutHint, Pane } from '@anthropic/ink' import { effortLevelToSymbol } from './EffortIndicator.js' export type Props = { diff --git a/src/components/NativeAutoUpdater.tsx b/src/components/NativeAutoUpdater.tsx index fcb448ade..174d07951 100644 --- a/src/components/NativeAutoUpdater.tsx +++ b/src/components/NativeAutoUpdater.tsx @@ -5,7 +5,7 @@ import { logForDebugging } from 'src/utils/debug.js' import { logError } from 'src/utils/log.js' import { useInterval } from 'usehooks-ts' import { useUpdateNotification } from '../hooks/useUpdateNotification.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import type { AutoUpdaterResult } from '../utils/autoUpdater.js' import { getMaxVersion, getMaxVersionMessage } from '../utils/autoUpdater.js' import { isAutoUpdaterDisabled } from '../utils/config.js' diff --git a/src/components/NotebookEditToolUseRejectedMessage.tsx b/src/components/NotebookEditToolUseRejectedMessage.tsx index 4eb3cf887..fc4e4f317 100644 --- a/src/components/NotebookEditToolUseRejectedMessage.tsx +++ b/src/components/NotebookEditToolUseRejectedMessage.tsx @@ -1,7 +1,7 @@ import { relative } from 'path' import * as React from 'react' import { getCwd } from 'src/utils/cwd.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { HighlightedCode } from './HighlightedCode.js' import { MessageResponse } from './MessageResponse.js' diff --git a/src/components/OffscreenFreeze.tsx b/src/components/OffscreenFreeze.tsx index 51595bb6c..8a94b5bd9 100644 --- a/src/components/OffscreenFreeze.tsx +++ b/src/components/OffscreenFreeze.tsx @@ -1,6 +1,5 @@ import React, { useContext, useRef } from 'react' -import { useTerminalViewport } from '../ink/hooks/use-terminal-viewport.js' -import { Box } from '../ink.js' +import { useTerminalViewport, Box } from '@anthropic/ink' import { InVirtualListContext } from './messageActions.js' type Props = { diff --git a/src/components/Onboarding.tsx b/src/components/Onboarding.tsx index 80083ff91..51dc04306 100644 --- a/src/components/Onboarding.tsx +++ b/src/components/Onboarding.tsx @@ -8,7 +8,7 @@ import { shouldOfferTerminalSetup, } from '../commands/terminalSetup/terminalSetup.js' import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Link, Newline, Text, useTheme } from '../ink.js' +import { Box, Link, Newline, Text, useTheme } from '@anthropic/ink' import { useKeybindings } from '../keybindings/useKeybinding.js' import { isAnthropicAuthEnabled } from '../utils/auth.js' import { normalizeApiKeyForConfig } from '../utils/authPortable.js' diff --git a/src/components/OutputStylePicker.tsx b/src/components/OutputStylePicker.tsx index 4ff039a82..5717a26e6 100644 --- a/src/components/OutputStylePicker.tsx +++ b/src/components/OutputStylePicker.tsx @@ -5,12 +5,11 @@ import { OUTPUT_STYLE_CONFIG, type OutputStyleConfig, } from '../constants/outputStyles.js' -import { Box, Text } from '../ink.js' +import { Box, Text, Dialog } from '@anthropic/ink' import type { OutputStyle } from '../utils/config.js' import { getCwd } from '../utils/cwd.js' import type { OptionWithDescription } from './CustomSelect/select.js' import { Select } from './CustomSelect/select.js' -import { Dialog } from './design-system/Dialog.js' const DEFAULT_OUTPUT_STYLE_LABEL = 'Default' const DEFAULT_OUTPUT_STYLE_DESCRIPTION = diff --git a/src/components/PackageManagerAutoUpdater.tsx b/src/components/PackageManagerAutoUpdater.tsx index e97d32b12..ffbb9e632 100644 --- a/src/components/PackageManagerAutoUpdater.tsx +++ b/src/components/PackageManagerAutoUpdater.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useState } from 'react' import { useInterval } from 'usehooks-ts' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { type AutoUpdaterResult, getLatestVersionFromGcs, diff --git a/src/components/Passes/Passes.tsx b/src/components/Passes/Passes.tsx index 69388618a..94b6b2d52 100644 --- a/src/components/Passes/Passes.tsx +++ b/src/components/Passes/Passes.tsx @@ -3,9 +3,9 @@ import { useCallback, useEffect, useState } from 'react' import type { CommandResultDisplay } from '../../commands.js' import { TEARDROP_ASTERISK } from '../../constants/figures.js' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import { setClipboard } from '../../ink/termio/osc.js' +import { setClipboard } from '@anthropic/ink' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- enter to copy link -import { Box, Link, Text, useInput } from '../../ink.js' +import { Box, Link, Text, useInput } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { logEvent } from '../../services/analytics/index.js' import { @@ -19,7 +19,7 @@ import type { } from '../../services/oauth/types.js' import { count } from '../../utils/array.js' import { logError } from '../../utils/log.js' -import { Pane } from '../design-system/Pane.js' +import { Pane } from '@anthropic/ink' type PassStatus = { passNumber: number diff --git a/src/components/PrBadge.tsx b/src/components/PrBadge.tsx index bb0aef9e7..e2f7a2a7b 100644 --- a/src/components/PrBadge.tsx +++ b/src/components/PrBadge.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Link, Text } from '../ink.js' +import { Link, Text } from '@anthropic/ink' import type { PrReviewState } from '../utils/ghPrStatus.js' type Props = { diff --git a/src/components/PressEnterToContinue.tsx b/src/components/PressEnterToContinue.tsx index 662c7af85..49b398b12 100644 --- a/src/components/PressEnterToContinue.tsx +++ b/src/components/PressEnterToContinue.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' export function PressEnterToContinue(): React.ReactNode { return ( diff --git a/src/components/PromptInput/HistorySearchInput.tsx b/src/components/PromptInput/HistorySearchInput.tsx index 22830119d..b8eaf8714 100644 --- a/src/components/PromptInput/HistorySearchInput.tsx +++ b/src/components/PromptInput/HistorySearchInput.tsx @@ -1,6 +1,5 @@ import * as React from 'react' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, Text } from '../../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import TextInput from '../TextInput.js' type Props = { diff --git a/src/components/PromptInput/IssueFlagBanner.tsx b/src/components/PromptInput/IssueFlagBanner.tsx index 723678eaf..39cb491b9 100644 --- a/src/components/PromptInput/IssueFlagBanner.tsx +++ b/src/components/PromptInput/IssueFlagBanner.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { FLAG_ICON } from '../../constants/figures.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' /** * ANT-ONLY: Banner shown in the transcript that prompts users to report diff --git a/src/components/PromptInput/Notifications.tsx b/src/components/PromptInput/Notifications.tsx index d89d596b3..fb905efda 100644 --- a/src/components/PromptInput/Notifications.tsx +++ b/src/components/PromptInput/Notifications.tsx @@ -13,7 +13,7 @@ import { useIdeConnectionStatus } from '../../hooks/useIdeConnectionStatus.js' import type { IDESelection } from '../../hooks/useIdeSelection.js' import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' import { useVoiceEnabled } from '../../hooks/useVoiceEnabled.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useClaudeAiLimits } from '../../services/claudeAiLimitsHook.js' import { calculateTokenWarningState } from '../../services/compact/autoCompact.js' import type { MCPServerConnection } from '../../services/mcp/types.js' diff --git a/src/components/PromptInput/PromptInput.tsx b/src/components/PromptInput/PromptInput.tsx index a678d41f6..e62da28de 100644 --- a/src/components/PromptInput/PromptInput.tsx +++ b/src/components/PromptInput/PromptInput.tsx @@ -63,9 +63,7 @@ import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' import { usePromptSuggestion } from '../../hooks/usePromptSuggestion.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' import { useTypeahead } from '../../hooks/useTypeahead.js' -import type { BorderTextOptions } from '../../ink/render-border.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, type ClickEvent, type Key, Text, useInput } from '../../ink.js' +import { Box, type BorderTextOptions, type ClickEvent, type Key, stringWidth, Text, useInput } from '@anthropic/ink' import { useOptionalKeybindingContext } from '../../keybindings/KeybindingContext.js' import { getShortcutDisplay } from '../../keybindings/shortcutFormat.js' import { diff --git a/src/components/PromptInput/PromptInputFooter.tsx b/src/components/PromptInput/PromptInputFooter.tsx index 652bdf3f0..46b3981f5 100644 --- a/src/components/PromptInput/PromptInputFooter.tsx +++ b/src/components/PromptInput/PromptInputFooter.tsx @@ -8,7 +8,7 @@ import type { VerificationStatus } from '../../hooks/useApiKeyVerification.js' import type { IDESelection } from '../../hooks/useIdeSelection.js' import { useSettings } from '../../hooks/useSettings.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { MCPServerConnection } from '../../services/mcp/types.js' import { useAppState } from '../../state/AppState.js' import type { ToolPermissionContext } from '../../Tool.js' diff --git a/src/components/PromptInput/PromptInputFooterLeftSide.tsx b/src/components/PromptInput/PromptInputFooterLeftSide.tsx index fc1be8124..8130c8ef1 100644 --- a/src/components/PromptInput/PromptInputFooterLeftSide.tsx +++ b/src/components/PromptInput/PromptInputFooterLeftSide.tsx @@ -6,7 +6,7 @@ const coordinatorModule = feature('COORDINATOR_MODE') ? (require('../../coordinator/coordinatorMode.js') as typeof import('../../coordinator/coordinatorMode.js')) : undefined /* eslint-enable @typescript-eslint/no-require-imports */ -import { Box, Text, Link } from '../../ink.js' +import { Box, Text, Link } from '@anthropic/ink' import * as React from 'react' import figures from 'figures' import { @@ -39,8 +39,7 @@ import { useAppState, useAppStateStore } from 'src/state/AppState.js' import { getIsRemoteMode } from '../../bootstrap/state.js' import HistorySearchInput from './HistorySearchInput.js' import { usePrStatus } from '../../hooks/usePrStatus.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' -import { Byline } from '../design-system/Byline.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { useTerminalSize } from '../../hooks/useTerminalSize.js' import { useTasksV2 } from '../../hooks/useTasksV2.js' import { formatDuration } from '../../utils/format.js' @@ -48,8 +47,7 @@ import { VoiceWarmupHint } from './VoiceIndicator.js' import { useVoiceEnabled } from '../../hooks/useVoiceEnabled.js' import { useVoiceState } from '../../context/voice.js' import { isFullscreenEnvEnabled } from '../../utils/fullscreen.js' -import { isXtermJs } from '../../ink/terminal.js' -import { useHasSelection, useSelection } from '../../ink/hooks/use-selection.js' +import { isXtermJs, useHasSelection, useSelection } from '@anthropic/ink' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' import { getPlatform } from '../../utils/platform.js' import { PrBadge } from '../PrBadge.js' diff --git a/src/components/PromptInput/PromptInputFooterSuggestions.tsx b/src/components/PromptInput/PromptInputFooterSuggestions.tsx index 7a5d70260..689123581 100644 --- a/src/components/PromptInput/PromptInputFooterSuggestions.tsx +++ b/src/components/PromptInput/PromptInputFooterSuggestions.tsx @@ -1,8 +1,7 @@ import * as React from 'react' import { memo, type ReactNode } from 'react' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, Text } from '../../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import { truncatePathMiddle, truncateToWidth } from '../../utils/format.js' import type { Theme } from '../../utils/theme.js' diff --git a/src/components/PromptInput/PromptInputHelpMenu.tsx b/src/components/PromptInput/PromptInputHelpMenu.tsx index 5f15327d2..88aa5dfc2 100644 --- a/src/components/PromptInput/PromptInputHelpMenu.tsx +++ b/src/components/PromptInput/PromptInputHelpMenu.tsx @@ -1,6 +1,6 @@ import { feature } from 'bun:bundle' import * as React from 'react' -import { Box, Text } from 'src/ink.js' +import { Box, Text } from '@anthropic/ink' import { getPlatform } from 'src/utils/platform.js' import { isKeybindingCustomizationEnabled } from '../../keybindings/loadUserBindings.js' import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js' diff --git a/src/components/PromptInput/PromptInputModeIndicator.tsx b/src/components/PromptInput/PromptInputModeIndicator.tsx index 4aa66bf7b..e493dba2d 100644 --- a/src/components/PromptInput/PromptInputModeIndicator.tsx +++ b/src/components/PromptInput/PromptInputModeIndicator.tsx @@ -1,6 +1,6 @@ import figures from 'figures' import * as React from 'react' -import { Box, Text } from 'src/ink.js' +import { Box, Text } from '@anthropic/ink' import { AGENT_COLOR_TO_THEME_COLOR, AGENT_COLORS, diff --git a/src/components/PromptInput/PromptInputQueuedCommands.tsx b/src/components/PromptInput/PromptInputQueuedCommands.tsx index c4637b803..e96ba3532 100644 --- a/src/components/PromptInput/PromptInputQueuedCommands.tsx +++ b/src/components/PromptInput/PromptInputQueuedCommands.tsx @@ -1,7 +1,7 @@ import { feature } from 'bun:bundle' import * as React from 'react' import { useMemo } from 'react' -import { Box } from 'src/ink.js' +import { Box } from '@anthropic/ink' import { useAppState } from 'src/state/AppState.js' import { STATUS_TAG, diff --git a/src/components/PromptInput/PromptInputStashNotice.tsx b/src/components/PromptInput/PromptInputStashNotice.tsx index 8a44e8607..c33f43190 100644 --- a/src/components/PromptInput/PromptInputStashNotice.tsx +++ b/src/components/PromptInput/PromptInputStashNotice.tsx @@ -1,6 +1,6 @@ import figures from 'figures' import * as React from 'react' -import { Box, Text } from 'src/ink.js' +import { Box, Text } from '@anthropic/ink' type Props = { hasStash: boolean diff --git a/src/components/PromptInput/SandboxPromptFooterHint.tsx b/src/components/PromptInput/SandboxPromptFooterHint.tsx index 1324a9832..e470509d4 100644 --- a/src/components/PromptInput/SandboxPromptFooterHint.tsx +++ b/src/components/PromptInput/SandboxPromptFooterHint.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { type ReactNode, useEffect, useRef, useState } from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js' import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js' diff --git a/src/components/PromptInput/ShimmeredInput.tsx b/src/components/PromptInput/ShimmeredInput.tsx index 11da7ad76..a14afbc9b 100644 --- a/src/components/PromptInput/ShimmeredInput.tsx +++ b/src/components/PromptInput/ShimmeredInput.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Ansi, Box, Text, useAnimationFrame } from '../../ink.js' +import { Ansi, Box, Text, useAnimationFrame } from '@anthropic/ink' import { segmentTextByHighlights, type TextHighlight, diff --git a/src/components/PromptInput/VoiceIndicator.tsx b/src/components/PromptInput/VoiceIndicator.tsx index 6dc73baf2..c6fe0b07f 100644 --- a/src/components/PromptInput/VoiceIndicator.tsx +++ b/src/components/PromptInput/VoiceIndicator.tsx @@ -1,7 +1,7 @@ import { feature } from 'bun:bundle' import * as React from 'react' import { useSettings } from '../../hooks/useSettings.js' -import { Box, Text, useAnimationFrame } from '../../ink.js' +import { Box, Text, useAnimationFrame } from '@anthropic/ink' import { interpolateColor, toRGBColor } from '../Spinner/utils.js' type Props = { diff --git a/src/components/PromptInput/utils.ts b/src/components/PromptInput/utils.ts index eb5cc8128..8b59fdf74 100644 --- a/src/components/PromptInput/utils.ts +++ b/src/components/PromptInput/utils.ts @@ -2,7 +2,7 @@ import { hasUsedBackslashReturn, isShiftEnterKeyBindingInstalled, } from '../../commands/terminalSetup/terminalSetup.js' -import type { Key } from '../../ink.js' +import type { Key } from '@anthropic/ink' import { getGlobalConfig } from '../../utils/config.js' import { env } from '../../utils/env.js' /** diff --git a/src/components/QuickOpenDialog.tsx b/src/components/QuickOpenDialog.tsx index 37b7bb7e1..6945fb682 100644 --- a/src/components/QuickOpenDialog.tsx +++ b/src/components/QuickOpenDialog.tsx @@ -4,15 +4,14 @@ import { useEffect, useRef, useState } from 'react' import { useRegisterOverlay } from '../context/overlayContext.js' import { generateFileSuggestions } from '../hooks/fileSuggestions.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { logEvent } from '../services/analytics/index.js' import { getCwd } from '../utils/cwd.js' import { openFileInExternalEditor } from '../utils/editor.js' import { truncatePathMiddle, truncateToWidth } from '../utils/format.js' import { highlightMatch } from '../utils/highlightMatch.js' import { readFileInRange } from '../utils/readFileInRange.js' -import { FuzzyPicker } from './design-system/FuzzyPicker.js' -import { LoadingState } from './design-system/LoadingState.js' +import { FuzzyPicker, LoadingState } from '@anthropic/ink' type Props = { onDone: () => void diff --git a/src/components/RemoteCallout.tsx b/src/components/RemoteCallout.tsx index d6b0af589..7d9dff991 100644 --- a/src/components/RemoteCallout.tsx +++ b/src/components/RemoteCallout.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useRef } from 'react' import { isBridgeEnabled } from '../bridge/bridgeEnabled.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { getClaudeAIOAuthTokens } from '../utils/auth.js' import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js' import type { OptionWithDescription } from './CustomSelect/select.js' diff --git a/src/components/RemoteEnvironmentDialog.tsx b/src/components/RemoteEnvironmentDialog.tsx index b0f47a60c..436b59583 100644 --- a/src/components/RemoteEnvironmentDialog.tsx +++ b/src/components/RemoteEnvironmentDialog.tsx @@ -2,7 +2,7 @@ import chalk from 'chalk' import figures from 'figures' import * as React from 'react' import { useEffect, useState } from 'react' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { toError } from '../utils/errors.js' import { logError } from '../utils/log.js' @@ -15,10 +15,7 @@ import { getEnvironmentSelectionInfo } from '../utils/teleport/environmentSelect import type { EnvironmentResource } from '../utils/teleport/environments.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { Select } from './CustomSelect/select.js' -import { Byline } from './design-system/Byline.js' -import { Dialog } from './design-system/Dialog.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' -import { LoadingState } from './design-system/LoadingState.js' +import { Byline, Dialog, KeyboardShortcutHint, LoadingState } from '@anthropic/ink' const DIALOG_TITLE = 'Select Remote Environment' const SETUP_HINT = `Configure environments at: https://claude.ai/code` diff --git a/src/components/ResumeTask.tsx b/src/components/ResumeTask.tsx index 8b657ab0d..c81ed0291 100644 --- a/src/components/ResumeTask.tsx +++ b/src/components/ResumeTask.tsx @@ -5,7 +5,7 @@ import { fetchCodeSessionsFromSessionsAPI, } from 'src/utils/teleport/api.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw j/k/arrow list navigation -import { Box, Text, useInput } from '../ink.js' +import { Box, Text, useInput } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js' import { logForDebugging } from '../utils/debug.js' @@ -13,8 +13,7 @@ import { detectCurrentRepository } from '../utils/detectRepository.js' import { formatRelativeTime } from '../utils/format.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { Select } from './CustomSelect/index.js' -import { Byline } from './design-system/Byline.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { Spinner } from './Spinner.js' import { TeleportError } from './TeleportError.js' diff --git a/src/components/SandboxViolationExpandedView.tsx b/src/components/SandboxViolationExpandedView.tsx index 4b8bbbd7a..93a448c87 100644 --- a/src/components/SandboxViolationExpandedView.tsx +++ b/src/components/SandboxViolationExpandedView.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { type ReactNode, useEffect, useState } from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import type { SandboxViolationEvent } from '../utils/sandbox/sandbox-adapter.js' import { SandboxManager } from '../utils/sandbox/sandbox-adapter.js' diff --git a/src/components/ScrollKeybindingHandler.tsx b/src/components/ScrollKeybindingHandler.tsx index e51787f9f..cdaac3256 100644 --- a/src/components/ScrollKeybindingHandler.tsx +++ b/src/components/ScrollKeybindingHandler.tsx @@ -4,13 +4,8 @@ import { useCopyOnSelect, useSelectionBgColor, } from '../hooks/useCopyOnSelect.js' -import type { ScrollBoxHandle } from '../ink/components/ScrollBox.js' -import { useSelection } from '../ink/hooks/use-selection.js' -import type { FocusMove, SelectionState } from '../ink/selection.js' -import { isXtermJs } from '../ink/terminal.js' -import { getClipboardPath } from '../ink/termio/osc.js' -// eslint-disable-next-line custom-rules/prefer-use-keybindings -- Esc needs conditional propagation based on selection state -import { type Key, useInput } from '../ink.js' +import type { ScrollBoxHandle, FocusMove, SelectionState } from '@anthropic/ink' +import { useSelection, type Key, useInput, isXtermJs, getClipboardPath } from '@anthropic/ink' import { useKeybindings } from '../keybindings/useKeybinding.js' import { logForDebugging } from '../utils/debug.js' diff --git a/src/components/SearchBox.tsx b/src/components/SearchBox.tsx index d35d67edd..9fd4a7fb4 100644 --- a/src/components/SearchBox.tsx +++ b/src/components/SearchBox.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' type Props = { query: string diff --git a/src/components/SessionBackgroundHint.tsx b/src/components/SessionBackgroundHint.tsx index a7f5e8f59..aa336afb0 100644 --- a/src/components/SessionBackgroundHint.tsx +++ b/src/components/SessionBackgroundHint.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useCallback, useState } from 'react' import { useDoublePress } from '../hooks/useDoublePress.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js' import { @@ -16,7 +16,7 @@ import { import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js' import { env } from '../utils/env.js' import { isEnvTruthy } from '../utils/envUtils.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' +import { KeyboardShortcutHint } from '@anthropic/ink' type Props = { onBackgroundSession: () => void diff --git a/src/components/SessionPreview.tsx b/src/components/SessionPreview.tsx index 2d0e10a97..2e0f74b2d 100644 --- a/src/components/SessionPreview.tsx +++ b/src/components/SessionPreview.tsx @@ -1,6 +1,6 @@ import type { UUID } from 'crypto' import React, { useCallback } from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text, Byline, KeyboardShortcutHint, LoadingState } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { getAllBaseTools } from '../tools.js' import type { LogOption } from '../types/logs.js' @@ -11,9 +11,6 @@ import { loadFullLog, } from '../utils/sessionStorage.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' -import { Byline } from './design-system/Byline.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' -import { LoadingState } from './design-system/LoadingState.js' import { Messages } from './Messages.js' type Props = { diff --git a/src/components/Settings/Config.tsx b/src/components/Settings/Config.tsx index 09f832f0c..2a4bc73c0 100644 --- a/src/components/Settings/Config.tsx +++ b/src/components/Settings/Config.tsx @@ -1,13 +1,6 @@ // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered import { feature } from 'bun:bundle' -import { - Box, - Text, - useTheme, - useThemeSetting, - useTerminalFocus, -} from '../../ink.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' +import { type KeyboardEvent, Box, Text, useTheme, useThemeSetting, useTerminalFocus } from '@anthropic/ink' import * as React from 'react' import { useState, useCallback } from 'react' import { @@ -67,7 +60,7 @@ import { ChannelDowngradeDialog, type ChannelDowngradeChoice, } from '../ChannelDowngradeDialog.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { Select } from '../CustomSelect/index.js' import { OutputStylePicker } from '../OutputStylePicker.js' import { LanguagePicker } from '../LanguagePicker.js' @@ -76,10 +69,9 @@ import { getMemoryFiles, hasExternalClaudeMdIncludes, } from 'src/utils/claudemd.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' -import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' -import { Byline } from '../design-system/Byline.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { useTabHeaderFocus } from '../design-system/Tabs.js' +import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' import { useIsInsideModal } from '../../context/modalContext.js' import { SearchBox } from '../SearchBox.js' import { diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx index d54f6e758..b6a5516dd 100644 --- a/src/components/Settings/Settings.tsx +++ b/src/components/Settings/Settings.tsx @@ -8,8 +8,8 @@ import { useIsInsideModal, useModalOrTerminalSize, } from '../../context/modalContext.js' -import { Pane } from '../design-system/Pane.js' -import { Tabs, Tab } from '../design-system/Tabs.js' +import { Pane, Tabs } from '@anthropic/ink' +import { Tab } from '../design-system/Tabs.js' import { Status, buildDiagnostics } from './Status.js' import { Config } from './Config.js' import { Usage } from './Usage.js' diff --git a/src/components/Settings/Status.tsx b/src/components/Settings/Status.tsx index 1cd4aac14..acadfcc06 100644 --- a/src/components/Settings/Status.tsx +++ b/src/components/Settings/Status.tsx @@ -4,7 +4,7 @@ import { Suspense, use } from 'react' import { getSessionId } from '../../bootstrap/state.js' import type { LocalJSXCommandContext } from '../../commands.js' import { useIsInsideModal } from '../../context/modalContext.js' -import { Box, Text, useTheme } from '../../ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import { type AppState, useAppState } from '../../state/AppState.js' import { getCwd } from '../../utils/cwd.js' import { getCurrentSessionTitle } from '../../utils/sessionStorage.js' diff --git a/src/components/Settings/Usage.tsx b/src/components/Settings/Usage.tsx index d52d19577..403e050eb 100644 --- a/src/components/Settings/Usage.tsx +++ b/src/components/Settings/Usage.tsx @@ -4,7 +4,7 @@ import { extraUsage as extraUsageCommand } from 'src/commands/extra-usage/index. import { formatCost } from 'src/cost-tracker.js' import { getSubscriptionType } from 'src/utils/auth.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { type ExtraUsage, @@ -16,8 +16,7 @@ import { formatResetText } from '../../utils/format.js' import { logError } from '../../utils/log.js' import { jsonStringify } from '../../utils/slowOperations.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' -import { Byline } from '../design-system/Byline.js' -import { ProgressBar } from '../design-system/ProgressBar.js' +import { Byline, ProgressBar } from '@anthropic/ink' import { isEligibleForOverageCreditGrant, OverageCreditUpsell, diff --git a/src/components/ShowInIDEPrompt.tsx b/src/components/ShowInIDEPrompt.tsx index e5ff331a3..e522cc6ae 100644 --- a/src/components/ShowInIDEPrompt.tsx +++ b/src/components/ShowInIDEPrompt.tsx @@ -1,10 +1,9 @@ import { basename, relative } from 'path' import React from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text, Pane } from '@anthropic/ink' import { getCwd } from '../utils/cwd.js' import { isSupportedVSCodeTerminal } from '../utils/ide.js' import { Select } from './CustomSelect/index.js' -import { Pane } from './design-system/Pane.js' import type { PermissionOption, PermissionOptionWithLabel, diff --git a/src/components/SkillImprovementSurvey.tsx b/src/components/SkillImprovementSurvey.tsx index f42e27f23..ad7b09147 100644 --- a/src/components/SkillImprovementSurvey.tsx +++ b/src/components/SkillImprovementSurvey.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef } from 'react' import { BLACK_CIRCLE, BULLET_OPERATOR } from '../constants/figures.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import type { SkillUpdate } from '../utils/hooks/skillImprovement.js' import { normalizeFullWidthDigits } from '../utils/stringUtils.js' import { isValidResponseInput } from './FeedbackSurvey/FeedbackSurveyView.js' diff --git a/src/components/Spinner.tsx b/src/components/Spinner.tsx index dc8692215..089bf4d8e 100644 --- a/src/components/Spinner.tsx +++ b/src/components/Spinner.tsx @@ -1,5 +1,5 @@ // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered -import { Box, Text } from '../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import * as React from 'react' import { useEffect, useMemo, useRef, useState } from 'react' import { @@ -27,7 +27,6 @@ import { useTasksV2 } from '../hooks/useTasksV2.js' import type { Task } from '../utils/tasks.js' import { useAppState } from '../state/AppState.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { stringWidth } from '../ink/stringWidth.js' import { getDefaultCharacters, type SpinnerMode } from './Spinner/index.js' import { SpinnerAnimationRow } from './Spinner/SpinnerAnimationRow.js' import { useSettings } from '../hooks/useSettings.js' @@ -45,7 +44,7 @@ import { } from '../bootstrap/state.js' import { TeammateSpinnerTree } from './Spinner/TeammateSpinnerTree.js' -import { useAnimationFrame } from '../ink.js' +import { useAnimationFrame } from '@anthropic/ink' import { getGlobalConfig } from '../utils/config.js' export type { SpinnerMode } from './Spinner/index.js' diff --git a/src/components/Spinner/FlashingChar.tsx b/src/components/Spinner/FlashingChar.tsx index 7f67a47ad..bfb190999 100644 --- a/src/components/Spinner/FlashingChar.tsx +++ b/src/components/Spinner/FlashingChar.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Text, useTheme } from '../../ink.js' +import { Text, useTheme } from '@anthropic/ink' import { getTheme, type Theme } from '../../utils/theme.js' import { interpolateColor, parseRGB, toRGBColor } from './utils.js' diff --git a/src/components/Spinner/GlimmerMessage.tsx b/src/components/Spinner/GlimmerMessage.tsx index 3e488f9a1..9f8ee9265 100644 --- a/src/components/Spinner/GlimmerMessage.tsx +++ b/src/components/Spinner/GlimmerMessage.tsx @@ -1,6 +1,5 @@ import * as React from 'react' -import { stringWidth } from '../../ink/stringWidth.js' -import { Text, useTheme } from '../../ink.js' +import { Text, stringWidth, useTheme } from '@anthropic/ink' import { getGraphemeSegmenter } from '../../utils/intl.js' import { getTheme, type Theme } from '../../utils/theme.js' import type { SpinnerMode } from './types.js' diff --git a/src/components/Spinner/ShimmerChar.tsx b/src/components/Spinner/ShimmerChar.tsx index 038ffb33d..0daa44c67 100644 --- a/src/components/Spinner/ShimmerChar.tsx +++ b/src/components/Spinner/ShimmerChar.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import type { Theme } from '../../utils/theme.js' type Props = { diff --git a/src/components/Spinner/SpinnerAnimationRow.tsx b/src/components/Spinner/SpinnerAnimationRow.tsx index 93b2fc64a..57b8358f2 100644 --- a/src/components/Spinner/SpinnerAnimationRow.tsx +++ b/src/components/Spinner/SpinnerAnimationRow.tsx @@ -1,13 +1,13 @@ import figures from 'figures' import * as React from 'react' import { useMemo, useRef } from 'react' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, Text, useAnimationFrame } from '../../ink.js' +import { Box, Text, useAnimationFrame, stringWidth, Byline } from '@anthropic/ink' +import { toInkColor } from '../../utils/ink.js' import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js' import { formatDuration, formatNumber } from '../../utils/format.js' -import { toInkColor } from '../../utils/ink.js' + import type { Theme } from '../../utils/theme.js' -import { Byline } from '../design-system/Byline.js' + import { GlimmerMessage } from './GlimmerMessage.js' import { SpinnerGlyph } from './SpinnerGlyph.js' import type { SpinnerMode } from './types.js' @@ -236,7 +236,6 @@ export function SpinnerAnimationRow({ totalTokens > 0 && availableSpace > usedAfterTimer + tokensWidth - const thinkingOnly = showThinking && thinkingStatus === 'thinking' && diff --git a/src/components/Spinner/SpinnerGlyph.tsx b/src/components/Spinner/SpinnerGlyph.tsx index 242d05971..d7db456db 100644 --- a/src/components/Spinner/SpinnerGlyph.tsx +++ b/src/components/Spinner/SpinnerGlyph.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text, useTheme } from '../../ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import { getTheme, type Theme } from '../../utils/theme.js' import { getDefaultCharacters, diff --git a/src/components/Spinner/TeammateSpinnerLine.tsx b/src/components/Spinner/TeammateSpinnerLine.tsx index ee6807f76..736623ce4 100644 --- a/src/components/Spinner/TeammateSpinnerLine.tsx +++ b/src/components/Spinner/TeammateSpinnerLine.tsx @@ -6,8 +6,8 @@ import { getSpinnerVerbs } from '../../constants/spinnerVerbs.js' import { TURN_COMPLETION_VERBS } from '../../constants/turnCompletionVerbs.js' import { useElapsedTime } from '../../hooks/useElapsedTime.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, Text } from '../../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' +import { toInkColor } from '../../utils/ink.js' import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js' import { summarizeRecentActivities } from '../../utils/collapseReadSearch.js' import { @@ -15,7 +15,7 @@ import { formatNumber, truncateToWidth, } from '../../utils/format.js' -import { toInkColor } from '../../utils/ink.js' + import { TEAMMATE_SELECT_HINT } from './teammateSelectHint.js' type Props = { diff --git a/src/components/Spinner/TeammateSpinnerTree.tsx b/src/components/Spinner/TeammateSpinnerTree.tsx index 331126f71..8c297ed57 100644 --- a/src/components/Spinner/TeammateSpinnerTree.tsx +++ b/src/components/Spinner/TeammateSpinnerTree.tsx @@ -1,6 +1,6 @@ import figures from 'figures' import * as React from 'react' -import { Box, Text, type TextProps } from '../../ink.js' +import { Box, Text, type TextProps } from '@anthropic/ink' import { useAppState } from '../../state/AppState.js' import { getRunningTeammatesSorted } from '../../tasks/InProcessTeammateTask/InProcessTeammateTask.js' import { formatNumber } from '../../utils/format.js' diff --git a/src/components/Spinner/useShimmerAnimation.ts b/src/components/Spinner/useShimmerAnimation.ts index d1d4ea947..d71d11f71 100644 --- a/src/components/Spinner/useShimmerAnimation.ts +++ b/src/components/Spinner/useShimmerAnimation.ts @@ -1,6 +1,5 @@ import { useMemo } from 'react' -import { stringWidth } from '../../ink/stringWidth.js' -import { type DOMElement, useAnimationFrame } from '../../ink.js' +import { type DOMElement, useAnimationFrame, stringWidth } from '@anthropic/ink' import type { SpinnerMode } from './types.js' export function useShimmerAnimation( diff --git a/src/components/Spinner/utils.ts b/src/components/Spinner/utils.ts index 7c0c54d00..0b90a7a59 100644 --- a/src/components/Spinner/utils.ts +++ b/src/components/Spinner/utils.ts @@ -1,4 +1,4 @@ -import type { RGBColor as RGBColorString } from '../../ink/styles.js' +import type { RGBColor as RGBColorString } from '@anthropic/ink' import type { RGBColor as RGBColorType } from './types.js' export function getDefaultCharacters(): string[] { diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx index b2dc29168..6d311259a 100644 --- a/src/components/Stats.tsx +++ b/src/components/Stats.tsx @@ -13,11 +13,9 @@ import React, { import stripAnsi from 'strip-ansi' import type { CommandResultDisplay } from '../commands.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { applyColor } from '../ink/colorize.js' -import { stringWidth as getStringWidth } from '../ink/stringWidth.js' -import type { Color } from '../ink/styles.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw j/k/arrow stats navigation -import { Ansi, Box, Text, useInput } from '../ink.js' +import { Ansi, applyColor, Box, Text, useInput, stringWidth as getStringWidth, type Color, Pane, Tabs } from '@anthropic/ink' +import { Tab, useTabHeaderFocus } from './design-system/Tabs.js' import { useKeybinding } from '../keybindings/useKeybinding.js' import { getGlobalConfig } from '../utils/config.js' import { formatDuration, formatNumber } from '../utils/format.js' @@ -32,8 +30,6 @@ import { } from '../utils/stats.js' import { resolveThemeSetting } from '../utils/systemTheme.js' import { getTheme, themeColorToAnsi } from '../utils/theme.js' -import { Pane } from './design-system/Pane.js' -import { Tab, Tabs, useTabHeaderFocus } from './design-system/Tabs.js' import { Spinner } from './Spinner.js' function formatPeakDay(dateStr: string): string { diff --git a/src/components/StatusLine.tsx b/src/components/StatusLine.tsx index ad90655f4..b2f2c7c91 100644 --- a/src/components/StatusLine.tsx +++ b/src/components/StatusLine.tsx @@ -25,7 +25,7 @@ import { } from '../cost-tracker.js' import { useMainLoopModel } from '../hooks/useMainLoopModel.js' import { type ReadonlySettings, useSettings } from '../hooks/useSettings.js' -import { Ansi, Box, Text } from '../ink.js' +import { Ansi, Box, Text } from '@anthropic/ink' import { getRawUtilization } from '../services/claudeAiLimits.js' import type { Message } from '../types/message.js' import type { StatusLineCommandInput } from '../types/statusLine.js' diff --git a/src/components/StatusNotices.tsx b/src/components/StatusNotices.tsx index a62df498e..ae25c5d9f 100644 --- a/src/components/StatusNotices.tsx +++ b/src/components/StatusNotices.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { use } from 'react' -import { Box } from '../ink.js' +import { Box } from '@anthropic/ink' import type { AgentDefinitionsResult } from '../tools/AgentTool/loadAgentsDir.js' import { getMemoryFiles } from '../utils/claudemd.js' import { getGlobalConfig } from '../utils/config.js' diff --git a/src/components/StructuredDiff.tsx b/src/components/StructuredDiff.tsx index 237dfea2b..890977701 100644 --- a/src/components/StructuredDiff.tsx +++ b/src/components/StructuredDiff.tsx @@ -2,7 +2,7 @@ import type { StructuredPatchHunk } from 'diff' import * as React from 'react' import { memo } from 'react' import { useSettings } from '../hooks/useSettings.js' -import { Box, NoSelect, RawAnsi, useTheme } from '../ink.js' +import { Box, NoSelect, RawAnsi, useTheme } from '@anthropic/ink' import { isFullscreenEnvEnabled } from '../utils/fullscreen.js' import sliceAnsi from '../utils/sliceAnsi.js' import { expectColorDiff } from './StructuredDiff/colorDiff.js' diff --git a/src/components/StructuredDiff/Fallback.tsx b/src/components/StructuredDiff/Fallback.tsx index 335391e0a..0331a0ed5 100644 --- a/src/components/StructuredDiff/Fallback.tsx +++ b/src/components/StructuredDiff/Fallback.tsx @@ -2,8 +2,7 @@ import { diffWordsWithSpace, type StructuredPatchHunk } from 'diff' import * as React from 'react' import { useMemo } from 'react' import type { ThemeName } from 'src/utils/theme.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, NoSelect, Text, useTheme, wrapText } from '../../ink.js' +import { Box, NoSelect, Text, stringWidth, useTheme, wrapText } from '@anthropic/ink' /* * StructuredDiffFallback Component: Word-Level Diff Highlighting Example diff --git a/src/components/StructuredDiffList.tsx b/src/components/StructuredDiffList.tsx index af0eeeb02..cfbedaecb 100644 --- a/src/components/StructuredDiffList.tsx +++ b/src/components/StructuredDiffList.tsx @@ -1,6 +1,6 @@ import type { StructuredPatchHunk } from 'diff' import * as React from 'react' -import { Box, NoSelect, Text } from '../ink.js' +import { Box, NoSelect, Text } from '@anthropic/ink' import { intersperse } from '../utils/array.js' import { StructuredDiff } from './StructuredDiff.js' diff --git a/src/components/TagTabs.tsx b/src/components/TagTabs.tsx index 786a1f81d..f974bdad4 100644 --- a/src/components/TagTabs.tsx +++ b/src/components/TagTabs.tsx @@ -1,6 +1,5 @@ import React from 'react' -import { stringWidth } from '../ink/stringWidth.js' -import { Box, Text } from '../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import { truncateToWidth } from '../utils/format.js' // Constants for width calculations - derived from actual rendered strings diff --git a/src/components/TaskListV2.tsx b/src/components/TaskListV2.tsx index d9d4dfa04..6ab77ccbb 100644 --- a/src/components/TaskListV2.tsx +++ b/src/components/TaskListV2.tsx @@ -1,8 +1,7 @@ import figures from 'figures' import * as React from 'react' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { stringWidth } from '../ink/stringWidth.js' -import { Box, Text } from '../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import { useAppState } from '../state/AppState.js' import { isInProcessTeammateTask } from '../tasks/InProcessTeammateTask/types.js' import { diff --git a/src/components/TeammateViewHeader.tsx b/src/components/TeammateViewHeader.tsx index 01e64571d..1613e7aca 100644 --- a/src/components/TeammateViewHeader.tsx +++ b/src/components/TeammateViewHeader.tsx @@ -1,9 +1,9 @@ import * as React from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text, KeyboardShortcutHint } from '@anthropic/ink' +import { toInkColor } from '../utils/ink.js' import { useAppState } from '../state/AppState.js' import { getViewedTeammateTask } from '../state/selectors.js' -import { toInkColor } from '../utils/ink.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' + import { OffscreenFreeze } from './OffscreenFreeze.js' /** diff --git a/src/components/TeleportError.tsx b/src/components/TeleportError.tsx index 343ef7427..260f488d3 100644 --- a/src/components/TeleportError.tsx +++ b/src/components/TeleportError.tsx @@ -4,10 +4,10 @@ import { checkNeedsClaudeAiLogin, } from 'src/utils/background/remote/preconditions.js' import { gracefulShutdownSync } from 'src/utils/gracefulShutdown.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { ConsoleOAuthFlow } from './ConsoleOAuthFlow.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { TeleportStash } from './TeleportStash.js' export type TeleportLocalErrorType = 'needsLogin' | 'needsGitStash' diff --git a/src/components/TeleportProgress.tsx b/src/components/TeleportProgress.tsx index f8ef62110..6ecb4c7cf 100644 --- a/src/components/TeleportProgress.tsx +++ b/src/components/TeleportProgress.tsx @@ -1,8 +1,8 @@ import figures from 'figures' import * as React from 'react' import { useState } from 'react' -import type { Root } from '../ink.js' -import { Box, Text, useAnimationFrame } from '../ink.js' +import type { Root } from '@anthropic/ink' +import { Box, Text, useAnimationFrame } from '@anthropic/ink' import { AppStateProvider } from '../state/AppState.js' import { checkOutTeleportedSessionBranch, diff --git a/src/components/TeleportRepoMismatchDialog.tsx b/src/components/TeleportRepoMismatchDialog.tsx index 126a9c432..83e55bd90 100644 --- a/src/components/TeleportRepoMismatchDialog.tsx +++ b/src/components/TeleportRepoMismatchDialog.tsx @@ -1,12 +1,12 @@ import React, { useCallback, useState } from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { getDisplayPath } from '../utils/file.js' import { removePathFromRepo, validateRepoAtPath, } from '../utils/githubRepoPathMapping.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { Spinner } from './Spinner.js' type Props = { diff --git a/src/components/TeleportResumeWrapper.tsx b/src/components/TeleportResumeWrapper.tsx index 60e6c7806..9a2a6261e 100644 --- a/src/components/TeleportResumeWrapper.tsx +++ b/src/components/TeleportResumeWrapper.tsx @@ -9,7 +9,7 @@ import { type TeleportSource, useTeleportResume, } from '../hooks/useTeleportResume.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { ResumeTask } from './ResumeTask.js' import { Spinner } from './Spinner.js' diff --git a/src/components/TeleportStash.tsx b/src/components/TeleportStash.tsx index 8baa30580..bb60e45a4 100644 --- a/src/components/TeleportStash.tsx +++ b/src/components/TeleportStash.tsx @@ -1,11 +1,10 @@ import figures from 'figures' import React, { useEffect, useState } from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text, Dialog } from '@anthropic/ink' import { logForDebugging } from '../utils/debug.js' import type { GitFileStatus } from '../utils/git.js' import { getFileStatus, stashToCleanState } from '../utils/git.js' import { Select } from './CustomSelect/index.js' -import { Dialog } from './design-system/Dialog.js' import { Spinner } from './Spinner.js' type TeleportStashProps = { diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx index 486c73ef2..0e1308336 100644 --- a/src/components/TextInput.tsx +++ b/src/components/TextInput.tsx @@ -5,13 +5,7 @@ import { useVoiceState } from '../context/voice.js' import { useClipboardImageHint } from '../hooks/useClipboardImageHint.js' import { useSettings } from '../hooks/useSettings.js' import { useTextInput } from '../hooks/useTextInput.js' -import { - Box, - color, - useAnimationFrame, - useTerminalFocus, - useTheme, -} from '../ink.js' +import { Box, color, useAnimationFrame, useTerminalFocus, useTheme } from '@anthropic/ink' import type { BaseTextInputProps } from '../types/textInputTypes.js' import { isEnvTruthy } from '../utils/envUtils.js' import type { TextHighlight } from '../utils/textHighlighting.js' diff --git a/src/components/ThemePicker.tsx b/src/components/ThemePicker.tsx index b14bcfd2c..2c99fc455 100644 --- a/src/components/ThemePicker.tsx +++ b/src/components/ThemePicker.tsx @@ -2,13 +2,7 @@ import { feature } from 'bun:bundle' import * as React from 'react' import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' -import { - Box, - Text, - usePreviewTheme, - useTheme, - useThemeSetting, -} from '../ink.js' +import { Box, Text, usePreviewTheme, useTheme, useThemeSetting } from '@anthropic/ink' import { useRegisterKeybindingContext } from '../keybindings/KeybindingContext.js' import { useKeybinding } from '../keybindings/useKeybinding.js' import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js' @@ -17,8 +11,7 @@ import { gracefulShutdown } from '../utils/gracefulShutdown.js' import { updateSettingsForSource } from '../utils/settings/settings.js' import type { ThemeSetting } from '../utils/theme.js' import { Select } from './CustomSelect/index.js' -import { Byline } from './design-system/Byline.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { getColorModuleUnavailableReason, getSyntaxTheme, diff --git a/src/components/ThinkingToggle.tsx b/src/components/ThinkingToggle.tsx index f17636cde..da2a78687 100644 --- a/src/components/ThinkingToggle.tsx +++ b/src/components/ThinkingToggle.tsx @@ -1,13 +1,11 @@ import * as React from 'react' import { useState } from 'react' import { useExitOnCtrlCDWithKeybindings } from 'src/hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { Select } from './CustomSelect/index.js' -import { Byline } from './design-system/Byline.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' -import { Pane } from './design-system/Pane.js' +import { Byline, KeyboardShortcutHint, Pane } from '@anthropic/ink' export type Props = { currentValue: boolean diff --git a/src/components/TokenWarning.tsx b/src/components/TokenWarning.tsx index 3ccb7235f..fe134c7ec 100644 --- a/src/components/TokenWarning.tsx +++ b/src/components/TokenWarning.tsx @@ -1,7 +1,7 @@ import { feature } from 'bun:bundle' import * as React from 'react' import { useSyncExternalStore } from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js' import { calculateTokenWarningState, diff --git a/src/components/ToolUseLoader.tsx b/src/components/ToolUseLoader.tsx index 52652be99..de8fdb3c2 100644 --- a/src/components/ToolUseLoader.tsx +++ b/src/components/ToolUseLoader.tsx @@ -1,7 +1,8 @@ import React from 'react' import { BLACK_CIRCLE } from '../constants/figures.js' + +import { Box, Text } from '@anthropic/ink' import { useBlink } from '../hooks/useBlink.js' -import { Box, Text } from '../ink.js' type Props = { isError: boolean diff --git a/src/components/TrustDialog/TrustDialog.tsx b/src/components/TrustDialog/TrustDialog.tsx index a57408392..88a0c897f 100644 --- a/src/components/TrustDialog/TrustDialog.tsx +++ b/src/components/TrustDialog/TrustDialog.tsx @@ -4,7 +4,7 @@ import { logEvent } from 'src/services/analytics/index.js' import { setSessionTrustAccepted } from '../../bootstrap/state.js' import type { Command } from '../../commands.js' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Link, Text } from '../../ink.js' +import { Box, Link, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { getMcpConfigsByScope } from '../../services/mcp/config.js' import { BASH_TOOL_NAME } from '../../tools/BashTool/toolName.js' diff --git a/src/components/ValidationErrorsList.tsx b/src/components/ValidationErrorsList.tsx index a7eea5400..6620ab2fd 100644 --- a/src/components/ValidationErrorsList.tsx +++ b/src/components/ValidationErrorsList.tsx @@ -1,6 +1,6 @@ import setWith from 'lodash-es/setWith.js' import * as React from 'react' -import { Box, Text, useTheme } from '../ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import type { ValidationError } from '../utils/settings/validation.js' import { type TreeNode, treeify } from '../utils/treeify.js' diff --git a/src/components/VimTextInput.tsx b/src/components/VimTextInput.tsx index bc8e8211f..42dfa252e 100644 --- a/src/components/VimTextInput.tsx +++ b/src/components/VimTextInput.tsx @@ -2,7 +2,7 @@ import chalk from 'chalk' import React from 'react' import { useClipboardImageHint } from '../hooks/useClipboardImageHint.js' import { useVimInput } from '../hooks/useVimInput.js' -import { Box, color, useTerminalFocus, useTheme } from '../ink.js' +import { Box, color, useTerminalFocus, useTheme } from '@anthropic/ink' import type { VimTextInputProps } from '../types/textInputTypes.js' import type { TextHighlight } from '../utils/textHighlighting.js' import { BaseTextInput } from './BaseTextInput.js' diff --git a/src/components/VirtualMessageList.tsx b/src/components/VirtualMessageList.tsx index bbe4a196c..5797297f6 100644 --- a/src/components/VirtualMessageList.tsx +++ b/src/components/VirtualMessageList.tsx @@ -10,11 +10,7 @@ import { useSyncExternalStore, } from 'react' import { useVirtualScroll } from '../hooks/useVirtualScroll.js' -import type { ScrollBoxHandle } from '../ink/components/ScrollBox.js' -import type { DOMElement } from '../ink/dom.js' -import type { MatchPosition } from '../ink/render-to-screen.js' -import { Box } from '../ink.js' -import type { RenderableMessage } from '../types/message.js' +import { Box, type DOMElement, type ScrollBoxHandle, type MatchPosition } from '@anthropic/ink' import { TextHoverColorContext } from './design-system/ThemedText.js' import { ScrollChromeContext } from './FullscreenLayout.js' diff --git a/src/components/WorkflowMultiselectDialog.tsx b/src/components/WorkflowMultiselectDialog.tsx index e06737514..d5e1d5460 100644 --- a/src/components/WorkflowMultiselectDialog.tsx +++ b/src/components/WorkflowMultiselectDialog.tsx @@ -1,12 +1,9 @@ import React, { useCallback, useState } from 'react' import type { Workflow } from '../commands/install-github-app/types.js' import type { ExitState } from '../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Link, Text } from '../ink.js' +import { Box, Link, Text, Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { SelectMulti } from './CustomSelect/SelectMulti.js' -import { Byline } from './design-system/Byline.js' -import { Dialog } from './design-system/Dialog.js' -import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' type WorkflowOption = { value: Workflow diff --git a/src/components/WorktreeExitDialog.tsx b/src/components/WorktreeExitDialog.tsx index ba5ab0f83..83ea8d554 100644 --- a/src/components/WorktreeExitDialog.tsx +++ b/src/components/WorktreeExitDialog.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react' import type { CommandResultDisplay } from 'src/commands.js' import { logEvent } from 'src/services/analytics/index.js' import { logForDebugging } from 'src/utils/debug.js' -import { Box, Text } from '../ink.js' +import { Box, Text, Dialog } from '@anthropic/ink' import { execFileNoThrow } from '../utils/execFileNoThrow.js' import { getPlansDirectory } from '../utils/plans.js' import { setCwd } from '../utils/Shell.js' @@ -13,7 +13,6 @@ import { killTmuxSession, } from '../utils/worktree.js' import { Select } from './CustomSelect/select.js' -import { Dialog } from './design-system/Dialog.js' import { Spinner } from './Spinner.js' // Inline require breaks the cycle this file would otherwise close: diff --git a/src/components/agents/AgentDetail.tsx b/src/components/agents/AgentDetail.tsx index 4c817b134..88f368dc6 100644 --- a/src/components/agents/AgentDetail.tsx +++ b/src/components/agents/AgentDetail.tsx @@ -1,7 +1,6 @@ import figures from 'figures' import * as React from 'react' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text } from '../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import type { Tools } from '../../Tool.js' import { getAgentColor } from '../../tools/AgentTool/agentColorManager.js' diff --git a/src/components/agents/AgentEditor.tsx b/src/components/agents/AgentEditor.tsx index e5c7b1847..7cbd52b96 100644 --- a/src/components/agents/AgentEditor.tsx +++ b/src/components/agents/AgentEditor.tsx @@ -3,8 +3,7 @@ import figures from 'figures' import * as React from 'react' import { useCallback, useMemo, useState } from 'react' import { useSetAppState } from 'src/state/AppState.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text } from '../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import type { Tools } from '../../Tool.js' import { diff --git a/src/components/agents/AgentNavigationFooter.tsx b/src/components/agents/AgentNavigationFooter.tsx index 9c4fa9f76..e52149f6a 100644 --- a/src/components/agents/AgentNavigationFooter.tsx +++ b/src/components/agents/AgentNavigationFooter.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' type Props = { instructions?: string diff --git a/src/components/agents/AgentsList.tsx b/src/components/agents/AgentsList.tsx index 6eadf1ef7..4be51e11b 100644 --- a/src/components/agents/AgentsList.tsx +++ b/src/components/agents/AgentsList.tsx @@ -1,8 +1,7 @@ import figures from 'figures' import * as React from 'react' import type { SettingSource } from 'src/utils/settings/constants.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text } from '../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import type { ResolvedAgent } from '../../tools/AgentTool/agentDisplay.js' import { AGENT_SOURCE_GROUPS, @@ -12,8 +11,7 @@ import { } from '../../tools/AgentTool/agentDisplay.js' import type { AgentDefinition } from '../../tools/AgentTool/loadAgentsDir.js' import { count } from '../../utils/array.js' -import { Dialog } from '../design-system/Dialog.js' -import { Divider } from '../design-system/Divider.js' +import { Dialog, Divider } from '@anthropic/ink' import { getAgentSourceDisplayName } from './utils.js' type Props = { diff --git a/src/components/agents/AgentsMenu.tsx b/src/components/agents/AgentsMenu.tsx index 91de932b4..00c3a9ce3 100644 --- a/src/components/agents/AgentsMenu.tsx +++ b/src/components/agents/AgentsMenu.tsx @@ -5,7 +5,7 @@ import type { SettingSource } from 'src/utils/settings/constants.js' import type { CommandResultDisplay } from '../../commands.js' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' import { useMergedTools } from '../../hooks/useMergedTools.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useAppState, useSetAppState } from '../../state/AppState.js' import type { Tools } from '../../Tool.js' import { @@ -19,7 +19,7 @@ import { import { toError } from '../../utils/errors.js' import { logError } from '../../utils/log.js' import { Select } from '../CustomSelect/select.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { AgentDetail } from './AgentDetail.js' import { AgentEditor } from './AgentEditor.js' import { AgentNavigationFooter } from './AgentNavigationFooter.js' diff --git a/src/components/agents/ColorPicker.tsx b/src/components/agents/ColorPicker.tsx index 8549424cd..7db5ea288 100644 --- a/src/components/agents/ColorPicker.tsx +++ b/src/components/agents/ColorPicker.tsx @@ -1,7 +1,7 @@ import figures from 'figures' import React, { useState } from 'react' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text } from '../../ink.js' +import type { KeyboardEvent } from '@anthropic/ink' +import { Box, Text } from '@anthropic/ink' import { AGENT_COLOR_TO_THEME_COLOR, AGENT_COLORS, diff --git a/src/components/agents/ModelSelector.tsx b/src/components/agents/ModelSelector.tsx index 4f1b2e8af..c3707d3fd 100644 --- a/src/components/agents/ModelSelector.tsx +++ b/src/components/agents/ModelSelector.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getAgentModelOptions } from '../../utils/model/agent.js' import { Select } from '../CustomSelect/select.js' diff --git a/src/components/agents/ToolSelector.tsx b/src/components/agents/ToolSelector.tsx index 9bc20b7d8..96b9f4b68 100644 --- a/src/components/agents/ToolSelector.tsx +++ b/src/components/agents/ToolSelector.tsx @@ -21,12 +21,11 @@ import { TodoWriteTool } from 'src/tools/TodoWriteTool/TodoWriteTool.js' import { TungstenTool } from 'src/tools/TungstenTool/TungstenTool.js' import { WebFetchTool } from 'src/tools/WebFetchTool/WebFetchTool.js' import { WebSearchTool } from 'src/tools/WebSearchTool/WebSearchTool.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text } from '../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { count } from '../../utils/array.js' import { plural } from '../../utils/stringUtils.js' -import { Divider } from '../design-system/Divider.js' +import { Divider } from '@anthropic/ink' type Props = { tools: Tools diff --git a/src/components/agents/new-agent-creation/wizard-steps/ColorStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/ColorStep.tsx index adc35e27c..7763daac6 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/ColorStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/ColorStep.tsx @@ -1,10 +1,8 @@ import React, { type ReactNode } from 'react' -import { Box } from '../../../../ink.js' +import { Box, Byline, KeyboardShortcutHint } from '@anthropic/ink' import { useKeybinding } from '../../../../keybindings/useKeybinding.js' import type { AgentColorName } from '../../../../tools/AgentTool/agentColorManager.js' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' import { ColorPicker } from '../../ColorPicker.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/ConfirmStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/ConfirmStep.tsx index bfa035eb5..ef02cbefe 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/ConfirmStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/ConfirmStep.tsx @@ -1,6 +1,5 @@ import React, { type ReactNode } from 'react' -import type { KeyboardEvent } from '../../../../ink/events/keyboard-event.js' -import { Box, Text } from '../../../../ink.js' +import { type KeyboardEvent, Box, Byline, KeyboardShortcutHint, Text } from '@anthropic/ink' import { useKeybinding } from '../../../../keybindings/useKeybinding.js' import { isAutoMemoryEnabled } from '../../../../memdir/paths.js' import type { Tools } from '../../../../Tool.js' @@ -9,8 +8,6 @@ import type { AgentDefinition } from '../../../../tools/AgentTool/loadAgentsDir. import { truncateToWidth } from '../../../../utils/format.js' import { getAgentModelDisplay } from '../../../../utils/model/agent.js' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' import { getNewRelativeAgentFilePath } from '../../agentFileUtils.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/DescriptionStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/DescriptionStep.tsx index 1138cc3d3..50a0d0590 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/DescriptionStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/DescriptionStep.tsx @@ -1,10 +1,8 @@ import React, { type ReactNode, useCallback, useState } from 'react' -import { Box, Text } from '../../../../ink.js' +import { Box, Byline, KeyboardShortcutHint, Text } from '@anthropic/ink' import { useKeybinding } from '../../../../keybindings/useKeybinding.js' import { editPromptInEditor } from '../../../../utils/promptEditor.js' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' import TextInput from '../../../TextInput.js' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/GenerateStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/GenerateStep.tsx index 1cb7ae69d..1e0b88512 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/GenerateStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/GenerateStep.tsx @@ -1,12 +1,11 @@ import { APIUserAbortError } from '@anthropic-ai/sdk' import React, { type ReactNode, useCallback, useRef, useState } from 'react' import { useMainLoopModel } from '../../../../hooks/useMainLoopModel.js' -import { Box, Text } from '../../../../ink.js' +import { Box, Byline, Text } from '@anthropic/ink' import { useKeybinding } from '../../../../keybindings/useKeybinding.js' import { createAbortController } from '../../../../utils/abortController.js' import { editPromptInEditor } from '../../../../utils/promptEditor.js' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' -import { Byline } from '../../../design-system/Byline.js' import { Spinner } from '../../../Spinner.js' import TextInput from '../../../TextInput.js' import { useWizard } from '../../../wizard/index.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/LocationStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/LocationStep.tsx index a7fd0a2bc..d846cb39b 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/LocationStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/LocationStep.tsx @@ -1,10 +1,8 @@ import React, { type ReactNode } from 'react' -import { Box } from '../../../../ink.js' +import { Box, Byline, KeyboardShortcutHint } from '@anthropic/ink' import type { SettingSource } from '../../../../utils/settings/constants.js' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' import { Select } from '../../../CustomSelect/select.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' import type { AgentWizardData } from '../types.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/MemoryStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/MemoryStep.tsx index 3c987cf77..d9fb92ed5 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/MemoryStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/MemoryStep.tsx @@ -1,5 +1,5 @@ import React, { type ReactNode } from 'react' -import { Box } from '../../../../ink.js' +import { Box, Byline, KeyboardShortcutHint } from '@anthropic/ink' import { useKeybinding } from '../../../../keybindings/useKeybinding.js' import { isAutoMemoryEnabled } from '../../../../memdir/paths.js' import { @@ -8,8 +8,6 @@ import { } from '../../../../tools/AgentTool/agentMemory.js' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' import { Select } from '../../../CustomSelect/select.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' import type { AgentWizardData } from '../types.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/MethodStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/MethodStep.tsx index 8f8252e12..552c20d1c 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/MethodStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/MethodStep.tsx @@ -1,9 +1,7 @@ import React, { type ReactNode } from 'react' -import { Box } from '../../../../ink.js' +import { Box, Byline, KeyboardShortcutHint } from '@anthropic/ink' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' import { Select } from '../../../CustomSelect/select.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' import type { AgentWizardData } from '../types.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/ModelStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/ModelStep.tsx index 586cc6cc8..a92ecde4a 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/ModelStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/ModelStep.tsx @@ -1,7 +1,6 @@ import React, { type ReactNode } from 'react' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' import { ModelSelector } from '../../ModelSelector.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/PromptStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/PromptStep.tsx index 4d6747520..c1e1f5260 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/PromptStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/PromptStep.tsx @@ -1,10 +1,8 @@ import React, { type ReactNode, useCallback, useState } from 'react' -import { Box, Text } from '../../../../ink.js' +import { Box, Byline, KeyboardShortcutHint, Text } from '@anthropic/ink' import { useKeybinding } from '../../../../keybindings/useKeybinding.js' import { editPromptInEditor } from '../../../../utils/promptEditor.js' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' import TextInput from '../../../TextInput.js' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/ToolsStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/ToolsStep.tsx index 501509ff5..19a8360c4 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/ToolsStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/ToolsStep.tsx @@ -1,8 +1,7 @@ import React, { type ReactNode } from 'react' import type { Tools } from '../../../../Tool.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' import { ToolSelector } from '../../ToolSelector.js' diff --git a/src/components/agents/new-agent-creation/wizard-steps/TypeStep.tsx b/src/components/agents/new-agent-creation/wizard-steps/TypeStep.tsx index 6ff025492..6c57440c8 100644 --- a/src/components/agents/new-agent-creation/wizard-steps/TypeStep.tsx +++ b/src/components/agents/new-agent-creation/wizard-steps/TypeStep.tsx @@ -1,10 +1,8 @@ import React, { type ReactNode, useState } from 'react' -import { Box, Text } from '../../../../ink.js' +import { Box, Byline, KeyboardShortcutHint, Text } from '@anthropic/ink' import { useKeybinding } from '../../../../keybindings/useKeybinding.js' import type { AgentDefinition } from '../../../../tools/AgentTool/loadAgentsDir.js' import { ConfigurableShortcutHint } from '../../../ConfigurableShortcutHint.js' -import { Byline } from '../../../design-system/Byline.js' -import { KeyboardShortcutHint } from '../../../design-system/KeyboardShortcutHint.js' import TextInput from '../../../TextInput.js' import { useWizard } from '../../../wizard/index.js' import { WizardDialogLayout } from '../../../wizard/WizardDialogLayout.js' diff --git a/src/components/design-system/Byline.tsx b/src/components/design-system/Byline.tsx index b0ddc97f3..d2b73b77a 100644 --- a/src/components/design-system/Byline.tsx +++ b/src/components/design-system/Byline.tsx @@ -1,5 +1,5 @@ import React, { Children, isValidElement } from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' type Props = { /** The items to join with a middot separator */ diff --git a/src/components/design-system/Dialog.tsx b/src/components/design-system/Dialog.tsx index 4472bd0d0..dfd65449e 100644 --- a/src/components/design-system/Dialog.tsx +++ b/src/components/design-system/Dialog.tsx @@ -3,7 +3,7 @@ import { type ExitState, useExitOnCtrlCDWithKeybindings, } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import type { Theme } from '../../utils/theme.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' diff --git a/src/components/design-system/Divider.tsx b/src/components/design-system/Divider.tsx index a88982be5..2606a5d5b 100644 --- a/src/components/design-system/Divider.tsx +++ b/src/components/design-system/Divider.tsx @@ -1,7 +1,6 @@ import React from 'react' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Ansi, Text } from '../../ink.js' +import { Ansi, Text, stringWidth } from '@anthropic/ink' import type { Theme } from '../../utils/theme.js' type DividerProps = { diff --git a/src/components/design-system/FuzzyPicker.tsx b/src/components/design-system/FuzzyPicker.tsx index fc1b9fe9e..8218f4074 100644 --- a/src/components/design-system/FuzzyPicker.tsx +++ b/src/components/design-system/FuzzyPicker.tsx @@ -2,9 +2,7 @@ import * as React from 'react' import { useEffect, useState } from 'react' import { useSearchInput } from '../../hooks/useSearchInput.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { clamp } from '../../ink/layout/geometry.js' -import { Box, Text, useTerminalFocus } from '../../ink.js' +import { type KeyboardEvent, Box, clamp, Text, useTerminalFocus } from '@anthropic/ink' import { SearchBox } from '../SearchBox.js' import { Byline } from './Byline.js' import { KeyboardShortcutHint } from './KeyboardShortcutHint.js' diff --git a/src/components/design-system/KeyboardShortcutHint.tsx b/src/components/design-system/KeyboardShortcutHint.tsx index 7d3c136d1..45489dfac 100644 --- a/src/components/design-system/KeyboardShortcutHint.tsx +++ b/src/components/design-system/KeyboardShortcutHint.tsx @@ -1,5 +1,5 @@ import React from 'react' -import Text from '../../ink/components/Text.js' +import { Text } from '@anthropic/ink' type Props = { /** The key or chord to display (e.g., "ctrl+o", "Enter", "↑/↓") */ diff --git a/src/components/design-system/ListItem.tsx b/src/components/design-system/ListItem.tsx index 2d142be03..bfdbb2747 100644 --- a/src/components/design-system/ListItem.tsx +++ b/src/components/design-system/ListItem.tsx @@ -1,8 +1,7 @@ import figures from 'figures' import type { ReactNode } from 'react' import React from 'react' -import { useDeclaredCursor } from '../../ink/hooks/use-declared-cursor.js' -import { Box, Text } from '../../ink.js' +import { useDeclaredCursor, Box, Text } from '@anthropic/ink' type ListItemProps = { /** diff --git a/src/components/design-system/LoadingState.tsx b/src/components/design-system/LoadingState.tsx index 046f726fa..ff6de70ec 100644 --- a/src/components/design-system/LoadingState.tsx +++ b/src/components/design-system/LoadingState.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { Spinner } from '../Spinner.js' type LoadingStateProps = { diff --git a/src/components/design-system/Pane.tsx b/src/components/design-system/Pane.tsx index 9c10907d3..e6641d18b 100644 --- a/src/components/design-system/Pane.tsx +++ b/src/components/design-system/Pane.tsx @@ -1,6 +1,6 @@ import React from 'react' import { useIsInsideModal } from '../../context/modalContext.js' -import { Box } from '../../ink.js' +import { Box } from '@anthropic/ink' import type { Theme } from '../../utils/theme.js' import { Divider } from './Divider.js' diff --git a/src/components/design-system/ProgressBar.tsx b/src/components/design-system/ProgressBar.tsx index 590fcd265..b69b47bcf 100644 --- a/src/components/design-system/ProgressBar.tsx +++ b/src/components/design-system/ProgressBar.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import type { Theme } from '../../utils/theme.js' type Props = { diff --git a/src/components/design-system/Ratchet.tsx b/src/components/design-system/Ratchet.tsx index 91580ff05..d47caa017 100644 --- a/src/components/design-system/Ratchet.tsx +++ b/src/components/design-system/Ratchet.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useLayoutEffect, useRef, useState } from 'react' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { useTerminalViewport } from '../../ink/hooks/use-terminal-viewport.js' -import { Box, type DOMElement, measureElement } from '../../ink.js' +import { useTerminalViewport, Box, type DOMElement, measureElement } from '@anthropic/ink' type Props = { children: React.ReactNode diff --git a/src/components/design-system/StatusIcon.tsx b/src/components/design-system/StatusIcon.tsx index 832c83a9e..d37d4d23b 100644 --- a/src/components/design-system/StatusIcon.tsx +++ b/src/components/design-system/StatusIcon.tsx @@ -1,6 +1,6 @@ import figures from 'figures' import React from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' type Status = 'success' | 'error' | 'warning' | 'info' | 'pending' | 'loading' diff --git a/src/components/design-system/Tabs.tsx b/src/components/design-system/Tabs.tsx index 40bae7baa..886b84673 100644 --- a/src/components/design-system/Tabs.tsx +++ b/src/components/design-system/Tabs.tsx @@ -10,10 +10,7 @@ import { useModalScrollRef, } from '../../context/modalContext.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import ScrollBox from '../../ink/components/ScrollBox.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, Text } from '../../ink.js' +import { Box, ScrollBox, Text, stringWidth, type KeyboardEvent } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import type { Theme } from '../../utils/theme.js' diff --git a/src/components/design-system/ThemeProvider.tsx b/src/components/design-system/ThemeProvider.tsx index ef60d23a1..55eb90447 100644 --- a/src/components/design-system/ThemeProvider.tsx +++ b/src/components/design-system/ThemeProvider.tsx @@ -6,7 +6,7 @@ import React, { useMemo, useState, } from 'react' -import useStdin from '../../ink/hooks/use-stdin.js' +import { useStdin } from '@anthropic/ink' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' import { getSystemThemeName, diff --git a/src/components/design-system/ThemedBox.tsx b/src/components/design-system/ThemedBox.tsx index 10fbe9137..008a2ab9e 100644 --- a/src/components/design-system/ThemedBox.tsx +++ b/src/components/design-system/ThemedBox.tsx @@ -1,10 +1,5 @@ import React, { type PropsWithChildren, type Ref } from 'react' -import Box from '../../ink/components/Box.js' -import type { DOMElement } from '../../ink/dom.js' -import type { ClickEvent } from '../../ink/events/click-event.js' -import type { FocusEvent } from '../../ink/events/focus-event.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import type { Color, Styles } from '../../ink/styles.js' +import { type ClickEvent, DOMElement, type FocusEvent, type KeyboardEvent, Color, type Styles, Box as BaseBox } from '@anthropic/ink' import { getTheme, type Theme } from '../../utils/theme.js' import { useTheme } from './ThemeProvider.js' diff --git a/src/components/design-system/ThemedText.tsx b/src/components/design-system/ThemedText.tsx index 3c32b8bc3..c89b1749a 100644 --- a/src/components/design-system/ThemedText.tsx +++ b/src/components/design-system/ThemedText.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react' import React, { useContext } from 'react' -import Text from '../../ink/components/Text.js' -import type { Color, Styles } from '../../ink/styles.js' +import { Text } from '@anthropic/ink' +import type { Color, Styles } from '@anthropic/ink' import { getTheme, type Theme } from '../../utils/theme.js' import { useTheme } from './ThemeProvider.js' diff --git a/src/components/design-system/color.ts b/src/components/design-system/color.ts index d21e51279..56c8612de 100644 --- a/src/components/design-system/color.ts +++ b/src/components/design-system/color.ts @@ -1,5 +1,4 @@ -import { type ColorType, colorize } from '../../ink/colorize.js' -import type { Color } from '../../ink/styles.js' +import { type ColorType, colorize, type Color } from '@anthropic/ink' import { getTheme, type Theme, type ThemeName } from '../../utils/theme.js' /** diff --git a/src/components/diff/DiffDetailView.tsx b/src/components/diff/DiffDetailView.tsx index af79d9365..43fb442fc 100644 --- a/src/components/diff/DiffDetailView.tsx +++ b/src/components/diff/DiffDetailView.tsx @@ -2,10 +2,10 @@ import type { StructuredPatchHunk } from 'diff' import { resolve } from 'path' import React, { useMemo } from 'react' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getCwd } from '../../utils/cwd.js' import { readFileSafe } from '../../utils/file.js' -import { Divider } from '../design-system/Divider.js' +import { Divider } from '@anthropic/ink' import { StructuredDiff } from '../StructuredDiff.js' type Props = { diff --git a/src/components/diff/DiffDialog.tsx b/src/components/diff/DiffDialog.tsx index 8847a25fc..3f6757b66 100644 --- a/src/components/diff/DiffDialog.tsx +++ b/src/components/diff/DiffDialog.tsx @@ -4,13 +4,12 @@ import type { CommandResultDisplay } from '../../commands.js' import { useRegisterOverlay } from '../../context/overlayContext.js' import { type DiffData, useDiffData } from '../../hooks/useDiffData.js' import { type TurnDiff, useTurnDiffs } from '../../hooks/useTurnDiffs.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js' import type { Message } from '../../types/message.js' import { plural } from '../../utils/stringUtils.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' +import { Byline, Dialog } from '@anthropic/ink' import { DiffDetailView } from './DiffDetailView.js' import { DiffFileList } from './DiffFileList.js' diff --git a/src/components/diff/DiffFileList.tsx b/src/components/diff/DiffFileList.tsx index 76c3040aa..ef498925b 100644 --- a/src/components/diff/DiffFileList.tsx +++ b/src/components/diff/DiffFileList.tsx @@ -2,7 +2,7 @@ import figures from 'figures' import React, { useMemo } from 'react' import type { DiffFile } from '../../hooks/useDiffData.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { truncateStartToWidth } from '../../utils/format.js' import { plural } from '../../utils/stringUtils.js' diff --git a/src/components/grove/Grove.tsx b/src/components/grove/Grove.tsx index 0998fcf05..938971b99 100644 --- a/src/components/grove/Grove.tsx +++ b/src/components/grove/Grove.tsx @@ -3,7 +3,7 @@ import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, } from 'src/services/analytics/index.js' -import { Box, Link, Text, useInput } from '../../ink.js' +import { Box, Link, Text, useInput } from '@anthropic/ink' import { type AccountSettings, calculateShouldShowGrove, @@ -14,9 +14,7 @@ import { updateGroveSettings, } from '../../services/api/grove.js' import { Select } from '../CustomSelect/index.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' export type GroveDecision = | 'accept_opt_in' diff --git a/src/components/hooks/HooksConfigMenu.tsx b/src/components/hooks/HooksConfigMenu.tsx index 425af2798..f59ced979 100644 --- a/src/components/hooks/HooksConfigMenu.tsx +++ b/src/components/hooks/HooksConfigMenu.tsx @@ -16,7 +16,7 @@ import type { HookEvent } from 'src/entrypoints/agentSdkTypes.js' import { useAppState, useAppStateStore } from 'src/state/AppState.js' import type { CommandResultDisplay } from '../../commands.js' import { useSettingsChange } from '../../hooks/useSettingsChange.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { getHookEventMetadata, @@ -31,7 +31,7 @@ import { getSettingsForSource, } from '../../utils/settings/settings.js' import { plural } from '../../utils/stringUtils.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { SelectEventMode } from './SelectEventMode.js' import { SelectHookMode } from './SelectHookMode.js' import { SelectMatcherMode } from './SelectMatcherMode.js' diff --git a/src/components/hooks/PromptDialog.tsx b/src/components/hooks/PromptDialog.tsx index f5566ec46..2bca1ceb4 100644 --- a/src/components/hooks/PromptDialog.tsx +++ b/src/components/hooks/PromptDialog.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import type { PromptRequest } from '../../types/hooks.js' import { Select } from '../CustomSelect/select.js' diff --git a/src/components/hooks/SelectEventMode.tsx b/src/components/hooks/SelectEventMode.tsx index a18d01952..c232f745a 100644 --- a/src/components/hooks/SelectEventMode.tsx +++ b/src/components/hooks/SelectEventMode.tsx @@ -11,10 +11,10 @@ import figures from 'figures' import * as React from 'react' import type { HookEvent } from 'src/entrypoints/agentSdkTypes.js' import type { HookEventMetadata } from 'src/utils/hooks/hooksConfigManager.js' -import { Box, Link, Text } from '../../ink.js' +import { Box, Link, Text } from '@anthropic/ink' import { plural } from '../../utils/stringUtils.js' import { Select } from '../CustomSelect/select.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' type Props = { hookEventMetadata: Record diff --git a/src/components/hooks/SelectHookMode.tsx b/src/components/hooks/SelectHookMode.tsx index 5764ae9d5..b27195b14 100644 --- a/src/components/hooks/SelectHookMode.tsx +++ b/src/components/hooks/SelectHookMode.tsx @@ -8,14 +8,14 @@ import * as React from 'react' import type { HookEvent } from 'src/entrypoints/agentSdkTypes.js' import type { HookEventMetadata } from 'src/utils/hooks/hooksConfigManager.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getHookDisplayText, hookSourceHeaderDisplayString, type IndividualHookConfig, } from '../../utils/hooks/hooksSettings.js' import { Select } from '../CustomSelect/select.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' type Props = { selectedEvent: HookEvent diff --git a/src/components/hooks/SelectMatcherMode.tsx b/src/components/hooks/SelectMatcherMode.tsx index 6792a47b1..c82987f02 100644 --- a/src/components/hooks/SelectMatcherMode.tsx +++ b/src/components/hooks/SelectMatcherMode.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react' import type { HookEvent } from 'src/entrypoints/agentSdkTypes.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { type HookSource, hookSourceInlineDisplayString, @@ -14,7 +14,7 @@ import { } from '../../utils/hooks/hooksSettings.js' import { plural } from '../../utils/stringUtils.js' import { Select } from '../CustomSelect/select.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' type MatcherWithSource = { matcher: string diff --git a/src/components/hooks/ViewHookMode.tsx b/src/components/hooks/ViewHookMode.tsx index 5766ead25..b79c09cca 100644 --- a/src/components/hooks/ViewHookMode.tsx +++ b/src/components/hooks/ViewHookMode.tsx @@ -5,12 +5,12 @@ * confirmation screen and directs users to settings.json or Claude for edits. */ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { hookSourceDescriptionDisplayString, type IndividualHookConfig, } from '../../utils/hooks/hooksSettings.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' type Props = { selectedHook: IndividualHookConfig diff --git a/src/components/mcp/CapabilitiesSection.tsx b/src/components/mcp/CapabilitiesSection.tsx index a5f98466a..dd0033382 100644 --- a/src/components/mcp/CapabilitiesSection.tsx +++ b/src/components/mcp/CapabilitiesSection.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { Box, Text } from '../../ink.js' -import { Byline } from '../design-system/Byline.js' +import { Box, Text } from '@anthropic/ink' +import { Byline } from '@anthropic/ink' type Props = { serverToolsCount: number diff --git a/src/components/mcp/ElicitationDialog.tsx b/src/components/mcp/ElicitationDialog.tsx index dbf8f22f9..c93003106 100644 --- a/src/components/mcp/ElicitationDialog.tsx +++ b/src/components/mcp/ElicitationDialog.tsx @@ -10,7 +10,7 @@ import { useRegisterOverlay } from '../../context/overlayContext.js' import { useNotifyAfterTimeout } from '../../hooks/useNotifyAfterTimeout.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw text input for elicitation form -import { Box, Text, useInput } from '../../ink.js' +import { Box, Text, useInput } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import type { ElicitationRequestEvent } from '../../services/mcp/elicitationHandler.js' import { openBrowser } from '../../utils/browser.js' @@ -27,9 +27,7 @@ import { } from '../../utils/mcp/elicitationValidation.js' import { plural } from '../../utils/stringUtils.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import TextInput from '../TextInput.js' type Props = { diff --git a/src/components/mcp/MCPAgentServerMenu.tsx b/src/components/mcp/MCPAgentServerMenu.tsx index b02f79dac..913dee9bb 100644 --- a/src/components/mcp/MCPAgentServerMenu.tsx +++ b/src/components/mcp/MCPAgentServerMenu.tsx @@ -1,7 +1,7 @@ import figures from 'figures' import React, { useCallback, useEffect, useRef, useState } from 'react' import type { CommandResultDisplay } from '../../commands.js' -import { Box, color, Link, Text, useTheme } from '../../ink.js' +import { Box, color, Link, Text, useTheme } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { AuthenticationCancelledError, @@ -10,9 +10,7 @@ import { import { capitalize } from '../../utils/stringUtils.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' import { Select } from '../CustomSelect/index.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { Spinner } from '../Spinner.js' import type { AgentMcpServerInfo } from './types.js' diff --git a/src/components/mcp/MCPListPanel.tsx b/src/components/mcp/MCPListPanel.tsx index af93c5538..365599284 100644 --- a/src/components/mcp/MCPListPanel.tsx +++ b/src/components/mcp/MCPListPanel.tsx @@ -1,16 +1,14 @@ import figures from 'figures' import React, { useCallback, useState } from 'react' import type { CommandResultDisplay } from '../../commands.js' -import { Box, color, Link, Text, useTheme } from '../../ink.js' +import { Box, color, Link, Text, useTheme } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import type { ConfigScope } from '../../services/mcp/types.js' import { describeMcpConfigFilePath } from '../../services/mcp/utils.js' import { isDebugMode } from '../../utils/debug.js' import { plural } from '../../utils/stringUtils.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { McpParsingWarnings } from './McpParsingWarnings.js' import type { AgentMcpServerInfo, ServerInfo } from './types.js' diff --git a/src/components/mcp/MCPReconnect.tsx b/src/components/mcp/MCPReconnect.tsx index 209c80541..496edaf2f 100644 --- a/src/components/mcp/MCPReconnect.tsx +++ b/src/components/mcp/MCPReconnect.tsx @@ -1,7 +1,7 @@ import figures from 'figures' import React, { useEffect, useState } from 'react' import type { CommandResultDisplay } from '../../commands.js' -import { Box, color, Text, useTheme } from '../../ink.js' +import { Box, color, Text, useTheme } from '@anthropic/ink' import { useMcpReconnect } from '../../services/mcp/MCPConnectionManager.js' import { useAppStateStore } from '../../state/AppState.js' import { Spinner } from '../Spinner.js' diff --git a/src/components/mcp/MCPRemoteServerMenu.tsx b/src/components/mcp/MCPRemoteServerMenu.tsx index 0a8efb76d..41665860e 100644 --- a/src/components/mcp/MCPRemoteServerMenu.tsx +++ b/src/components/mcp/MCPRemoteServerMenu.tsx @@ -8,9 +8,9 @@ import type { CommandResultDisplay } from '../../commands.js' import { getOauthConfig } from '../../constants/oauth.js' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { setClipboard } from '../../ink/termio/osc.js' +import { setClipboard } from '@anthropic/ink' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw j/k/arrow menu navigation -import { Box, color, Link, Text, useInput, useTheme } from '../../ink.js' +import { Box, color, Link, Text, useInput, useTheme } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { AuthenticationCancelledError, @@ -37,8 +37,7 @@ import { logMCPDebug } from '../../utils/log.js' import { capitalize } from '../../utils/stringUtils.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' import { Select } from '../CustomSelect/index.js' -import { Byline } from '../design-system/Byline.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { Spinner } from '../Spinner.js' import TextInput from '../TextInput.js' import { CapabilitiesSection } from './CapabilitiesSection.js' diff --git a/src/components/mcp/MCPStdioServerMenu.tsx b/src/components/mcp/MCPStdioServerMenu.tsx index 7caba350e..4132cdfe7 100644 --- a/src/components/mcp/MCPStdioServerMenu.tsx +++ b/src/components/mcp/MCPStdioServerMenu.tsx @@ -2,7 +2,7 @@ import figures from 'figures' import React, { useState } from 'react' import type { CommandResultDisplay } from '../../commands.js' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, color, Text, useTheme } from '../../ink.js' +import { Box, color, Text, useTheme } from '@anthropic/ink' import { getMcpConfigByName } from '../../services/mcp/config.js' import { useMcpReconnect, @@ -17,8 +17,7 @@ import { errorMessage } from '../../utils/errors.js' import { capitalize } from '../../utils/stringUtils.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' import { Select } from '../CustomSelect/index.js' -import { Byline } from '../design-system/Byline.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { Spinner } from '../Spinner.js' import { CapabilitiesSection } from './CapabilitiesSection.js' import type { StdioServerInfo } from './types.js' diff --git a/src/components/mcp/MCPToolDetailView.tsx b/src/components/mcp/MCPToolDetailView.tsx index b1ccb4d73..6b0b60173 100644 --- a/src/components/mcp/MCPToolDetailView.tsx +++ b/src/components/mcp/MCPToolDetailView.tsx @@ -1,12 +1,12 @@ import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { extractMcpToolDisplayName, getMcpDisplayName, } from '../../services/mcp/mcpStringUtils.js' import type { Tool } from '../../Tool.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import type { ServerInfo } from './types.js' type Props = { diff --git a/src/components/mcp/MCPToolListView.tsx b/src/components/mcp/MCPToolListView.tsx index 923791187..ad3eb3695 100644 --- a/src/components/mcp/MCPToolListView.tsx +++ b/src/components/mcp/MCPToolListView.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { extractMcpToolDisplayName, getMcpDisplayName, @@ -10,9 +10,7 @@ import type { Tool } from '../../Tool.js' import { plural } from '../../utils/stringUtils.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' import { Select } from '../CustomSelect/index.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import type { ServerInfo } from './types.js' type Props = { diff --git a/src/components/mcp/McpParsingWarnings.tsx b/src/components/mcp/McpParsingWarnings.tsx index 49e8353b9..c50946a91 100644 --- a/src/components/mcp/McpParsingWarnings.tsx +++ b/src/components/mcp/McpParsingWarnings.tsx @@ -6,7 +6,7 @@ import { getScopeLabel, } from 'src/services/mcp/utils.js' import type { ValidationError } from 'src/utils/settings/validation.js' -import { Box, Link, Text } from '../../ink.js' +import { Box, Link, Text } from '@anthropic/ink' function McpConfigErrorSection({ scope, diff --git a/src/components/memory/MemoryFileSelector.tsx b/src/components/memory/MemoryFileSelector.tsx index 2e2bdf623..fcd5834c6 100644 --- a/src/components/memory/MemoryFileSelector.tsx +++ b/src/components/memory/MemoryFileSelector.tsx @@ -6,7 +6,7 @@ import * as React from 'react' import { use, useEffect, useState } from 'react' import { getOriginalCwd } from '../../bootstrap/state.js' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Text } from '../../ink.js' +import { Box, Text, ListItem } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { getAutoMemPath, isAutoMemoryEnabled } from '../../memdir/paths.js' import { logEvent } from '../../services/analytics/index.js' @@ -22,7 +22,6 @@ import { formatRelativeTimeAgo } from '../../utils/format.js' import { projectIsInGitRepo } from '../../utils/memory/versions.js' import { updateSettingsForSource } from '../../utils/settings/settings.js' import { Select } from '../CustomSelect/index.js' -import { ListItem } from '../design-system/ListItem.js' /* eslint-disable @typescript-eslint/no-require-imports */ const teamMemPaths = feature('TEAMMEM') diff --git a/src/components/memory/MemoryUpdateNotification.tsx b/src/components/memory/MemoryUpdateNotification.tsx index 890a567b0..5e2db19b7 100644 --- a/src/components/memory/MemoryUpdateNotification.tsx +++ b/src/components/memory/MemoryUpdateNotification.tsx @@ -1,7 +1,7 @@ import { homedir } from 'os' import { relative } from 'path' import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getCwd } from '../../utils/cwd.js' export function getRelativeMemoryPath(path: string): string { diff --git a/src/components/messageActions.tsx b/src/components/messageActions.tsx index 3e368c306..2ecc0ab5a 100644 --- a/src/components/messageActions.tsx +++ b/src/components/messageActions.tsx @@ -1,7 +1,7 @@ import figures from 'figures' import type { RefObject } from 'react' import React, { useCallback, useMemo, useRef } from 'react' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybindings } from '../keybindings/useKeybinding.js' import { logEvent } from '../services/analytics/index.js' import type { diff --git a/src/components/messages/AdvisorMessage.tsx b/src/components/messages/AdvisorMessage.tsx index 4a77fe7ca..890e52405 100644 --- a/src/components/messages/AdvisorMessage.tsx +++ b/src/components/messages/AdvisorMessage.tsx @@ -1,6 +1,6 @@ import figures from 'figures' import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { AdvisorBlock } from '../../utils/advisor.js' import { renderModelName } from '../../utils/model/model.js' import { jsonStringify } from '../../utils/slowOperations.js' diff --git a/src/components/messages/AssistantRedactedThinkingMessage.tsx b/src/components/messages/AssistantRedactedThinkingMessage.tsx index eb0f66d35..4d825701d 100644 --- a/src/components/messages/AssistantRedactedThinkingMessage.tsx +++ b/src/components/messages/AssistantRedactedThinkingMessage.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' type Props = { addMargin: boolean diff --git a/src/components/messages/AssistantTextMessage.tsx b/src/components/messages/AssistantTextMessage.tsx index 005d2481e..56ffa054e 100644 --- a/src/components/messages/AssistantTextMessage.tsx +++ b/src/components/messages/AssistantTextMessage.tsx @@ -3,7 +3,7 @@ import React, { useContext } from 'react' import { ERROR_MESSAGE_USER_ABORT } from 'src/services/compact/compact.js' import { isRateLimitErrorMessage } from 'src/services/rateLimitMessages.js' import { BLACK_CIRCLE } from '../../constants/figures.js' -import { Box, NoSelect, Text } from '../../ink.js' +import { Box, NoSelect, Text } from '@anthropic/ink' import { API_ERROR_MESSAGE_PREFIX, API_TIMEOUT_ERROR_MESSAGE, diff --git a/src/components/messages/AssistantThinkingMessage.tsx b/src/components/messages/AssistantThinkingMessage.tsx index 2fc88512d..979f75637 100644 --- a/src/components/messages/AssistantThinkingMessage.tsx +++ b/src/components/messages/AssistantThinkingMessage.tsx @@ -3,7 +3,7 @@ import type { ThinkingBlockParam, } from '@anthropic-ai/sdk/resources/index.mjs' import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { CtrlOToExpand } from '../CtrlOToExpand.js' import { Markdown } from '../Markdown.js' diff --git a/src/components/messages/AssistantToolUseMessage.tsx b/src/components/messages/AssistantToolUseMessage.tsx index 65a92aad6..8f2dd965b 100644 --- a/src/components/messages/AssistantToolUseMessage.tsx +++ b/src/components/messages/AssistantToolUseMessage.tsx @@ -4,8 +4,7 @@ import { useTerminalSize } from 'src/hooks/useTerminalSize.js' import type { ThemeName } from 'src/utils/theme.js' import type { Command } from '../../commands.js' import { BLACK_CIRCLE } from '../../constants/figures.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Box, Text, useTheme } from '../../ink.js' +import { Box, Text, stringWidth, useTheme } from '@anthropic/ink' import { useAppStateMaybeOutsideOfProvider } from '../../state/AppState.js' import { findToolByName, diff --git a/src/components/messages/AttachmentMessage.tsx b/src/components/messages/AttachmentMessage.tsx index 51f9ea67d..77363371d 100644 --- a/src/components/messages/AttachmentMessage.tsx +++ b/src/components/messages/AttachmentMessage.tsx @@ -1,6 +1,8 @@ // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered import React, { useMemo } from 'react' -import { Ansi, Box, Text } from '../../ink.js' +import { Ansi, Box, Text } from '@anthropic/ink' +import { FilePathLink } from '../FilePathLink.js' +import { toInkColor } from '../../utils/ink.js' import type { Attachment } from 'src/utils/attachments.js' import type { NullRenderingAttachmentType } from './nullRenderingAttachments.js' import { useAppState } from '../../state/AppState.js' @@ -13,7 +15,7 @@ import { DiagnosticsDisplay } from '../DiagnosticsDisplay.js' import { getContentText } from 'src/utils/messages.js' import type { Theme } from 'src/utils/theme.js' import { UserImageMessage } from './UserImageMessage.js' -import { toInkColor } from '../../utils/ink.js' + import { jsonParse } from '../../utils/slowOperations.js' import { plural } from '../../utils/stringUtils.js' import { isEnvTruthy } from '../../utils/envUtils.js' @@ -26,7 +28,7 @@ import { BLACK_CIRCLE } from '../../constants/figures.js' import { TeammateMessageContent } from './UserTeammateMessage.js' import { isShutdownApproved } from '../../utils/teammateMailbox.js' import { CtrlOToExpand } from '../CtrlOToExpand.js' -import { FilePathLink } from '../FilePathLink.js' + import { feature } from 'bun:bundle' import { useSelectedMessageBg } from '../messageActions.js' diff --git a/src/components/messages/CollapsedReadSearchContent.tsx b/src/components/messages/CollapsedReadSearchContent.tsx index d8df34f69..5a0f1eb04 100644 --- a/src/components/messages/CollapsedReadSearchContent.tsx +++ b/src/components/messages/CollapsedReadSearchContent.tsx @@ -2,7 +2,7 @@ import { feature } from 'bun:bundle' import { basename } from 'path' import React, { useRef } from 'react' import { useMinDisplayTime } from '../../hooks/useMinDisplayTime.js' -import { Ansi, Box, Text, useTheme } from '../../ink.js' +import { Ansi, Box, Text, useTheme } from '@anthropic/ink' import { findToolByName, type Tools } from '../../Tool.js' import { getReplPrimitiveTools } from '../../tools/REPLTool/primitiveTools.js' import type { diff --git a/src/components/messages/CompactBoundaryMessage.tsx b/src/components/messages/CompactBoundaryMessage.tsx index 7c4e87af1..54033b23e 100644 --- a/src/components/messages/CompactBoundaryMessage.tsx +++ b/src/components/messages/CompactBoundaryMessage.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js' export function CompactBoundaryMessage(): React.ReactNode { diff --git a/src/components/messages/HighlightedThinkingText.tsx b/src/components/messages/HighlightedThinkingText.tsx index 1b4fd0c3c..14c61ff09 100644 --- a/src/components/messages/HighlightedThinkingText.tsx +++ b/src/components/messages/HighlightedThinkingText.tsx @@ -2,7 +2,7 @@ import figures from 'figures' import * as React from 'react' import { useContext } from 'react' import { useQueuedMessage } from '../../context/QueuedMessageContext.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { formatBriefTimestamp } from '../../utils/formatBriefTimestamp.js' import { findThinkingTriggerPositions, diff --git a/src/components/messages/HookProgressMessage.tsx b/src/components/messages/HookProgressMessage.tsx index 61bfddf96..68b1bd2d1 100644 --- a/src/components/messages/HookProgressMessage.tsx +++ b/src/components/messages/HookProgressMessage.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import type { HookEvent } from 'src/entrypoints/agentSdkTypes.js' import type { buildMessageLookups } from 'src/utils/messages.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { MessageResponse } from '../MessageResponse.js' type Props = { diff --git a/src/components/messages/PlanApprovalMessage.tsx b/src/components/messages/PlanApprovalMessage.tsx index a7fbced71..bb0a582dd 100644 --- a/src/components/messages/PlanApprovalMessage.tsx +++ b/src/components/messages/PlanApprovalMessage.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { Markdown } from '../../components/Markdown.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { jsonParse } from '../../utils/slowOperations.js' import { type IdleNotificationMessage, diff --git a/src/components/messages/RateLimitMessage.tsx b/src/components/messages/RateLimitMessage.tsx index c9a42815b..8ec18da7e 100644 --- a/src/components/messages/RateLimitMessage.tsx +++ b/src/components/messages/RateLimitMessage.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react' import { extraUsage } from 'src/commands/extra-usage/index.js' -import { Box, Text } from 'src/ink.js' +import { Box, Text } from '@anthropic/ink' import { useClaudeAiLimits } from 'src/services/claudeAiLimitsHook.js' import { shouldProcessMockLimits } from 'src/services/rateLimitMocking.js' // Used for /mock-limits command import { diff --git a/src/components/messages/ShutdownMessage.tsx b/src/components/messages/ShutdownMessage.tsx index 82e0d59e1..6f0dcbd97 100644 --- a/src/components/messages/ShutdownMessage.tsx +++ b/src/components/messages/ShutdownMessage.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { isShutdownApproved, isShutdownRejected, diff --git a/src/components/messages/SystemAPIErrorMessage.tsx b/src/components/messages/SystemAPIErrorMessage.tsx index c87dec717..30c5a2495 100644 --- a/src/components/messages/SystemAPIErrorMessage.tsx +++ b/src/components/messages/SystemAPIErrorMessage.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useState } from 'react' -import { Box, Text } from 'src/ink.js' +import { Box, Text } from '@anthropic/ink' import { formatAPIError } from 'src/services/api/errorUtils.js' import type { SystemAPIErrorMessage } from 'src/types/message.js' import { useInterval } from 'usehooks-ts' diff --git a/src/components/messages/SystemTextMessage.tsx b/src/components/messages/SystemTextMessage.tsx index 7d05f054a..a592c79de 100644 --- a/src/components/messages/SystemTextMessage.tsx +++ b/src/components/messages/SystemTextMessage.tsx @@ -1,5 +1,6 @@ // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered -import { Box, Text, type TextProps } from '../../ink.js' +import { Box, Link, Text, type TextProps } from '@anthropic/ink' +import { FilePathLink } from '../FilePathLink.js' import { feature } from 'bun:bundle' import * as React from 'react' import { useState } from 'react' @@ -12,7 +13,7 @@ import { import figures from 'figures' import { basename } from 'path' import { MessageResponse } from '../MessageResponse.js' -import { FilePathLink } from '../FilePathLink.js' + import { openPath } from '../../utils/browser.js' /* eslint-disable @typescript-eslint/no-require-imports */ const teamMemSaved = feature('TEAMMEM') @@ -36,7 +37,6 @@ import { formatSecondsShort, } from '../../utils/format.js' import { getGlobalConfig } from '../../utils/config.js' -import Link from '../../ink/components/Link.js' import ThemedText from '../design-system/ThemedText.js' import { CtrlOToExpand } from '../CtrlOToExpand.js' import { useAppStateStore } from '../../state/AppState.js' @@ -108,7 +108,6 @@ export function SystemTextMessage({ return null } - if (message.subtype === 'bridge_status') { return } diff --git a/src/components/messages/TaskAssignmentMessage.tsx b/src/components/messages/TaskAssignmentMessage.tsx index 1f7797873..146068a4f 100644 --- a/src/components/messages/TaskAssignmentMessage.tsx +++ b/src/components/messages/TaskAssignmentMessage.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { isTaskAssignment, type TaskAssignmentMessage, diff --git a/src/components/messages/UserAgentNotificationMessage.tsx b/src/components/messages/UserAgentNotificationMessage.tsx index 7e19c34d7..8bf906bea 100644 --- a/src/components/messages/UserAgentNotificationMessage.tsx +++ b/src/components/messages/UserAgentNotificationMessage.tsx @@ -1,7 +1,7 @@ import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import * as React from 'react' import { BLACK_CIRCLE } from '../../constants/figures.js' -import { Box, Text, type TextProps } from '../../ink.js' +import { Box, Text, type TextProps } from '@anthropic/ink' import { extractTag } from '../../utils/messages.js' type Props = { diff --git a/src/components/messages/UserBashInputMessage.tsx b/src/components/messages/UserBashInputMessage.tsx index c78fafea1..42e40df75 100644 --- a/src/components/messages/UserBashInputMessage.tsx +++ b/src/components/messages/UserBashInputMessage.tsx @@ -1,6 +1,6 @@ import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { extractTag } from '../../utils/messages.js' type Props = { diff --git a/src/components/messages/UserChannelMessage.tsx b/src/components/messages/UserChannelMessage.tsx index 8e7101b1a..a37f05c8c 100644 --- a/src/components/messages/UserChannelMessage.tsx +++ b/src/components/messages/UserChannelMessage.tsx @@ -2,7 +2,7 @@ import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import * as React from 'react' import { CHANNEL_ARROW } from '../../constants/figures.js' import { CHANNEL_TAG } from '../../constants/xml.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { truncateToWidth } from '../../utils/format.js' type Props = { diff --git a/src/components/messages/UserCommandMessage.tsx b/src/components/messages/UserCommandMessage.tsx index 31f6b2871..1c9502c32 100644 --- a/src/components/messages/UserCommandMessage.tsx +++ b/src/components/messages/UserCommandMessage.tsx @@ -2,7 +2,7 @@ import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import figures from 'figures' import * as React from 'react' import { COMMAND_MESSAGE_TAG } from '../../constants/xml.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { extractTag } from '../../utils/messages.js' type Props = { diff --git a/src/components/messages/UserImageMessage.tsx b/src/components/messages/UserImageMessage.tsx index 3f542dfb6..ca960d59c 100644 --- a/src/components/messages/UserImageMessage.tsx +++ b/src/components/messages/UserImageMessage.tsx @@ -1,8 +1,6 @@ import * as React from 'react' import { pathToFileURL } from 'url' -import Link from '../../ink/components/Link.js' -import { supportsHyperlinks } from '../../ink/supports-hyperlinks.js' -import { Box, Text } from '../../ink.js' +import { Box, Link, supportsHyperlinks, Text } from '@anthropic/ink' import { getStoredImagePath } from '../../utils/imageStore.js' import { MessageResponse } from '../MessageResponse.js' diff --git a/src/components/messages/UserLocalCommandOutputMessage.tsx b/src/components/messages/UserLocalCommandOutputMessage.tsx index b1c95616a..3467686b4 100644 --- a/src/components/messages/UserLocalCommandOutputMessage.tsx +++ b/src/components/messages/UserLocalCommandOutputMessage.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { DIAMOND_FILLED, DIAMOND_OPEN } from '../../constants/figures.js' import { NO_CONTENT_MESSAGE } from '../../constants/messages.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { extractTag } from '../../utils/messages.js' import { Markdown } from '../Markdown.js' import { MessageResponse } from '../MessageResponse.js' diff --git a/src/components/messages/UserMemoryInputMessage.tsx b/src/components/messages/UserMemoryInputMessage.tsx index 25a8d7a1c..cbdfdcd5d 100644 --- a/src/components/messages/UserMemoryInputMessage.tsx +++ b/src/components/messages/UserMemoryInputMessage.tsx @@ -1,7 +1,7 @@ import sample from 'lodash-es/sample.js' import * as React from 'react' import { useMemo } from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { extractTag } from '../../utils/messages.js' import { MessageResponse } from '../MessageResponse.js' diff --git a/src/components/messages/UserPlanMessage.tsx b/src/components/messages/UserPlanMessage.tsx index 5ef8fa89a..30f2b369b 100644 --- a/src/components/messages/UserPlanMessage.tsx +++ b/src/components/messages/UserPlanMessage.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { Markdown } from '../Markdown.js' type Props = { diff --git a/src/components/messages/UserPromptMessage.tsx b/src/components/messages/UserPromptMessage.tsx index 090cac272..00f201368 100644 --- a/src/components/messages/UserPromptMessage.tsx +++ b/src/components/messages/UserPromptMessage.tsx @@ -2,7 +2,7 @@ import { feature } from 'bun:bundle' import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import React, { useContext, useMemo } from 'react' import { getKairosActive, getUserMsgOptIn } from '../../bootstrap/state.js' -import { Box } from '../../ink.js' +import { Box } from '@anthropic/ink' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' import { useAppState } from '../../state/AppState.js' import { isEnvTruthy } from '../../utils/envUtils.js' diff --git a/src/components/messages/UserResourceUpdateMessage.tsx b/src/components/messages/UserResourceUpdateMessage.tsx index ce1f4f5d5..ec0f5a2ac 100644 --- a/src/components/messages/UserResourceUpdateMessage.tsx +++ b/src/components/messages/UserResourceUpdateMessage.tsx @@ -1,7 +1,7 @@ import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import * as React from 'react' import { REFRESH_ARROW } from '../../constants/figures.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' type Props = { addMargin: boolean diff --git a/src/components/messages/UserTeammateMessage.tsx b/src/components/messages/UserTeammateMessage.tsx index 4c174ff1c..e75256abc 100644 --- a/src/components/messages/UserTeammateMessage.tsx +++ b/src/components/messages/UserTeammateMessage.tsx @@ -2,8 +2,9 @@ import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import figures from 'figures' import * as React from 'react' import { TEAMMATE_MESSAGE_TAG } from '../../constants/xml.js' -import { Ansi, Box, Text, type TextProps } from '../../ink.js' +import { Ansi, Box, Text, type TextProps } from '@anthropic/ink' import { toInkColor } from '../../utils/ink.js' + import { jsonParse } from '../../utils/slowOperations.js' import { isShutdownApproved } from '../../utils/teammateMailbox.js' import { MessageResponse } from '../MessageResponse.js' diff --git a/src/components/messages/UserToolResultMessage/RejectedPlanMessage.tsx b/src/components/messages/UserToolResultMessage/RejectedPlanMessage.tsx index bee8d5c3a..ec2130230 100644 --- a/src/components/messages/UserToolResultMessage/RejectedPlanMessage.tsx +++ b/src/components/messages/UserToolResultMessage/RejectedPlanMessage.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { Markdown } from 'src/components/Markdown.js' import { MessageResponse } from 'src/components/MessageResponse.js' -import { Box, Text } from '../../../ink.js' +import { Box, Text } from '@anthropic/ink' type Props = { plan: string diff --git a/src/components/messages/UserToolResultMessage/RejectedToolUseMessage.tsx b/src/components/messages/UserToolResultMessage/RejectedToolUseMessage.tsx index b387b0fea..5764a4e17 100644 --- a/src/components/messages/UserToolResultMessage/RejectedToolUseMessage.tsx +++ b/src/components/messages/UserToolResultMessage/RejectedToolUseMessage.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Text } from '../../../ink.js' +import { Text } from '@anthropic/ink' import { MessageResponse } from '../../MessageResponse.js' export function RejectedToolUseMessage(): React.ReactNode { diff --git a/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx b/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx index 33249d591..a178e8e34 100644 --- a/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +++ b/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx @@ -2,7 +2,7 @@ import { feature } from 'bun:bundle' import type { ToolResultBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import * as React from 'react' import { BULLET_OPERATOR } from '../../../constants/figures.js' -import { Text } from '../../../ink.js' +import { Text } from '@anthropic/ink' import { filterToolProgressMessages, type Tool, diff --git a/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx b/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx index c1a37ef4e..c53d81e6d 100644 --- a/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +++ b/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useTerminalSize } from '../../../hooks/useTerminalSize.js' -import { useTheme } from '../../../ink.js' +import { useTheme } from '@anthropic/ink' import { filterToolProgressMessages, type Tool, diff --git a/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx b/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx index 931e0df3e..ffb8e23d5 100644 --- a/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +++ b/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx @@ -2,7 +2,7 @@ import { feature } from 'bun:bundle' import figures from 'figures' import * as React from 'react' import { SentryErrorBoundary } from 'src/components/SentryErrorBoundary.js' -import { Box, Text, useTheme } from '../../../ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import { useAppState } from '../../../state/AppState.js' import { filterToolProgressMessages, diff --git a/src/components/messages/teamMemCollapsed.tsx b/src/components/messages/teamMemCollapsed.tsx index 63fcdaf0e..eaa9219ab 100644 --- a/src/components/messages/teamMemCollapsed.tsx +++ b/src/components/messages/teamMemCollapsed.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import type { CollapsedReadSearchGroup } from '../../types/message.js' /** diff --git a/src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.tsx b/src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.tsx index 3768dbd73..562c6653c 100644 --- a/src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.tsx +++ b/src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.tsx @@ -12,8 +12,7 @@ import React, { } from 'react' import { useSettings } from '../../../hooks/useSettings.js' import { useTerminalSize } from '../../../hooks/useTerminalSize.js' -import { stringWidth } from '../../../ink/stringWidth.js' -import { useTheme } from '../../../ink.js' +import { stringWidth, useTheme } from '@anthropic/ink' import { useKeybindings } from '../../../keybindings/useKeybinding.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, diff --git a/src/components/permissions/AskUserQuestionPermissionRequest/PreviewBox.tsx b/src/components/permissions/AskUserQuestionPermissionRequest/PreviewBox.tsx index 7b4fd6149..686f2f4c9 100644 --- a/src/components/permissions/AskUserQuestionPermissionRequest/PreviewBox.tsx +++ b/src/components/permissions/AskUserQuestionPermissionRequest/PreviewBox.tsx @@ -1,8 +1,7 @@ import React, { Suspense, use, useMemo } from 'react' import { useSettings } from '../../../hooks/useSettings.js' import { useTerminalSize } from '../../../hooks/useTerminalSize.js' -import { stringWidth } from '../../../ink/stringWidth.js' -import { Ansi, Box, Text, useTheme } from '../../../ink.js' +import { Ansi, Box, Text, stringWidth, useTheme } from '@anthropic/ink' import { type CliHighlight, getCliHighlightPromise, diff --git a/src/components/permissions/AskUserQuestionPermissionRequest/PreviewQuestionView.tsx b/src/components/permissions/AskUserQuestionPermissionRequest/PreviewQuestionView.tsx index 78289da5f..6e80ce25f 100644 --- a/src/components/permissions/AskUserQuestionPermissionRequest/PreviewQuestionView.tsx +++ b/src/components/permissions/AskUserQuestionPermissionRequest/PreviewQuestionView.tsx @@ -1,8 +1,7 @@ import figures from 'figures' import React, { useCallback, useMemo, useRef, useState } from 'react' import { useTerminalSize } from '../../../hooks/useTerminalSize.js' -import type { KeyboardEvent } from '../../../ink/events/keyboard-event.js' -import { Box, Text } from '../../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useKeybinding, useKeybindings, @@ -12,7 +11,7 @@ import type { Question } from '../../../tools/AskUserQuestionTool/AskUserQuestio import { getExternalEditor } from '../../../utils/editor.js' import { toIDEDisplayName } from '../../../utils/ide.js' import { editPromptInEditor } from '../../../utils/promptEditor.js' -import { Divider } from '../../design-system/Divider.js' +import { Divider } from '@anthropic/ink' import TextInput from '../../TextInput.js' import { PermissionRequestTitle } from '../PermissionRequestTitle.js' import { PreviewBox } from './PreviewBox.js' diff --git a/src/components/permissions/AskUserQuestionPermissionRequest/QuestionNavigationBar.tsx b/src/components/permissions/AskUserQuestionPermissionRequest/QuestionNavigationBar.tsx index 3440e9daf..082ba086c 100644 --- a/src/components/permissions/AskUserQuestionPermissionRequest/QuestionNavigationBar.tsx +++ b/src/components/permissions/AskUserQuestionPermissionRequest/QuestionNavigationBar.tsx @@ -1,8 +1,7 @@ import figures from 'figures' import React, { useMemo } from 'react' import { useTerminalSize } from '../../../hooks/useTerminalSize.js' -import { stringWidth } from '../../../ink/stringWidth.js' -import { Box, Text } from '../../../ink.js' +import { Box, Text, stringWidth } from '@anthropic/ink' import type { Question } from '../../../tools/AskUserQuestionTool/AskUserQuestionTool.js' import { truncateToWidth } from '../../../utils/format.js' diff --git a/src/components/permissions/AskUserQuestionPermissionRequest/QuestionView.tsx b/src/components/permissions/AskUserQuestionPermissionRequest/QuestionView.tsx index ef45238ab..ec4aa3ad7 100644 --- a/src/components/permissions/AskUserQuestionPermissionRequest/QuestionView.tsx +++ b/src/components/permissions/AskUserQuestionPermissionRequest/QuestionView.tsx @@ -1,7 +1,6 @@ import figures from 'figures' import React, { useCallback, useState } from 'react' -import type { KeyboardEvent } from '../../../ink/events/keyboard-event.js' -import { Box, Text } from '../../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useAppState } from '../../../state/AppState.js' import type { Question, @@ -17,8 +16,9 @@ import { Select, SelectMulti, } from '../../CustomSelect/index.js' -import { Divider } from '../../design-system/Divider.js' +import { Divider } from '@anthropic/ink' import { FilePathLink } from '../../FilePathLink.js' + import { PermissionRequestTitle } from '../PermissionRequestTitle.js' import { PreviewQuestionView } from './PreviewQuestionView.js' import { QuestionNavigationBar } from './QuestionNavigationBar.js' diff --git a/src/components/permissions/AskUserQuestionPermissionRequest/SubmitQuestionsView.tsx b/src/components/permissions/AskUserQuestionPermissionRequest/SubmitQuestionsView.tsx index b17a26c2a..37e7e832c 100644 --- a/src/components/permissions/AskUserQuestionPermissionRequest/SubmitQuestionsView.tsx +++ b/src/components/permissions/AskUserQuestionPermissionRequest/SubmitQuestionsView.tsx @@ -1,10 +1,10 @@ import figures from 'figures' import React from 'react' -import { Box, Text } from '../../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { Question } from '../../../tools/AskUserQuestionTool/AskUserQuestionTool.js' import type { PermissionDecision } from '../../../utils/permissions/PermissionResult.js' import { Select } from '../../CustomSelect/index.js' -import { Divider } from '../../design-system/Divider.js' +import { Divider } from '@anthropic/ink' import { PermissionRequestTitle } from '../PermissionRequestTitle.js' import { PermissionRuleExplanation } from '../PermissionRuleExplanation.js' import { QuestionNavigationBar } from './QuestionNavigationBar.js' diff --git a/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx b/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx index 1eb1ffffe..fb2c06da9 100644 --- a/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +++ b/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx @@ -1,7 +1,7 @@ import { feature } from 'bun:bundle' import figures from 'figures' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Box, Text, useTheme } from '../../../ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import { useKeybinding } from '../../../keybindings/useKeybinding.js' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../../services/analytics/growthbook.js' import { diff --git a/src/components/permissions/ComputerUseApproval/ComputerUseApproval.tsx b/src/components/permissions/ComputerUseApproval/ComputerUseApproval.tsx index c591082e4..6eecf1be3 100644 --- a/src/components/permissions/ComputerUseApproval/ComputerUseApproval.tsx +++ b/src/components/permissions/ComputerUseApproval/ComputerUseApproval.tsx @@ -7,12 +7,12 @@ import { DEFAULT_GRANT_FLAGS } from '@ant/computer-use-mcp/types' import figures from 'figures' import * as React from 'react' import { useMemo, useState } from 'react' -import { Box, Text } from '../../../ink.js' +import { Box, Text } from '@anthropic/ink' import { execFileNoThrow } from '../../../utils/execFileNoThrow.js' import { plural } from '../../../utils/stringUtils.js' import type { OptionWithDescription } from '../../CustomSelect/select.js' import { Select } from '../../CustomSelect/select.js' -import { Dialog } from '../../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' type ComputerUseApprovalProps = { request: CuPermissionRequest diff --git a/src/components/permissions/EnterPlanModePermissionRequest/EnterPlanModePermissionRequest.tsx b/src/components/permissions/EnterPlanModePermissionRequest/EnterPlanModePermissionRequest.tsx index 4251891e0..9922f1680 100644 --- a/src/components/permissions/EnterPlanModePermissionRequest/EnterPlanModePermissionRequest.tsx +++ b/src/components/permissions/EnterPlanModePermissionRequest/EnterPlanModePermissionRequest.tsx @@ -1,6 +1,6 @@ import React from 'react' import { handlePlanModeTransition } from '../../../bootstrap/state.js' -import { Box, Text } from '../../../ink.js' +import { Box, Text } from '@anthropic/ink' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, diff --git a/src/components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.tsx b/src/components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.tsx index fddadaa7e..5a3ee5f1f 100644 --- a/src/components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.tsx +++ b/src/components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.tsx @@ -29,8 +29,7 @@ import { } from '../../../bootstrap/state.js' import { generateSessionName } from '../../../commands/rename/generateSessionName.js' import { launchUltraplan } from '../../../commands/ultraplan.js' -import type { KeyboardEvent } from '../../../ink/events/keyboard-event.js' -import { Box, Text } from '../../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import type { AppState } from '../../../state/AppStateStore.js' import { AGENT_TOOL_NAME } from '../../../tools/AgentTool/constants.js' import { EXIT_PLAN_MODE_V2_TOOL_NAME } from '../../../tools/ExitPlanModeTool/constants.js' diff --git a/src/components/permissions/FallbackPermissionRequest.tsx b/src/components/permissions/FallbackPermissionRequest.tsx index 9b7fee994..23075aa58 100644 --- a/src/components/permissions/FallbackPermissionRequest.tsx +++ b/src/components/permissions/FallbackPermissionRequest.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useMemo } from 'react' import { getOriginalCwd } from '../../bootstrap/state.js' -import { Box, Text, useTheme } from '../../ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import { sanitizeToolNameForAnalytics } from '../../services/analytics/metadata.js' import { env } from '../../utils/env.js' import { shouldShowAlwaysAllowOptions } from '../../utils/permissions/permissionsLoader.js' diff --git a/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx b/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx index d3bae2c17..9f8683017 100644 --- a/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +++ b/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx @@ -3,7 +3,7 @@ import React from 'react' import { FileEditToolDiff } from 'src/components/FileEditToolDiff.js' import { getCwd } from 'src/utils/cwd.js' import type { z } from 'zod/v4' -import { Text } from '../../../ink.js' +import { Text } from '@anthropic/ink' import { FileEditTool } from '../../../tools/FileEditTool/FileEditTool.js' import { FilePermissionDialog } from '../FilePermissionDialog/FilePermissionDialog.js' import { diff --git a/src/components/permissions/FilePermissionDialog/FilePermissionDialog.tsx b/src/components/permissions/FilePermissionDialog/FilePermissionDialog.tsx index b645949dc..58ac9b118 100644 --- a/src/components/permissions/FilePermissionDialog/FilePermissionDialog.tsx +++ b/src/components/permissions/FilePermissionDialog/FilePermissionDialog.tsx @@ -1,7 +1,7 @@ import { relative } from 'path' import React, { useMemo } from 'react' import { useDiffInIDE } from '../../../hooks/useDiffInIDE.js' -import { Box, Text } from '../../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolUseContext } from '../../../Tool.js' import { getLanguageName } from '../../../utils/cliHighlight.js' import { getCwd } from '../../../utils/cwd.js' diff --git a/src/components/permissions/FilePermissionDialog/permissionOptions.tsx b/src/components/permissions/FilePermissionDialog/permissionOptions.tsx index 3a3507234..3709a6502 100644 --- a/src/components/permissions/FilePermissionDialog/permissionOptions.tsx +++ b/src/components/permissions/FilePermissionDialog/permissionOptions.tsx @@ -2,7 +2,7 @@ import { homedir } from 'os' import { basename, join, sep } from 'path' import React, { type ReactNode } from 'react' import { getOriginalCwd } from '../../../bootstrap/state.js' -import { Text } from '../../../ink.js' +import { Text } from '@anthropic/ink' import { getShortcutDisplay } from '../../../keybindings/shortcutFormat.js' import type { ToolPermissionContext } from '../../../Tool.js' import { expandPath, getDirectoryForPath } from '../../../utils/path.js' diff --git a/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx b/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx index ce352858d..744673193 100644 --- a/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +++ b/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx @@ -1,7 +1,7 @@ import { basename, relative } from 'path' import React, { useMemo } from 'react' import type { z } from 'zod/v4' -import { Text } from '../../../ink.js' +import { Text } from '@anthropic/ink' import { FileWriteTool } from '../../../tools/FileWriteTool/FileWriteTool.js' import { getCwd } from '../../../utils/cwd.js' import { isENOENT } from '../../../utils/errors.js' diff --git a/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx b/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx index 36147ef03..38084661f 100644 --- a/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +++ b/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useMemo } from 'react' import { useTerminalSize } from '../../../hooks/useTerminalSize.js' -import { Box, NoSelect, Text } from '../../../ink.js' +import { Box, NoSelect, Text } from '@anthropic/ink' import { intersperse } from '../../../utils/array.js' import { getPatchForDisplay } from '../../../utils/diff.js' import { HighlightedCode } from '../../HighlightedCode.js' diff --git a/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx b/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx index ebfdc8817..67828f563 100644 --- a/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +++ b/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text, useTheme } from '../../../ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import { FallbackPermissionRequest } from '../FallbackPermissionRequest.js' import { FilePermissionDialog } from '../FilePermissionDialog/FilePermissionDialog.js' import type { ToolInput } from '../FilePermissionDialog/useFilePermissionDialog.js' diff --git a/src/components/permissions/NotebookEditPermissionRequest/NotebookEditPermissionRequest.tsx b/src/components/permissions/NotebookEditPermissionRequest/NotebookEditPermissionRequest.tsx index 6c03b94d3..9ebc09642 100644 --- a/src/components/permissions/NotebookEditPermissionRequest/NotebookEditPermissionRequest.tsx +++ b/src/components/permissions/NotebookEditPermissionRequest/NotebookEditPermissionRequest.tsx @@ -1,7 +1,7 @@ import { basename } from 'path' import React from 'react' import type { z } from 'zod/v4' -import { Text } from '../../../ink.js' +import { Text } from '@anthropic/ink' import { NotebookEditTool } from '../../../tools/NotebookEditTool/NotebookEditTool.js' import { logError } from '../../../utils/log.js' import { FilePermissionDialog } from '../FilePermissionDialog/FilePermissionDialog.js' diff --git a/src/components/permissions/NotebookEditPermissionRequest/NotebookEditToolDiff.tsx b/src/components/permissions/NotebookEditPermissionRequest/NotebookEditToolDiff.tsx index 9b5373142..4822dac9a 100644 --- a/src/components/permissions/NotebookEditPermissionRequest/NotebookEditToolDiff.tsx +++ b/src/components/permissions/NotebookEditPermissionRequest/NotebookEditToolDiff.tsx @@ -1,7 +1,7 @@ import { relative } from 'path' import * as React from 'react' import { Suspense, use, useMemo } from 'react' -import { Box, NoSelect, Text } from '../../../ink.js' +import { Box, NoSelect, Text } from '@anthropic/ink' import type { NotebookCellType, NotebookContent, diff --git a/src/components/permissions/PermissionDecisionDebugInfo.tsx b/src/components/permissions/PermissionDecisionDebugInfo.tsx index afd855343..fbbaf6b18 100644 --- a/src/components/permissions/PermissionDecisionDebugInfo.tsx +++ b/src/components/permissions/PermissionDecisionDebugInfo.tsx @@ -2,7 +2,7 @@ import { feature } from 'bun:bundle' import chalk from 'chalk' import figures from 'figures' import React, { useMemo } from 'react' -import { Ansi, Box, color, Text, useTheme } from '../../ink.js' +import { Ansi, Box, color, Text, useTheme } from '@anthropic/ink' import { useAppState } from '../../state/AppState.js' import type { PermissionMode } from '../../utils/permissions/PermissionMode.js' import { permissionModeTitle } from '../../utils/permissions/PermissionMode.js' diff --git a/src/components/permissions/PermissionDialog.tsx b/src/components/permissions/PermissionDialog.tsx index 210bbb16e..40e99f50f 100644 --- a/src/components/permissions/PermissionDialog.tsx +++ b/src/components/permissions/PermissionDialog.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box } from '../../ink.js' +import { Box } from '@anthropic/ink' import type { Theme } from '../../utils/theme.js' import { PermissionRequestTitle } from './PermissionRequestTitle.js' import type { WorkerBadgeProps } from './WorkerBadge.js' diff --git a/src/components/permissions/PermissionExplanation.tsx b/src/components/permissions/PermissionExplanation.tsx index 2fe08a858..367fc7ccb 100644 --- a/src/components/permissions/PermissionExplanation.tsx +++ b/src/components/permissions/PermissionExplanation.tsx @@ -1,5 +1,5 @@ import React, { Suspense, use, useState } from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { logEvent } from '../../services/analytics/index.js' import type { Message } from '../../types/message.js' diff --git a/src/components/permissions/PermissionPrompt.tsx b/src/components/permissions/PermissionPrompt.tsx index ae9ba0730..0e16fad25 100644 --- a/src/components/permissions/PermissionPrompt.tsx +++ b/src/components/permissions/PermissionPrompt.tsx @@ -1,5 +1,5 @@ import React, { type ReactNode, useCallback, useMemo, useState } from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { KeybindingAction } from '../../keybindings/types.js' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { diff --git a/src/components/permissions/PermissionRequestTitle.tsx b/src/components/permissions/PermissionRequestTitle.tsx index 953cca22b..eafa1f71f 100644 --- a/src/components/permissions/PermissionRequestTitle.tsx +++ b/src/components/permissions/PermissionRequestTitle.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { Theme } from '../../utils/theme.js' import type { WorkerBadgeProps } from './WorkerBadge.js' diff --git a/src/components/permissions/PermissionRuleExplanation.tsx b/src/components/permissions/PermissionRuleExplanation.tsx index 406f7e3b8..3ed14a120 100644 --- a/src/components/permissions/PermissionRuleExplanation.tsx +++ b/src/components/permissions/PermissionRuleExplanation.tsx @@ -1,7 +1,8 @@ import { feature } from 'bun:bundle' import chalk from 'chalk' import React from 'react' -import { Ansi, Box, Text } from '../../ink.js' +import { Ansi, Box, Text } from '@anthropic/ink' +import ThemedText from '../design-system/ThemedText.js' import { useAppState } from '../../state/AppState.js' import type { PermissionDecision, @@ -9,7 +10,6 @@ import type { } from '../../utils/permissions/PermissionResult.js' import { permissionRuleValueToString } from '../../utils/permissions/permissionRuleParser.js' import type { Theme } from '../../utils/theme.js' -import ThemedText from '../design-system/ThemedText.js' export type PermissionRuleExplanationProps = { permissionResult: PermissionDecision diff --git a/src/components/permissions/PowerShellPermissionRequest/PowerShellPermissionRequest.tsx b/src/components/permissions/PowerShellPermissionRequest/PowerShellPermissionRequest.tsx index 5dcd0e488..16183ec79 100644 --- a/src/components/permissions/PowerShellPermissionRequest/PowerShellPermissionRequest.tsx +++ b/src/components/permissions/PowerShellPermissionRequest/PowerShellPermissionRequest.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Box, Text, useTheme } from '../../../ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import { useKeybinding } from '../../../keybindings/useKeybinding.js' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../../services/analytics/growthbook.js' import { diff --git a/src/components/permissions/SandboxPermissionRequest.tsx b/src/components/permissions/SandboxPermissionRequest.tsx index 9dc4d6629..cf7ef1195 100644 --- a/src/components/permissions/SandboxPermissionRequest.tsx +++ b/src/components/permissions/SandboxPermissionRequest.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from 'src/ink.js' +import { Box, Text } from '@anthropic/ink' import { type NetworkHostPattern, shouldAllowManagedSandboxDomainsOnly, diff --git a/src/components/permissions/SedEditPermissionRequest/SedEditPermissionRequest.tsx b/src/components/permissions/SedEditPermissionRequest/SedEditPermissionRequest.tsx index 209cd08f4..2135b5e22 100644 --- a/src/components/permissions/SedEditPermissionRequest/SedEditPermissionRequest.tsx +++ b/src/components/permissions/SedEditPermissionRequest/SedEditPermissionRequest.tsx @@ -5,7 +5,7 @@ import { getCwd } from 'src/utils/cwd.js' import { isENOENT } from 'src/utils/errors.js' import { detectEncodingForResolvedPath } from 'src/utils/fileRead.js' import { getFsImplementation } from 'src/utils/fsOperations.js' -import { Text } from '../../../ink.js' +import { Text } from '@anthropic/ink' import { BashTool } from '../../../tools/BashTool/BashTool.js' import { applySedSubstitution, diff --git a/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx b/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx index 799c88705..d9e4050fc 100644 --- a/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx +++ b/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useMemo } from 'react' import { logError } from 'src/utils/log.js' import { getOriginalCwd } from '../../../bootstrap/state.js' -import { Box, Text } from '../../../ink.js' +import { Box, Text } from '@anthropic/ink' import { sanitizeToolNameForAnalytics } from '../../../services/analytics/metadata.js' import { SKILL_TOOL_NAME } from '../../../tools/SkillTool/constants.js' import { SkillTool } from '../../../tools/SkillTool/SkillTool.js' diff --git a/src/components/permissions/WebFetchPermissionRequest/WebFetchPermissionRequest.tsx b/src/components/permissions/WebFetchPermissionRequest/WebFetchPermissionRequest.tsx index da2498885..5e0625498 100644 --- a/src/components/permissions/WebFetchPermissionRequest/WebFetchPermissionRequest.tsx +++ b/src/components/permissions/WebFetchPermissionRequest/WebFetchPermissionRequest.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react' -import { Box, Text, useTheme } from '../../../ink.js' +import { Box, Text, useTheme } from '@anthropic/ink' import { WebFetchTool } from '../../../tools/WebFetchTool/WebFetchTool.js' import { shouldShowAlwaysAllowOptions } from '../../../utils/permissions/permissionsLoader.js' import { diff --git a/src/components/permissions/WorkerBadge.tsx b/src/components/permissions/WorkerBadge.tsx index 61d5873ab..959346e6e 100644 --- a/src/components/permissions/WorkerBadge.tsx +++ b/src/components/permissions/WorkerBadge.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { BLACK_CIRCLE } from '../../constants/figures.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { toInkColor } from '../../utils/ink.js' export type WorkerBadgeProps = { diff --git a/src/components/permissions/WorkerPendingPermission.tsx b/src/components/permissions/WorkerPendingPermission.tsx index 06aab0334..2d7ef596e 100644 --- a/src/components/permissions/WorkerPendingPermission.tsx +++ b/src/components/permissions/WorkerPendingPermission.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getAgentName, getTeammateColor, diff --git a/src/components/permissions/rules/AddPermissionRules.tsx b/src/components/permissions/rules/AddPermissionRules.tsx index 6e48e1dcb..e62442c3c 100644 --- a/src/components/permissions/rules/AddPermissionRules.tsx +++ b/src/components/permissions/rules/AddPermissionRules.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useCallback } from 'react' import { Select } from '../../../components/CustomSelect/select.js' -import { Box, Text } from '../../../ink.js' +import { Box, Dialog, Text } from '@anthropic/ink' import type { ToolPermissionContext } from '../../../Tool.js' import type { PermissionBehavior, @@ -25,7 +25,6 @@ import { import { getRelativeSettingsFilePathForSource } from '../../../utils/settings/settings.js' import { plural } from '../../../utils/stringUtils.js' import type { OptionWithDescription } from '../../CustomSelect/select.js' -import { Dialog } from '../../design-system/Dialog.js' import { PermissionRuleDescription } from './PermissionRuleDescription.js' export function optionForPermissionSaveDestination( diff --git a/src/components/permissions/rules/AddWorkspaceDirectory.tsx b/src/components/permissions/rules/AddWorkspaceDirectory.tsx index 07d0a00ef..589928782 100644 --- a/src/components/permissions/rules/AddWorkspaceDirectory.tsx +++ b/src/components/permissions/rules/AddWorkspaceDirectory.tsx @@ -7,16 +7,13 @@ import { validateDirectoryForWorkspace, } from '../../../commands/add-dir/validation.js' import TextInput from '../../../components/TextInput.js' -import type { KeyboardEvent } from '../../../ink/events/keyboard-event.js' -import { Box, Text } from '../../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../../keybindings/useKeybinding.js' import type { ToolPermissionContext } from '../../../Tool.js' import { getDirectoryCompletions } from '../../../utils/suggestions/directoryCompletion.js' import { ConfigurableShortcutHint } from '../../ConfigurableShortcutHint.js' import { Select } from '../../CustomSelect/select.js' -import { Byline } from '../../design-system/Byline.js' -import { Dialog } from '../../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { PromptInputFooterSuggestions, type SuggestionItem, diff --git a/src/components/permissions/rules/PermissionRuleDescription.tsx b/src/components/permissions/rules/PermissionRuleDescription.tsx index ac8f0cd23..d4591b8d9 100644 --- a/src/components/permissions/rules/PermissionRuleDescription.tsx +++ b/src/components/permissions/rules/PermissionRuleDescription.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Text } from '../../../ink.js' +import { Text } from '@anthropic/ink' import { BashTool } from '../../../tools/BashTool/BashTool.js' import type { PermissionRuleValue } from '../../../utils/permissions/PermissionRule.js' diff --git a/src/components/permissions/rules/PermissionRuleInput.tsx b/src/components/permissions/rules/PermissionRuleInput.tsx index 36dfb6b63..fae8553d9 100644 --- a/src/components/permissions/rules/PermissionRuleInput.tsx +++ b/src/components/permissions/rules/PermissionRuleInput.tsx @@ -4,7 +4,7 @@ import { useState } from 'react' import TextInput from '../../../components/TextInput.js' import { useExitOnCtrlCDWithKeybindings } from '../../../hooks/useExitOnCtrlCDWithKeybindings.js' import { useTerminalSize } from '../../../hooks/useTerminalSize.js' -import { Box, Newline, Text } from '../../../ink.js' +import { Box, Newline, Text } from '@anthropic/ink' import { useKeybinding } from '../../../keybindings/useKeybinding.js' import { BashTool } from '../../../tools/BashTool/BashTool.js' import { WebFetchTool } from '../../../tools/WebFetchTool/WebFetchTool.js' diff --git a/src/components/permissions/rules/PermissionRuleList.tsx b/src/components/permissions/rules/PermissionRuleList.tsx index 129b58083..035c23932 100644 --- a/src/components/permissions/rules/PermissionRuleList.tsx +++ b/src/components/permissions/rules/PermissionRuleList.tsx @@ -12,8 +12,7 @@ import type { CommandResultDisplay } from '../../../commands.js' import { Select } from '../../../components/CustomSelect/select.js' import { useExitOnCtrlCDWithKeybindings } from '../../../hooks/useExitOnCtrlCDWithKeybindings.js' import { useSearchInput } from '../../../hooks/useSearchInput.js' -import type { KeyboardEvent } from '../../../ink/events/keyboard-event.js' -import { Box, Text, useTerminalFocus } from '../../../ink.js' +import { type KeyboardEvent, Box, Text, useTerminalFocus } from '@anthropic/ink' import { useKeybinding } from '../../../keybindings/useKeybinding.js' import { type AutoModeDenial, @@ -34,13 +33,8 @@ import { } from '../../../utils/permissions/permissions.js' import type { UnreachableRule } from '../../../utils/permissions/shadowedRuleDetection.js' import { jsonStringify } from '../../../utils/slowOperations.js' -import { Pane } from '../../design-system/Pane.js' -import { - Tab, - Tabs, - useTabHeaderFocus, - useTabsWidth, -} from '../../design-system/Tabs.js' +import { Pane, Tabs } from '@anthropic/ink' +import { Tab, useTabHeaderFocus, useTabsWidth } from '../../design-system/Tabs.js' import { SearchBox } from '../../SearchBox.js' import type { Option } from '../../ui/option.js' import { AddPermissionRules } from './AddPermissionRules.js' diff --git a/src/components/permissions/rules/RecentDenialsTab.tsx b/src/components/permissions/rules/RecentDenialsTab.tsx index 17c13844d..1006c98a7 100644 --- a/src/components/permissions/rules/RecentDenialsTab.tsx +++ b/src/components/permissions/rules/RecentDenialsTab.tsx @@ -1,13 +1,13 @@ import * as React from 'react' import { useCallback, useEffect, useState } from 'react' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- 'r' is a view-specific key, not a global keybinding -import { Box, Text, useInput } from '../../../ink.js' +import { Box, Text, useInput } from '@anthropic/ink' import { type AutoModeDenial, getAutoModeDenials, } from '../../../utils/autoModeDenials.js' import { Select } from '../../CustomSelect/select.js' -import { StatusIcon } from '../../design-system/StatusIcon.js' +import { StatusIcon } from '@anthropic/ink' import { useTabHeaderFocus } from '../../design-system/Tabs.js' type Props = { diff --git a/src/components/permissions/rules/RemoveWorkspaceDirectory.tsx b/src/components/permissions/rules/RemoveWorkspaceDirectory.tsx index e6eefade2..8d7e6fa99 100644 --- a/src/components/permissions/rules/RemoveWorkspaceDirectory.tsx +++ b/src/components/permissions/rules/RemoveWorkspaceDirectory.tsx @@ -1,10 +1,10 @@ import * as React from 'react' import { useCallback } from 'react' import { Select } from '../../../components/CustomSelect/select.js' -import { Box, Text } from '../../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolPermissionContext } from '../../../Tool.js' import { applyPermissionUpdate } from '../../../utils/permissions/PermissionUpdate.js' -import { Dialog } from '../../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' type Props = { directoryPath: string diff --git a/src/components/permissions/rules/WorkspaceTab.tsx b/src/components/permissions/rules/WorkspaceTab.tsx index 0dab0c7d0..65a747893 100644 --- a/src/components/permissions/rules/WorkspaceTab.tsx +++ b/src/components/permissions/rules/WorkspaceTab.tsx @@ -4,7 +4,7 @@ import { useCallback, useEffect } from 'react' import { getOriginalCwd } from '../../../bootstrap/state.js' import type { CommandResultDisplay } from '../../../commands.js' import { Select } from '../../../components/CustomSelect/select.js' -import { Box, Text } from '../../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolPermissionContext } from '../../../Tool.js' import { useTabHeaderFocus } from '../../design-system/Tabs.js' diff --git a/src/components/permissions/shellPermissionHelpers.tsx b/src/components/permissions/shellPermissionHelpers.tsx index 2c7a2db95..9b7c945f0 100644 --- a/src/components/permissions/shellPermissionHelpers.tsx +++ b/src/components/permissions/shellPermissionHelpers.tsx @@ -1,7 +1,7 @@ import { basename, sep } from 'path' import React, { type ReactNode } from 'react' import { getOriginalCwd } from '../../bootstrap/state.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import type { PermissionUpdate } from '../../utils/permissions/PermissionUpdateSchema.js' import { permissionRuleExtractPrefix } from '../../utils/permissions/shellRuleMatching.js' diff --git a/src/components/sandbox/SandboxConfigTab.tsx b/src/components/sandbox/SandboxConfigTab.tsx index 58bfba688..37e00ce56 100644 --- a/src/components/sandbox/SandboxConfigTab.tsx +++ b/src/components/sandbox/SandboxConfigTab.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { SandboxManager, shouldAllowManagedSandboxDomainsOnly, diff --git a/src/components/sandbox/SandboxDependenciesTab.tsx b/src/components/sandbox/SandboxDependenciesTab.tsx index 75091910d..24efdf1ef 100644 --- a/src/components/sandbox/SandboxDependenciesTab.tsx +++ b/src/components/sandbox/SandboxDependenciesTab.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getPlatform } from '../../utils/platform.js' import type { SandboxDependencyCheck } from '../../utils/sandbox/sandbox-adapter.js' diff --git a/src/components/sandbox/SandboxDoctorSection.tsx b/src/components/sandbox/SandboxDoctorSection.tsx index 5e7198c38..effa9500e 100644 --- a/src/components/sandbox/SandboxDoctorSection.tsx +++ b/src/components/sandbox/SandboxDoctorSection.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js' export function SandboxDoctorSection(): React.ReactNode { diff --git a/src/components/sandbox/SandboxOverridesTab.tsx b/src/components/sandbox/SandboxOverridesTab.tsx index 74c6d224b..2c14400b2 100644 --- a/src/components/sandbox/SandboxOverridesTab.tsx +++ b/src/components/sandbox/SandboxOverridesTab.tsx @@ -1,8 +1,9 @@ import React from 'react' -import { Box, color, Link, Text, useTheme } from '../../ink.js' +import { Box, color, Link, Text, useTheme } from '@anthropic/ink' import type { CommandResultDisplay } from '../../types/command.js' import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js' import { Select } from '../CustomSelect/select.js' +// useTabHeaderFocus not available in ink.js facade import { useTabHeaderFocus } from '../design-system/Tabs.js' type Props = { diff --git a/src/components/sandbox/SandboxSettings.tsx b/src/components/sandbox/SandboxSettings.tsx index 05998577b..845540b07 100644 --- a/src/components/sandbox/SandboxSettings.tsx +++ b/src/components/sandbox/SandboxSettings.tsx @@ -1,13 +1,13 @@ import React from 'react' -import { Box, color, Link, Text, useTheme } from '../../ink.js' +import { Box, color, Link, Text, useTheme } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import type { CommandResultDisplay } from '../../types/command.js' import type { SandboxDependencyCheck } from '../../utils/sandbox/sandbox-adapter.js' import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js' import { getSettings_DEPRECATED } from '../../utils/settings/settings.js' import { Select } from '../CustomSelect/select.js' -import { Pane } from '../design-system/Pane.js' -import { Tab, Tabs, useTabHeaderFocus } from '../design-system/Tabs.js' +import { Pane, Tabs } from '@anthropic/ink' +import { Tab, useTabHeaderFocus } from '../design-system/Tabs.js' import { SandboxConfigTab } from './SandboxConfigTab.js' import { SandboxDependenciesTab } from './SandboxDependenciesTab.js' import { SandboxOverridesTab } from './SandboxOverridesTab.js' diff --git a/src/components/shell/OutputLine.tsx b/src/components/shell/OutputLine.tsx index cf72760db..0b2c280af 100644 --- a/src/components/shell/OutputLine.tsx +++ b/src/components/shell/OutputLine.tsx @@ -1,8 +1,9 @@ import * as React from 'react' import { useMemo } from 'react' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Ansi, Text } from '../../ink.js' +import { Ansi, Text } from '@anthropic/ink' import { createHyperlink } from '../../utils/hyperlink.js' + import { jsonParse, jsonStringify } from '../../utils/slowOperations.js' import { renderTruncatedContent } from '../../utils/terminal.js' import { MessageResponse } from '../MessageResponse.js' diff --git a/src/components/shell/ShellProgressMessage.tsx b/src/components/shell/ShellProgressMessage.tsx index 99da5ac3b..a99bdbd0d 100644 --- a/src/components/shell/ShellProgressMessage.tsx +++ b/src/components/shell/ShellProgressMessage.tsx @@ -1,6 +1,6 @@ import React from 'react' import stripAnsi from 'strip-ansi' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { formatFileSize } from '../../utils/format.js' import { MessageResponse } from '../MessageResponse.js' import { OffscreenFreeze } from '../OffscreenFreeze.js' diff --git a/src/components/shell/ShellTimeDisplay.tsx b/src/components/shell/ShellTimeDisplay.tsx index 7e619dfba..67b5c373a 100644 --- a/src/components/shell/ShellTimeDisplay.tsx +++ b/src/components/shell/ShellTimeDisplay.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { formatDuration } from '../../utils/format.js' type Props = { diff --git a/src/components/skills/SkillsMenu.tsx b/src/components/skills/SkillsMenu.tsx index 5733688b9..4b33eee57 100644 --- a/src/components/skills/SkillsMenu.tsx +++ b/src/components/skills/SkillsMenu.tsx @@ -8,7 +8,7 @@ import { getCommandName, type PromptCommand, } from '../../commands.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { estimateSkillFrontmatterTokens, getSkillsPath, @@ -21,7 +21,7 @@ import { } from '../../utils/settings/constants.js' import { plural } from '../../utils/stringUtils.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' // Skills are always PromptCommands with CommandBase properties type SkillCommand = CommandBase & PromptCommand diff --git a/src/components/tasks/AsyncAgentDetailDialog.tsx b/src/components/tasks/AsyncAgentDetailDialog.tsx index 4174d4fa5..2070b9a68 100644 --- a/src/components/tasks/AsyncAgentDetailDialog.tsx +++ b/src/components/tasks/AsyncAgentDetailDialog.tsx @@ -1,17 +1,14 @@ import React, { useMemo } from 'react' import type { DeepImmutable } from 'src/types/utils.js' import { useElapsedTime } from '../../hooks/useElapsedTime.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text, useTheme } from '../../ink.js' +import { type KeyboardEvent, Box, Text, useTheme } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { getEmptyToolPermissionContext } from '../../Tool.js' import type { LocalAgentTaskState } from '../../tasks/LocalAgentTask/LocalAgentTask.js' import { getTools } from '../../tools.js' import { formatNumber } from '../../utils/format.js' import { extractTag } from '../../utils/messages.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { UserPlanMessage } from '../messages/UserPlanMessage.js' import { renderToolActivity } from './renderToolActivity.js' import { getTaskStatusColor, getTaskStatusIcon } from './taskStatusUtils.js' diff --git a/src/components/tasks/BackgroundTask.tsx b/src/components/tasks/BackgroundTask.tsx index fd48d09e7..7fdf1fa4f 100644 --- a/src/components/tasks/BackgroundTask.tsx +++ b/src/components/tasks/BackgroundTask.tsx @@ -1,9 +1,10 @@ import * as React from 'react' -import { Text } from 'src/ink.js' +import { Text } from '@anthropic/ink' +import { toInkColor } from '../../utils/ink.js' import type { BackgroundTaskState } from 'src/tasks/types.js' import type { DeepImmutable } from 'src/types/utils.js' import { truncate } from 'src/utils/format.js' -import { toInkColor } from 'src/utils/ink.js' + import { plural } from 'src/utils/stringUtils.js' import { DIAMOND_FILLED, DIAMOND_OPEN } from '../../constants/figures.js' import { RemoteSessionProgress } from './RemoteSessionProgress.js' diff --git a/src/components/tasks/BackgroundTaskStatus.tsx b/src/components/tasks/BackgroundTaskStatus.tsx index 26d46cf98..c315bfa90 100644 --- a/src/components/tasks/BackgroundTaskStatus.tsx +++ b/src/components/tasks/BackgroundTaskStatus.tsx @@ -2,7 +2,7 @@ import figures from 'figures' import * as React from 'react' import { useMemo, useState } from 'react' import { useTerminalSize } from 'src/hooks/useTerminalSize.js' -import { stringWidth } from 'src/ink/stringWidth.js' +import { stringWidth } from '@anthropic/ink' import { useAppState, useSetAppState } from 'src/state/AppState.js' import { enterTeammateView, @@ -16,14 +16,14 @@ import { type TaskState, } from 'src/tasks/types.js' import { calculateHorizontalScrollWindow } from 'src/utils/horizontalScroll.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { AGENT_COLOR_TO_THEME_COLOR, AGENT_COLORS, type AgentColorName, } from '../../tools/AgentTool/agentColorManager.js' import type { Theme } from '../../utils/theme.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { KeyboardShortcutHint } from '@anthropic/ink' import { shouldHideTasksFooter } from './taskStatusUtils.js' type Props = { diff --git a/src/components/tasks/BackgroundTasksDialog.tsx b/src/components/tasks/BackgroundTasksDialog.tsx index d9f119cf1..30eca7444 100644 --- a/src/components/tasks/BackgroundTasksDialog.tsx +++ b/src/components/tasks/BackgroundTasksDialog.tsx @@ -45,14 +45,11 @@ import { stopUltraplan } from '../../commands/ultraplan.js' import type { CommandResultDisplay } from '../../commands.js' import { useRegisterOverlay } from '../../context/overlayContext.js' import type { ExitState } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text } from '../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js' import { count } from '../../utils/array.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { AsyncAgentDetailDialog } from './AsyncAgentDetailDialog.js' import { BackgroundTask as BackgroundTaskComponent } from './BackgroundTask.js' import { DreamDetailDialog } from './DreamDetailDialog.js' diff --git a/src/components/tasks/DreamDetailDialog.tsx b/src/components/tasks/DreamDetailDialog.tsx index bea310946..67baab993 100644 --- a/src/components/tasks/DreamDetailDialog.tsx +++ b/src/components/tasks/DreamDetailDialog.tsx @@ -1,14 +1,11 @@ import React from 'react' import type { DeepImmutable } from 'src/types/utils.js' import { useElapsedTime } from '../../hooks/useElapsedTime.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text } from '../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import type { DreamTaskState } from '../../tasks/DreamTask/DreamTask.js' import { plural } from '../../utils/stringUtils.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' type Props = { task: DeepImmutable diff --git a/src/components/tasks/InProcessTeammateDetailDialog.tsx b/src/components/tasks/InProcessTeammateDetailDialog.tsx index b59bbbd5e..c0a755a60 100644 --- a/src/components/tasks/InProcessTeammateDetailDialog.tsx +++ b/src/components/tasks/InProcessTeammateDetailDialog.tsx @@ -1,17 +1,15 @@ import React, { useMemo } from 'react' import type { DeepImmutable } from 'src/types/utils.js' import { useElapsedTime } from '../../hooks/useElapsedTime.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text, useTheme } from '../../ink.js' +import { type KeyboardEvent, Box, Text, useTheme } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { getEmptyToolPermissionContext } from '../../Tool.js' import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js' import { getTools } from '../../tools.js' import { formatNumber, truncateToWidth } from '../../utils/format.js' + +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { toInkColor } from '../../utils/ink.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' import { renderToolActivity } from './renderToolActivity.js' import { describeTeammateActivity } from './taskStatusUtils.js' diff --git a/src/components/tasks/RemoteSessionDetailDialog.tsx b/src/components/tasks/RemoteSessionDetailDialog.tsx index 55c897fd9..ec2c43b42 100644 --- a/src/components/tasks/RemoteSessionDetailDialog.tsx +++ b/src/components/tasks/RemoteSessionDetailDialog.tsx @@ -6,8 +6,7 @@ import type { DeepImmutable } from 'src/types/utils.js' import type { CommandResultDisplay } from '../../commands.js' import { DIAMOND_FILLED, DIAMOND_OPEN } from '../../constants/figures.js' import { useElapsedTime } from '../../hooks/useElapsedTime.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Link, Text } from '../../ink.js' +import { type KeyboardEvent, Box, Link, Text } from '@anthropic/ink' import type { RemoteAgentTaskState } from '../../tasks/RemoteAgentTask/RemoteAgentTask.js' import { getRemoteTaskSessionUrl } from '../../tasks/RemoteAgentTask/RemoteAgentTask.js' import { @@ -24,9 +23,7 @@ import { EMPTY_LOOKUPS, normalizeMessages } from '../../utils/messages.js' import { plural } from '../../utils/stringUtils.js' import { teleportResumeCodeSession } from '../../utils/teleport.js' import { Select } from '../CustomSelect/select.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' import { Message } from '../Message.js' import { formatReviewStageCounts, diff --git a/src/components/tasks/RemoteSessionProgress.tsx b/src/components/tasks/RemoteSessionProgress.tsx index c1711cd8a..d7e0f8e68 100644 --- a/src/components/tasks/RemoteSessionProgress.tsx +++ b/src/components/tasks/RemoteSessionProgress.tsx @@ -3,7 +3,7 @@ import type { RemoteAgentTaskState } from 'src/tasks/RemoteAgentTask/RemoteAgent import type { DeepImmutable } from 'src/types/utils.js' import { DIAMOND_FILLED, DIAMOND_OPEN } from '../../constants/figures.js' import { useSettings } from '../../hooks/useSettings.js' -import { Text, useAnimationFrame } from '../../ink.js' +import { Text, useAnimationFrame } from '@anthropic/ink' import { count } from '../../utils/array.js' import { getRainbowColor } from '../../utils/thinking.js' diff --git a/src/components/tasks/ShellDetailDialog.tsx b/src/components/tasks/ShellDetailDialog.tsx index a81bafc8b..7627ec561 100644 --- a/src/components/tasks/ShellDetailDialog.tsx +++ b/src/components/tasks/ShellDetailDialog.tsx @@ -8,8 +8,7 @@ import React, { import type { DeepImmutable } from 'src/types/utils.js' import type { CommandResultDisplay } from '../../commands.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box, Text } from '../../ink.js' +import { type KeyboardEvent, Box, Text } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import type { LocalShellTaskState } from '../../tasks/LocalShellTask/guards.js' import { @@ -19,9 +18,7 @@ import { } from '../../utils/format.js' import { tailFile } from '../../utils/fsOperations.js' import { getTaskOutputPath } from '../../utils/task/diskOutput.js' -import { Byline } from '../design-system/Byline.js' -import { Dialog } from '../design-system/Dialog.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink' type Props = { shell: DeepImmutable diff --git a/src/components/tasks/ShellProgress.tsx b/src/components/tasks/ShellProgress.tsx index b70494c16..d5da46f63 100644 --- a/src/components/tasks/ShellProgress.tsx +++ b/src/components/tasks/ShellProgress.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react' import React from 'react' -import { Text } from 'src/ink.js' +import { Text } from '@anthropic/ink' import type { TaskStatus } from 'src/Task.js' import type { LocalShellTaskState } from 'src/tasks/LocalShellTask/guards.js' import type { DeepImmutable } from 'src/types/utils.js' diff --git a/src/components/tasks/renderToolActivity.tsx b/src/components/tasks/renderToolActivity.tsx index a6e1c60a2..70abaa1bb 100644 --- a/src/components/tasks/renderToolActivity.tsx +++ b/src/components/tasks/renderToolActivity.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import type { Tools } from '../../Tool.js' import { findToolByName } from '../../Tool.js' import type { ToolActivity } from '../../tasks/LocalAgentTask/LocalAgentTask.js' diff --git a/src/components/teams/TeamStatus.tsx b/src/components/teams/TeamStatus.tsx index ee066875b..79bf89667 100644 --- a/src/components/teams/TeamStatus.tsx +++ b/src/components/teams/TeamStatus.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { useAppState } from '../../state/AppState.js' type Props = { diff --git a/src/components/teams/TeamsDialog.tsx b/src/components/teams/TeamsDialog.tsx index 872212115..a21caaebe 100644 --- a/src/components/teams/TeamsDialog.tsx +++ b/src/components/teams/TeamsDialog.tsx @@ -4,9 +4,8 @@ import * as React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { useInterval } from 'usehooks-ts' import { useRegisterOverlay } from '../../context/overlayContext.js' -import { stringWidth } from '../../ink/stringWidth.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw j/k/arrow dialog navigation -import { Box, Text, useInput } from '../../ink.js' +import { Box, Text, useInput, stringWidth } from '@anthropic/ink' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js' import { @@ -63,7 +62,7 @@ import { sendShutdownRequestToMailbox, writeToMailbox, } from '../../utils/teammateMailbox.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import ThemedText from '../design-system/ThemedText.js' type Props = { diff --git a/src/components/ui/OrderedList.tsx b/src/components/ui/OrderedList.tsx index ef468156f..dde5e5998 100644 --- a/src/components/ui/OrderedList.tsx +++ b/src/components/ui/OrderedList.tsx @@ -4,7 +4,7 @@ import React, { type ReactNode, useContext, } from 'react' -import { Box } from '../../ink.js' +import { Box } from '@anthropic/ink' import { OrderedListItem, OrderedListItemContext } from './OrderedListItem.js' const OrderedListContext = createContext({ marker: '' }) diff --git a/src/components/ui/OrderedListItem.tsx b/src/components/ui/OrderedListItem.tsx index f217f0eaf..7b60152c1 100644 --- a/src/components/ui/OrderedListItem.tsx +++ b/src/components/ui/OrderedListItem.tsx @@ -1,5 +1,5 @@ import React, { createContext, type ReactNode, useContext } from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' export const OrderedListItemContext = createContext({ marker: '' }) diff --git a/src/components/ui/TreeSelect.tsx b/src/components/ui/TreeSelect.tsx index 55ca33c88..9e6ec7f49 100644 --- a/src/components/ui/TreeSelect.tsx +++ b/src/components/ui/TreeSelect.tsx @@ -1,6 +1,5 @@ import React from 'react' -import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' -import { Box } from '../../ink.js' +import { type KeyboardEvent, Box } from '@anthropic/ink' import { type OptionWithDescription, Select } from '../CustomSelect/select.js' export type TreeNode = { diff --git a/src/components/ultraplan/UltraplanChoiceDialog.tsx b/src/components/ultraplan/UltraplanChoiceDialog.tsx index f1fd40186..28a225088 100644 --- a/src/components/ultraplan/UltraplanChoiceDialog.tsx +++ b/src/components/ultraplan/UltraplanChoiceDialog.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { join } from 'path'; import { writeFile } from 'fs/promises'; import figures from 'figures'; -import { Box, Text, useInput, wrapText } from '../../ink.js'; +import { Box, Text, useInput, wrapText } from '@anthropic/ink'; import { useTerminalSize } from '../../hooks/useTerminalSize.js'; import { Select } from '../CustomSelect/select.js'; import { PermissionDialog } from '../permissions/PermissionDialog.js'; diff --git a/src/components/ultraplan/UltraplanLaunchDialog.tsx b/src/components/ultraplan/UltraplanLaunchDialog.tsx index bdba02de3..612384b58 100644 --- a/src/components/ultraplan/UltraplanLaunchDialog.tsx +++ b/src/components/ultraplan/UltraplanLaunchDialog.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Box, Text, Link } from '../../ink.js'; +import { Box, Text, Link } from '@anthropic/ink'; import { Select } from '../CustomSelect/select.js'; import { PermissionDialog } from '../permissions/PermissionDialog.js'; import { useAppState, useSetAppState } from '../../state/AppState.js'; diff --git a/src/components/wizard/WizardDialogLayout.tsx b/src/components/wizard/WizardDialogLayout.tsx index 34f20a261..0a03b255e 100644 --- a/src/components/wizard/WizardDialogLayout.tsx +++ b/src/components/wizard/WizardDialogLayout.tsx @@ -1,6 +1,6 @@ import React, { type ReactNode } from 'react' import type { Theme } from '../../utils/theme.js' -import { Dialog } from '../design-system/Dialog.js' +import { Dialog } from '@anthropic/ink' import { useWizard } from './useWizard.js' import { WizardNavigationFooter } from './WizardNavigationFooter.js' diff --git a/src/components/wizard/WizardNavigationFooter.tsx b/src/components/wizard/WizardNavigationFooter.tsx index 35a03ee81..96c547c65 100644 --- a/src/components/wizard/WizardNavigationFooter.tsx +++ b/src/components/wizard/WizardNavigationFooter.tsx @@ -1,9 +1,8 @@ import React, { type ReactNode } from 'react' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' -import { Byline } from '../design-system/Byline.js' -import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' type Props = { instructions?: ReactNode diff --git a/src/context/QueuedMessageContext.tsx b/src/context/QueuedMessageContext.tsx index 575fc8619..d78c9487b 100644 --- a/src/context/QueuedMessageContext.tsx +++ b/src/context/QueuedMessageContext.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box } from '../ink.js' +import { Box } from '@anthropic/ink' type QueuedMessageContextValue = { isQueued: boolean diff --git a/src/context/modalContext.tsx b/src/context/modalContext.tsx index b2263a071..7d8498784 100644 --- a/src/context/modalContext.tsx +++ b/src/context/modalContext.tsx @@ -1,5 +1,5 @@ import { createContext, type RefObject, useContext } from 'react' -import type { ScrollBoxHandle } from '../ink/components/ScrollBox.js' +import type { ScrollBoxHandle } from '@anthropic/ink' /** * Set by FullscreenLayout when rendering content in its `modal` slot — diff --git a/src/context/overlayContext.tsx b/src/context/overlayContext.tsx index 602c1268d..406e03f98 100644 --- a/src/context/overlayContext.tsx +++ b/src/context/overlayContext.tsx @@ -13,7 +13,7 @@ * so no manual cleanup or state management is needed. */ import { useContext, useEffect, useLayoutEffect } from 'react' -import instances from '../ink/instances.js' +import { instances } from '@anthropic/ink' import { AppStoreContext, useAppState } from '../state/AppState.js' // Non-modal overlays that shouldn't disable TextInput focus diff --git a/src/dialogLaunchers.tsx b/src/dialogLaunchers.tsx index 3d2e01e14..5a6ed7372 100644 --- a/src/dialogLaunchers.tsx +++ b/src/dialogLaunchers.tsx @@ -9,7 +9,7 @@ import React from 'react' import type { AssistantSession } from './assistant/sessionDiscovery.js' import type { StatsStore } from './context/stats.js' -import type { Root } from './ink.js' +import type { Root } from '@anthropic/ink' import { renderAndRun, showSetupDialog } from './interactiveHelpers.js' import { KeybindingSetup } from './keybindings/KeybindingProviderSetup.js' import type { AppState } from './state/AppStateStore.js' diff --git a/src/hooks/notifs/useCanSwitchToExistingSubscription.tsx b/src/hooks/notifs/useCanSwitchToExistingSubscription.tsx index 0d70d5a2b..37d8542b0 100644 --- a/src/hooks/notifs/useCanSwitchToExistingSubscription.tsx +++ b/src/hooks/notifs/useCanSwitchToExistingSubscription.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { getOauthProfileFromApiKey } from 'src/services/oauth/getOauthProfile.js' import { isClaudeAISubscriber } from 'src/utils/auth.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { logEvent } from '../../services/analytics/index.js' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' import { useStartupNotification } from './useStartupNotification.js' diff --git a/src/hooks/notifs/useIDEStatusIndicator.tsx b/src/hooks/notifs/useIDEStatusIndicator.tsx index 4be07f551..70175d10f 100644 --- a/src/hooks/notifs/useIDEStatusIndicator.tsx +++ b/src/hooks/notifs/useIDEStatusIndicator.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef } from 'react' import { useNotifications } from 'src/context/notifications.js' -import { Text } from 'src/ink.js' +import { Text } from '@anthropic/ink' import type { MCPServerConnection } from 'src/services/mcp/types.js' import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js' import { diff --git a/src/hooks/notifs/useLspInitializationNotification.tsx b/src/hooks/notifs/useLspInitializationNotification.tsx index f86243a51..7b096f2b0 100644 --- a/src/hooks/notifs/useLspInitializationNotification.tsx +++ b/src/hooks/notifs/useLspInitializationNotification.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useInterval } from 'usehooks-ts' import { getIsRemoteMode, getIsScrollDraining } from '../../bootstrap/state.js' import { useNotifications } from '../../context/notifications.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { getInitializationStatus, getLspServerManager, diff --git a/src/hooks/notifs/useMcpConnectivityStatus.tsx b/src/hooks/notifs/useMcpConnectivityStatus.tsx index 83072ba0f..89adb8382 100644 --- a/src/hooks/notifs/useMcpConnectivityStatus.tsx +++ b/src/hooks/notifs/useMcpConnectivityStatus.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useEffect } from 'react' import { useNotifications } from 'src/context/notifications.js' import { getIsRemoteMode } from '../../bootstrap/state.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { hasClaudeAiMcpEverConnected } from '../../services/mcp/claudeai.js' import type { MCPServerConnection } from '../../services/mcp/types.js' diff --git a/src/hooks/notifs/usePluginAutoupdateNotification.tsx b/src/hooks/notifs/usePluginAutoupdateNotification.tsx index bec229328..7468e8ba0 100644 --- a/src/hooks/notifs/usePluginAutoupdateNotification.tsx +++ b/src/hooks/notifs/usePluginAutoupdateNotification.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useEffect, useState } from 'react' import { getIsRemoteMode } from '../../bootstrap/state.js' import { useNotifications } from '../../context/notifications.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { logForDebugging } from '../../utils/debug.js' import { onPluginsAutoUpdated } from '../../utils/plugins/pluginAutoupdate.js' diff --git a/src/hooks/notifs/usePluginInstallationStatus.tsx b/src/hooks/notifs/usePluginInstallationStatus.tsx index 20055403d..9ab3de8fe 100644 --- a/src/hooks/notifs/usePluginInstallationStatus.tsx +++ b/src/hooks/notifs/usePluginInstallationStatus.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useEffect, useMemo } from 'react' import { getIsRemoteMode } from '../../bootstrap/state.js' import { useNotifications } from '../../context/notifications.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { useAppState } from '../../state/AppState.js' import { logForDebugging } from '../../utils/debug.js' import { plural } from '../../utils/stringUtils.js' diff --git a/src/hooks/notifs/useRateLimitWarningNotification.tsx b/src/hooks/notifs/useRateLimitWarningNotification.tsx index bfd3f193f..0e8520e96 100644 --- a/src/hooks/notifs/useRateLimitWarningNotification.tsx +++ b/src/hooks/notifs/useRateLimitWarningNotification.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useEffect, useMemo, useRef, useState } from 'react' import { useNotifications } from 'src/context/notifications.js' -import { Text } from 'src/ink.js' +import { Text } from '@anthropic/ink' import { getRateLimitWarning, getUsingOverageText, diff --git a/src/hooks/toolPermission/handlers/interactiveHandler.ts b/src/hooks/toolPermission/handlers/interactiveHandler.ts index 6b3e4e80d..3439b9fcd 100644 --- a/src/hooks/toolPermission/handlers/interactiveHandler.ts +++ b/src/hooks/toolPermission/handlers/interactiveHandler.ts @@ -4,7 +4,7 @@ import { randomUUID } from 'crypto' import { logForDebugging } from 'src/utils/debug.js' import { getAllowedChannels } from '../../../bootstrap/state.js' import type { BridgePermissionCallbacks } from '../../../bridge/bridgePermissionCallbacks.js' -import { getTerminalFocused } from '../../../ink/terminal-focus-state.js' +import { getTerminalFocused } from '@anthropic/ink' import { CHANNEL_PERMISSION_REQUEST_METHOD, type ChannelPermissionRequestParams, diff --git a/src/hooks/useArrowKeyHistory.tsx b/src/hooks/useArrowKeyHistory.tsx index 69e7d6460..d874e32b0 100644 --- a/src/hooks/useArrowKeyHistory.tsx +++ b/src/hooks/useArrowKeyHistory.tsx @@ -4,7 +4,7 @@ import { useNotifications } from 'src/context/notifications.js' import { ConfigurableShortcutHint } from '../components/ConfigurableShortcutHint.js' import { FOOTER_TEMPORARY_STATUS_TIMEOUT } from '../components/PromptInput/Notifications.js' import { getHistory } from '../history.js' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import type { PromptInputMode } from '../types/textInputTypes.js' import type { HistoryEntry, PastedContent } from '../utils/config.js' diff --git a/src/hooks/useAssistantHistory.ts b/src/hooks/useAssistantHistory.ts index c5348d20e..36da73b92 100644 --- a/src/hooks/useAssistantHistory.ts +++ b/src/hooks/useAssistantHistory.ts @@ -13,7 +13,7 @@ import { type HistoryAuthCtx, type HistoryPage, } from '../assistant/sessionHistory.js' -import type { ScrollBoxHandle } from '../ink/components/ScrollBox.js' +import type { ScrollBoxHandle } from '@anthropic/ink' import type { RemoteSessionConfig } from '../remote/RemoteSessionManager.js' import { convertSDKMessage } from '../remote/sdkMessageAdapter.js' import type { Message, SystemInformationalMessage } from '../types/message.js' diff --git a/src/hooks/useAwaySummary.ts b/src/hooks/useAwaySummary.ts index 115209ae6..9caf55f24 100644 --- a/src/hooks/useAwaySummary.ts +++ b/src/hooks/useAwaySummary.ts @@ -1,9 +1,6 @@ import { feature } from 'bun:bundle' import { useEffect, useRef } from 'react' -import { - getTerminalFocusState, - subscribeTerminalFocus, -} from '../ink/terminal-focus-state.js' +import { getTerminalFocusState, subscribeTerminalFocus } from '@anthropic/ink' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js' import { generateAwaySummary } from '../services/awaySummary.js' import type { Message } from '../types/message.js' diff --git a/src/hooks/useBackgroundTaskNavigation.ts b/src/hooks/useBackgroundTaskNavigation.ts index c1a9b14fd..ad94c641d 100644 --- a/src/hooks/useBackgroundTaskNavigation.ts +++ b/src/hooks/useBackgroundTaskNavigation.ts @@ -1,7 +1,6 @@ import { useEffect, useRef } from 'react' -import { KeyboardEvent } from '../ink/events/keyboard-event.js' -// eslint-disable-next-line custom-rules/prefer-use-keybindings -- backward-compat bridge until REPL wires handleKeyDown to -import { useInput } from '../ink.js' +import { KeyboardEvent, useInput } from '@anthropic/ink' +// backward-compat bridge until REPL wires handleKeyDown to import { type AppState, useAppState, diff --git a/src/hooks/useBlink.ts b/src/hooks/useBlink.ts index 33cac6974..cecf54c53 100644 --- a/src/hooks/useBlink.ts +++ b/src/hooks/useBlink.ts @@ -1,4 +1,4 @@ -import { type DOMElement, useAnimationFrame, useTerminalFocus } from '../ink.js' +import { type DOMElement, useAnimationFrame, useTerminalFocus } from '@anthropic/ink' const BLINK_INTERVAL_MS = 600 diff --git a/src/hooks/useCanUseTool.tsx b/src/hooks/useCanUseTool.tsx index 78b9397c0..cf8af94f8 100644 --- a/src/hooks/useCanUseTool.tsx +++ b/src/hooks/useCanUseTool.tsx @@ -8,7 +8,7 @@ import { } from 'src/services/analytics/index.js' import { sanitizeToolNameForAnalytics } from 'src/services/analytics/metadata.js' import type { ToolUseConfirm } from '../components/permissions/PermissionRequest.js' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import type { ToolPermissionContext, Tool as ToolType, diff --git a/src/hooks/useChromeExtensionNotification.tsx b/src/hooks/useChromeExtensionNotification.tsx index dc058df0e..4f043de2e 100644 --- a/src/hooks/useChromeExtensionNotification.tsx +++ b/src/hooks/useChromeExtensionNotification.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { isClaudeAISubscriber } from '../utils/auth.js' import { isChromeExtensionInstalled, diff --git a/src/hooks/useCopyOnSelect.ts b/src/hooks/useCopyOnSelect.ts index 778ef5a1d..e1a239009 100644 --- a/src/hooks/useCopyOnSelect.ts +++ b/src/hooks/useCopyOnSelect.ts @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react' -import { useTheme } from '../components/design-system/ThemeProvider.js' -import type { useSelection } from '../ink/hooks/use-selection.js' +import { useTheme } from '@anthropic/ink' +import type { useSelection } from '@anthropic/ink' import { getGlobalConfig } from '../utils/config.js' import { getTheme } from '../utils/theme.js' diff --git a/src/hooks/useExitOnCtrlCD.ts b/src/hooks/useExitOnCtrlCD.ts index 23ba7ad58..e9af0157c 100644 --- a/src/hooks/useExitOnCtrlCD.ts +++ b/src/hooks/useExitOnCtrlCD.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo, useState } from 'react' -import useApp from '../ink/hooks/use-app.js' +import { useApp } from '@anthropic/ink' import type { KeybindingContextName } from '../keybindings/types.js' import { useDoublePress } from './useDoublePress.js' diff --git a/src/hooks/useGlobalKeybindings.tsx b/src/hooks/useGlobalKeybindings.tsx index a41b1b6a5..1dd171cb8 100644 --- a/src/hooks/useGlobalKeybindings.tsx +++ b/src/hooks/useGlobalKeybindings.tsx @@ -6,7 +6,7 @@ */ import { feature } from 'bun:bundle' import { useCallback } from 'react' -import instances from '../ink/instances.js' +import { instances } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import type { Screen } from '../screens/REPL.js' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js' diff --git a/src/hooks/useHistorySearch.ts b/src/hooks/useHistorySearch.ts index b48c880b7..04bf31b67 100644 --- a/src/hooks/useHistorySearch.ts +++ b/src/hooks/useHistorySearch.ts @@ -5,9 +5,8 @@ import { getValueFromInput, } from '../components/PromptInput/inputModes.js' import { makeHistoryReader } from '../history.js' -import { KeyboardEvent } from '../ink/events/keyboard-event.js' -// eslint-disable-next-line custom-rules/prefer-use-keybindings -- backward-compat bridge until consumers wire handleKeyDown to -import { useInput } from '../ink.js' +import { KeyboardEvent, useInput } from '@anthropic/ink' +// backward-compat bridge until consumers wire handleKeyDown to import { useKeybinding, useKeybindings } from '../keybindings/useKeybinding.js' import type { PromptInputMode } from '../types/textInputTypes.js' import type { HistoryEntry } from '../utils/config.js' diff --git a/src/hooks/useInboxPoller.ts b/src/hooks/useInboxPoller.ts index 361ba636d..ac2c98555 100644 --- a/src/hooks/useInboxPoller.ts +++ b/src/hooks/useInboxPoller.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef } from 'react' import { useInterval } from 'usehooks-ts' import type { ToolUseConfirm } from '../components/permissions/PermissionRequest.js' import { TEAMMATE_MESSAGE_TAG } from '../constants/xml.js' -import { useTerminalNotification } from '../ink/useTerminalNotification.js' +import { useTerminalNotification } from '@anthropic/ink' import { sendNotification } from '../services/notifier.js' import { type AppState, diff --git a/src/hooks/useNotifyAfterTimeout.ts b/src/hooks/useNotifyAfterTimeout.ts index 8b0ce315c..c9c19f191 100644 --- a/src/hooks/useNotifyAfterTimeout.ts +++ b/src/hooks/useNotifyAfterTimeout.ts @@ -3,7 +3,7 @@ import { getLastInteractionTime, updateLastInteractionTime, } from '../bootstrap/state.js' -import { useTerminalNotification } from '../ink/useTerminalNotification.js' +import { useTerminalNotification } from '@anthropic/ink' import { sendNotification } from '../services/notifier.js' // The time threshold in milliseconds for considering an interaction "recent" (6 seconds) export const DEFAULT_INTERACTION_THRESHOLD_MS = 6000 diff --git a/src/hooks/useOfficialMarketplaceNotification.tsx b/src/hooks/useOfficialMarketplaceNotification.tsx index 25cf62254..6ba179e9b 100644 --- a/src/hooks/useOfficialMarketplaceNotification.tsx +++ b/src/hooks/useOfficialMarketplaceNotification.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import type { Notification } from '../context/notifications.js' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { logForDebugging } from '../utils/debug.js' import { checkAndInstallOfficialMarketplace } from '../utils/plugins/officialMarketplaceStartupCheck.js' import { useStartupNotification } from './notifs/useStartupNotification.js' diff --git a/src/hooks/usePasteHandler.ts b/src/hooks/usePasteHandler.ts index d6257b9a2..a2ed7f306 100644 --- a/src/hooks/usePasteHandler.ts +++ b/src/hooks/usePasteHandler.ts @@ -2,7 +2,7 @@ import { basename } from 'path' import React from 'react' import { logError } from 'src/utils/log.js' import { useDebounceCallback } from 'usehooks-ts' -import type { InputEvent, Key } from '../ink.js' +import type { InputEvent, Key } from '@anthropic/ink' import { getImageFromClipboard, isImageFilePath, diff --git a/src/hooks/usePluginRecommendationBase.tsx b/src/hooks/usePluginRecommendationBase.tsx index 23930fba4..1e2fd7b5d 100644 --- a/src/hooks/usePluginRecommendationBase.tsx +++ b/src/hooks/usePluginRecommendationBase.tsx @@ -8,7 +8,7 @@ import figures from 'figures' import * as React from 'react' import { getIsRemoteMode } from '../bootstrap/state.js' import type { useNotifications } from '../context/notifications.js' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { logError } from '../utils/log.js' import { getPluginById } from '../utils/plugins/marketplaceManager.js' diff --git a/src/hooks/usePromptSuggestion.ts b/src/hooks/usePromptSuggestion.ts index 0a0a35f9c..e080314a8 100644 --- a/src/hooks/usePromptSuggestion.ts +++ b/src/hooks/usePromptSuggestion.ts @@ -1,5 +1,5 @@ import { useCallback, useRef } from 'react' -import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js' +import { useTerminalFocus } from '@anthropic/ink' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, diff --git a/src/hooks/useReplBridge.tsx b/src/hooks/useReplBridge.tsx index 522202891..7be394869 100644 --- a/src/hooks/useReplBridge.tsx +++ b/src/hooks/useReplBridge.tsx @@ -19,7 +19,7 @@ import type { SDKMessage, } from '../entrypoints/agentSdkTypes.js' import type { SDKControlResponse } from '../entrypoints/sdk/controlTypes.js' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js' import { useAppState, diff --git a/src/hooks/useSearchInput.ts b/src/hooks/useSearchInput.ts index a72fbf4b4..41860b0eb 100644 --- a/src/hooks/useSearchInput.ts +++ b/src/hooks/useSearchInput.ts @@ -1,7 +1,6 @@ import { useCallback, useState } from 'react' -import { KeyboardEvent } from '../ink/events/keyboard-event.js' -// eslint-disable-next-line custom-rules/prefer-use-keybindings -- backward-compat bridge until consumers wire handleKeyDown to -import { useInput } from '../ink.js' +import { KeyboardEvent, useInput } from '@anthropic/ink' +// backward-compat bridge until consumers wire handleKeyDown to import { Cursor, getLastKill, diff --git a/src/hooks/useTerminalSize.ts b/src/hooks/useTerminalSize.ts index 68e24df87..944a5b0e0 100644 --- a/src/hooks/useTerminalSize.ts +++ b/src/hooks/useTerminalSize.ts @@ -1,8 +1,5 @@ import { useContext } from 'react' -import { - type TerminalSize, - TerminalSizeContext, -} from 'src/ink/components/TerminalSizeContext.js' +import { type TerminalSize, TerminalSizeContext } from '@anthropic/ink' export function useTerminalSize(): TerminalSize { const size = useContext(TerminalSizeContext) diff --git a/src/hooks/useTextInput.ts b/src/hooks/useTextInput.ts index 90c4c4f82..21e0dbf19 100644 --- a/src/hooks/useTextInput.ts +++ b/src/hooks/useTextInput.ts @@ -3,7 +3,7 @@ import { useNotifications } from 'src/context/notifications.js' import stripAnsi from 'strip-ansi' import { markBackslashReturnUsed } from '../commands/terminalSetup/terminalSetup.js' import { addToHistory } from '../history.js' -import type { Key } from '../ink.js' +import type { Key } from '@anthropic/ink' import type { InlineGhostText, TextInputState, diff --git a/src/hooks/useTypeahead.tsx b/src/hooks/useTypeahead.tsx index 3e2dbd220..5e0a736bf 100644 --- a/src/hooks/useTypeahead.tsx +++ b/src/hooks/useTypeahead.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useNotifications } from 'src/context/notifications.js' -import { Text } from 'src/ink.js' +import { Text } from '@anthropic/ink' import { logEvent } from 'src/services/analytics/index.js' import { useDebounceCallback } from 'usehooks-ts' import { type Command, getCommandName } from '../commands.js' @@ -17,9 +17,8 @@ import { useIsModalOverlayActive, useRegisterOverlay, } from '../context/overlayContext.js' -import { KeyboardEvent } from '../ink/events/keyboard-event.js' -// eslint-disable-next-line custom-rules/prefer-use-keybindings -- backward-compat bridge until consumers wire handleKeyDown to -import { useInput } from '../ink.js' +import { KeyboardEvent, useInput } from '@anthropic/ink' +// backward-compat bridge until consumers wire handleKeyDown to import { useOptionalKeybindingContext, useRegisterKeybindingContext, diff --git a/src/hooks/useVimInput.ts b/src/hooks/useVimInput.ts index 0aabc9117..f49bb5003 100644 --- a/src/hooks/useVimInput.ts +++ b/src/hooks/useVimInput.ts @@ -1,5 +1,5 @@ import React, { useCallback, useState } from 'react' -import type { Key } from '../ink.js' +import type { Key } from '@anthropic/ink' import type { VimInputState, VimMode } from '../types/textInputTypes.js' import { Cursor } from '../utils/Cursor.js' import { lastGrapheme } from '../utils/intl.js' diff --git a/src/hooks/useVirtualScroll.ts b/src/hooks/useVirtualScroll.ts index 388b0bad9..bdfe337dd 100644 --- a/src/hooks/useVirtualScroll.ts +++ b/src/hooks/useVirtualScroll.ts @@ -7,8 +7,7 @@ import { useRef, useSyncExternalStore, } from 'react' -import type { ScrollBoxHandle } from '../ink/components/ScrollBox.js' -import type { DOMElement } from '../ink/dom.js' +import type { ScrollBoxHandle, DOMElement } from '@anthropic/ink' /** * Estimated height (rows) for items not yet measured. Intentionally LOW: diff --git a/src/hooks/useVoice.ts b/src/hooks/useVoice.ts index 30c099170..0ac154e37 100644 --- a/src/hooks/useVoice.ts +++ b/src/hooks/useVoice.ts @@ -8,7 +8,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { useSetVoiceState } from '../context/voice.js' -import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js' +import { useTerminalFocus } from '@anthropic/ink' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, diff --git a/src/hooks/useVoiceIntegration.tsx b/src/hooks/useVoiceIntegration.tsx index 7cedb1c0f..b798a61ae 100644 --- a/src/hooks/useVoiceIntegration.tsx +++ b/src/hooks/useVoiceIntegration.tsx @@ -8,9 +8,8 @@ import { useSetVoiceState, useVoiceState, } from '../context/voice.js' -import { KeyboardEvent } from '../ink/events/keyboard-event.js' -// eslint-disable-next-line custom-rules/prefer-use-keybindings -- backward-compat bridge until REPL wires handleKeyDown to -import { useInput } from '../ink.js' +import { KeyboardEvent, useInput } from '@anthropic/ink' +// backward-compat bridge until REPL wires handleKeyDown to import { useOptionalKeybindingContext } from '../keybindings/KeybindingContext.js' import { keystrokesEqual } from '../keybindings/resolver.js' import type { ParsedKeystroke } from '../keybindings/types.js' diff --git a/src/ink.ts b/src/ink.ts deleted file mode 100644 index a06b343cc..000000000 --- a/src/ink.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { createElement, type ReactNode } from 'react' -import { ThemeProvider } from './components/design-system/ThemeProvider.js' -import inkRender, { - type Instance, - createRoot as inkCreateRoot, - type RenderOptions, - type Root, -} from './ink/root.js' - -export type { RenderOptions, Instance, Root } - -// Wrap all CC render calls with ThemeProvider so ThemedBox/ThemedText work -// without every call site having to mount it. Ink itself is theme-agnostic. -function withTheme(node: ReactNode): ReactNode { - return createElement(ThemeProvider, null, node) -} - -export async function render( - node: ReactNode, - options?: NodeJS.WriteStream | RenderOptions, -): Promise { - return inkRender(withTheme(node), options) -} - -export async function createRoot(options?: RenderOptions): Promise { - const root = await inkCreateRoot(options) - return { - ...root, - render: node => root.render(withTheme(node)), - } -} - -export { color } from './components/design-system/color.js' -export type { Props as BoxProps } from './components/design-system/ThemedBox.js' -export { default as Box } from './components/design-system/ThemedBox.js' -export type { Props as TextProps } from './components/design-system/ThemedText.js' -export { default as Text } from './components/design-system/ThemedText.js' -export { - ThemeProvider, - usePreviewTheme, - useTheme, - useThemeSetting, -} from './components/design-system/ThemeProvider.js' -export { Ansi } from './ink/Ansi.js' -export type { Props as AppProps } from './ink/components/AppContext.js' -export type { Props as BaseBoxProps } from './ink/components/Box.js' -export { default as BaseBox } from './ink/components/Box.js' -export type { - ButtonState, - Props as ButtonProps, -} from './ink/components/Button.js' -export { default as Button } from './ink/components/Button.js' -export type { Props as LinkProps } from './ink/components/Link.js' -export { default as Link } from './ink/components/Link.js' -export type { Props as NewlineProps } from './ink/components/Newline.js' -export { default as Newline } from './ink/components/Newline.js' -export { NoSelect } from './ink/components/NoSelect.js' -export { RawAnsi } from './ink/components/RawAnsi.js' -export { default as Spacer } from './ink/components/Spacer.js' -export type { Props as StdinProps } from './ink/components/StdinContext.js' -export type { Props as BaseTextProps } from './ink/components/Text.js' -export { default as BaseText } from './ink/components/Text.js' -export type { DOMElement } from './ink/dom.js' -export { ClickEvent } from './ink/events/click-event.js' -export { EventEmitter } from './ink/events/emitter.js' -export { Event } from './ink/events/event.js' -export type { Key } from './ink/events/input-event.js' -export { InputEvent } from './ink/events/input-event.js' -export type { TerminalFocusEventType } from './ink/events/terminal-focus-event.js' -export { TerminalFocusEvent } from './ink/events/terminal-focus-event.js' -export { FocusManager } from './ink/focus.js' -export type { FlickerReason } from './ink/frame.js' -export { useAnimationFrame } from './ink/hooks/use-animation-frame.js' -export { default as useApp } from './ink/hooks/use-app.js' -export { default as useInput } from './ink/hooks/use-input.js' -export { useAnimationTimer, useInterval } from './ink/hooks/use-interval.js' -export { useSelection } from './ink/hooks/use-selection.js' -export { default as useStdin } from './ink/hooks/use-stdin.js' -export { useTabStatus } from './ink/hooks/use-tab-status.js' -export { useTerminalFocus } from './ink/hooks/use-terminal-focus.js' -export { useTerminalTitle } from './ink/hooks/use-terminal-title.js' -export { useTerminalViewport } from './ink/hooks/use-terminal-viewport.js' -export { default as measureElement } from './ink/measure-element.js' -export { supportsTabStatus } from './ink/termio/osc.js' -export { default as wrapText } from './ink/wrap-text.js' diff --git a/src/ink/src/bootstrap/state.ts b/src/ink/src/bootstrap/state.ts deleted file mode 100644 index 875ce2bdc..000000000 --- a/src/ink/src/bootstrap/state.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Auto-generated type stub — replace with real implementation -export type flushInteractionTime = any; diff --git a/src/ink/src/native-ts/yoga-layout/index.ts b/src/ink/src/native-ts/yoga-layout/index.ts deleted file mode 100644 index c75a2b090..000000000 --- a/src/ink/src/native-ts/yoga-layout/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Auto-generated type stub — replace with real implementation -export type getYogaCounters = any; diff --git a/src/ink/src/utils/debug.ts b/src/ink/src/utils/debug.ts deleted file mode 100644 index c64d5960c..000000000 --- a/src/ink/src/utils/debug.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Auto-generated type stub — replace with real implementation -export type logForDebugging = any; diff --git a/src/ink/src/utils/log.ts b/src/ink/src/utils/log.ts deleted file mode 100644 index cf30e90da..000000000 --- a/src/ink/src/utils/log.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Auto-generated type stub — replace with real implementation -export type logError = any; diff --git a/src/interactiveHelpers.tsx b/src/interactiveHelpers.tsx index 72bf5e7be..3c9946b29 100644 --- a/src/interactiveHelpers.tsx +++ b/src/interactiveHelpers.tsx @@ -18,8 +18,8 @@ import type { Command } from './commands.js' import { createStatsStore, type StatsStore } from './context/stats.js' import { getSystemContext } from './context.js' import { initializeTelemetryAfterTrust } from './entrypoints/init.js' -import { isSynchronizedOutputSupported } from './ink/terminal.js' -import type { RenderOptions, Root, TextProps } from './ink.js' +import { isSynchronizedOutputSupported } from '@anthropic/ink' +import type { RenderOptions, Root, TextProps } from '@anthropic/ink' import { KeybindingSetup } from './keybindings/KeybindingProviderSetup.js' import { startDeferredPrefetches } from './main.js' import { @@ -102,7 +102,7 @@ export async function exitWithMessage( beforeExit?: () => Promise }, ): Promise { - const { Text } = await import('./ink.js') + const { Text } = await import('@anthropic/ink') const color = options?.color const exitCode = options?.exitCode ?? 1 root.render( diff --git a/src/keybindings/KeybindingContext.tsx b/src/keybindings/KeybindingContext.tsx index bc85e81c0..edfedad54 100644 --- a/src/keybindings/KeybindingContext.tsx +++ b/src/keybindings/KeybindingContext.tsx @@ -5,7 +5,7 @@ import React, { useLayoutEffect, useMemo, } from 'react' -import type { Key } from '../ink.js' +import type { Key } from '@anthropic/ink' import { type ChordResolveResult, getBindingDisplayText, diff --git a/src/keybindings/KeybindingProviderSetup.tsx b/src/keybindings/KeybindingProviderSetup.tsx index 2397468c8..bb0f7ddca 100644 --- a/src/keybindings/KeybindingProviderSetup.tsx +++ b/src/keybindings/KeybindingProviderSetup.tsx @@ -8,11 +8,11 @@ */ import React, { useCallback, useEffect, useRef, useState } from 'react' import { useNotifications } from '../context/notifications.js' -import type { InputEvent } from '../ink/events/input-event.js' +import type { InputEvent } from '@anthropic/ink' // ChordInterceptor intentionally uses useInput to intercept all keystrokes before // other handlers process them - this is required for chord sequence support // eslint-disable-next-line custom-rules/prefer-use-keybindings -import { type Key, useInput } from '../ink.js' +import { type Key, useInput } from '@anthropic/ink' import { count } from '../utils/array.js' import { logForDebugging } from '../utils/debug.js' import { plural } from '../utils/stringUtils.js' diff --git a/src/keybindings/match.ts b/src/keybindings/match.ts index 2b407173f..c915f328e 100644 --- a/src/keybindings/match.ts +++ b/src/keybindings/match.ts @@ -1,4 +1,4 @@ -import type { Key } from '../ink.js' +import type { Key } from '@anthropic/ink' import type { ParsedBinding, ParsedKeystroke } from './types.js' /** diff --git a/src/keybindings/resolver.ts b/src/keybindings/resolver.ts index 746404957..f58babeb6 100644 --- a/src/keybindings/resolver.ts +++ b/src/keybindings/resolver.ts @@ -1,4 +1,4 @@ -import type { Key } from '../ink.js' +import type { Key } from '@anthropic/ink' import { getKeyName, matchesBinding } from './match.js' import { chordToString } from './parser.js' import type { diff --git a/src/keybindings/useKeybinding.ts b/src/keybindings/useKeybinding.ts index 02b07ce19..1068852be 100644 --- a/src/keybindings/useKeybinding.ts +++ b/src/keybindings/useKeybinding.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect } from 'react' -import type { InputEvent } from '../ink/events/input-event.js' -import { type Key, useInput } from '../ink.js' +import type { InputEvent } from '@anthropic/ink' +import { type Key, useInput } from '@anthropic/ink' import { useOptionalKeybindingContext } from './KeybindingContext.js' import type { KeybindingContextName } from './types.js' diff --git a/src/main.tsx b/src/main.tsx index 5ceb6405d..bf7b2a252 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -41,7 +41,7 @@ import { getRemoteSessionUrl } from './constants/product.js' import { getSystemContext, getUserContext } from './context.js' import { init, initializeTelemetryAfterTrust } from './entrypoints/init.js' import { addToHistory } from './history.js' -import type { Root } from './ink.js' +import type { Root } from '@anthropic/ink' import { launchRepl } from './replLauncher.js' import { hasGrowthBookEnvOverride, @@ -153,7 +153,7 @@ import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, } from 'src/services/analytics/index.js' -import { initializeAnalyticsGates } from 'src/services/analytics/sink.js' + import { getOriginalCwd, setAdditionalDirectoriesForClaudeMd, @@ -173,7 +173,7 @@ import { launchTeleportRepoMismatchDialog, launchTeleportResumeWrapper, } from './dialogLaunchers.js' -import { SHOW_CURSOR } from './ink/termio/dec.js' +import { SHOW_CURSOR } from '@anthropic/ink' import { exitWithError, exitWithMessage, @@ -441,6 +441,7 @@ import { type ThinkingConfig, } from './utils/thinking.js' import { initUser, resetUserCache } from './utils/user.js' +import { initializeAnalyticsGates } from './services/analytics/sink.js' import { getTmuxInstallInstructions, isTmuxAvailable, @@ -3232,7 +3233,7 @@ async function run(): Promise { installAsciicastRecorder() } - const { createRoot } = await import('./ink.js') + const { createRoot } = await import('@anthropic/ink') root = await createRoot(ctx.renderOptions) // Log startup time now, before any blocking dialog renders. Logging @@ -6024,7 +6025,7 @@ async function run(): Promise { .action(async () => { const [{ setupTokenHandler }, { createRoot }] = await Promise.all([ import('./cli/handlers/util.js'), - import('./ink.js'), + import('@anthropic/ink'), ]) const root = await createRoot(getBaseRenderOptions(false)) await setupTokenHandler(root) @@ -6144,7 +6145,7 @@ async function run(): Promise { .action(async () => { const [{ doctorHandler }, { createRoot }] = await Promise.all([ import('./cli/handlers/util.js'), - import('./ink.js'), + import('@anthropic/ink'), ]) const root = await createRoot(getBaseRenderOptions(false)) await doctorHandler(root) diff --git a/src/replLauncher.tsx b/src/replLauncher.tsx index 664e95839..91550a3bd 100644 --- a/src/replLauncher.tsx +++ b/src/replLauncher.tsx @@ -1,6 +1,6 @@ import React from 'react' import type { StatsStore } from './context/stats.js' -import type { Root } from './ink.js' +import type { Root } from '@anthropic/ink' import type { Props as REPLProps } from './screens/REPL.js' import type { AppState } from './state/AppStateStore.js' import type { FpsMetrics } from './utils/fpsTracker.js' diff --git a/src/screens/Doctor.tsx b/src/screens/Doctor.tsx index d8de3714a..6ba73f2a4 100644 --- a/src/screens/Doctor.tsx +++ b/src/screens/Doctor.tsx @@ -15,13 +15,13 @@ import { getClaudeConfigHomeDir } from 'src/utils/envUtils.js' import type { SettingSource } from 'src/utils/settings/constants.js' import { getOriginalCwd } from '../bootstrap/state.js' import type { CommandResultDisplay } from '../commands.js' -import { Pane } from '../components/design-system/Pane.js' +import { Pane } from '@anthropic/ink' import { PressEnterToContinue } from '../components/PressEnterToContinue.js' import { SandboxDoctorSection } from '../components/sandbox/SandboxDoctorSection.js' import { ValidationErrorsList } from '../components/ValidationErrorsList.js' import { useSettingsErrors } from '../hooks/notifs/useSettingsErrors.js' import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybindings } from '../keybindings/useKeybinding.js' import { useAppState } from '../state/AppState.js' import { getPluginErrorMessage } from '../types/plugin.js' diff --git a/src/screens/REPL.tsx b/src/screens/REPL.tsx index 83669907c..be3fe3d68 100644 --- a/src/screens/REPL.tsx +++ b/src/screens/REPL.tsx @@ -14,19 +14,18 @@ import { dirname, join } from 'path'; import { tmpdir } from 'os'; import figures from 'figures'; // eslint-disable-next-line custom-rules/prefer-use-keybindings -- / n N Esc [ v are bare letters in transcript modal context, same class as g/G/j/k in ScrollKeybindingHandler -import { useInput } from '../ink.js'; -import { useSearchInput } from '../hooks/useSearchInput.js'; -import { useTerminalSize } from '../hooks/useTerminalSize.js'; -import { useSearchHighlight } from '../ink/hooks/use-search-highlight.js'; -import type { JumpHandle } from '../components/VirtualMessageList.js'; -import { renderMessagesToPlainText } from '../utils/exportRenderer.js'; -import { openFileInExternalEditor } from '../utils/editor.js'; -import { writeFile } from 'fs/promises'; -import { Box, Text, useStdin, useTheme, useTerminalFocus, useTerminalTitle, useTabStatus } from '../ink.js'; -import type { TabStatusKind } from '../ink/hooks/use-tab-status.js'; -import { CostThresholdDialog } from '../components/CostThresholdDialog.js'; -import { IdleReturnDialog } from '../components/IdleReturnDialog.js'; -import * as React from 'react'; +import { useInput } from '@anthropic/ink' +import { useSearchInput } from '../hooks/useSearchInput.js' +import { useTerminalSize } from '../hooks/useTerminalSize.js' +import { useSearchHighlight } from '@anthropic/ink' +import type { JumpHandle } from '../components/VirtualMessageList.js' +import { renderMessagesToPlainText } from '../utils/exportRenderer.js' +import { openFileInExternalEditor } from '../utils/editor.js' +import { writeFile } from 'fs/promises' +import { type TabStatusKind, Box, Text, useStdin, useTheme, useTerminalFocus, useTerminalTitle, useTabStatus } from '@anthropic/ink' +import { CostThresholdDialog } from '../components/CostThresholdDialog.js' +import { IdleReturnDialog } from '../components/IdleReturnDialog.js' +import * as React from 'react' import { useEffect, useMemo, @@ -36,12 +35,14 @@ import { useDeferredValue, useLayoutEffect, type RefObject, -} from 'react'; -import { useNotifications } from '../context/notifications.js'; -import { sendNotification } from '../services/notifier.js'; -import { startPreventSleep, stopPreventSleep } from '../services/preventSleep.js'; -import { useTerminalNotification } from '../ink/useTerminalNotification.js'; -import { hasCursorUpViewportYankBug } from '../ink/terminal.js'; +} from 'react' +import { useNotifications } from '../context/notifications.js' +import { sendNotification } from '../services/notifier.js' +import { + startPreventSleep, + stopPreventSleep, +} from '../services/preventSleep.js' +import { useTerminalNotification, hasCursorUpViewportYankBug } from '@anthropic/ink' import { createFileStateCacheWithSizeLimit, mergeFileStateCaches, @@ -446,13 +447,21 @@ import { UltraplanChoiceDialog } from '../components/ultraplan/UltraplanChoiceDi import { UltraplanLaunchDialog } from '../components/ultraplan/UltraplanLaunchDialog.js'; import { launchUltraplan } from '../commands/ultraplan.js'; // Session manager removed - using AppState now -import type { RemoteSessionConfig } from '../remote/RemoteSessionManager.js'; -import { REMOTE_SAFE_COMMANDS } from '../commands.js'; -import type { RemoteMessageContent } from '../utils/teleport/api.js'; -import { FullscreenLayout, useUnseenDivider, computeUnseenDivider } from '../components/FullscreenLayout.js'; -import { isFullscreenEnvEnabled, maybeGetTmuxMouseHint, isMouseTrackingEnabled } from '../utils/fullscreen.js'; -import { AlternateScreen } from '../ink/components/AlternateScreen.js'; -import { ScrollKeybindingHandler } from '../components/ScrollKeybindingHandler.js'; +import type { RemoteSessionConfig } from '../remote/RemoteSessionManager.js' +import { REMOTE_SAFE_COMMANDS } from '../commands.js' +import type { RemoteMessageContent } from '../utils/teleport/api.js' +import { + FullscreenLayout, + useUnseenDivider, + computeUnseenDivider, +} from '../components/FullscreenLayout.js' +import { + isFullscreenEnvEnabled, + maybeGetTmuxMouseHint, + isMouseTrackingEnabled, +} from '../utils/fullscreen.js' +import { AlternateScreen } from '@anthropic/ink' +import { ScrollKeybindingHandler } from '../components/ScrollKeybindingHandler.js' import { useMessageActions, MessageActionsKeybindings, @@ -460,10 +469,13 @@ import { type MessageActionsState, type MessageActionsNav, type MessageActionCaps, -} from '../components/messageActions.js'; -import { setClipboard } from '../ink/termio/osc.js'; -import type { ScrollBoxHandle } from '../ink/components/ScrollBox.js'; -import { createAttachmentMessage, getQueuedCommandAttachments } from '../utils/attachments.js'; +} from '../components/messageActions.js' +import { setClipboard } from '@anthropic/ink' +import type { ScrollBoxHandle } from '@anthropic/ink' +import { + createAttachmentMessage, + getQueuedCommandAttachments, +} from '../utils/attachments.js' // Stable empty array for hooks that accept MCPServerConnection[] — avoids // creating a new [] literal on every render in remote mode, which would diff --git a/src/screens/ResumeConversation.tsx b/src/screens/ResumeConversation.tsx index 019327ff3..a3a8229a2 100644 --- a/src/screens/ResumeConversation.tsx +++ b/src/screens/ResumeConversation.tsx @@ -7,8 +7,8 @@ import type { Command } from '../commands.js' import { LogSelector } from '../components/LogSelector.js' import { Spinner } from '../components/Spinner.js' import { restoreCostStateForSession } from '../cost-tracker.js' -import { setClipboard } from '../ink/termio/osc.js' -import { Box, Text } from '../ink.js' +import { setClipboard } from '@anthropic/ink' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, diff --git a/src/services/mcpServerApproval.tsx b/src/services/mcpServerApproval.tsx index 4b92d1280..78be862f3 100644 --- a/src/services/mcpServerApproval.tsx +++ b/src/services/mcpServerApproval.tsx @@ -1,7 +1,7 @@ import React from 'react' import { MCPServerApprovalDialog } from '../components/MCPServerApprovalDialog.js' import { MCPServerMultiselectDialog } from '../components/MCPServerMultiselectDialog.js' -import type { Root } from '../ink.js' +import type { Root } from '@anthropic/ink' import { KeybindingSetup } from '../keybindings/KeybindingProviderSetup.js' import { AppStateProvider } from '../state/AppState.js' import { getMcpConfigsByScope } from './mcp/config.js' diff --git a/src/services/notifier.ts b/src/services/notifier.ts index a1a865cf1..1eb8f99db 100644 --- a/src/services/notifier.ts +++ b/src/services/notifier.ts @@ -1,4 +1,4 @@ -import type { TerminalNotification } from '../ink/useTerminalNotification.js' +import type { TerminalNotification } from '@anthropic/ink' import { getGlobalConfig } from '../utils/config.js' import { env } from '../utils/env.js' import { execFileNoThrow } from '../utils/execFileNoThrow.js' diff --git a/src/services/remoteManagedSettings/securityCheck.tsx b/src/services/remoteManagedSettings/securityCheck.tsx index 857103408..f0d9b1ca7 100644 --- a/src/services/remoteManagedSettings/securityCheck.tsx +++ b/src/services/remoteManagedSettings/securityCheck.tsx @@ -6,7 +6,7 @@ import { hasDangerousSettings, hasDangerousSettingsChanged, } from '../../components/ManagedSettingsSecurityDialog/utils.js' -import { render } from '../../ink.js' +import { wrappedRender as render } from '@anthropic/ink' import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js' import { AppStateProvider } from '../../state/AppState.js' import { gracefulShutdownSync } from '../../utils/gracefulShutdown.js' diff --git a/src/services/tips/tipRegistry.ts b/src/services/tips/tipRegistry.ts index c99edfdc9..05b727dcc 100644 --- a/src/services/tips/tipRegistry.ts +++ b/src/services/tips/tipRegistry.ts @@ -8,7 +8,7 @@ import { } from 'src/utils/settings/settings.js' import { shouldOfferTerminalSetup } from '../../commands/terminalSetup/terminalSetup.js' import { getDesktopUpsellConfig } from '../../components/DesktopUpsell/DesktopUpsellStartup.js' -import { color } from '../../components/design-system/color.js' +import { color } from '@anthropic/ink' import { shouldShowOverageCreditUpsell } from '../../components/LogoV2/OverageCreditUpsell.js' import { getShortcutDisplay } from '../../keybindings/shortcutFormat.js' import { isKairosCronEnabled } from '../../tools/ScheduleCronTool/prompt.js' diff --git a/src/tools/AgentTool/UI.tsx b/src/tools/AgentTool/UI.tsx index aaa312e20..b6bc20da4 100644 --- a/src/tools/AgentTool/UI.tsx +++ b/src/tools/AgentTool/UI.tsx @@ -8,8 +8,7 @@ import { CtrlOToExpand, SubAgentProvider, } from 'src/components/CtrlOToExpand.js' -import { Byline } from 'src/components/design-system/Byline.js' -import { KeyboardShortcutHint } from 'src/components/design-system/KeyboardShortcutHint.js' +import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import type { z } from 'zod/v4' import { AgentProgressLine } from '../../components/AgentProgressLine.js' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' @@ -18,7 +17,7 @@ import { Markdown } from '../../components/Markdown.js' import { Message as MessageComponent } from '../../components/Message.js' import { MessageResponse } from '../../components/MessageResponse.js' import { ToolUseLoader } from '../../components/ToolUseLoader.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getDumpPromptsPath } from '../../services/api/dumpPrompts.js' import { findToolByName, type Tools } from '../../Tool.js' import type { Message, ProgressMessage } from '../../types/message.js' diff --git a/src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx b/src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx index e71a5c665..4c77e7da6 100644 --- a/src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx +++ b/src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx @@ -8,7 +8,7 @@ import { MessageResponse } from 'src/components/MessageResponse.js' import { BLACK_CIRCLE } from 'src/constants/figures.js' import { getModeColor } from 'src/utils/permissions/PermissionMode.js' import { z } from 'zod/v4' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { Tool } from '../../Tool.js' import { buildTool, type ToolDef } from '../../Tool.js' import { lazySchema } from '../../utils/lazySchema.js' diff --git a/src/tools/BashTool/BashToolResultMessage.tsx b/src/tools/BashTool/BashToolResultMessage.tsx index 4b5c0b613..6b1534bfb 100644 --- a/src/tools/BashTool/BashToolResultMessage.tsx +++ b/src/tools/BashTool/BashToolResultMessage.tsx @@ -1,10 +1,10 @@ import React from 'react' import { removeSandboxViolationTags } from 'src/utils/sandbox/sandbox-ui-utils.js' -import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js' +import { KeyboardShortcutHint } from '@anthropic/ink' import { MessageResponse } from '../../components/MessageResponse.js' import { OutputLine } from '../../components/shell/OutputLine.js' import { ShellTimeDisplay } from '../../components/shell/ShellTimeDisplay.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { Out as BashOut } from './BashTool.js' type Props = { diff --git a/src/tools/BashTool/UI.tsx b/src/tools/BashTool/UI.tsx index 1d5c3f5aa..2b0b3bfd3 100644 --- a/src/tools/BashTool/UI.tsx +++ b/src/tools/BashTool/UI.tsx @@ -1,10 +1,10 @@ import type { ToolResultBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import * as React from 'react' -import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js' +import { KeyboardShortcutHint } from '@anthropic/ink' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' import { MessageResponse } from '../../components/MessageResponse.js' import { ShellProgressMessage } from '../../components/shell/ShellProgressMessage.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js' import { useAppStateStore, useSetAppState } from '../../state/AppState.js' diff --git a/src/tools/BriefTool/UI.tsx b/src/tools/BriefTool/UI.tsx index e68adccf6..ea3a28f9f 100644 --- a/src/tools/BriefTool/UI.tsx +++ b/src/tools/BriefTool/UI.tsx @@ -2,7 +2,7 @@ import figures from 'figures' import React from 'react' import { Markdown } from '../../components/Markdown.js' import { BLACK_CIRCLE } from '../../constants/figures.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ProgressMessage } from '../../types/message.js' import { getDisplayPath } from '../../utils/file.js' import { formatFileSize } from '../../utils/format.js' diff --git a/src/tools/ConfigTool/UI.tsx b/src/tools/ConfigTool/UI.tsx index 36ce77e62..da8ced8a1 100644 --- a/src/tools/ConfigTool/UI.tsx +++ b/src/tools/ConfigTool/UI.tsx @@ -1,6 +1,6 @@ import React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { jsonStringify } from '../../utils/slowOperations.js' import type { Input, Output } from './ConfigTool.js' diff --git a/src/tools/EnterPlanModeTool/UI.tsx b/src/tools/EnterPlanModeTool/UI.tsx index 830708309..a1bc5d6c8 100644 --- a/src/tools/EnterPlanModeTool/UI.tsx +++ b/src/tools/EnterPlanModeTool/UI.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { BLACK_CIRCLE } from 'src/constants/figures.js' import { getModeColor } from 'src/utils/permissions/PermissionMode.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import type { ThemeName } from '../../utils/theme.js' diff --git a/src/tools/EnterWorktreeTool/UI.tsx b/src/tools/EnterWorktreeTool/UI.tsx index 5a859fd72..985186d34 100644 --- a/src/tools/EnterWorktreeTool/UI.tsx +++ b/src/tools/EnterWorktreeTool/UI.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import type { ThemeName } from '../../utils/theme.js' diff --git a/src/tools/ExitPlanModeTool/UI.tsx b/src/tools/ExitPlanModeTool/UI.tsx index b73c4e53f..789ea4ccf 100644 --- a/src/tools/ExitPlanModeTool/UI.tsx +++ b/src/tools/ExitPlanModeTool/UI.tsx @@ -4,7 +4,7 @@ import { MessageResponse } from 'src/components/MessageResponse.js' import { RejectedPlanMessage } from 'src/components/messages/UserToolResultMessage/RejectedPlanMessage.js' import { BLACK_CIRCLE } from 'src/constants/figures.js' import { getModeColor } from 'src/utils/permissions/PermissionMode.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import { getDisplayPath } from '../../utils/file.js' diff --git a/src/tools/ExitWorktreeTool/UI.tsx b/src/tools/ExitWorktreeTool/UI.tsx index d624f9720..a3eccf4ba 100644 --- a/src/tools/ExitWorktreeTool/UI.tsx +++ b/src/tools/ExitWorktreeTool/UI.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import type { ThemeName } from '../../utils/theme.js' diff --git a/src/tools/FileEditTool/UI.tsx b/src/tools/FileEditTool/UI.tsx index b43f520ee..57223d000 100644 --- a/src/tools/FileEditTool/UI.tsx +++ b/src/tools/FileEditTool/UI.tsx @@ -7,8 +7,9 @@ import { MessageResponse } from 'src/components/MessageResponse.js' import { extractTag } from 'src/utils/messages.js' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' import { FileEditToolUpdatedMessage } from '../../components/FileEditToolUpdatedMessage.js' + +import { Text } from '@anthropic/ink' import { FilePathLink } from '../../components/FilePathLink.js' -import { Text } from '../../ink.js' import type { Tools } from '../../Tool.js' import type { Message, ProgressMessage } from '../../types/message.js' import { adjustHunkLineNumbers, CONTEXT_LINES } from '../../utils/diff.js' diff --git a/src/tools/FileReadTool/UI.tsx b/src/tools/FileReadTool/UI.tsx index f7849957e..017d55e86 100644 --- a/src/tools/FileReadTool/UI.tsx +++ b/src/tools/FileReadTool/UI.tsx @@ -2,9 +2,10 @@ import type { ToolResultBlockParam } from '@anthropic-ai/sdk/resources/index.mjs import * as React from 'react' import { extractTag } from 'src/utils/messages.js' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' -import { FilePathLink } from '../../components/FilePathLink.js' + import { MessageResponse } from '../../components/MessageResponse.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' +import { FilePathLink } from '../../components/FilePathLink.js' import { FILE_NOT_FOUND_CWD_NOTE, getDisplayPath } from '../../utils/file.js' import { formatFileSize } from '../../utils/format.js' import { getPlansDirectory } from '../../utils/plans.js' diff --git a/src/tools/FileWriteTool/UI.tsx b/src/tools/FileWriteTool/UI.tsx index 183edc811..8ea218fb7 100644 --- a/src/tools/FileWriteTool/UI.tsx +++ b/src/tools/FileWriteTool/UI.tsx @@ -9,10 +9,11 @@ import { CtrlOToExpand } from '../../components/CtrlOToExpand.js' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' import { FileEditToolUpdatedMessage } from '../../components/FileEditToolUpdatedMessage.js' import { FileEditToolUseRejectedMessage } from '../../components/FileEditToolUseRejectedMessage.js' -import { FilePathLink } from '../../components/FilePathLink.js' + import { HighlightedCode } from '../../components/HighlightedCode.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' +import { FilePathLink } from '../../components/FilePathLink.js' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import { getCwd } from '../../utils/cwd.js' diff --git a/src/tools/GlobTool/UI.tsx b/src/tools/GlobTool/UI.tsx index 03a76237b..b70934fa4 100644 --- a/src/tools/GlobTool/UI.tsx +++ b/src/tools/GlobTool/UI.tsx @@ -4,7 +4,7 @@ import { MessageResponse } from 'src/components/MessageResponse.js' import { extractTag } from 'src/utils/messages.js' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' import { TOOL_SUMMARY_MAX_LENGTH } from '../../constants/toolLimits.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { FILE_NOT_FOUND_CWD_NOTE, getDisplayPath } from '../../utils/file.js' import { truncate } from '../../utils/format.js' import { GrepTool } from '../GrepTool/GrepTool.js' diff --git a/src/tools/GrepTool/UI.tsx b/src/tools/GrepTool/UI.tsx index dd26d0cb7..4bc319175 100644 --- a/src/tools/GrepTool/UI.tsx +++ b/src/tools/GrepTool/UI.tsx @@ -4,7 +4,7 @@ import { CtrlOToExpand } from '../../components/CtrlOToExpand.js' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' import { MessageResponse } from '../../components/MessageResponse.js' import { TOOL_SUMMARY_MAX_LENGTH } from '../../constants/toolLimits.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import { FILE_NOT_FOUND_CWD_NOTE, getDisplayPath } from '../../utils/file.js' diff --git a/src/tools/LSPTool/UI.tsx b/src/tools/LSPTool/UI.tsx index 2ca53e9a3..2a5403b12 100644 --- a/src/tools/LSPTool/UI.tsx +++ b/src/tools/LSPTool/UI.tsx @@ -3,7 +3,7 @@ import React from 'react' import { CtrlOToExpand } from '../../components/CtrlOToExpand.js' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' import { MessageResponse } from '../../components/MessageResponse.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { getDisplayPath } from '../../utils/file.js' import { extractTag } from '../../utils/messages.js' import type { Input, Output } from './LSPTool.js' diff --git a/src/tools/ListMcpResourcesTool/UI.tsx b/src/tools/ListMcpResourcesTool/UI.tsx index 4343123c0..6f9b18559 100644 --- a/src/tools/ListMcpResourcesTool/UI.tsx +++ b/src/tools/ListMcpResourcesTool/UI.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' import { OutputLine } from '../../components/shell/OutputLine.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import { jsonStringify } from '../../utils/slowOperations.js' diff --git a/src/tools/MCPTool/UI.tsx b/src/tools/MCPTool/UI.tsx index bdfb3e4ab..5fa55c7a2 100644 --- a/src/tools/MCPTool/UI.tsx +++ b/src/tools/MCPTool/UI.tsx @@ -2,19 +2,19 @@ import { feature } from 'bun:bundle' import figures from 'figures' import * as React from 'react' import type { z } from 'zod/v4' -import { ProgressBar } from '../../components/design-system/ProgressBar.js' +import { ProgressBar } from '@anthropic/ink' import { MessageResponse } from '../../components/MessageResponse.js' import { linkifyUrlsInText, OutputLine, } from '../../components/shell/OutputLine.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Ansi, Box, Text } from '../../ink.js' +import { Ansi, Box, Text, stringWidth } from '@anthropic/ink' +import { createHyperlink } from '../../utils/hyperlink.js' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import type { MCPProgress } from '../../types/tools.js' import { formatNumber } from '../../utils/format.js' -import { createHyperlink } from '../../utils/hyperlink.js' + import { getContentSizeEstimate, type MCPToolResult, diff --git a/src/tools/NotebookEditTool/UI.tsx b/src/tools/NotebookEditTool/UI.tsx index 3d777308a..e209aaba7 100644 --- a/src/tools/NotebookEditTool/UI.tsx +++ b/src/tools/NotebookEditTool/UI.tsx @@ -5,11 +5,12 @@ import { extractTag } from 'src/utils/messages.js' import type { ThemeName } from 'src/utils/theme.js' import type { z } from 'zod/v4' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' -import { FilePathLink } from '../../components/FilePathLink.js' + import { HighlightedCode } from '../../components/HighlightedCode.js' import { MessageResponse } from '../../components/MessageResponse.js' import { NotebookEditToolUseRejectedMessage } from '../../components/NotebookEditToolUseRejectedMessage.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' +import { FilePathLink } from '../../components/FilePathLink.js' import type { Tools } from '../../Tool.js' import { getDisplayPath } from '../../utils/file.js' import type { inputSchema, Output } from './NotebookEditTool.js' diff --git a/src/tools/PowerShellTool/UI.tsx b/src/tools/PowerShellTool/UI.tsx index 0f9438510..8b27bfa12 100644 --- a/src/tools/PowerShellTool/UI.tsx +++ b/src/tools/PowerShellTool/UI.tsx @@ -1,12 +1,12 @@ import type { ToolResultBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' import * as React from 'react' -import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js' +import { KeyboardShortcutHint } from '@anthropic/ink' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' import { MessageResponse } from '../../components/MessageResponse.js' import { OutputLine } from '../../components/shell/OutputLine.js' import { ShellProgressMessage } from '../../components/shell/ShellProgressMessage.js' import { ShellTimeDisplay } from '../../components/shell/ShellTimeDisplay.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { Tool } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import type { PowerShellProgress } from '../../types/tools.js' diff --git a/src/tools/ReadMcpResourceTool/UI.tsx b/src/tools/ReadMcpResourceTool/UI.tsx index 7459608a3..d2641adc2 100644 --- a/src/tools/ReadMcpResourceTool/UI.tsx +++ b/src/tools/ReadMcpResourceTool/UI.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import type { z } from 'zod/v4' import { MessageResponse } from '../../components/MessageResponse.js' import { OutputLine } from '../../components/shell/OutputLine.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import { jsonStringify } from '../../utils/slowOperations.js' diff --git a/src/tools/RemoteTriggerTool/UI.tsx b/src/tools/RemoteTriggerTool/UI.tsx index 3b44a786e..5aae25157 100644 --- a/src/tools/RemoteTriggerTool/UI.tsx +++ b/src/tools/RemoteTriggerTool/UI.tsx @@ -1,6 +1,6 @@ import React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { countCharInString } from '../../utils/stringUtils.js' import type { Input, Output } from './RemoteTriggerTool.js' diff --git a/src/tools/ScheduleCronTool/UI.tsx b/src/tools/ScheduleCronTool/UI.tsx index 439f7048f..7e4928a81 100644 --- a/src/tools/ScheduleCronTool/UI.tsx +++ b/src/tools/ScheduleCronTool/UI.tsx @@ -1,6 +1,6 @@ import React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { truncate } from '../../utils/format.js' import type { CreateOutput } from './CronCreateTool.js' import type { DeleteOutput } from './CronDeleteTool.js' diff --git a/src/tools/SendMessageTool/UI.tsx b/src/tools/SendMessageTool/UI.tsx index 2cd05a935..38d17a8d4 100644 --- a/src/tools/SendMessageTool/UI.tsx +++ b/src/tools/SendMessageTool/UI.tsx @@ -1,6 +1,6 @@ import React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { jsonParse } from '../../utils/slowOperations.js' import type { Input, SendMessageToolOutput } from './SendMessageTool.js' diff --git a/src/tools/SkillTool/UI.tsx b/src/tools/SkillTool/UI.tsx index 558ef9c57..7898ee348 100644 --- a/src/tools/SkillTool/UI.tsx +++ b/src/tools/SkillTool/UI.tsx @@ -5,10 +5,10 @@ import { FallbackToolUseErrorMessage } from 'src/components/FallbackToolUseError import { FallbackToolUseRejectedMessage } from 'src/components/FallbackToolUseRejectedMessage.js' import type { z } from 'zod/v4' import type { Command } from '../../commands.js' -import { Byline } from '../../components/design-system/Byline.js' +import { Byline } from '@anthropic/ink' import { Message as MessageComponent } from '../../components/Message.js' import { MessageResponse } from '../../components/MessageResponse.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { Tools } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import { buildSubagentLookups, EMPTY_LOOKUPS } from '../../utils/messages.js' diff --git a/src/tools/SkillTool/prompt.ts b/src/tools/SkillTool/prompt.ts index b05a58f3d..ef8267a78 100644 --- a/src/tools/SkillTool/prompt.ts +++ b/src/tools/SkillTool/prompt.ts @@ -6,7 +6,7 @@ import { getSlashCommandToolSkills, } from 'src/commands.js' import { COMMAND_NAME_TAG } from '../../constants/xml.js' -import { stringWidth } from '../../ink/stringWidth.js' +import { stringWidth } from '@anthropic/ink' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, diff --git a/src/tools/TaskOutputTool/TaskOutputTool.tsx b/src/tools/TaskOutputTool/TaskOutputTool.tsx index 9d8897411..4da47ffbc 100644 --- a/src/tools/TaskOutputTool/TaskOutputTool.tsx +++ b/src/tools/TaskOutputTool/TaskOutputTool.tsx @@ -3,7 +3,7 @@ import { z } from 'zod/v4' import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js' import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage.js' import { MessageResponse } from '../../components/MessageResponse.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js' import type { TaskType } from '../../Task.js' import type { Tool } from '../../Tool.js' diff --git a/src/tools/TaskStopTool/UI.tsx b/src/tools/TaskStopTool/UI.tsx index 6d25894bf..e4b2e8501 100644 --- a/src/tools/TaskStopTool/UI.tsx +++ b/src/tools/TaskStopTool/UI.tsx @@ -1,7 +1,6 @@ import React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' -import { stringWidth } from '../../ink/stringWidth.js' -import { Text } from '../../ink.js' +import { Text, stringWidth } from '@anthropic/ink' import { truncateToWidthNoEllipsis } from '../../utils/format.js' import type { Output } from './TaskStopTool.js' diff --git a/src/tools/WebFetchTool/UI.tsx b/src/tools/WebFetchTool/UI.tsx index a32c95463..546bf727e 100644 --- a/src/tools/WebFetchTool/UI.tsx +++ b/src/tools/WebFetchTool/UI.tsx @@ -1,7 +1,7 @@ import React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' import { TOOL_SUMMARY_MAX_LENGTH } from '../../constants/toolLimits.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ToolProgressData } from '../../Tool.js' import type { ProgressMessage } from '../../types/message.js' import { formatFileSize, truncate } from '../../utils/format.js' diff --git a/src/tools/WebSearchTool/UI.tsx b/src/tools/WebSearchTool/UI.tsx index af8584b29..005f95db9 100644 --- a/src/tools/WebSearchTool/UI.tsx +++ b/src/tools/WebSearchTool/UI.tsx @@ -1,7 +1,7 @@ import React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' import { TOOL_SUMMARY_MAX_LENGTH } from '../../constants/toolLimits.js' -import { Box, Text } from '../../ink.js' +import { Box, Text } from '@anthropic/ink' import type { ProgressMessage } from '../../types/message.js' import { truncate } from '../../utils/format.js' import type { diff --git a/src/types/ink-elements.d.ts b/src/types/ink-elements.d.ts index d5053283e..f98f08e53 100644 --- a/src/types/ink-elements.d.ts +++ b/src/types/ink-elements.d.ts @@ -2,11 +2,7 @@ // Note: The detailed prop types are defined in ink-jsx.d.ts via React module augmentation. // This file provides the global JSX namespace fallback declarations. import type { ReactNode, Ref } from 'react'; -import type { ClickEvent } from '../ink/events/click-event.js'; -import type { FocusEvent } from '../ink/events/focus-event.js'; -import type { KeyboardEvent } from '../ink/events/keyboard-event.js'; -import type { Styles, TextStyles } from '../ink/styles.js'; -import type { DOMElement } from '../ink/dom.js'; +import type { ClickEvent, FocusEvent, KeyboardEvent, Styles, TextStyles, DOMElement } from '@anthropic/ink'; declare global { namespace JSX { diff --git a/src/types/ink-jsx.d.ts b/src/types/ink-jsx.d.ts index 8c3704959..2c8911c71 100644 --- a/src/types/ink-jsx.d.ts +++ b/src/types/ink-jsx.d.ts @@ -9,11 +9,7 @@ * augmentation to work correctly. */ import type { ReactNode, Ref } from 'react'; -import type { ClickEvent } from '../ink/events/click-event.js'; -import type { FocusEvent } from '../ink/events/focus-event.js'; -import type { KeyboardEvent } from '../ink/events/keyboard-event.js'; -import type { Styles, TextStyles } from '../ink/styles.js'; -import type { DOMElement } from '../ink/dom.js'; +import type { ClickEvent, FocusEvent, KeyboardEvent, Styles, TextStyles, DOMElement } from '@anthropic/ink'; declare module 'react' { namespace JSX { diff --git a/src/types/textInputTypes.ts b/src/types/textInputTypes.ts index 1e77b56fe..7e5cb047e 100644 --- a/src/types/textInputTypes.ts +++ b/src/types/textInputTypes.ts @@ -2,7 +2,7 @@ import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs import type { UUID } from 'crypto' import type React from 'react' import type { PermissionResult } from '../entrypoints/agentSdkTypes.js' -import type { Key } from '../ink.js' +import type { Key } from '@anthropic/ink' import type { PastedContent } from '../utils/config.js' import type { ImageDimensions } from '../utils/imageResizer.js' import type { TextHighlight } from '../utils/textHighlighting.js' diff --git a/src/utils/Cursor.ts b/src/utils/Cursor.ts index 0317ece77..6968ff473 100644 --- a/src/utils/Cursor.ts +++ b/src/utils/Cursor.ts @@ -1,5 +1,4 @@ -import { stringWidth } from '../ink/stringWidth.js' -import { wrapAnsi } from '../ink/wrapAnsi.js' +import { stringWidth, wrapAnsi } from '@anthropic/ink' import { firstGrapheme, getGraphemeSegmenter, diff --git a/src/utils/__tests__/treeify.test.ts b/src/utils/__tests__/treeify.test.ts index e9f553755..6e60c66bc 100644 --- a/src/utils/__tests__/treeify.test.ts +++ b/src/utils/__tests__/treeify.test.ts @@ -8,7 +8,7 @@ mock.module("figures", () => ({ }, })); -mock.module("src/components/design-system/color.js", () => ({ +mock.module("src/ink.js", () => ({ color: (colorKey: string, themeName: string) => (text: string) => text, })); diff --git a/src/utils/ansiToPng.ts b/src/utils/ansiToPng.ts index f823fbbf8..499ecedc6 100644 --- a/src/utils/ansiToPng.ts +++ b/src/utils/ansiToPng.ts @@ -19,7 +19,7 @@ */ import { deflateSync } from 'zlib' -import { stringWidth } from '../ink/stringWidth.js' +import { stringWidth } from '@anthropic/ink' import { type AnsiColor, DEFAULT_BG, diff --git a/src/utils/autoRunIssue.tsx b/src/utils/autoRunIssue.tsx index 971f25635..3a6275bb5 100644 --- a/src/utils/autoRunIssue.tsx +++ b/src/utils/autoRunIssue.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useEffect, useRef } from 'react' -import { KeyboardShortcutHint } from '../components/design-system/KeyboardShortcutHint.js' -import { Box, Text } from '../ink.js' +import { KeyboardShortcutHint } from '@anthropic/ink' +import { Box, Text } from '@anthropic/ink' import { useKeybinding } from '../keybindings/useKeybinding.js' type Props = { diff --git a/src/utils/claudeInChrome/mcpServer.ts b/src/utils/claudeInChrome/mcpServer.ts index 4195d2c4f..02f0d629c 100644 --- a/src/utils/claudeInChrome/mcpServer.ts +++ b/src/utils/claudeInChrome/mcpServer.ts @@ -4,6 +4,7 @@ import { type Logger, type PermissionMode, } from '@ant/claude-for-chrome-mcp' +import { initializeAnalyticsSink } from '../../services/analytics/sink.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { format } from 'util' import { shutdownDatadog } from '../../services/analytics/datadog.js' @@ -13,7 +14,7 @@ import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, } from '../../services/analytics/index.js' -import { initializeAnalyticsSink } from '../../services/analytics/sink.js' + import { getClaudeAIOAuthTokens } from '../auth.js' import { enableConfigs, getGlobalConfig, saveGlobalConfig } from '../config.js' import { logForDebugging } from '../debug.js' diff --git a/src/utils/claudeInChrome/toolRendering.tsx b/src/utils/claudeInChrome/toolRendering.tsx index d760085fb..6f77bcf0b 100644 --- a/src/utils/claudeInChrome/toolRendering.tsx +++ b/src/utils/claudeInChrome/toolRendering.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' -import { supportsHyperlinks } from '../../ink/supports-hyperlinks.js' -import { Link, Text } from '../../ink.js' +import { supportsHyperlinks } from '@anthropic/ink' +import { Link, Text } from '@anthropic/ink' import { renderToolResultMessage as renderDefaultMCPToolResultMessage } from '../../tools/MCPTool/UI.js' import type { MCPToolResult } from '../../utils/mcpValidation.js' import { truncateToWidth } from '../format.js' diff --git a/src/utils/completionCache.ts b/src/utils/completionCache.ts index 3f0c9d2f7..ec288c3e6 100644 --- a/src/utils/completionCache.ts +++ b/src/utils/completionCache.ts @@ -3,8 +3,8 @@ import { mkdir, readFile, writeFile } from 'fs/promises' import { homedir } from 'os' import { dirname, join } from 'path' import { pathToFileURL } from 'url' -import { color } from '../components/design-system/color.js' -import { supportsHyperlinks } from '../ink/supports-hyperlinks.js' +import { color } from '@anthropic/ink' +import { supportsHyperlinks } from '@anthropic/ink' import { logForDebugging } from './debug.js' import { isENOENT } from './errors.js' import { execFileNoThrow } from './execFileNoThrow.js' diff --git a/src/utils/computerUse/mcpServer.ts b/src/utils/computerUse/mcpServer.ts index d51d80ab2..f759335c5 100644 --- a/src/utils/computerUse/mcpServer.ts +++ b/src/utils/computerUse/mcpServer.ts @@ -2,13 +2,14 @@ import { buildComputerUseTools, createComputerUseMcpServer, } from '@ant/computer-use-mcp' +import { initializeAnalyticsSink } from '../../services/analytics/sink.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js' import { homedir } from 'os' import { shutdownDatadog } from '../../services/analytics/datadog.js' import { shutdown1PEventLogging } from '../../services/analytics/firstPartyEventLogger.js' -import { initializeAnalyticsSink } from '../../services/analytics/sink.js' + import { enableConfigs } from '../config.js' import { logForDebugging } from '../debug.js' import { filterAppsForDescription } from './appNames.js' diff --git a/src/utils/computerUse/toolRendering.tsx b/src/utils/computerUse/toolRendering.tsx index 4b5da230e..0295e19e3 100644 --- a/src/utils/computerUse/toolRendering.tsx +++ b/src/utils/computerUse/toolRendering.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { MessageResponse } from '../../components/MessageResponse.js' -import { Text } from '../../ink.js' +import { Text } from '@anthropic/ink' import { truncateToWidth } from '../format.js' import type { MCPToolResult } from '../mcpValidation.js' diff --git a/src/utils/deepLink/protocolHandler.ts b/src/utils/deepLink/protocolHandler.ts index 0134aef51..511754dc7 100644 --- a/src/utils/deepLink/protocolHandler.ts +++ b/src/utils/deepLink/protocolHandler.ts @@ -11,6 +11,7 @@ * directly — there is no terminal attached. */ +import { parseDeepLink } from './parseDeepLink.js' import { homedir } from 'os' import { logForDebugging } from '../debug.js' import { @@ -19,7 +20,7 @@ import { } from '../githubRepoPathMapping.js' import { jsonStringify } from '../slowOperations.js' import { readLastFetchTime } from './banner.js' -import { parseDeepLink } from './parseDeepLink.js' + import { MACOS_BUNDLE_ID } from './registerProtocol.js' import { launchInTerminal } from './terminalLauncher.js' diff --git a/src/utils/deepLink/registerProtocol.ts b/src/utils/deepLink/registerProtocol.ts index 0e630ee61..a877cbaaa 100644 --- a/src/utils/deepLink/registerProtocol.ts +++ b/src/utils/deepLink/registerProtocol.ts @@ -13,6 +13,7 @@ * Windows — Writes registry keys under HKEY_CURRENT_USER\Software\Classes */ +import { DEEP_LINK_PROTOCOL } from './parseDeepLink.js' import { promises as fs } from 'fs' import * as os from 'os' import * as path from 'path' @@ -28,7 +29,6 @@ import { execFileNoThrow } from '../execFileNoThrow.js' import { getInitialSettings } from '../settings/settings.js' import { which } from '../which.js' import { getUserBinDir, getXDGDataHome } from '../xdg.js' -import { DEEP_LINK_PROTOCOL } from './parseDeepLink.js' export const MACOS_BUNDLE_ID = 'com.anthropic.claude-code-url-handler' const APP_NAME = 'Claude Code URL Handler' diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 2924b77b9..2cd6aa86e 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -6,7 +6,7 @@ import { } from 'child_process' import memoize from 'lodash-es/memoize.js' import { basename } from 'path' -import instances from '../ink/instances.js' +import { instances } from '@anthropic/ink' import { logForDebugging } from './debug.js' import { whichSync } from './which.js' diff --git a/src/utils/gracefulShutdown.ts b/src/utils/gracefulShutdown.ts index 391dce288..8b8a5b9c7 100644 --- a/src/utils/gracefulShutdown.ts +++ b/src/utils/gracefulShutdown.ts @@ -10,25 +10,7 @@ import { getSessionId, isSessionPersistenceDisabled, } from '../bootstrap/state.js' -import instances from '../ink/instances.js' -import { - DISABLE_KITTY_KEYBOARD, - DISABLE_MODIFY_OTHER_KEYS, -} from '../ink/termio/csi.js' -import { - DBP, - DFE, - DISABLE_MOUSE_TRACKING, - EXIT_ALT_SCREEN, - SHOW_CURSOR, -} from '../ink/termio/dec.js' -import { - CLEAR_ITERM2_PROGRESS, - CLEAR_TAB_STATUS, - CLEAR_TERMINAL_TITLE, - supportsTabStatus, - wrapForMultiplexer, -} from '../ink/termio/osc.js' +import { DISABLE_KITTY_KEYBOARD, DISABLE_MODIFY_OTHER_KEYS, DBP, DFE, DISABLE_MOUSE_TRACKING, EXIT_ALT_SCREEN, SHOW_CURSOR, CLEAR_ITERM2_PROGRESS, CLEAR_TAB_STATUS, CLEAR_TERMINAL_TITLE, instances, supportsTabStatus, wrapForMultiplexer } from '@anthropic/ink' import { shutdownDatadog } from '../services/analytics/datadog.js' import { shutdown1PEventLogging } from '../services/analytics/firstPartyEventLogger.js' import { diff --git a/src/utils/highlightMatch.tsx b/src/utils/highlightMatch.tsx index 31ed36045..23945f976 100644 --- a/src/utils/highlightMatch.tsx +++ b/src/utils/highlightMatch.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Text } from '../ink.js' +import { Text } from '@anthropic/ink' /** * Inverse-highlight every occurrence of `query` in `text` (case-insensitive). diff --git a/src/utils/hyperlink.ts b/src/utils/hyperlink.ts index 306dd93ad..732527380 100644 --- a/src/utils/hyperlink.ts +++ b/src/utils/hyperlink.ts @@ -1,5 +1,5 @@ import chalk from 'chalk' -import { supportsHyperlinks } from '../ink/supports-hyperlinks.js' +import { supportsHyperlinks } from '@anthropic/ink' // OSC 8 hyperlink escape sequences // Format: \e]8;;URL\e\\TEXT\e]8;;\e\\ diff --git a/src/utils/ink.ts b/src/utils/ink.ts index ea2669aca..915837bc9 100644 --- a/src/utils/ink.ts +++ b/src/utils/ink.ts @@ -1,4 +1,4 @@ -import type { TextProps } from '../ink.js' +import type { TextProps } from '@anthropic/ink' import { AGENT_COLOR_TO_THEME_COLOR, type AgentColorName, diff --git a/src/utils/logoV2Utils.ts b/src/utils/logoV2Utils.ts index f7f9720b4..4357119d7 100644 --- a/src/utils/logoV2Utils.ts +++ b/src/utils/logoV2Utils.ts @@ -1,5 +1,5 @@ import { getDirectConnectServerUrl, getSessionId } from '../bootstrap/state.js' -import { stringWidth } from '../ink/stringWidth.js' +import { stringWidth } from '@anthropic/ink' import type { LogOption } from '../types/logs.js' import { getSubscriptionName, isClaudeAISubscriber } from './auth.js' import { getCwd } from './cwd.js' diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts index b2c673e6d..ae1e3902e 100644 --- a/src/utils/markdown.ts +++ b/src/utils/markdown.ts @@ -1,13 +1,13 @@ import chalk from 'chalk' import { marked, type Token, type Tokens } from 'marked' import stripAnsi from 'strip-ansi' -import { color } from '../components/design-system/color.js' +import { color } from '@anthropic/ink' import { BLOCKQUOTE_BAR } from '../constants/figures.js' -import { stringWidth } from '../ink/stringWidth.js' -import { supportsHyperlinks } from '../ink/supports-hyperlinks.js' +import { stringWidth, supportsHyperlinks } from '@anthropic/ink' +import { createHyperlink } from '../utils/hyperlink.js' import type { CliHighlight } from './cliHighlight.js' import { logForDebugging } from './debug.js' -import { createHyperlink } from './hyperlink.js' + import { stripPromptXMLTags } from './messages.js' import type { ThemeName } from './theme.js' diff --git a/src/utils/preflightChecks.tsx b/src/utils/preflightChecks.tsx index 445cd12a8..fed5b1236 100644 --- a/src/utils/preflightChecks.tsx +++ b/src/utils/preflightChecks.tsx @@ -4,7 +4,7 @@ import { logEvent } from 'src/services/analytics/index.js' import { Spinner } from '../components/Spinner.js' import { getOauthConfig } from '../constants/oauth.js' import { useTimeout } from '../hooks/useTimeout.js' -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import { getSSLErrorHint } from '../services/api/errorUtils.js' import { getUserAgent } from './http.js' import { logError } from './log.js' diff --git a/src/utils/promptEditor.ts b/src/utils/promptEditor.ts index 36a2b7bcd..0b0a65241 100644 --- a/src/utils/promptEditor.ts +++ b/src/utils/promptEditor.ts @@ -3,7 +3,7 @@ import { formatPastedTextRef, getPastedTextRefNumLines, } from '../history.js' -import instances from '../ink/instances.js' +import { instances } from '@anthropic/ink' import type { PastedContent } from './config.js' import { classifyGuiEditor, getExternalEditor } from './editor.js' import { execSync_DEPRECATED } from './execSyncWrapper.js' diff --git a/src/utils/renderOptions.ts b/src/utils/renderOptions.ts index 37bb65050..c601b36a3 100644 --- a/src/utils/renderOptions.ts +++ b/src/utils/renderOptions.ts @@ -1,6 +1,6 @@ import { openSync } from 'fs' import { ReadStream } from 'tty' -import type { RenderOptions } from '../ink.js' +import type { RenderOptions } from '@anthropic/ink' import { isEnvTruthy } from './envUtils.js' import { logError } from './log.js' diff --git a/src/utils/sinks.ts b/src/utils/sinks.ts index 386ce6ae4..ba3999587 100644 --- a/src/utils/sinks.ts +++ b/src/utils/sinks.ts @@ -1,5 +1,6 @@ -import { initializeAnalyticsSink } from '../services/analytics/sink.js' + import { initializeErrorLogSink } from './errorLogSink.js' +import { initializeAnalyticsSink } from '../services/analytics/sink.js' /** * Attach error log and analytics sinks, draining any events queued before diff --git a/src/utils/sliceAnsi.ts b/src/utils/sliceAnsi.ts index 8e1a0a07f..565a5f8cd 100644 --- a/src/utils/sliceAnsi.ts +++ b/src/utils/sliceAnsi.ts @@ -5,7 +5,7 @@ import { tokenize, undoAnsiCodes, } from '@alcalzone/ansi-tokenize' -import { stringWidth } from '../ink/stringWidth.js' +import { stringWidth } from '@anthropic/ink' // A code is an "end code" if its code equals its endCode (e.g., hyperlink close) function isEndCode(code: AnsiCode): boolean { diff --git a/src/utils/staticRender.tsx b/src/utils/staticRender.tsx index 66a809c19..2f066fe96 100644 --- a/src/utils/staticRender.tsx +++ b/src/utils/staticRender.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useLayoutEffect } from 'react' import { PassThrough } from 'stream' import stripAnsi from 'strip-ansi' -import { render, useApp } from '../ink.js' +import { wrappedRender as render, useApp } from '@anthropic/ink' // This is a workaround for the fact that Ink doesn't support multiple // components in the same render tree. Instead of using a we just render diff --git a/src/utils/status.tsx b/src/utils/status.tsx index 6d66bc83f..5b4012fff 100644 --- a/src/utils/status.tsx +++ b/src/utils/status.tsx @@ -1,7 +1,7 @@ import chalk from 'chalk' import figures from 'figures' import * as React from 'react' -import { color, Text } from '../ink.js' +import { color, Text } from '@anthropic/ink' import type { MCPServerConnection } from '../services/mcp/types.js' import { getAccountInformation, isClaudeAISubscriber } from './auth.js' import { diff --git a/src/utils/statusNoticeDefinitions.tsx b/src/utils/statusNoticeDefinitions.tsx index e04f9e3d3..9ad173fd0 100644 --- a/src/utils/statusNoticeDefinitions.tsx +++ b/src/utils/statusNoticeDefinitions.tsx @@ -1,5 +1,5 @@ // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered -import { Box, Text } from '../ink.js' +import { Box, Text } from '@anthropic/ink' import * as React from 'react' import { getLargeMemoryFiles, diff --git a/src/utils/swarm/It2SetupPrompt.tsx b/src/utils/swarm/It2SetupPrompt.tsx index 688e2c8f4..166c984c7 100644 --- a/src/utils/swarm/It2SetupPrompt.tsx +++ b/src/utils/swarm/It2SetupPrompt.tsx @@ -3,11 +3,11 @@ import { type OptionWithDescription, Select, } from '../../components/CustomSelect/index.js' -import { Pane } from '../../components/design-system/Pane.js' +import { Pane } from '@anthropic/ink' import { Spinner } from '../../components/Spinner.js' import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- enter to proceed through setup steps -import { Box, Text, useInput } from '../../ink.js' +import { Box, Text, useInput } from '@anthropic/ink' import { useKeybinding } from '../../keybindings/useKeybinding.js' import { detectPythonPackageManager, diff --git a/src/utils/teleport.tsx b/src/utils/teleport.tsx index 04202dbc7..513196fe4 100644 --- a/src/utils/teleport.tsx +++ b/src/utils/teleport.tsx @@ -17,7 +17,7 @@ import { } from '../components/TeleportError.js' import { getOauthConfig } from '../constants/oauth.js' import type { SDKMessage } from '../entrypoints/agentSdkTypes.js' -import type { Root } from '../ink.js' +import type { Root } from '@anthropic/ink' import { KeybindingSetup } from '../keybindings/KeybindingProviderSetup.js' import { queryHaiku } from '../services/api/claude.js' import { diff --git a/src/utils/terminal.ts b/src/utils/terminal.ts index a87f020c8..a935f700c 100644 --- a/src/utils/terminal.ts +++ b/src/utils/terminal.ts @@ -1,6 +1,6 @@ import chalk from 'chalk' import { ctrlOToExpand } from '../components/CtrlOToExpand.js' -import { stringWidth } from '../ink/stringWidth.js' +import { stringWidth } from '@anthropic/ink' import sliceAnsi from './sliceAnsi.js' // Text rendering utilities for terminal display diff --git a/src/utils/terminalPanel.ts b/src/utils/terminalPanel.ts index 034d22bed..9cf56c554 100644 --- a/src/utils/terminalPanel.ts +++ b/src/utils/terminalPanel.ts @@ -17,7 +17,7 @@ import { spawn, spawnSync } from 'child_process' import { getSessionId } from '../bootstrap/state.js' -import instances from '../ink/instances.js' +import { instances } from '@anthropic/ink' import { registerCleanup } from './cleanupRegistry.js' import { pwd } from './cwd.js' import { logForDebugging } from './debug.js' diff --git a/src/utils/treeify.ts b/src/utils/treeify.ts index 28dc9d83b..3b270ad4d 100644 --- a/src/utils/treeify.ts +++ b/src/utils/treeify.ts @@ -1,5 +1,5 @@ import figures from 'figures' -import { color } from '../components/design-system/color.js' +import { color } from '@anthropic/ink' import type { Theme, ThemeName } from './theme.js' export type TreeNode = { diff --git a/src/utils/truncate.ts b/src/utils/truncate.ts index fa35c10e4..aff5c72ef 100644 --- a/src/utils/truncate.ts +++ b/src/utils/truncate.ts @@ -1,6 +1,6 @@ // Width-aware truncation/wrapping — needs ink/stringWidth (not leaf-safe). -import { stringWidth } from '../ink/stringWidth.js' +import { stringWidth } from '@anthropic/ink' import { getGraphemeSegmenter } from './intl.js' /**