nastech-tui 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +11 -0
- package/README.md +346 -0
- package/eslint.config.mjs +111 -0
- package/package.json +51 -0
- package/packages/nastech-ink/ambient.d.ts +83 -0
- package/packages/nastech-ink/index.d.ts +40 -0
- package/packages/nastech-ink/index.js +1 -0
- package/packages/nastech-ink/nastech-ink/ambient.d.ts +83 -0
- package/packages/nastech-ink/nastech-ink/index.d.ts +40 -0
- package/packages/nastech-ink/nastech-ink/index.js +1 -0
- package/packages/nastech-ink/nastech-ink/package.json +54 -0
- package/packages/nastech-ink/nastech-ink/src/bootstrap/state.ts +9 -0
- package/packages/nastech-ink/nastech-ink/src/entry-exports.ts +32 -0
- package/packages/nastech-ink/nastech-ink/src/hooks/use-stderr.ts +15 -0
- package/packages/nastech-ink/nastech-ink/src/hooks/use-stdout.ts +15 -0
- package/packages/nastech-ink/nastech-ink/src/ink/Ansi.tsx +435 -0
- package/packages/nastech-ink/nastech-ink/src/ink/app-mouse.test.ts +123 -0
- package/packages/nastech-ink/nastech-ink/src/ink/bidi.ts +145 -0
- package/packages/nastech-ink/nastech-ink/src/ink/cache-eviction.ts +45 -0
- package/packages/nastech-ink/nastech-ink/src/ink/clearTerminal.ts +68 -0
- package/packages/nastech-ink/nastech-ink/src/ink/colorize.test.ts +60 -0
- package/packages/nastech-ink/nastech-ink/src/ink/colorize.ts +277 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/App.tsx +830 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/AppContext.ts +20 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Box.tsx +294 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Button.tsx +236 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Link.tsx +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Newline.tsx +43 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Spacer.tsx +23 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/StdinContext.ts +25 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Text.test.ts +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Text.tsx +336 -0
- package/packages/nastech-ink/nastech-ink/src/ink/constants.ts +6 -0
- package/packages/nastech-ink/nastech-ink/src/ink/cursor.ts +5 -0
- package/packages/nastech-ink/nastech-ink/src/ink/devtools.ts +2 -0
- package/packages/nastech-ink/nastech-ink/src/ink/dom.ts +495 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/click-event.ts +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/dispatcher.ts +242 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/emitter.ts +40 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/event-handlers.ts +84 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/event.ts +11 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/focus-event.ts +18 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/input-event.ts +176 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/mouse-event.ts +18 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/paste-event.ts +10 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/resize-event.ts +12 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-event.ts +107 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
- package/packages/nastech-ink/nastech-ink/src/ink/focus.ts +219 -0
- package/packages/nastech-ink/nastech-ink/src/ink/frame.ts +124 -0
- package/packages/nastech-ink/nastech-ink/src/ink/get-max-width.ts +27 -0
- package/packages/nastech-ink/nastech-ink/src/ink/global.d.ts +1 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hit-test.test.ts +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hit-test.ts +224 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-app.ts +9 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-input.ts +95 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
- package/packages/nastech-ink/package.json +57 -0
- package/packages/nastech-ink/src/bootstrap/state.ts +9 -0
- package/packages/nastech-ink/src/entry-exports.ts +32 -0
- package/packages/nastech-ink/src/hooks/use-stderr.ts +15 -0
- package/packages/nastech-ink/src/hooks/use-stdout.ts +15 -0
- package/packages/nastech-ink/src/ink/Ansi.tsx +435 -0
- package/packages/nastech-ink/src/ink/app-mouse.test.ts +123 -0
- package/packages/nastech-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
- package/packages/nastech-ink/src/ink/bidi.ts +145 -0
- package/packages/nastech-ink/src/ink/cache-eviction.ts +45 -0
- package/packages/nastech-ink/src/ink/clearTerminal.ts +68 -0
- package/packages/nastech-ink/src/ink/colorize.test.ts +60 -0
- package/packages/nastech-ink/src/ink/colorize.ts +277 -0
- package/packages/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
- package/packages/nastech-ink/src/ink/components/App.tsx +855 -0
- package/packages/nastech-ink/src/ink/components/AppContext.ts +20 -0
- package/packages/nastech-ink/src/ink/components/Box.tsx +294 -0
- package/packages/nastech-ink/src/ink/components/Button.tsx +236 -0
- package/packages/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
- package/packages/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
- package/packages/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
- package/packages/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
- package/packages/nastech-ink/src/ink/components/Link.tsx +38 -0
- package/packages/nastech-ink/src/ink/components/Newline.tsx +43 -0
- package/packages/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
- package/packages/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
- package/packages/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
- package/packages/nastech-ink/src/ink/components/Spacer.tsx +23 -0
- package/packages/nastech-ink/src/ink/components/StdinContext.ts +25 -0
- package/packages/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
- package/packages/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
- package/packages/nastech-ink/src/ink/components/Text.test.ts +38 -0
- package/packages/nastech-ink/src/ink/components/Text.tsx +336 -0
- package/packages/nastech-ink/src/ink/constants.ts +6 -0
- package/packages/nastech-ink/src/ink/cursor.ts +5 -0
- package/packages/nastech-ink/src/ink/devtools.ts +2 -0
- package/packages/nastech-ink/src/ink/dom.ts +495 -0
- package/packages/nastech-ink/src/ink/events/click-event.ts +38 -0
- package/packages/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
- package/packages/nastech-ink/src/ink/events/dispatcher.ts +242 -0
- package/packages/nastech-ink/src/ink/events/emitter.ts +40 -0
- package/packages/nastech-ink/src/ink/events/event-handlers.ts +84 -0
- package/packages/nastech-ink/src/ink/events/event.ts +11 -0
- package/packages/nastech-ink/src/ink/events/focus-event.ts +18 -0
- package/packages/nastech-ink/src/ink/events/input-event.ts +176 -0
- package/packages/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
- package/packages/nastech-ink/src/ink/events/mouse-event.ts +18 -0
- package/packages/nastech-ink/src/ink/events/paste-event.ts +10 -0
- package/packages/nastech-ink/src/ink/events/resize-event.ts +12 -0
- package/packages/nastech-ink/src/ink/events/terminal-event.ts +107 -0
- package/packages/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
- package/packages/nastech-ink/src/ink/focus.ts +219 -0
- package/packages/nastech-ink/src/ink/frame.ts +124 -0
- package/packages/nastech-ink/src/ink/get-max-width.ts +27 -0
- package/packages/nastech-ink/src/ink/global.d.ts +1 -0
- package/packages/nastech-ink/src/ink/hit-test.test.ts +38 -0
- package/packages/nastech-ink/src/ink/hit-test.ts +224 -0
- package/packages/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
- package/packages/nastech-ink/src/ink/hooks/use-app.ts +9 -0
- package/packages/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
- package/packages/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
- package/packages/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
- package/packages/nastech-ink/src/ink/hooks/use-input.ts +95 -0
- package/packages/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
- package/packages/nastech-ink/src/ink/hooks/use-search-highlight.ts +56 -0
- package/packages/nastech-ink/src/ink/hooks/use-selection.ts +101 -0
- package/packages/nastech-ink/src/ink/hooks/use-stdin.ts +9 -0
- package/packages/nastech-ink/src/ink/hooks/use-tab-status.ts +71 -0
- package/packages/nastech-ink/src/ink/hooks/use-terminal-focus.ts +18 -0
- package/packages/nastech-ink/src/ink/hooks/use-terminal-title.ts +34 -0
- package/packages/nastech-ink/src/ink/hooks/use-terminal-viewport.ts +100 -0
- package/packages/nastech-ink/src/ink/hyperlinkHover.ts +52 -0
- package/packages/nastech-ink/src/ink/ink-cursor-advance.test.ts +234 -0
- package/packages/nastech-ink/src/ink/ink-resize.test.ts +50 -0
- package/packages/nastech-ink/src/ink/ink.tsx +2705 -0
- package/packages/nastech-ink/src/ink/instances.ts +10 -0
- package/packages/nastech-ink/src/ink/layout/engine.ts +6 -0
- package/packages/nastech-ink/src/ink/layout/geometry.ts +98 -0
- package/packages/nastech-ink/src/ink/layout/node.ts +145 -0
- package/packages/nastech-ink/src/ink/layout/yoga.ts +313 -0
- package/packages/nastech-ink/src/ink/line-width-cache.ts +38 -0
- package/packages/nastech-ink/src/ink/log-update.test.ts +223 -0
- package/packages/nastech-ink/src/ink/log-update.ts +752 -0
- package/packages/nastech-ink/src/ink/lru.ts +14 -0
- package/packages/nastech-ink/src/ink/measure-element.ts +23 -0
- package/packages/nastech-ink/src/ink/measure-text.ts +50 -0
- package/packages/nastech-ink/src/ink/node-cache.ts +53 -0
- package/packages/nastech-ink/src/ink/optimizer.ts +99 -0
- package/packages/nastech-ink/src/ink/output.ts +845 -0
- package/packages/nastech-ink/src/ink/parse-keypress.test.ts +133 -0
- package/packages/nastech-ink/src/ink/parse-keypress.ts +848 -0
- package/packages/nastech-ink/src/ink/reconciler.ts +382 -0
- package/packages/nastech-ink/src/ink/render-border.ts +206 -0
- package/packages/nastech-ink/src/ink/render-node-to-output.ts +1582 -0
- package/packages/nastech-ink/src/ink/render-to-screen.ts +236 -0
- package/packages/nastech-ink/src/ink/renderer.ts +169 -0
- package/packages/nastech-ink/src/ink/root.ts +204 -0
- package/packages/nastech-ink/src/ink/screen.ts +1590 -0
- package/packages/nastech-ink/src/ink/searchHighlight.ts +91 -0
- package/packages/nastech-ink/src/ink/selection.test.ts +82 -0
- package/packages/nastech-ink/src/ink/selection.ts +1143 -0
- package/packages/nastech-ink/src/ink/squash-text-nodes.ts +74 -0
- package/packages/nastech-ink/src/ink/stringWidth.ts +341 -0
- package/packages/nastech-ink/src/ink/styles.ts +750 -0
- package/packages/nastech-ink/src/ink/supports-hyperlinks.ts +51 -0
- package/packages/nastech-ink/src/ink/tabstops.ts +44 -0
- package/packages/nastech-ink/src/ink/terminal-focus-state.ts +52 -0
- package/packages/nastech-ink/src/ink/terminal-querier.ts +222 -0
- package/packages/nastech-ink/src/ink/terminal.test.ts +15 -0
- package/packages/nastech-ink/src/ink/terminal.ts +299 -0
- package/packages/nastech-ink/src/ink/termio/ansi.ts +75 -0
- package/packages/nastech-ink/src/ink/termio/csi.ts +334 -0
- package/packages/nastech-ink/src/ink/termio/dec.ts +99 -0
- package/packages/nastech-ink/src/ink/termio/esc.ts +69 -0
- package/packages/nastech-ink/src/ink/termio/osc.test.ts +191 -0
- package/packages/nastech-ink/src/ink/termio/osc.ts +724 -0
- package/packages/nastech-ink/src/ink/termio/parser.ts +467 -0
- package/packages/nastech-ink/src/ink/termio/sgr.ts +362 -0
- package/packages/nastech-ink/src/ink/termio/tokenize.test.ts +185 -0
- package/packages/nastech-ink/src/ink/termio/tokenize.ts +350 -0
- package/packages/nastech-ink/src/ink/termio/types.ts +230 -0
- package/packages/nastech-ink/src/ink/termio.ts +42 -0
- package/packages/nastech-ink/src/ink/useTerminalNotification.ts +110 -0
- package/packages/nastech-ink/src/ink/warn.ts +15 -0
- package/packages/nastech-ink/src/ink/widest-line.ts +22 -0
- package/packages/nastech-ink/src/ink/wrap-text.test.ts +17 -0
- package/packages/nastech-ink/src/ink/wrap-text.ts +144 -0
- package/packages/nastech-ink/src/ink/wrapAnsi.ts +13 -0
- package/packages/nastech-ink/src/native-ts/yoga-layout/enums.ts +112 -0
- package/packages/nastech-ink/src/native-ts/yoga-layout/index.ts +2326 -0
- package/packages/nastech-ink/src/utils/debug.ts +6 -0
- package/packages/nastech-ink/src/utils/earlyInput.ts +131 -0
- package/packages/nastech-ink/src/utils/env.ts +66 -0
- package/packages/nastech-ink/src/utils/envUtils.ts +13 -0
- package/packages/nastech-ink/src/utils/execFileNoThrow.test.ts +146 -0
- package/packages/nastech-ink/src/utils/execFileNoThrow.ts +115 -0
- package/packages/nastech-ink/src/utils/fullscreen.ts +3 -0
- package/packages/nastech-ink/src/utils/intl.ts +87 -0
- package/packages/nastech-ink/src/utils/log.ts +7 -0
- package/packages/nastech-ink/src/utils/semver.ts +57 -0
- package/packages/nastech-ink/src/utils/sliceAnsi.ts +106 -0
- package/packages/nastech-ink/text-input.d.ts +2 -0
- package/packages/nastech-ink/text-input.js +1 -0
- package/scripts/build.mjs +61 -0
- package/scripts/profile-tui.mjs +121 -0
- package/src/__tests__/activeSessionSwitcher.test.ts +157 -0
- package/src/__tests__/appChromeStatusRule.test.tsx +84 -0
- package/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
- package/src/__tests__/approvalAction.test.ts +50 -0
- package/src/__tests__/asCommandDispatch.test.ts +27 -0
- package/src/__tests__/blockLayout.test.ts +122 -0
- package/src/__tests__/clipboard.test.ts +369 -0
- package/src/__tests__/constants.test.ts +53 -0
- package/src/__tests__/createGatewayEventHandler.test.ts +1091 -0
- package/src/__tests__/createSlashHandler.test.ts +822 -0
- package/src/__tests__/creditsCommand.test.ts +144 -0
- package/src/__tests__/cursorDriftRegression.test.ts +114 -0
- package/src/__tests__/details.test.ts +115 -0
- package/src/__tests__/emoji.test.ts +64 -0
- package/src/__tests__/externalLink.test.ts +144 -0
- package/src/__tests__/forceTruecolor.test.ts +191 -0
- package/src/__tests__/gatewayClient.test.ts +394 -0
- package/src/__tests__/gatewayRecovery.test.ts +47 -0
- package/src/__tests__/markdown.test.ts +331 -0
- package/src/__tests__/mathUnicode.test.ts +293 -0
- package/src/__tests__/memoryMonitor.test.ts +102 -0
- package/src/__tests__/messageLine.test.ts +19 -0
- package/src/__tests__/messages.test.ts +92 -0
- package/src/__tests__/orchestratorPromptSession.test.ts +64 -0
- package/src/__tests__/osc52.test.ts +67 -0
- package/src/__tests__/parentLog.test.ts +75 -0
- package/src/__tests__/paths.test.ts +70 -0
- package/src/__tests__/platform.test.ts +556 -0
- package/src/__tests__/precisionWheel.test.ts +44 -0
- package/src/__tests__/prompt.test.ts +31 -0
- package/src/__tests__/providers.test.ts +65 -0
- package/src/__tests__/reasoning.test.ts +76 -0
- package/src/__tests__/rpc.test.ts +27 -0
- package/src/__tests__/scroll.test.ts +99 -0
- package/src/__tests__/slashParity.test.ts +123 -0
- package/src/__tests__/spawnHistoryStore.test.ts +46 -0
- package/src/__tests__/stateIsolation.test.ts +46 -0
- package/src/__tests__/statusBarTicker.test.ts +18 -0
- package/src/__tests__/statusRule.test.ts +32 -0
- package/src/__tests__/streamingMarkdown.test.ts +121 -0
- package/src/__tests__/subagentTree.test.ts +407 -0
- package/src/__tests__/syntax.test.ts +45 -0
- package/src/__tests__/terminalModes.test.ts +39 -0
- package/src/__tests__/terminalParity.test.ts +77 -0
- package/src/__tests__/terminalSetup.test.ts +386 -0
- package/src/__tests__/termux.test.ts +35 -0
- package/src/__tests__/termuxComposerLayout.test.ts +40 -0
- package/src/__tests__/text.test.ts +233 -0
- package/src/__tests__/textInputBurstInput.test.ts +40 -0
- package/src/__tests__/textInputCursorSourceOfTruth.test.ts +50 -0
- package/src/__tests__/textInputFastEcho.test.ts +200 -0
- package/src/__tests__/textInputLineNav.test.ts +55 -0
- package/src/__tests__/textInputPassThrough.test.ts +59 -0
- package/src/__tests__/textInputRightClick.test.ts +48 -0
- package/src/__tests__/textInputWrap.test.ts +151 -0
- package/src/__tests__/theme.test.ts +311 -0
- package/src/__tests__/turnControllerNotice.test.ts +43 -0
- package/src/__tests__/turnStore.test.ts +66 -0
- package/src/__tests__/useCompletion.test.ts +35 -0
- package/src/__tests__/useComposerState.test.ts +59 -0
- package/src/__tests__/useConfigSync.test.ts +460 -0
- package/src/__tests__/useInputHandlers.test.ts +77 -0
- package/src/__tests__/useQueue.test.ts +28 -0
- package/src/__tests__/useSessionLifecycle.test.ts +60 -0
- package/src/__tests__/useVirtualHistoryHeights.test.ts +39 -0
- package/src/__tests__/viewport.test.ts +58 -0
- package/src/__tests__/viewportStore.test.ts +85 -0
- package/src/__tests__/virtualHeights.test.ts +96 -0
- package/src/__tests__/virtualHistoryClamp.test.ts +19 -0
- package/src/__tests__/virtualHistoryOffsetCache.test.ts +282 -0
- package/src/__tests__/wheelAccel.test.ts +138 -0
- package/src/app/createGatewayEventHandler.ts +833 -0
- package/src/app/createSlashHandler.ts +130 -0
- package/src/app/delegationStore.ts +77 -0
- package/src/app/gatewayContext.tsx +19 -0
- package/src/app/gatewayRecovery.ts +35 -0
- package/src/app/inputSelectionStore.ts +15 -0
- package/src/app/interfaces.ts +394 -0
- package/src/app/overlayStore.ts +53 -0
- package/src/app/scroll.ts +71 -0
- package/src/app/setupHandoff.ts +54 -0
- package/src/app/slash/commands/core.ts +648 -0
- package/src/app/slash/commands/credits.ts +57 -0
- package/src/app/slash/commands/debug.ts +48 -0
- package/src/app/slash/commands/ops.ts +717 -0
- package/src/app/slash/commands/session.ts +554 -0
- package/src/app/slash/commands/setup.ts +20 -0
- package/src/app/slash/registry.ts +20 -0
- package/src/app/slash/types.ts +21 -0
- package/src/app/spawnHistoryStore.ts +159 -0
- package/src/app/turnController.ts +866 -0
- package/src/app/turnStore.ts +85 -0
- package/src/app/uiStore.ts +44 -0
- package/src/app/useComposerState.ts +367 -0
- package/src/app/useConfigSync.ts +288 -0
- package/src/app/useInputHandlers.ts +576 -0
- package/src/app/useLongRunToolCharms.ts +69 -0
- package/src/app/useMainApp.ts +1039 -0
- package/src/app/useSessionLifecycle.ts +366 -0
- package/src/app/useSubmission.ts +429 -0
- package/src/app.tsx +25 -0
- package/src/banner.ts +93 -0
- package/src/components/activeSessionSwitcher.tsx +635 -0
- package/src/components/agentsOverlay.tsx +1073 -0
- package/src/components/appChrome.tsx +554 -0
- package/src/components/appLayout.tsx +444 -0
- package/src/components/appOverlays.tsx +254 -0
- package/src/components/branding.tsx +466 -0
- package/src/components/fpsOverlay.tsx +30 -0
- package/src/components/helpHint.tsx +73 -0
- package/src/components/markdown.tsx +1119 -0
- package/src/components/maskedPrompt.tsx +34 -0
- package/src/components/messageLine.tsx +237 -0
- package/src/components/modelPicker.tsx +527 -0
- package/src/components/overlayControls.tsx +50 -0
- package/src/components/pluginsHub.tsx +238 -0
- package/src/components/prompts.tsx +276 -0
- package/src/components/queuedMessages.tsx +64 -0
- package/src/components/sessionPicker.tsx +227 -0
- package/src/components/skillsHub.tsx +308 -0
- package/src/components/streamingAssistant.tsx +110 -0
- package/src/components/streamingMarkdown.tsx +174 -0
- package/src/components/textInput.tsx +1340 -0
- package/src/components/themed.tsx +30 -0
- package/src/components/thinking.tsx +1224 -0
- package/src/components/todoPanel.tsx +93 -0
- package/src/config/env.ts +64 -0
- package/src/config/limits.ts +13 -0
- package/src/config/timing.ts +6 -0
- package/src/content/charms.ts +1 -0
- package/src/content/faces.ts +17 -0
- package/src/content/fortunes.ts +30 -0
- package/src/content/hotkeys.ts +37 -0
- package/src/content/placeholders.ts +13 -0
- package/src/content/setup.ts +17 -0
- package/src/content/verbs.ts +38 -0
- package/src/domain/blockLayout.ts +146 -0
- package/src/domain/details.ts +76 -0
- package/src/domain/messages.ts +91 -0
- package/src/domain/paths.ts +16 -0
- package/src/domain/providers.ts +11 -0
- package/src/domain/roles.ts +9 -0
- package/src/domain/slash.ts +10 -0
- package/src/domain/usage.ts +3 -0
- package/src/domain/viewport.ts +51 -0
- package/src/entry.tsx +104 -0
- package/src/gatewayClient.ts +730 -0
- package/src/gatewayTypes.ts +568 -0
- package/src/hooks/useCompletion.ts +112 -0
- package/src/hooks/useGitBranch.ts +72 -0
- package/src/hooks/useInputHistory.ts +11 -0
- package/src/hooks/useQueue.ts +76 -0
- package/src/hooks/useVirtualHistory.ts +554 -0
- package/src/lib/circularBuffer.ts +48 -0
- package/src/lib/clipboard.ts +182 -0
- package/src/lib/editor.test.ts +74 -0
- package/src/lib/editor.ts +47 -0
- package/src/lib/emoji.ts +55 -0
- package/src/lib/externalCli.ts +16 -0
- package/src/lib/externalLink.ts +435 -0
- package/src/lib/forceTruecolor.ts +60 -0
- package/src/lib/fpsStore.ts +51 -0
- package/src/lib/fuzzy.test.ts +109 -0
- package/src/lib/fuzzy.ts +177 -0
- package/src/lib/gracefulExit.ts +47 -0
- package/src/lib/history.ts +82 -0
- package/src/lib/inputMetrics.ts +203 -0
- package/src/lib/liveProgress.test.ts +116 -0
- package/src/lib/liveProgress.ts +79 -0
- package/src/lib/mathUnicode.ts +770 -0
- package/src/lib/memory.test.ts +155 -0
- package/src/lib/memory.ts +188 -0
- package/src/lib/memoryMonitor.ts +109 -0
- package/src/lib/messages.test.ts +29 -0
- package/src/lib/messages.ts +8 -0
- package/src/lib/openExternalUrl.test.ts +217 -0
- package/src/lib/openExternalUrl.ts +158 -0
- package/src/lib/osc52.ts +73 -0
- package/src/lib/parentLog.ts +57 -0
- package/src/lib/perfPane.tsx +107 -0
- package/src/lib/platform.ts +409 -0
- package/src/lib/precisionWheel.ts +48 -0
- package/src/lib/prompt.ts +35 -0
- package/src/lib/reasoning.ts +55 -0
- package/src/lib/rpc.ts +41 -0
- package/src/lib/subagentTree.ts +355 -0
- package/src/lib/syntax.ts +117 -0
- package/src/lib/terminalModes.ts +51 -0
- package/src/lib/terminalParity.ts +78 -0
- package/src/lib/terminalSetup.ts +444 -0
- package/src/lib/termux.ts +29 -0
- package/src/lib/text.test.ts +18 -0
- package/src/lib/text.ts +339 -0
- package/src/lib/todo.test.ts +21 -0
- package/src/lib/todo.ts +9 -0
- package/src/lib/viewportStore.ts +124 -0
- package/src/lib/virtualHeights.ts +145 -0
- package/src/lib/wheelAccel.ts +190 -0
- package/src/protocol/interpolation.ts +3 -0
- package/src/protocol/paste.ts +1 -0
- package/src/theme.ts +589 -0
- package/src/types/nastech-ink.d.ts +176 -0
- package/src/types.ts +212 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { isIP } from 'node:net'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
const titleCache = new Map<string, string>()
|
|
6
|
+
const titleInflight = new Map<string, Promise<string>>()
|
|
7
|
+
const titleSubs = new Map<string, Set<(value: string) => void>>()
|
|
8
|
+
|
|
9
|
+
const TITLE_CACHE_LIMIT = 500
|
|
10
|
+
const TITLE_MAX_LENGTH = 240
|
|
11
|
+
const TITLE_BYTE_BUDGET = 96 * 1024
|
|
12
|
+
const TITLE_TIMEOUT_MS = 5000
|
|
13
|
+
|
|
14
|
+
const TITLE_USER_AGENT =
|
|
15
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36'
|
|
16
|
+
|
|
17
|
+
const TITLE_ERROR_RE =
|
|
18
|
+
/\b(?:access denied|attention required|captcha|error|forbidden|just a moment|request blocked|too many requests)\b/i
|
|
19
|
+
|
|
20
|
+
const DOMAIN_RE = /^(?:www\.)?[a-z0-9](?:[a-z0-9-]*\.)+[a-z]{2,}(?::\d+)?(?:[/?#][^\s]*)?$/i
|
|
21
|
+
const SKIP_PROTO_RE = /^(?:file|data|mailto|javascript|blob|chrome|about|nastech):/i
|
|
22
|
+
const LOCAL_HOSTNAME_RE = /^(?:localhost|localhost\.localdomain)$/i
|
|
23
|
+
const LOCAL_HOST_SUFFIXES = ['.corp', '.home', '.internal', '.lan', '.local', '.localdomain']
|
|
24
|
+
const STATUS_PERMALINK_HOST_RE = /^(?:mobile\.)?(?:x|twitter)\.com$/i
|
|
25
|
+
const STATUS_PERMALINK_PATH_RE = /^\/[^/]+\/status\/\d+\/?$/i
|
|
26
|
+
|
|
27
|
+
const HTML_ENTITIES: Record<string, string> = {
|
|
28
|
+
'#39': "'",
|
|
29
|
+
amp: '&',
|
|
30
|
+
apos: "'",
|
|
31
|
+
gt: '>',
|
|
32
|
+
lt: '<',
|
|
33
|
+
nbsp: ' ',
|
|
34
|
+
quot: '"'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function normalizeExternalUrl(value: string): string {
|
|
38
|
+
const trimmed = value.trim()
|
|
39
|
+
|
|
40
|
+
if (!trimmed || /^https?:\/\//i.test(trimmed)) {
|
|
41
|
+
return trimmed
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return DOMAIN_RE.test(trimmed) ? `https://${trimmed}` : trimmed
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseUrl(value: string): null | URL {
|
|
48
|
+
try {
|
|
49
|
+
return new URL(normalizeExternalUrl(value))
|
|
50
|
+
} catch {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function titleCacheKey(value: string): string {
|
|
56
|
+
const url = parseUrl(value)
|
|
57
|
+
|
|
58
|
+
if (!url) {
|
|
59
|
+
return normalizeExternalUrl(value)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const host = url.hostname.replace(/^www\./i, '').toLowerCase()
|
|
63
|
+
const pathname = url.pathname === '/' ? '/' : url.pathname.replace(/\/+$/, '') || '/'
|
|
64
|
+
|
|
65
|
+
return `${host}${pathname}${url.search || ''}`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function cacheTitle(key: string, title: string): void {
|
|
69
|
+
if (titleCache.size >= TITLE_CACHE_LIMIT) {
|
|
70
|
+
titleCache.delete(titleCache.keys().next().value as string)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
titleCache.set(key, title)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function hostPathLabel(value: string): string {
|
|
77
|
+
const url = parseUrl(value)
|
|
78
|
+
|
|
79
|
+
if (!url) {
|
|
80
|
+
return value
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const host = url.hostname.replace(/^www\./, '')
|
|
84
|
+
const path = url.pathname && url.pathname !== '/' ? url.pathname.replace(/\/$/, '') : ''
|
|
85
|
+
|
|
86
|
+
return `${host}${path}`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function cleanSlug(segment: string): string {
|
|
90
|
+
try {
|
|
91
|
+
return decodeURIComponent(segment)
|
|
92
|
+
.replace(/\.a\d+\..*$/i, '')
|
|
93
|
+
.replace(/\.(?:html?|php|aspx?)$/i, '')
|
|
94
|
+
.replace(/(?:[-_.](?:[a-z]{1,3}\d{2,}|i\d{2,}))+$/i, '')
|
|
95
|
+
.replace(/[_-]+/g, ' ')
|
|
96
|
+
.replace(/\s+/g, ' ')
|
|
97
|
+
.trim()
|
|
98
|
+
} catch {
|
|
99
|
+
return ''
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function urlSlugTitleLabel(value: string): string {
|
|
104
|
+
const url = parseUrl(value)
|
|
105
|
+
|
|
106
|
+
if (url && STATUS_PERMALINK_HOST_RE.test(url.hostname) && STATUS_PERMALINK_PATH_RE.test(url.pathname)) {
|
|
107
|
+
return hostPathLabel(value)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const segment of url?.pathname.split('/').filter(Boolean).reverse() ?? []) {
|
|
111
|
+
const cleaned = cleanSlug(segment)
|
|
112
|
+
|
|
113
|
+
if (!cleaned || !/[a-z]/i.test(cleaned)) {
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (/^(?:[a-z]{1,3}\d+|\d+)$/i.test(cleaned.replace(/\s+/g, ''))) {
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const titled = cleaned.replace(/\b[a-z]/g, c => c.toUpperCase())
|
|
122
|
+
|
|
123
|
+
if (titled.length >= 4) {
|
|
124
|
+
return titled
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return hostPathLabel(value)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function parseIpv4Octets(value: string): null | [number, number, number, number] {
|
|
132
|
+
const parts = value.split('.')
|
|
133
|
+
|
|
134
|
+
if (parts.length !== 4) {
|
|
135
|
+
return null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const octets: number[] = []
|
|
139
|
+
|
|
140
|
+
for (const part of parts) {
|
|
141
|
+
if (!/^\d{1,3}$/.test(part)) {
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const next = Number(part)
|
|
146
|
+
|
|
147
|
+
if (!Number.isInteger(next) || next < 0 || next > 255) {
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
octets.push(next)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return [octets[0]!, octets[1]!, octets[2]!, octets[3]!]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isPrivateIpv4(value: string): boolean {
|
|
158
|
+
const octets = parseIpv4Octets(value)
|
|
159
|
+
|
|
160
|
+
if (!octets) {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const [a, b] = octets
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
a === 0 ||
|
|
168
|
+
a === 10 ||
|
|
169
|
+
a === 127 ||
|
|
170
|
+
a === 255 ||
|
|
171
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
172
|
+
(a === 169 && b === 254) ||
|
|
173
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
174
|
+
(a === 192 && b === 168) ||
|
|
175
|
+
(a === 198 && (b === 18 || b === 19))
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isPrivateIpv6(value: string): boolean {
|
|
180
|
+
const normalized = value.toLowerCase()
|
|
181
|
+
|
|
182
|
+
if (normalized === '::' || normalized === '::1') {
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (normalized.startsWith('fc') || normalized.startsWith('fd')) {
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (normalized.startsWith('fe8') || normalized.startsWith('fe9') || normalized.startsWith('fea') || normalized.startsWith('feb')) {
|
|
191
|
+
return true
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (normalized.startsWith('::ffff:')) {
|
|
195
|
+
return isPrivateIpv4(normalized.slice('::ffff:'.length))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function normalizeHostname(value: string): string {
|
|
202
|
+
const withoutBrackets = value.replace(/^\[/, '').replace(/\]$/, '')
|
|
203
|
+
const withoutZoneId = withoutBrackets.split('%', 1)[0]!
|
|
204
|
+
|
|
205
|
+
return withoutZoneId.replace(/\.$/, '').toLowerCase()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isPrivateOrLocalHost(hostname: string): boolean {
|
|
209
|
+
const normalized = normalizeHostname(hostname)
|
|
210
|
+
|
|
211
|
+
if (!normalized) {
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (LOCAL_HOSTNAME_RE.test(normalized)) {
|
|
216
|
+
return true
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (LOCAL_HOST_SUFFIXES.some(suffix => normalized.endsWith(suffix))) {
|
|
220
|
+
return true
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const ipVersion = isIP(normalized)
|
|
224
|
+
|
|
225
|
+
if (ipVersion === 4) {
|
|
226
|
+
return isPrivateIpv4(normalized)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (ipVersion === 6) {
|
|
230
|
+
return isPrivateIpv6(normalized)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Single-label hostnames are usually LAN names or enterprise intranet aliases.
|
|
234
|
+
return !normalized.includes('.')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function isTitleFetchable(value: string): boolean {
|
|
238
|
+
if (!value || SKIP_PROTO_RE.test(value)) {
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const url = parseUrl(value)
|
|
243
|
+
|
|
244
|
+
return Boolean(url && /^https?:$/.test(url.protocol) && !isPrivateOrLocalHost(url.hostname))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function decodeHtmlEntities(value: string): string {
|
|
248
|
+
return value
|
|
249
|
+
.replace(/&(amp|lt|gt|quot|apos|nbsp|#39);/gi, (_match, key: string) => HTML_ENTITIES[key.toLowerCase()] ?? '')
|
|
250
|
+
.replace(/&#x([0-9a-f]+);/gi, (_match, hex: string) => String.fromCodePoint(parseInt(hex, 16) || 32))
|
|
251
|
+
.replace(/&#(\d+);/g, (_match, decimal: string) => String.fromCodePoint(parseInt(decimal, 10) || 32))
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function parseHtmlTitle(html: string): string {
|
|
255
|
+
const raw = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]
|
|
256
|
+
|
|
257
|
+
return raw ? decodeHtmlEntities(raw).replace(/\s+/g, ' ').trim() : ''
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function readResponseSnippet(response: Response): Promise<string> {
|
|
261
|
+
const reader = response.body?.getReader()
|
|
262
|
+
|
|
263
|
+
if (!reader) {
|
|
264
|
+
return (await response.text()).slice(0, TITLE_BYTE_BUDGET)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const chunks: Uint8Array[] = []
|
|
268
|
+
let done = false
|
|
269
|
+
let bytes = 0
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
while (bytes < TITLE_BYTE_BUDGET) {
|
|
273
|
+
const chunk = await reader.read()
|
|
274
|
+
|
|
275
|
+
if (chunk.done) {
|
|
276
|
+
done = true
|
|
277
|
+
|
|
278
|
+
break
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const value = chunk.value
|
|
282
|
+
|
|
283
|
+
if (!value?.length) {
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const remaining = TITLE_BYTE_BUDGET - bytes
|
|
288
|
+
const next = value.length > remaining ? value.subarray(0, remaining) : value
|
|
289
|
+
|
|
290
|
+
chunks.push(next)
|
|
291
|
+
bytes += next.length
|
|
292
|
+
|
|
293
|
+
if (next.length < value.length) {
|
|
294
|
+
break
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
return ''
|
|
299
|
+
} finally {
|
|
300
|
+
if (!done) {
|
|
301
|
+
try {
|
|
302
|
+
await reader.cancel()
|
|
303
|
+
} catch {
|
|
304
|
+
// Ignore stream teardown failures.
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!chunks.length) {
|
|
310
|
+
return ''
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const joined = new Uint8Array(bytes)
|
|
314
|
+
let offset = 0
|
|
315
|
+
|
|
316
|
+
for (const chunk of chunks) {
|
|
317
|
+
joined.set(chunk, offset)
|
|
318
|
+
offset += chunk.length
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return new TextDecoder().decode(joined)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function usableTitle(value: string): string {
|
|
325
|
+
const clean = value.replace(/\s+/g, ' ').trim()
|
|
326
|
+
|
|
327
|
+
return clean && !TITLE_ERROR_RE.test(clean) ? clean : ''
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function fetchHtmlTitle(normalizedUrl: string): Promise<string> {
|
|
331
|
+
const controller = new AbortController()
|
|
332
|
+
const timeout = setTimeout(() => controller.abort(), TITLE_TIMEOUT_MS)
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
const response = await fetch(normalizedUrl, {
|
|
336
|
+
headers: {
|
|
337
|
+
Accept: 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.5',
|
|
338
|
+
'Accept-Language': 'en-US,en;q=0.7',
|
|
339
|
+
'User-Agent': TITLE_USER_AGENT
|
|
340
|
+
},
|
|
341
|
+
redirect: 'follow',
|
|
342
|
+
signal: controller.signal
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
if (!response.ok) {
|
|
346
|
+
return ''
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const contentType = response.headers.get('content-type')
|
|
350
|
+
|
|
351
|
+
if (contentType && !/(?:html|xml|text\/html)/i.test(contentType)) {
|
|
352
|
+
return ''
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const html = await readResponseSnippet(response)
|
|
356
|
+
|
|
357
|
+
return parseHtmlTitle(html).slice(0, TITLE_MAX_LENGTH)
|
|
358
|
+
} catch {
|
|
359
|
+
return ''
|
|
360
|
+
} finally {
|
|
361
|
+
clearTimeout(timeout)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function fetchLinkTitle(url: string): Promise<string> {
|
|
366
|
+
const normalizedUrl = normalizeExternalUrl(url)
|
|
367
|
+
const key = titleCacheKey(normalizedUrl)
|
|
368
|
+
|
|
369
|
+
if (!isTitleFetchable(normalizedUrl)) {
|
|
370
|
+
return Promise.resolve('')
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (titleCache.has(key)) {
|
|
374
|
+
return Promise.resolve(titleCache.get(key) ?? '')
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const pending = titleInflight.get(key)
|
|
378
|
+
|
|
379
|
+
if (pending) {
|
|
380
|
+
return pending
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const promise = fetchHtmlTitle(normalizedUrl)
|
|
384
|
+
.then(usableTitle)
|
|
385
|
+
.catch(() => '')
|
|
386
|
+
.then(clean => {
|
|
387
|
+
cacheTitle(key, clean)
|
|
388
|
+
titleSubs.get(key)?.forEach(sub => sub(clean))
|
|
389
|
+
|
|
390
|
+
return clean
|
|
391
|
+
})
|
|
392
|
+
.finally(() => {
|
|
393
|
+
titleInflight.delete(key)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
titleInflight.set(key, promise)
|
|
397
|
+
|
|
398
|
+
return promise
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function useLinkTitle(url?: null | string): string {
|
|
402
|
+
const normalizedUrl = useMemo(() => (url ? normalizeExternalUrl(url) : ''), [url])
|
|
403
|
+
const key = useMemo(() => (normalizedUrl ? titleCacheKey(normalizedUrl) : ''), [normalizedUrl])
|
|
404
|
+
const [title, setTitle] = useState(() => (key ? (titleCache.get(key) ?? '') : ''))
|
|
405
|
+
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
setTitle(key ? (titleCache.get(key) ?? '') : '')
|
|
408
|
+
|
|
409
|
+
if (!key || !isTitleFetchable(normalizedUrl)) {
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const subs = titleSubs.get(key) ?? new Set<(value: string) => void>()
|
|
414
|
+
|
|
415
|
+
subs.add(setTitle)
|
|
416
|
+
titleSubs.set(key, subs)
|
|
417
|
+
void fetchLinkTitle(normalizedUrl)
|
|
418
|
+
|
|
419
|
+
return () => {
|
|
420
|
+
subs.delete(setTitle)
|
|
421
|
+
|
|
422
|
+
if (!subs.size) {
|
|
423
|
+
titleSubs.delete(key)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}, [key, normalizedUrl])
|
|
427
|
+
|
|
428
|
+
return title
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function __resetLinkTitleCache(): void {
|
|
432
|
+
titleCache.clear()
|
|
433
|
+
titleInflight.clear()
|
|
434
|
+
titleSubs.clear()
|
|
435
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Targeted 24-bit truecolor override before chalk / supports-color imports.
|
|
3
|
+
*
|
|
4
|
+
* macOS Terminal.app before Tahoe 26 does not support RGB SGR, so do not
|
|
5
|
+
* infer truecolor from TERM_PROGRAM=Apple_Terminal. Users can still opt in
|
|
6
|
+
* explicitly on terminals that support RGB but do not advertise COLORTERM.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const TRUE_RE = /^(?:1|true|yes|on)$/i
|
|
10
|
+
const FALSE_RE = /^(?:0|false|no|off)$/i
|
|
11
|
+
|
|
12
|
+
export function shouldForceTruecolor(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
13
|
+
const override = (env.NASTECH_TUI_TRUECOLOR ?? '').trim()
|
|
14
|
+
|
|
15
|
+
if (FALSE_RE.test(override) || 'NO_COLOR' in env) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return TRUE_RE.test(override)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isAppleTerminal = (env: NodeJS.ProcessEnv = process.env) => (env.TERM_PROGRAM ?? '').trim() === 'Apple_Terminal'
|
|
23
|
+
|
|
24
|
+
const isAdvertisedTruecolor = (env: NodeJS.ProcessEnv = process.env) => {
|
|
25
|
+
const colorTerm = (env.COLORTERM ?? '').trim().toLowerCase()
|
|
26
|
+
const forceColor = (env.FORCE_COLOR ?? '').trim()
|
|
27
|
+
|
|
28
|
+
return colorTerm === 'truecolor' || colorTerm === '24bit' || forceColor === '3'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function shouldDowngradeAppleTerminalTruecolor(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
32
|
+
if (!isAppleTerminal(env)) {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (shouldForceTruecolor(env)) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return isAdvertisedTruecolor(env)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (shouldForceTruecolor()) {
|
|
44
|
+
if (!process.env.COLORTERM) {
|
|
45
|
+
process.env.COLORTERM = 'truecolor'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
process.env.FORCE_COLOR = '3'
|
|
49
|
+
} else if (shouldDowngradeAppleTerminalTruecolor()) {
|
|
50
|
+
// Terminal.app may advertise truecolor even when RGB SGR paths render
|
|
51
|
+
// incorrectly. Keep NasTech on the safer TERM-driven 256-color path unless
|
|
52
|
+
// users explicitly opt back in via NASTECH_TUI_TRUECOLOR=1.
|
|
53
|
+
delete process.env.COLORTERM
|
|
54
|
+
|
|
55
|
+
if ((process.env.FORCE_COLOR ?? '').trim() === '3') {
|
|
56
|
+
delete process.env.FORCE_COLOR
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export {}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Tiny FPS tracker fed by ink's onFrame callback. Each entry is an Ink
|
|
2
|
+
// frame (React commit + drain-only frames) — the right notion for
|
|
3
|
+
// user-perceived motion.
|
|
4
|
+
//
|
|
5
|
+
// Zero-cost when NASTECH_TUI_FPS is unset: trackFrame is undefined so the
|
|
6
|
+
// onFrame callback short-circuits at the optional chain.
|
|
7
|
+
|
|
8
|
+
import { atom } from 'nanostores'
|
|
9
|
+
|
|
10
|
+
import { SHOW_FPS } from '../config/env.js'
|
|
11
|
+
|
|
12
|
+
const WINDOW_SIZE = 30
|
|
13
|
+
|
|
14
|
+
export type FpsState = {
|
|
15
|
+
fps: number
|
|
16
|
+
/** Wraps at JS-safe int — diff pairs in a debug overlay safely. */
|
|
17
|
+
totalFrames: number
|
|
18
|
+
/** Ink render-phase total for the last frame. */
|
|
19
|
+
lastDurationMs: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const $fpsState = atom<FpsState>({ fps: 0, lastDurationMs: 0, totalFrames: 0 })
|
|
23
|
+
|
|
24
|
+
const timestamps: number[] = []
|
|
25
|
+
let totalFrames = 0
|
|
26
|
+
|
|
27
|
+
export const trackFrame = SHOW_FPS
|
|
28
|
+
? (durationMs: number) => {
|
|
29
|
+
timestamps.push(performance.now())
|
|
30
|
+
|
|
31
|
+
if (timestamps.length > WINDOW_SIZE) {
|
|
32
|
+
timestamps.shift()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
totalFrames++
|
|
36
|
+
|
|
37
|
+
if (timestamps.length < 2) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const elapsed = (timestamps[timestamps.length - 1]! - timestamps[0]!) / 1000
|
|
42
|
+
|
|
43
|
+
if (elapsed > 0) {
|
|
44
|
+
$fpsState.set({
|
|
45
|
+
fps: Math.round(((timestamps.length - 1) / elapsed) * 10) / 10,
|
|
46
|
+
lastDurationMs: Math.round(durationMs * 100) / 100,
|
|
47
|
+
totalFrames
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
: undefined
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { fuzzyRank, fuzzyScore, fuzzyScoreMulti } from './fuzzy.js'
|
|
4
|
+
|
|
5
|
+
describe('fuzzyScore', () => {
|
|
6
|
+
it('matches a query as a subsequence (g4o → gpt-4o)', () => {
|
|
7
|
+
expect(fuzzyScore('gpt-4o', 'g4o')).not.toBeNull()
|
|
8
|
+
expect(fuzzyScore('gpt-4o', 'gpt')).not.toBeNull()
|
|
9
|
+
expect(fuzzyScore('gpt-4o', '4o')).not.toBeNull()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('returns null when characters are out of order or absent', () => {
|
|
13
|
+
expect(fuzzyScore('gpt-4o', 'o4g')).toBeNull()
|
|
14
|
+
expect(fuzzyScore('gpt-4o', 'xyz')).toBeNull()
|
|
15
|
+
expect(fuzzyScore('gpt-4o', 'gptx')).toBeNull()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('returns matched positions into the original target', () => {
|
|
19
|
+
const m = fuzzyScore('gpt-4o', 'g4o')
|
|
20
|
+
// g@0, 4@4, o@5
|
|
21
|
+
expect(m?.positions).toEqual([0, 4, 5])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('treats an empty query as a zero-score match', () => {
|
|
25
|
+
expect(fuzzyScore('anything', '')).toEqual({ score: 0, positions: [] })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('scores an exact match highest', () => {
|
|
29
|
+
const exact = fuzzyScore('sonnet', 'sonnet')!.score
|
|
30
|
+
const prefix = fuzzyScore('sonnet-extended', 'sonnet')!.score
|
|
31
|
+
// s,o,n,n,e,t all present in order but scattered across word boundaries.
|
|
32
|
+
const scattered = fuzzyScore('snorkel-online-nnet', 'sonnet')!.score
|
|
33
|
+
|
|
34
|
+
expect(exact).toBeGreaterThan(prefix)
|
|
35
|
+
expect(prefix).toBeGreaterThan(scattered)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('ranks a prefix match above a scattered subsequence', () => {
|
|
39
|
+
const prefix = fuzzyScore('gpt-4o-mini', 'gpt')!.score
|
|
40
|
+
const scattered = fuzzyScore('a-g-p-t', 'gpt')!.score
|
|
41
|
+
|
|
42
|
+
expect(prefix).toBeGreaterThan(scattered)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('rewards word-boundary matches', () => {
|
|
46
|
+
// `s4` matching the `s` of sonnet and the `4` after a dash
|
|
47
|
+
const boundary = fuzzyScore('claude-sonnet-4', 'cs4')
|
|
48
|
+
expect(boundary).not.toBeNull()
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('fuzzyScoreMulti', () => {
|
|
53
|
+
it('requires every space-separated token to match (AND)', () => {
|
|
54
|
+
expect(fuzzyScoreMulti('claude-sonnet-4', 'clad snnt')).not.toBeNull()
|
|
55
|
+
expect(fuzzyScoreMulti('claude-sonnet-4', 'claude haiku')).toBeNull()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('unions matched positions across tokens, sorted', () => {
|
|
59
|
+
const m = fuzzyScoreMulti('claude-sonnet', 'son cla')
|
|
60
|
+
expect(m).not.toBeNull()
|
|
61
|
+
expect(m!.positions).toEqual([...m!.positions].sort((a, b) => a - b))
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('treats whitespace-only query as a zero-score match', () => {
|
|
65
|
+
expect(fuzzyScoreMulti('x', ' ')).toEqual({ score: 0, positions: [] })
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('fuzzyRank', () => {
|
|
70
|
+
const models = ['gpt-4o', 'gpt-4o-mini', 'claude-sonnet-4', 'claude-haiku', 'o1-preview']
|
|
71
|
+
|
|
72
|
+
it('drops non-matching items and ranks matches by score', () => {
|
|
73
|
+
const ranked = fuzzyRank(models, 'g4o', m => m)
|
|
74
|
+
const ids = ranked.map(r => r.item)
|
|
75
|
+
|
|
76
|
+
expect(ids).toContain('gpt-4o')
|
|
77
|
+
expect(ids).toContain('gpt-4o-mini')
|
|
78
|
+
expect(ids).not.toContain('claude-haiku')
|
|
79
|
+
// Shorter exact-ish prefix should outrank the longer variant.
|
|
80
|
+
expect(ids.indexOf('gpt-4o')).toBeLessThan(ids.indexOf('gpt-4o-mini'))
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('ranks son4 so a sonnet model surfaces', () => {
|
|
84
|
+
const ranked = fuzzyRank(models, 'son4', m => m)
|
|
85
|
+
expect(ranked[0]?.item).toBe('claude-sonnet-4')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('returns all items in original order for an empty query', () => {
|
|
89
|
+
const ranked = fuzzyRank(models, '', m => m)
|
|
90
|
+
expect(ranked.map(r => r.item)).toEqual(models)
|
|
91
|
+
expect(ranked.every(r => r.positions.length === 0)).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('is stable for equal scores (original index tiebreak)', () => {
|
|
95
|
+
const items = ['ab', 'ab', 'ab']
|
|
96
|
+
const ranked = fuzzyRank(items.map((v, i) => ({ v, i })), 'ab', x => x.v)
|
|
97
|
+
expect(ranked.map(r => r.item.i)).toEqual([0, 1, 2])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('matches across a derived key, not just the raw string', () => {
|
|
101
|
+
const providers = [
|
|
102
|
+
{ slug: 'openai', name: 'OpenAI' },
|
|
103
|
+
{ slug: 'anthropic', name: 'Anthropic' }
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
const ranked = fuzzyRank(providers, 'anth', p => `${p.name} ${p.slug}`)
|
|
107
|
+
expect(ranked[0]?.item.slug).toBe('anthropic')
|
|
108
|
+
})
|
|
109
|
+
})
|