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,93 @@
|
|
|
1
|
+
import { Box, Text } from '@nastechai/ink'
|
|
2
|
+
import { memo, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { countPendingTodos } from '../lib/liveProgress.js'
|
|
5
|
+
import { todoGlyph, todoTone } from '../lib/todo.js'
|
|
6
|
+
import type { Theme } from '../theme.js'
|
|
7
|
+
import type { TodoItem } from '../types.js'
|
|
8
|
+
|
|
9
|
+
const rowColor = (t: Theme, status: TodoItem['status']) => {
|
|
10
|
+
const tone = todoTone(status)
|
|
11
|
+
|
|
12
|
+
return tone === 'active' ? t.color.text : tone === 'body' ? t.color.statusFg : t.color.muted
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const TodoPanel = memo(function TodoPanel({
|
|
16
|
+
collapsed,
|
|
17
|
+
defaultCollapsed = false,
|
|
18
|
+
incomplete = false,
|
|
19
|
+
onToggle,
|
|
20
|
+
t,
|
|
21
|
+
todos
|
|
22
|
+
}: {
|
|
23
|
+
collapsed?: boolean
|
|
24
|
+
defaultCollapsed?: boolean
|
|
25
|
+
incomplete?: boolean
|
|
26
|
+
onToggle?: () => void
|
|
27
|
+
t: Theme
|
|
28
|
+
todos: TodoItem[]
|
|
29
|
+
}) {
|
|
30
|
+
// Fallback local state for archived todos in transcript where there's no
|
|
31
|
+
// external controller. Live TodoPanel passes collapsed+onToggle from the
|
|
32
|
+
// turn store so clicks still work there.
|
|
33
|
+
const [localCollapsed, setLocalCollapsed] = useState(defaultCollapsed)
|
|
34
|
+
const isControlled = typeof collapsed === 'boolean'
|
|
35
|
+
const effectiveCollapsed = isControlled ? collapsed : localCollapsed
|
|
36
|
+
|
|
37
|
+
const handleToggle = () => {
|
|
38
|
+
if (onToggle) {
|
|
39
|
+
onToggle()
|
|
40
|
+
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!isControlled) {
|
|
45
|
+
setLocalCollapsed(v => !v)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!todos.length) {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const done = todos.filter(todo => todo.status === 'completed').length
|
|
54
|
+
const pending = countPendingTodos(todos)
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
58
|
+
<Box onClick={handleToggle}>
|
|
59
|
+
<Text color={t.color.muted}>
|
|
60
|
+
<Text color={t.color.accent}>{effectiveCollapsed ? '▸ ' : '▾ '}</Text>
|
|
61
|
+
<Text bold color={t.color.text}>
|
|
62
|
+
Todo
|
|
63
|
+
</Text>{' '}
|
|
64
|
+
<Text color={t.color.statusFg} dim>
|
|
65
|
+
({done}/{todos.length})
|
|
66
|
+
</Text>
|
|
67
|
+
{incomplete && pending > 0 && (
|
|
68
|
+
<Text color={t.color.muted} dim>
|
|
69
|
+
{' '}
|
|
70
|
+
· incomplete · {pending} still {pending === 1 ? 'pending' : 'pending/in_progress'}
|
|
71
|
+
</Text>
|
|
72
|
+
)}
|
|
73
|
+
</Text>
|
|
74
|
+
</Box>
|
|
75
|
+
|
|
76
|
+
{!effectiveCollapsed && (
|
|
77
|
+
<Box flexDirection="column" marginLeft={2}>
|
|
78
|
+
{todos.map(todo => {
|
|
79
|
+
const tone = todoTone(todo.status)
|
|
80
|
+
const color = rowColor(t, todo.status)
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Text color={color} dim={tone === 'dim'} key={todo.id}>
|
|
84
|
+
<Text color={color}>{todoGlyph(todo.status)} </Text>
|
|
85
|
+
{todo.content}
|
|
86
|
+
</Text>
|
|
87
|
+
)
|
|
88
|
+
})}
|
|
89
|
+
</Box>
|
|
90
|
+
)}
|
|
91
|
+
</Box>
|
|
92
|
+
)
|
|
93
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { MouseTrackingMode } from '@nastechai/ink'
|
|
2
|
+
import { isTermuxTuiMode } from '../lib/termux.js'
|
|
3
|
+
|
|
4
|
+
const truthy = (v?: string) => /^(?:1|true|yes|on)$/i.test((v ?? '').trim())
|
|
5
|
+
const falsy = (v?: string) => /^(?:0|false|no|off)$/i.test((v ?? '').trim())
|
|
6
|
+
|
|
7
|
+
const parseToggle = (v?: string): boolean | null => {
|
|
8
|
+
const raw = (v ?? '').trim()
|
|
9
|
+
|
|
10
|
+
if (!raw) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (truthy(raw)) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (falsy(raw)) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const TERMUX_TUI_MODE = isTermuxTuiMode()
|
|
26
|
+
|
|
27
|
+
export const STARTUP_RESUME_ID = (process.env.NASTECH_TUI_RESUME ?? '').trim()
|
|
28
|
+
export const STARTUP_QUERY = (process.env.NASTECH_TUI_QUERY ?? '').trim()
|
|
29
|
+
export const STARTUP_IMAGE = (process.env.NASTECH_TUI_IMAGE ?? '').trim()
|
|
30
|
+
|
|
31
|
+
// Mouse tracking mode resolution at startup. Per-mode selection (off|wheel|
|
|
32
|
+
// buttons|all) lives in display.mouse_tracking in config.yaml — these env
|
|
33
|
+
// vars only set the boot-time default before that config is applied.
|
|
34
|
+
//
|
|
35
|
+
// Precedence (highest first):
|
|
36
|
+
//
|
|
37
|
+
// - NASTECH_TUI_MOUSE_TRACKING (truthy/falsy) explicitly overrides everything.
|
|
38
|
+
// This is the "force a value" knob and intentionally beats the legacy
|
|
39
|
+
// kill-switch and the Termux default.
|
|
40
|
+
// - NASTECH_TUI_DISABLE_MOUSE=1 forces mouse off — the legacy kill switch.
|
|
41
|
+
// - On Termux the default is mouse off so touch selection isn't intercepted
|
|
42
|
+
// by terminal mouse protocols. Desktop defaults to 'all' to preserve prior
|
|
43
|
+
// behavior.
|
|
44
|
+
const mouseTrackingOverride = parseToggle(process.env.NASTECH_TUI_MOUSE_TRACKING)
|
|
45
|
+
const mouseTrackingDisabledLegacy = truthy(process.env.NASTECH_TUI_DISABLE_MOUSE)
|
|
46
|
+
const resolvedBootMouseEnabled =
|
|
47
|
+
mouseTrackingOverride ?? (TERMUX_TUI_MODE ? false : !mouseTrackingDisabledLegacy)
|
|
48
|
+
export const MOUSE_TRACKING: MouseTrackingMode = resolvedBootMouseEnabled ? 'all' : 'off'
|
|
49
|
+
|
|
50
|
+
export const NO_CONFIRM_DESTRUCTIVE = truthy(process.env.NASTECH_TUI_NO_CONFIRM)
|
|
51
|
+
|
|
52
|
+
const inlineOverride = parseToggle(process.env.NASTECH_TUI_INLINE)
|
|
53
|
+
|
|
54
|
+
// Skip AlternateScreen — TUI renders into the primary buffer so the host
|
|
55
|
+
// terminal's native scrollback captures whatever scrolls off the top.
|
|
56
|
+
//
|
|
57
|
+
// On Termux we default this on: users often background/foreground the app,
|
|
58
|
+
// and primary-buffer rendering makes long-thread review and copy/paste much
|
|
59
|
+
// less fragile. Override explicitly with NASTECH_TUI_INLINE=0/1.
|
|
60
|
+
export const INLINE_MODE = inlineOverride ?? TERMUX_TUI_MODE
|
|
61
|
+
|
|
62
|
+
// Live FPS counter overlay, fed by ink's onFrame (real render rate, not a
|
|
63
|
+
// synthetic timer).
|
|
64
|
+
export const SHOW_FPS = truthy(process.env.NASTECH_TUI_FPS)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const LARGE_PASTE = { lines: 5 }
|
|
2
|
+
|
|
3
|
+
export const LIVE_RENDER_MAX_CHARS = 16_000
|
|
4
|
+
export const LIVE_RENDER_MAX_LINES = 240
|
|
5
|
+
|
|
6
|
+
export const LONG_MSG = 300
|
|
7
|
+
export const MAX_HISTORY = 800
|
|
8
|
+
export const THINKING_COT_MAX = 160
|
|
9
|
+
|
|
10
|
+
// Rows per wheel event (pre-accel). 1 keeps Ink's DECSTBM fast path live
|
|
11
|
+
// (each scroll < viewport-1) and produces smooth motion. wheelAccel.ts
|
|
12
|
+
// ramps this on sustained scrolls.
|
|
13
|
+
export const WHEEL_SCROLL_STEP = 1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LONG_RUN_CHARMS = ['still cooking…', 'polishing edges…', 'asking the void nicely…']
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const FORTUNES = [
|
|
2
|
+
'you are one clean refactor away from clarity',
|
|
3
|
+
'a tiny rename today prevents a huge bug tomorrow',
|
|
4
|
+
'your next commit message will be immaculate',
|
|
5
|
+
'the edge case you are ignoring is already solved in your head',
|
|
6
|
+
'minimal diff, maximal calm',
|
|
7
|
+
'today favors bold deletions over new abstractions',
|
|
8
|
+
'the right helper is already in your codebase',
|
|
9
|
+
'you will ship before overthinking catches up',
|
|
10
|
+
'tests are about to save your future self',
|
|
11
|
+
'your instincts are correctly suspicious of that one branch'
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const LEGENDARY = [
|
|
15
|
+
'legendary drop: one-line fix, first try',
|
|
16
|
+
'legendary drop: every flaky test passes cleanly',
|
|
17
|
+
'legendary drop: your diff teaches by itself'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const hash = (s: string) => [...s].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619), 2166136261) >>> 0
|
|
21
|
+
|
|
22
|
+
const fromScore = (n: number) => {
|
|
23
|
+
const rare = n % 20 === 0
|
|
24
|
+
const bag = rare ? LEGENDARY : FORTUNES
|
|
25
|
+
|
|
26
|
+
return `${rare ? '🌟' : '🔮'} ${bag[n % bag.length]}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const randomFortune = () => fromScore(Math.floor(Math.random() * 0x7fffffff))
|
|
30
|
+
export const dailyFortune = (seed: null | string) => fromScore(hash(`${seed || 'anon'}|${new Date().toDateString()}`))
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isMac, isRemoteShell } from '../lib/platform.js'
|
|
2
|
+
|
|
3
|
+
const action = isMac ? 'Cmd' : 'Ctrl'
|
|
4
|
+
const paste = isMac ? 'Cmd' : 'Alt'
|
|
5
|
+
|
|
6
|
+
const copyHotkeys: [string, string][] = isMac
|
|
7
|
+
? [
|
|
8
|
+
['Cmd+C', 'copy selection'],
|
|
9
|
+
['Ctrl+C', 'interrupt / clear draft / exit']
|
|
10
|
+
]
|
|
11
|
+
: isRemoteShell()
|
|
12
|
+
? [
|
|
13
|
+
['Cmd+C', 'copy selection when forwarded by the terminal'],
|
|
14
|
+
['Ctrl+C', 'copy selection / interrupt / clear draft / exit']
|
|
15
|
+
]
|
|
16
|
+
: [['Ctrl+C', 'copy selection / interrupt / clear draft / exit']]
|
|
17
|
+
|
|
18
|
+
export const HOTKEYS: [string, string][] = [
|
|
19
|
+
...copyHotkeys,
|
|
20
|
+
[action + '+D', 'exit'],
|
|
21
|
+
[action + '+G / Alt+G', 'open $EDITOR (Alt+G fallback for VSCode/Cursor)'],
|
|
22
|
+
[action + '+L', 'redraw / repaint'],
|
|
23
|
+
[paste + '+V / /paste', 'paste text; /paste attaches clipboard image'],
|
|
24
|
+
['Tab', 'apply completion'],
|
|
25
|
+
['↑/↓', 'completions / queue edit / history'],
|
|
26
|
+
['Ctrl+X', 'open live session switcher (deletes queued message while editing)'],
|
|
27
|
+
[action + '+A/E', 'home / end of line'],
|
|
28
|
+
[action + '+Z / ' + action + '+Y', 'undo / redo input edits'],
|
|
29
|
+
[action + '+W', 'delete word'],
|
|
30
|
+
[action + '+U/K', 'delete to start / end'],
|
|
31
|
+
[action + '+←/→', 'jump word'],
|
|
32
|
+
['Home/End', 'start / end of line'],
|
|
33
|
+
['Shift+Enter / Alt+Enter', 'insert newline'],
|
|
34
|
+
['\\+Enter', 'multi-line continuation (fallback)'],
|
|
35
|
+
['!<cmd>', 'run a shell command (e.g. !ls, !git status)'],
|
|
36
|
+
['{!<cmd>}', 'interpolate shell output inline (e.g. "branch is {!git branch --show-current}")']
|
|
37
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { pick } from '../lib/text.js'
|
|
2
|
+
|
|
3
|
+
export const PLACEHOLDERS = [
|
|
4
|
+
'Ask me anything…',
|
|
5
|
+
'Try "explain this codebase"',
|
|
6
|
+
'Try "write a test for…"',
|
|
7
|
+
'Try "refactor the auth module"',
|
|
8
|
+
'Try "/help" for commands',
|
|
9
|
+
'Try "fix the lint errors"',
|
|
10
|
+
'Try "how does the config loader work?"'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export const PLACEHOLDER = pick(PLACEHOLDERS)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PanelSection } from '../types.js'
|
|
2
|
+
|
|
3
|
+
export const SETUP_REQUIRED_TITLE = 'Setup Required'
|
|
4
|
+
|
|
5
|
+
export const buildSetupRequiredSections = (): PanelSection[] => [
|
|
6
|
+
{
|
|
7
|
+
text: 'NasTech needs a model provider before the TUI can start a session.'
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
rows: [
|
|
11
|
+
['/model', 'configure provider + model in-place'],
|
|
12
|
+
['/setup', 'run full first-time setup wizard in-place'],
|
|
13
|
+
['Ctrl+C', 'exit and run `nastech setup` manually']
|
|
14
|
+
],
|
|
15
|
+
title: 'Actions'
|
|
16
|
+
}
|
|
17
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const TOOL_VERBS: Record<string, string> = {
|
|
2
|
+
browser: 'browsing',
|
|
3
|
+
clarify: 'asking',
|
|
4
|
+
create_file: 'creating',
|
|
5
|
+
delegate_task: 'delegating',
|
|
6
|
+
delete_file: 'deleting',
|
|
7
|
+
execute_code: 'executing',
|
|
8
|
+
image_generate: 'generating',
|
|
9
|
+
list_files: 'listing',
|
|
10
|
+
memory: 'remembering',
|
|
11
|
+
patch: 'patching',
|
|
12
|
+
read_file: 'reading',
|
|
13
|
+
run_command: 'running',
|
|
14
|
+
search_code: 'searching',
|
|
15
|
+
search_files: 'searching',
|
|
16
|
+
terminal: 'terminal',
|
|
17
|
+
web_extract: 'extracting',
|
|
18
|
+
web_search: 'searching',
|
|
19
|
+
write_file: 'writing'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const VERBS = [
|
|
23
|
+
'pondering',
|
|
24
|
+
'contemplating',
|
|
25
|
+
'musing',
|
|
26
|
+
'cogitating',
|
|
27
|
+
'ruminating',
|
|
28
|
+
'deliberating',
|
|
29
|
+
'mulling',
|
|
30
|
+
'reflecting',
|
|
31
|
+
'processing',
|
|
32
|
+
'reasoning',
|
|
33
|
+
'analyzing',
|
|
34
|
+
'computing',
|
|
35
|
+
'synthesizing',
|
|
36
|
+
'formulating',
|
|
37
|
+
'brainstorming'
|
|
38
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { DetailsMode, Msg, SectionVisibility } from '../types.js'
|
|
2
|
+
|
|
3
|
+
import { sectionMode } from './details.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Visual group a transcript block belongs to. Blocks in the same group render
|
|
7
|
+
* flush; a single blank line opens at each group boundary. So a run of tool
|
|
8
|
+
* trails (or model paragraphs) reads as one section and the eye only catches a
|
|
9
|
+
* gap where the *kind* of content actually changes — the boundary between
|
|
10
|
+
* reasoning/tool trails, model prose, and notes/errors.
|
|
11
|
+
*
|
|
12
|
+
* user — the human turn (owns its separator + margins in MessageLine)
|
|
13
|
+
* model — assistant prose, the model's voice
|
|
14
|
+
* trail — reasoning + tool-call trails (the agent's working area)
|
|
15
|
+
* note — system notes and errors (a quieter band)
|
|
16
|
+
* diff — inline patch segments (an island, owns its own margins)
|
|
17
|
+
* slash — slash-command echoes (owns its margin)
|
|
18
|
+
* intro — banner / panels (rendered out-of-band, never gapped here)
|
|
19
|
+
*/
|
|
20
|
+
export type BlockGroup = 'diff' | 'intro' | 'model' | 'note' | 'slash' | 'trail' | 'user'
|
|
21
|
+
|
|
22
|
+
export const messageGroup = (msg: Pick<Msg, 'kind' | 'role'>): BlockGroup => {
|
|
23
|
+
switch (msg.kind) {
|
|
24
|
+
case 'intro':
|
|
25
|
+
case 'panel':
|
|
26
|
+
return 'intro'
|
|
27
|
+
case 'slash':
|
|
28
|
+
return 'slash'
|
|
29
|
+
case 'diff':
|
|
30
|
+
return 'diff'
|
|
31
|
+
case 'trail':
|
|
32
|
+
return 'trail'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (msg.role === 'user') {
|
|
36
|
+
return 'user'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Assistant prose is the model's voice; system notes/errors are their own
|
|
40
|
+
// band. (No runtime block uses role 'tool' — tool *results* fold into
|
|
41
|
+
// trails — so a stray 'tool' falls through to the note band harmlessly.)
|
|
42
|
+
return msg.role === 'assistant' ? 'model' : 'note'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Groups whose leading gap is already owned by their own chrome in
|
|
46
|
+
// MessageLine (the turn separator + top margin for user, the top margin for
|
|
47
|
+
// slash, the top+bottom margins for diff) or that are painted out-of-band
|
|
48
|
+
// (intro). The grouping primitive only spaces the model working area —
|
|
49
|
+
// model prose, reasoning/tool trails, and notes/errors.
|
|
50
|
+
const SELF_SPACED: ReadonlySet<BlockGroup> = new Set(['diff', 'intro', 'slash', 'user'])
|
|
51
|
+
|
|
52
|
+
// Groups that already paint a trailing blank line beneath themselves
|
|
53
|
+
// (marginBottom in MessageLine), so the block that follows must not add its
|
|
54
|
+
// own leading gap or the single boundary would become a double gap.
|
|
55
|
+
const PAINTS_TRAILING_GAP: ReadonlySet<BlockGroup> = new Set(['diff', 'user'])
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether `cur` renders one blank line above it, given the block rendered
|
|
59
|
+
* directly above it (`prev`). True only where the visual group changes, and
|
|
60
|
+
* only for the model-working-area bands (model / trail / note) — user, slash,
|
|
61
|
+
* diff, and intro keep their existing spacing.
|
|
62
|
+
*
|
|
63
|
+
* Streaming-safe by construction: the result depends on the *predecessor's*
|
|
64
|
+
* group, never on `cur`'s own (live, changing) content. The actively-streaming
|
|
65
|
+
* assistant block therefore computes the same gap while it streams as the
|
|
66
|
+
* settled segment does once it flushes, so the live area never jumps.
|
|
67
|
+
*/
|
|
68
|
+
export const hasLeadGap = (
|
|
69
|
+
prev: Pick<Msg, 'kind' | 'role'> | undefined,
|
|
70
|
+
cur: Pick<Msg, 'kind' | 'role'>
|
|
71
|
+
): boolean => {
|
|
72
|
+
const group = messageGroup(cur)
|
|
73
|
+
|
|
74
|
+
if (SELF_SPACED.has(group)) {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!prev) {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const prevGroup = messageGroup(prev)
|
|
83
|
+
|
|
84
|
+
return prevGroup !== group && !PAINTS_TRAILING_GAP.has(prevGroup)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DetailsCtx {
|
|
88
|
+
commandOverride?: boolean
|
|
89
|
+
detailsMode: DetailsMode
|
|
90
|
+
sections?: SectionVisibility
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const trailAllHidden = (ctx: DetailsCtx): boolean =>
|
|
94
|
+
sectionMode('thinking', ctx.detailsMode, ctx.sections, ctx.commandOverride) === 'hidden' &&
|
|
95
|
+
sectionMode('tools', ctx.detailsMode, ctx.sections, ctx.commandOverride) === 'hidden' &&
|
|
96
|
+
sectionMode('activity', ctx.detailsMode, ctx.sections, ctx.commandOverride) === 'hidden'
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Whether a settled transcript block paints anything. A trail renders nothing
|
|
100
|
+
* when it has no reasoning/tools/todos to show (e.g. the finalDetails segment
|
|
101
|
+
* that carries only a token tally) or when every section it does have is hidden
|
|
102
|
+
* (`/details hidden`); every other block draws at least one row. A block that
|
|
103
|
+
* renders nothing is *transparent* to grouping: the block below it draws its
|
|
104
|
+
* boundary against the nearest visible block instead (see prevRenderedMsg), so
|
|
105
|
+
* a hidden or content-less trail never leaves a floating blank line, doubles
|
|
106
|
+
* the gap after a user prompt, or pads the space above the final reply. In the
|
|
107
|
+
* default/collapsed modes content-bearing trails always render, so this is a
|
|
108
|
+
* no-op there.
|
|
109
|
+
*/
|
|
110
|
+
export const blockRenders = (msg: Pick<Msg, 'kind' | 'thinking' | 'todos' | 'tools'>, ctx: DetailsCtx): boolean => {
|
|
111
|
+
if (msg.kind !== 'trail') {
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (msg.todos?.length) {
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!(msg.tools?.length || msg.thinking?.trim())) {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return !trailAllHidden(ctx)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The nearest block above `index` that actually renders, resolved through a
|
|
128
|
+
* lazy accessor so it works over either the virtualized history rows or the
|
|
129
|
+
* live block list. This is the grouping predecessor — using it (instead of the
|
|
130
|
+
* literal previous row) keeps hidden trails from interrupting the rhythm.
|
|
131
|
+
*/
|
|
132
|
+
export const prevRenderedMsg = (
|
|
133
|
+
msgAt: (i: number) => Msg | undefined,
|
|
134
|
+
index: number,
|
|
135
|
+
ctx: DetailsCtx
|
|
136
|
+
): Msg | undefined => {
|
|
137
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
138
|
+
const candidate = msgAt(i)
|
|
139
|
+
|
|
140
|
+
if (candidate && blockRenders(candidate, ctx)) {
|
|
141
|
+
return candidate
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return undefined
|
|
146
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { DetailsMode, SectionName, SectionVisibility } from '../types.js'
|
|
2
|
+
|
|
3
|
+
const MODES = ['hidden', 'collapsed', 'expanded'] as const
|
|
4
|
+
|
|
5
|
+
export const SECTION_NAMES = ['thinking', 'tools', 'subagents', 'activity'] as const
|
|
6
|
+
|
|
7
|
+
// Out-of-the-box per-section defaults — applied when the user hasn't pinned
|
|
8
|
+
// an explicit override and layered ABOVE the global details_mode:
|
|
9
|
+
//
|
|
10
|
+
// - thinking / tools: expanded — stream open so the turn reads like a
|
|
11
|
+
// live transcript (reasoning + tool calls side by side) instead of a
|
|
12
|
+
// wall of chevrons the user has to click every turn.
|
|
13
|
+
// - activity: hidden — ambient meta (gateway hints, terminal-parity
|
|
14
|
+
// nudges, background notifications) is noise for typical use. Tool
|
|
15
|
+
// failures still render inline on the failing tool row, and ambient
|
|
16
|
+
// errors/warnings surface via the floating-alert backstop when every
|
|
17
|
+
// panel resolves to hidden.
|
|
18
|
+
// - subagents: not set — falls through to the global details_mode so
|
|
19
|
+
// Spawn trees stay under a chevron until a delegation actually happens.
|
|
20
|
+
//
|
|
21
|
+
// Opt out of any of these with `display.sections.<name>` in config.yaml
|
|
22
|
+
// or at runtime via `/details <name> collapsed|hidden`.
|
|
23
|
+
const SECTION_DEFAULTS: SectionVisibility = {
|
|
24
|
+
thinking: 'expanded',
|
|
25
|
+
tools: 'expanded',
|
|
26
|
+
activity: 'hidden'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const THINKING_FALLBACK: Record<string, DetailsMode> = {
|
|
30
|
+
collapsed: 'collapsed',
|
|
31
|
+
full: 'expanded',
|
|
32
|
+
truncated: 'collapsed'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const norm = (v: unknown) =>
|
|
36
|
+
String(v ?? '')
|
|
37
|
+
.trim()
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
|
|
40
|
+
export const parseDetailsMode = (v: unknown): DetailsMode | null => MODES.find(m => m === norm(v)) ?? null
|
|
41
|
+
|
|
42
|
+
export const isSectionName = (v: unknown): v is SectionName =>
|
|
43
|
+
typeof v === 'string' && (SECTION_NAMES as readonly string[]).includes(v)
|
|
44
|
+
|
|
45
|
+
export const resolveDetailsMode = (d?: { details_mode?: unknown; thinking_mode?: unknown } | null): DetailsMode =>
|
|
46
|
+
parseDetailsMode(d?.details_mode) ?? THINKING_FALLBACK[norm(d?.thinking_mode)] ?? 'collapsed'
|
|
47
|
+
|
|
48
|
+
// Build SectionVisibility from a free-form blob. Unknown section names and
|
|
49
|
+
// invalid modes are dropped silently — partial overrides are intentional, so
|
|
50
|
+
// missing keys fall through to SECTION_DEFAULTS / global at lookup time.
|
|
51
|
+
export const resolveSections = (raw: unknown): SectionVisibility =>
|
|
52
|
+
raw && typeof raw === 'object' && !Array.isArray(raw)
|
|
53
|
+
? (Object.fromEntries(
|
|
54
|
+
Object.entries(raw as Record<string, unknown>)
|
|
55
|
+
.map(([k, v]) => [k, parseDetailsMode(v)] as const)
|
|
56
|
+
.filter(([k, m]) => !!m && isSectionName(k))
|
|
57
|
+
) as SectionVisibility)
|
|
58
|
+
: {}
|
|
59
|
+
|
|
60
|
+
// Effective mode for one section: explicit override → global command mode →
|
|
61
|
+
// built-in live-stream defaults → global config mode.
|
|
62
|
+
//
|
|
63
|
+
// The `commandOverride` flag is set for in-session `/details <mode>` changes.
|
|
64
|
+
// That command should immediately apply to every section, including sections
|
|
65
|
+
// with built-in defaults like thinking/tools=expanded and activity=hidden. On
|
|
66
|
+
// startup/config sync we keep those defaults layered above the persisted global
|
|
67
|
+
// config so the TUI still opens live reasoning/tools by default unless the user
|
|
68
|
+
// pins explicit per-section overrides.
|
|
69
|
+
export const sectionMode = (
|
|
70
|
+
name: SectionName,
|
|
71
|
+
global: DetailsMode,
|
|
72
|
+
sections?: SectionVisibility,
|
|
73
|
+
commandOverride = false
|
|
74
|
+
): DetailsMode => sections?.[name] ?? (commandOverride ? global : (SECTION_DEFAULTS[name] ?? global))
|
|
75
|
+
|
|
76
|
+
export const nextDetailsMode = (m: DetailsMode): DetailsMode => MODES[(MODES.indexOf(m) + 1) % MODES.length]!
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { LONG_MSG } from '../config/limits.js'
|
|
2
|
+
import { buildToolTrailLine, fmtK } from '../lib/text.js'
|
|
3
|
+
import type { Msg, SessionInfo } from '../types.js'
|
|
4
|
+
|
|
5
|
+
export const introMsg = (info: SessionInfo): Msg => ({ info, kind: 'intro', role: 'system', text: '' })
|
|
6
|
+
|
|
7
|
+
export const imageTokenMeta = (info?: ImageMeta | null) => {
|
|
8
|
+
const { width, height, token_estimate: t } = info ?? {}
|
|
9
|
+
|
|
10
|
+
return [width && height ? `${width}x${height}` : '', (t ?? 0) > 0 ? `~${fmtK(t!)} tok` : '']
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.join(' · ')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const attachedImageNotice = (info?: ({ name?: string } & ImageMeta) | null) => {
|
|
16
|
+
const meta = imageTokenMeta(info)
|
|
17
|
+
const label = info?.name ? `📎 Attached image: ${info.name}` : '📎 Attached image'
|
|
18
|
+
|
|
19
|
+
return `${label}${meta ? ` · ${meta}` : ''}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const userDisplay = (text: string) => {
|
|
23
|
+
if (text.length <= LONG_MSG) {
|
|
24
|
+
return text
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const first = text.split('\n')[0]?.trim() ?? ''
|
|
28
|
+
const words = first.split(/\s+/).filter(Boolean)
|
|
29
|
+
const prefix = (words.length > 1 ? words.slice(0, 4).join(' ') : first).slice(0, 80)
|
|
30
|
+
|
|
31
|
+
return `${prefix || '(message)'} [long message]`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const toTranscriptMessages = (rows: unknown): Msg[] => {
|
|
35
|
+
if (!Array.isArray(rows)) {
|
|
36
|
+
return []
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const out: Msg[] = []
|
|
40
|
+
let pending: string[] = []
|
|
41
|
+
|
|
42
|
+
for (const row of rows) {
|
|
43
|
+
if (!row || typeof row !== 'object') {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { context, name, role, text } = row as TranscriptRow
|
|
48
|
+
|
|
49
|
+
if (role === 'tool') {
|
|
50
|
+
pending.push(buildToolTrailLine(name ?? 'tool', context ?? ''))
|
|
51
|
+
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof text !== 'string' || !text.trim()) {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (role === 'assistant') {
|
|
60
|
+
out.push({ role, text, ...(pending.length && { tools: pending }) })
|
|
61
|
+
pending = []
|
|
62
|
+
} else if (role === 'user' || role === 'system') {
|
|
63
|
+
out.push({ role, text })
|
|
64
|
+
pending = []
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return out
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const fmtDuration = (ms: number) => {
|
|
72
|
+
const t = Math.max(0, Math.floor(ms / 1000))
|
|
73
|
+
const h = Math.floor(t / 3600)
|
|
74
|
+
const m = Math.floor((t % 3600) / 60)
|
|
75
|
+
const s = t % 60
|
|
76
|
+
|
|
77
|
+
return h > 0 ? `${h}h ${m}m` : m > 0 ? `${m}m ${s}s` : `${s}s`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface ImageMeta {
|
|
81
|
+
height?: number
|
|
82
|
+
token_estimate?: number
|
|
83
|
+
width?: number
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface TranscriptRow {
|
|
87
|
+
context?: string
|
|
88
|
+
name?: string
|
|
89
|
+
role?: string
|
|
90
|
+
text?: string
|
|
91
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const shortCwd = (cwd: string, max = 28) => {
|
|
2
|
+
const h = process.env.HOME
|
|
3
|
+
const p = h && cwd.startsWith(h) ? `~${cwd.slice(h.length)}` : cwd
|
|
4
|
+
|
|
5
|
+
return p.length <= max ? p : `…${p.slice(-(max - 1))}`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const fmtCwdBranch = (cwd: string, branch: null | string, max = 40) => {
|
|
9
|
+
if (!branch) {
|
|
10
|
+
return shortCwd(cwd, max)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const tag = ` (${branch.length > 16 ? `…${branch.slice(-15)}` : branch})`
|
|
14
|
+
|
|
15
|
+
return `${shortCwd(cwd, Math.max(8, max - tag.length))}${tag}`
|
|
16
|
+
}
|