@alumbwe/anvil 1.0.0
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/README.md +84 -0
- package/bin/anvil +2 -0
- package/bin/tree-sitter.wasm +0 -0
- package/package.json +67 -0
- package/src/__tests__/README.md +256 -0
- package/src/__tests__/bash-mode.test.ts +442 -0
- package/src/__tests__/cli-args.test.ts +181 -0
- package/src/__tests__/cli-harness.test.ts +52 -0
- package/src/__tests__/e2e/first-time-login.test.ts +173 -0
- package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
- package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
- package/src/__tests__/e2e-cli.test.ts +183 -0
- package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
- package/src/__tests__/helpers/mock-api-client.ts +64 -0
- package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
- package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
- package/src/__tests__/home-directory-detection.test.ts +213 -0
- package/src/__tests__/integration/api-integration.test.ts +288 -0
- package/src/__tests__/integration/credentials-storage.test.ts +486 -0
- package/src/__tests__/integration/local-agents.test.ts +1187 -0
- package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
- package/src/__tests__/integration/login-polling-working.test.ts +285 -0
- package/src/__tests__/integration/queue-editing.test.tsx +261 -0
- package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
- package/src/__tests__/integration-tmux.test.ts +188 -0
- package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
- package/src/__tests__/launcher-disconnect.test.ts +104 -0
- package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
- package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
- package/src/__tests__/path-completion.test.ts +243 -0
- package/src/__tests__/project-files-chat-id.test.ts +34 -0
- package/src/__tests__/release/proxy-http-get.test.ts +667 -0
- package/src/__tests__/release/wrapper-safety.test.ts +412 -0
- package/src/__tests__/renderer-cleanup.test.ts +174 -0
- package/src/__tests__/rerender-perf.integration.test.ts +283 -0
- package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
- package/src/__tests__/terminal-watchdog.test.ts +388 -0
- package/src/__tests__/test-utils.ts +271 -0
- package/src/__tests__/tmux-poc.ts +150 -0
- package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
- package/src/__tests__/unit/copy-button.test.ts +157 -0
- package/src/__tests__/unit/create-run-config.test.ts +66 -0
- package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
- package/src/__tests__/unit/segmented-control.test.ts +152 -0
- package/src/__tests__/utils/env.test.ts +169 -0
- package/src/__tests__/utils/project-picker.test.ts +39 -0
- package/src/agents/bundled-agents.generated.d.ts +14 -0
- package/src/agents/bundled-agents.generated.ts +3692 -0
- package/src/app.tsx +399 -0
- package/src/chat.tsx +1698 -0
- package/src/cli-args.ts +134 -0
- package/src/commands/__tests__/bash-command.test.ts +445 -0
- package/src/commands/__tests__/command-args.test.ts +349 -0
- package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
- package/src/commands/__tests__/copy-conversation.test.ts +226 -0
- package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
- package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
- package/src/commands/__tests__/image.test.ts +99 -0
- package/src/commands/__tests__/init.test.ts +456 -0
- package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
- package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
- package/src/commands/__tests__/prompt-builders.test.ts +28 -0
- package/src/commands/__tests__/router-input.test.ts +298 -0
- package/src/commands/ads.ts +44 -0
- package/src/commands/command-registry.ts +726 -0
- package/src/commands/copy-conversation.ts +392 -0
- package/src/commands/help.ts +13 -0
- package/src/commands/image.ts +20 -0
- package/src/commands/init.ts +121 -0
- package/src/commands/process-diagnostics.ts +120 -0
- package/src/commands/prompt-builders.ts +100 -0
- package/src/commands/publish.ts +203 -0
- package/src/commands/router-utils.ts +80 -0
- package/src/commands/router.ts +487 -0
- package/src/commands/usage.ts +26 -0
- package/src/components/__tests__/ad-banner.test.tsx +83 -0
- package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
- package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
- package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
- package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
- package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
- package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
- package/src/components/__tests__/grid-layout.test.tsx +1051 -0
- package/src/components/__tests__/message-block.completion.test.tsx +104 -0
- package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
- package/src/components/__tests__/message-with-agents.test.tsx +579 -0
- package/src/components/__tests__/multiline-input.test.tsx +1132 -0
- package/src/components/__tests__/queue-panel.test.tsx +398 -0
- package/src/components/__tests__/selectable-list.test.ts +232 -0
- package/src/components/__tests__/status-bar.test.tsx +44 -0
- package/src/components/__tests__/status-indicator.test.tsx +204 -0
- package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
- package/src/components/__tests__/user-error-banner.test.tsx +102 -0
- package/src/components/ad-banner.tsx +323 -0
- package/src/components/agent-checklist.tsx +350 -0
- package/src/components/agent-mode-toggle.tsx +241 -0
- package/src/components/anvil-mark.tsx +52 -0
- package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
- package/src/components/ask-user/__tests__/validation.test.ts +213 -0
- package/src/components/ask-user/components/accordion-question.tsx +153 -0
- package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
- package/src/components/ask-user/components/options-list.tsx +133 -0
- package/src/components/ask-user/components/question-header.tsx +80 -0
- package/src/components/ask-user/components/question-option.tsx +89 -0
- package/src/components/ask-user/constants.ts +38 -0
- package/src/components/ask-user/index.tsx +631 -0
- package/src/components/ask-user/utils/validation.ts +85 -0
- package/src/components/attachment-card.tsx +65 -0
- package/src/components/blocks/agent-block-grid.tsx +58 -0
- package/src/components/blocks/agent-branch-item.tsx +311 -0
- package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
- package/src/components/blocks/agent-list-branch.tsx +77 -0
- package/src/components/blocks/ask-user-branch.tsx +81 -0
- package/src/components/blocks/block-helpers.ts +11 -0
- package/src/components/blocks/blocks-renderer.tsx +289 -0
- package/src/components/blocks/content-with-markdown.tsx +33 -0
- package/src/components/blocks/image-block.tsx +128 -0
- package/src/components/blocks/implementor-row.tsx +429 -0
- package/src/components/blocks/single-block.tsx +201 -0
- package/src/components/blocks/thinking-block.tsx +67 -0
- package/src/components/blocks/tool-block-group.tsx +57 -0
- package/src/components/blocks/tool-branch.tsx +206 -0
- package/src/components/blocks/user-content-copy.tsx +157 -0
- package/src/components/bottom-banner.tsx +136 -0
- package/src/components/build-mode-buttons.tsx +110 -0
- package/src/components/button.tsx +66 -0
- package/src/components/chat-header.tsx +77 -0
- package/src/components/chat-history-screen.tsx +413 -0
- package/src/components/chat-input-bar.tsx +523 -0
- package/src/components/clickable-title-box.tsx +66 -0
- package/src/components/clickable.tsx +119 -0
- package/src/components/collapse-button.tsx +34 -0
- package/src/components/copy-button.tsx +203 -0
- package/src/components/elapsed-timer.tsx +55 -0
- package/src/components/error-boundary.tsx +55 -0
- package/src/components/feedback-container.tsx +181 -0
- package/src/components/feedback-icon-button.tsx +89 -0
- package/src/components/feedback-input-mode.tsx +344 -0
- package/src/components/file-attachment-card.tsx +98 -0
- package/src/components/freebuff-active-session-summary.tsx +63 -0
- package/src/components/freebuff-landing-screen.tsx +865 -0
- package/src/components/freebuff-model-selector.tsx +1087 -0
- package/src/components/freebuff-referral-banner.tsx +604 -0
- package/src/components/freebuff-superseded-screen.tsx +62 -0
- package/src/components/grid-layout.tsx +80 -0
- package/src/components/help-banner.tsx +127 -0
- package/src/components/highlighted-text.tsx +52 -0
- package/src/components/image-card.tsx +167 -0
- package/src/components/image-thumbnail.tsx +114 -0
- package/src/components/input-cursor.tsx +77 -0
- package/src/components/input-mode-banner.tsx +57 -0
- package/src/components/load-previous-button.tsx +48 -0
- package/src/components/login-modal.tsx +475 -0
- package/src/components/message-block.tsx +346 -0
- package/src/components/message-footer.tsx +258 -0
- package/src/components/message-with-agents.tsx +506 -0
- package/src/components/mode-divider.tsx +43 -0
- package/src/components/multiline-input.tsx +1241 -0
- package/src/components/out-of-credits-banner.tsx +160 -0
- package/src/components/pending-attachments-banner.tsx +116 -0
- package/src/components/pending-bash-message.tsx +59 -0
- package/src/components/progress-bar.tsx +81 -0
- package/src/components/project-picker-screen.tsx +485 -0
- package/src/components/publish-confirmation.tsx +493 -0
- package/src/components/publish-container.tsx +687 -0
- package/src/components/queue-panel.tsx +310 -0
- package/src/components/raised-pill.tsx +95 -0
- package/src/components/renderers/plan-box.tsx +59 -0
- package/src/components/review-screen.tsx +112 -0
- package/src/components/scroll-to-bottom-button.tsx +34 -0
- package/src/components/segmented-control.tsx +187 -0
- package/src/components/selectable-list.tsx +248 -0
- package/src/components/selected-chips.tsx +80 -0
- package/src/components/separator.tsx +40 -0
- package/src/components/session-ended-banner.tsx +226 -0
- package/src/components/shimmer-text.tsx +248 -0
- package/src/components/status-bar.tsx +313 -0
- package/src/components/subscription-limit-banner.tsx +206 -0
- package/src/components/suggested-prompts.tsx +184 -0
- package/src/components/suggestion-menu.tsx +215 -0
- package/src/components/terminal-command-display.tsx +151 -0
- package/src/components/terminal-link.tsx +97 -0
- package/src/components/terminal-thinking-orb.tsx +95 -0
- package/src/components/text-attachment-card.tsx +77 -0
- package/src/components/thinking.tsx +109 -0
- package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
- package/src/components/tools/__tests__/code-search.test.tsx +45 -0
- package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
- package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
- package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
- package/src/components/tools/apply-patch.tsx +95 -0
- package/src/components/tools/code-search.tsx +45 -0
- package/src/components/tools/composio.tsx +152 -0
- package/src/components/tools/diff-viewer.tsx +76 -0
- package/src/components/tools/glob.tsx +61 -0
- package/src/components/tools/gravity-index.tsx +77 -0
- package/src/components/tools/list-directory.tsx +61 -0
- package/src/components/tools/read-docs.tsx +37 -0
- package/src/components/tools/read-files.tsx +91 -0
- package/src/components/tools/read-subtree.tsx +44 -0
- package/src/components/tools/read-url.tsx +33 -0
- package/src/components/tools/registry.ts +126 -0
- package/src/components/tools/render-ui.tsx +122 -0
- package/src/components/tools/run-terminal-command.tsx +79 -0
- package/src/components/tools/skill.tsx +29 -0
- package/src/components/tools/str-replace.tsx +77 -0
- package/src/components/tools/suggest-followups.tsx +369 -0
- package/src/components/tools/task-completed.tsx +16 -0
- package/src/components/tools/tool-call-item.tsx +273 -0
- package/src/components/tools/types.ts +55 -0
- package/src/components/tools/web-search.tsx +33 -0
- package/src/components/tools/write-file.tsx +16 -0
- package/src/components/tools/write-todos.tsx +95 -0
- package/src/components/top-banner.tsx +178 -0
- package/src/components/usage-banner.tsx +244 -0
- package/src/components/user-error-banner.tsx +56 -0
- package/src/components/validation-error-popover.tsx +192 -0
- package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
- package/src/contexts/chat-runtime-context.tsx +240 -0
- package/src/data/slash-commands.test.ts +24 -0
- package/src/data/slash-commands.ts +249 -0
- package/src/entry.ts +12 -0
- package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
- package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
- package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
- package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
- package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
- package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
- package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
- package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
- package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
- package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
- package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
- package/src/hooks/__tests__/use-input-history.test.ts +699 -0
- package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
- package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
- package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
- package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
- package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
- package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
- package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
- package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
- package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
- package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
- package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
- package/src/hooks/__tests__/use-timeout.test.ts +330 -0
- package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
- package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
- package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
- package/src/hooks/helpers/send-message.ts +654 -0
- package/src/hooks/stream-state.ts +83 -0
- package/src/hooks/use-activity-query.ts +668 -0
- package/src/hooks/use-agent-validation.ts +74 -0
- package/src/hooks/use-ask-user-bridge.ts +92 -0
- package/src/hooks/use-auth-query.ts +253 -0
- package/src/hooks/use-auth-state.ts +152 -0
- package/src/hooks/use-chat-input.ts +105 -0
- package/src/hooks/use-chat-keyboard.ts +321 -0
- package/src/hooks/use-chat-messages.ts +254 -0
- package/src/hooks/use-chat-state.ts +165 -0
- package/src/hooks/use-chat-streaming.ts +180 -0
- package/src/hooks/use-chat-ui.ts +131 -0
- package/src/hooks/use-clipboard.ts +151 -0
- package/src/hooks/use-connection-status.ts +142 -0
- package/src/hooks/use-directory-browser.ts +88 -0
- package/src/hooks/use-elapsed-time.ts +116 -0
- package/src/hooks/use-event.ts +34 -0
- package/src/hooks/use-exit-handler.ts +66 -0
- package/src/hooks/use-fetch-login-url.ts +65 -0
- package/src/hooks/use-fingerprint.ts +61 -0
- package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
- package/src/hooks/use-freebuff-session-progress.ts +34 -0
- package/src/hooks/use-freebuff-session.ts +806 -0
- package/src/hooks/use-freebuff-streak-query.ts +61 -0
- package/src/hooks/use-gravity-ad.ts +656 -0
- package/src/hooks/use-grid-layout.ts +73 -0
- package/src/hooks/use-input-history.ts +173 -0
- package/src/hooks/use-login-keyboard-handlers.ts +75 -0
- package/src/hooks/use-login-polling.ts +114 -0
- package/src/hooks/use-logo.tsx +161 -0
- package/src/hooks/use-message-queue.ts +387 -0
- package/src/hooks/use-now.ts +20 -0
- package/src/hooks/use-path-tab-completion.ts +88 -0
- package/src/hooks/use-publish-mutation.ts +60 -0
- package/src/hooks/use-queue-controls.ts +68 -0
- package/src/hooks/use-queue-ui.ts +74 -0
- package/src/hooks/use-scroll-management.ts +184 -0
- package/src/hooks/use-searchable-list.ts +98 -0
- package/src/hooks/use-send-message.ts +773 -0
- package/src/hooks/use-sheen-animation.tsx +88 -0
- package/src/hooks/use-subscription-query.ts +71 -0
- package/src/hooks/use-suggestion-engine.ts +783 -0
- package/src/hooks/use-terminal-breakpoints.ts +77 -0
- package/src/hooks/use-terminal-dimensions.ts +46 -0
- package/src/hooks/use-terminal-focus.ts +29 -0
- package/src/hooks/use-terminal-layout.ts +189 -0
- package/src/hooks/use-theme.tsx +140 -0
- package/src/hooks/use-timeout.ts +85 -0
- package/src/hooks/use-update-preference.ts +66 -0
- package/src/hooks/use-usage-monitor.ts +55 -0
- package/src/hooks/use-usage-query.ts +124 -0
- package/src/hooks/use-user-details-query.ts +93 -0
- package/src/hooks/use-why-did-you-update.ts +177 -0
- package/src/index.tsx +441 -0
- package/src/init/__tests__/init-direnv.test.ts +526 -0
- package/src/init/init-app.ts +34 -0
- package/src/init/init-direnv.ts +133 -0
- package/src/login/constants.ts +60 -0
- package/src/login/login-flow.ts +204 -0
- package/src/login/plain-login.ts +108 -0
- package/src/login/utils.ts +183 -0
- package/src/native/ripgrep.ts +80 -0
- package/src/polyfills/bun-strip-ansi.ts +12 -0
- package/src/pre-init/tree-sitter-wasm.ts +96 -0
- package/src/project-files.ts +102 -0
- package/src/smoke/terminal-command-broker.ts +439 -0
- package/src/state/__tests__/chat-store-focus.test.ts +22 -0
- package/src/state/__tests__/feedback-store.test.ts +257 -0
- package/src/state/chat-history-store.ts +30 -0
- package/src/state/chat-store.ts +544 -0
- package/src/state/feedback-store.ts +160 -0
- package/src/state/freebuff-model-store.ts +42 -0
- package/src/state/freebuff-session-store.ts +53 -0
- package/src/state/login-store.ts +150 -0
- package/src/state/message-block-store.ts +149 -0
- package/src/state/publish-store.ts +146 -0
- package/src/state/queue-panel-store.ts +26 -0
- package/src/state/review-store.ts +24 -0
- package/src/testing/env.ts +46 -0
- package/src/types/chat-state.ts +15 -0
- package/src/types/chat.ts +241 -0
- package/src/types/contracts/send-message.ts +12 -0
- package/src/types/env.ts +98 -0
- package/src/types/freebuff-session.ts +17 -0
- package/src/types/function-params.ts +28 -0
- package/src/types/react19-compat.d.ts +19 -0
- package/src/types/store.ts +111 -0
- package/src/types/theme-system.ts +144 -0
- package/src/types/utils.ts +3 -0
- package/src/utils/__tests__/active-run.test.ts +140 -0
- package/src/utils/__tests__/activity-tracker.test.ts +440 -0
- package/src/utils/__tests__/agent-display.test.ts +139 -0
- package/src/utils/__tests__/analytics-client.test.ts +262 -0
- package/src/utils/__tests__/anonymous-id.test.ts +74 -0
- package/src/utils/__tests__/anvil-api.test.ts +578 -0
- package/src/utils/__tests__/arrays.test.ts +264 -0
- package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
- package/src/utils/__tests__/block-processor.test.ts +810 -0
- package/src/utils/__tests__/chat-history.test.ts +203 -0
- package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
- package/src/utils/__tests__/chat-meta.test.ts +130 -0
- package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
- package/src/utils/__tests__/clipboard.test.ts +910 -0
- package/src/utils/__tests__/code-search-summary.test.ts +84 -0
- package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
- package/src/utils/__tests__/error-handling.test.ts +658 -0
- package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
- package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
- package/src/utils/__tests__/feedback-submission.test.ts +26 -0
- package/src/utils/__tests__/fetch-usage.test.ts +355 -0
- package/src/utils/__tests__/fingerprint.test.ts +144 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
- package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
- package/src/utils/__tests__/format-timeout.test.ts +87 -0
- package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
- package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
- package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
- package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
- package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
- package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
- package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
- package/src/utils/__tests__/image-dimensions.test.ts +249 -0
- package/src/utils/__tests__/image-processor.test.ts +209 -0
- package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
- package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
- package/src/utils/__tests__/layout-helpers.test.ts +36 -0
- package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
- package/src/utils/__tests__/log-shipper.test.ts +53 -0
- package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
- package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
- package/src/utils/__tests__/message-updater.test.ts +645 -0
- package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
- package/src/utils/__tests__/pending-attachments.test.ts +277 -0
- package/src/utils/__tests__/polling-backoff.test.ts +51 -0
- package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
- package/src/utils/__tests__/run-state-storage.test.ts +919 -0
- package/src/utils/__tests__/safe-json.test.ts +77 -0
- package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
- package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
- package/src/utils/__tests__/send-message-timer.test.ts +64 -0
- package/src/utils/__tests__/settings.test.ts +99 -0
- package/src/utils/__tests__/strings.test.ts +192 -0
- package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
- package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
- package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
- package/src/utils/__tests__/terminal-io.test.ts +137 -0
- package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
- package/src/utils/__tests__/text-layout.test.ts +82 -0
- package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
- package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
- package/src/utils/__tests__/trace-writer.test.ts +223 -0
- package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
- package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
- package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
- package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
- package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
- package/src/utils/active-run.ts +131 -0
- package/src/utils/activity-tracker.ts +66 -0
- package/src/utils/agent-display.ts +87 -0
- package/src/utils/agent-helpers.ts +27 -0
- package/src/utils/agent-id-utils.ts +14 -0
- package/src/utils/ai-message-id.ts +12 -0
- package/src/utils/analytics.ts +361 -0
- package/src/utils/anonymous-id.ts +87 -0
- package/src/utils/anvil-api.ts +612 -0
- package/src/utils/anvil-client.ts +217 -0
- package/src/utils/arrays.ts +117 -0
- package/src/utils/auth.ts +258 -0
- package/src/utils/bash-context-processor.ts +47 -0
- package/src/utils/bash-messages.ts +108 -0
- package/src/utils/block-margins.ts +35 -0
- package/src/utils/block-operations.ts +523 -0
- package/src/utils/block-processor.ts +213 -0
- package/src/utils/chat-history.ts +253 -0
- package/src/utils/chat-input-key-intercept.ts +52 -0
- package/src/utils/chat-meta.ts +90 -0
- package/src/utils/chat-scroll-accel.ts +151 -0
- package/src/utils/clipboard-image.ts +588 -0
- package/src/utils/clipboard.ts +397 -0
- package/src/utils/code-search-summary.ts +70 -0
- package/src/utils/collapse-helpers.ts +256 -0
- package/src/utils/config-dir.ts +24 -0
- package/src/utils/constants.ts +190 -0
- package/src/utils/create-event-handler-state.ts +73 -0
- package/src/utils/create-run-config.ts +121 -0
- package/src/utils/detect-shell.ts +116 -0
- package/src/utils/directory-browser.ts +72 -0
- package/src/utils/engagement.ts +55 -0
- package/src/utils/env.ts +92 -0
- package/src/utils/error-handling.ts +255 -0
- package/src/utils/error-messages.ts +62 -0
- package/src/utils/exit-cleanly.ts +86 -0
- package/src/utils/feedback-helpers.ts +103 -0
- package/src/utils/feedback-submission.ts +22 -0
- package/src/utils/fetch-usage.ts +76 -0
- package/src/utils/fingerprint.ts +245 -0
- package/src/utils/format-elapsed-time.ts +33 -0
- package/src/utils/format-session-units.ts +6 -0
- package/src/utils/format-timeout.ts +28 -0
- package/src/utils/format-validation-errors-for-message.ts +84 -0
- package/src/utils/freebuff-agent-selection.ts +33 -0
- package/src/utils/freebuff-instance-owner.ts +66 -0
- package/src/utils/freebuff-model-navigation.ts +50 -0
- package/src/utils/freebuff-premium-reset.ts +55 -0
- package/src/utils/freebuff-referral-cache.ts +51 -0
- package/src/utils/freebuff-session-api.ts +243 -0
- package/src/utils/freebuff-session-display.ts +21 -0
- package/src/utils/freebuff-streak-line.ts +93 -0
- package/src/utils/git.ts +17 -0
- package/src/utils/helpers.ts +47 -0
- package/src/utils/image-display.ts +67 -0
- package/src/utils/image-handler.ts +325 -0
- package/src/utils/image-processor.ts +133 -0
- package/src/utils/image-thumbnail.ts +78 -0
- package/src/utils/implementor-helpers.ts +787 -0
- package/src/utils/input-modes.ts +174 -0
- package/src/utils/keyboard-actions.ts +392 -0
- package/src/utils/keypad-keys.ts +47 -0
- package/src/utils/layout-helpers.ts +33 -0
- package/src/utils/lazy-response-ads.ts +1 -0
- package/src/utils/local-agent-registry.ts +474 -0
- package/src/utils/log-shipper.ts +126 -0
- package/src/utils/logger.ts +327 -0
- package/src/utils/markdown-renderer.tsx +1090 -0
- package/src/utils/math.ts +3 -0
- package/src/utils/message-block-helpers.ts +684 -0
- package/src/utils/message-history.ts +143 -0
- package/src/utils/message-tree-utils.ts +67 -0
- package/src/utils/message-updater.ts +279 -0
- package/src/utils/open-file.ts +146 -0
- package/src/utils/open-url.ts +63 -0
- package/src/utils/path-completion.ts +111 -0
- package/src/utils/path-helpers.ts +38 -0
- package/src/utils/pending-attachments.ts +360 -0
- package/src/utils/polling-backoff.ts +59 -0
- package/src/utils/project-picker.ts +12 -0
- package/src/utils/queue-panel-actions.ts +70 -0
- package/src/utils/recent-projects.ts +156 -0
- package/src/utils/renderer-cleanup.ts +233 -0
- package/src/utils/response-ad-positions.ts +1 -0
- package/src/utils/run-state-storage.ts +603 -0
- package/src/utils/safe-json.ts +153 -0
- package/src/utils/sdk-event-handlers.ts +571 -0
- package/src/utils/send-message-helpers.ts +210 -0
- package/src/utils/send-message-timer.ts +110 -0
- package/src/utils/settings.ts +226 -0
- package/src/utils/skill-registry.ts +94 -0
- package/src/utils/spawn-agent-matcher.ts +55 -0
- package/src/utils/status-indicator-state.ts +107 -0
- package/src/utils/stream-chunk-processor.ts +57 -0
- package/src/utils/strings.ts +271 -0
- package/src/utils/subscription.ts +31 -0
- package/src/utils/syntax-highlighter.tsx +19 -0
- package/src/utils/terminal-color-detection.ts +474 -0
- package/src/utils/terminal-command-broker.ts +467 -0
- package/src/utils/terminal-enter-detection.ts +63 -0
- package/src/utils/terminal-images.ts +229 -0
- package/src/utils/terminal-io.ts +85 -0
- package/src/utils/terminal-protocol-controller.ts +173 -0
- package/src/utils/terminal-reset-sequences.ts +33 -0
- package/src/utils/terminal-title.ts +84 -0
- package/src/utils/terminal-watchdog.ts +366 -0
- package/src/utils/text-layout.ts +149 -0
- package/src/utils/theme-config.ts +141 -0
- package/src/utils/theme-system.ts +1206 -0
- package/src/utils/think-tag-parser.ts +95 -0
- package/src/utils/time-format.ts +21 -0
- package/src/utils/trace-writer.ts +162 -0
- package/src/utils/ui-constants.ts +69 -0
- package/src/utils/update-checker.ts +110 -0
- package/src/utils/usage-banner-state.ts +146 -0
- package/src/utils/validation-error-formatting.ts +86 -0
- package/src/utils/validation-error-helpers.ts +19 -0
- package/src/utils/windows-terminal-health.ts +124 -0
- package/src/utils/word-wrap-utils.ts +53 -0
- package/src/utils/write-file-atomic.ts +54 -0
- package/src/utils/yield-to-event-loop.ts +9 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal image rendering utilities
|
|
3
|
+
* Supports iTerm2 inline images protocol and Kitty graphics protocol
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getCliEnv } from './env'
|
|
7
|
+
|
|
8
|
+
import type { CliEnv } from '../types/env'
|
|
9
|
+
|
|
10
|
+
export type TerminalImageProtocol = 'iterm2' | 'kitty' | 'sixel' | 'none'
|
|
11
|
+
|
|
12
|
+
let cachedProtocol: TerminalImageProtocol | null = null
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect which image protocol the terminal supports
|
|
16
|
+
*/
|
|
17
|
+
export function detectTerminalImageSupport(
|
|
18
|
+
env: CliEnv = getCliEnv(),
|
|
19
|
+
): TerminalImageProtocol {
|
|
20
|
+
if (cachedProtocol !== null) {
|
|
21
|
+
return cachedProtocol
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check for iTerm2
|
|
25
|
+
if (env.TERM_PROGRAM === 'iTerm.app') {
|
|
26
|
+
cachedProtocol = 'iterm2'
|
|
27
|
+
return cachedProtocol
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check for Kitty
|
|
31
|
+
if (
|
|
32
|
+
env.TERM === 'xterm-kitty' ||
|
|
33
|
+
env.KITTY_WINDOW_ID !== undefined
|
|
34
|
+
) {
|
|
35
|
+
cachedProtocol = 'kitty'
|
|
36
|
+
return cachedProtocol
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check for Sixel support (less common)
|
|
40
|
+
if (
|
|
41
|
+
env.TERM?.includes('sixel') ||
|
|
42
|
+
env.SIXEL_SUPPORT === 'true'
|
|
43
|
+
) {
|
|
44
|
+
cachedProtocol = 'sixel'
|
|
45
|
+
return cachedProtocol
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
cachedProtocol = 'none'
|
|
49
|
+
return cachedProtocol
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if terminal supports inline images
|
|
54
|
+
*/
|
|
55
|
+
export function supportsInlineImages(): boolean {
|
|
56
|
+
return detectTerminalImageSupport() !== 'none'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate iTerm2 inline image escape sequence
|
|
61
|
+
* @param base64Data - Base64 encoded image data
|
|
62
|
+
* @param options - Display options
|
|
63
|
+
*/
|
|
64
|
+
function generateITerm2ImageSequence(
|
|
65
|
+
base64Data: string,
|
|
66
|
+
options: {
|
|
67
|
+
width?: number | string // cells or 'auto'
|
|
68
|
+
height?: number | string // cells or 'auto'
|
|
69
|
+
preserveAspectRatio?: boolean
|
|
70
|
+
inline?: boolean
|
|
71
|
+
name?: string
|
|
72
|
+
} = {},
|
|
73
|
+
): string {
|
|
74
|
+
const {
|
|
75
|
+
width = 'auto',
|
|
76
|
+
height = 'auto',
|
|
77
|
+
preserveAspectRatio = true,
|
|
78
|
+
inline = true,
|
|
79
|
+
name,
|
|
80
|
+
} = options
|
|
81
|
+
|
|
82
|
+
// Build the parameter string
|
|
83
|
+
const params: string[] = []
|
|
84
|
+
|
|
85
|
+
if (inline) {
|
|
86
|
+
params.push('inline=1')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (width !== 'auto') {
|
|
90
|
+
params.push(`width=${width}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (height !== 'auto') {
|
|
94
|
+
params.push(`height=${height}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!preserveAspectRatio) {
|
|
98
|
+
params.push('preserveAspectRatio=0')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (name) {
|
|
102
|
+
params.push(`name=${Buffer.from(name).toString('base64')}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add size parameter (required)
|
|
106
|
+
params.push(`size=${base64Data.length}`)
|
|
107
|
+
|
|
108
|
+
const paramString = params.join(';')
|
|
109
|
+
|
|
110
|
+
// Format: ESC ] 1337 ; File = [params] : base64data BEL
|
|
111
|
+
// Using \x1b for ESC and \x07 for BEL
|
|
112
|
+
return `\x1b]1337;File=${paramString}:${base64Data}\x07`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generate Kitty graphics protocol escape sequence
|
|
117
|
+
* @param base64Data - Base64 encoded image data
|
|
118
|
+
* @param options - Display options
|
|
119
|
+
*/
|
|
120
|
+
function generateKittyImageSequence(
|
|
121
|
+
base64Data: string,
|
|
122
|
+
options: {
|
|
123
|
+
width?: number // cells
|
|
124
|
+
height?: number // cells
|
|
125
|
+
id?: number
|
|
126
|
+
} = {},
|
|
127
|
+
): string {
|
|
128
|
+
const { width, height, id } = options
|
|
129
|
+
|
|
130
|
+
// Build key-value pairs for the control data
|
|
131
|
+
const kvPairs: string[] = [
|
|
132
|
+
'a=T', // action: transmit and display
|
|
133
|
+
'f=100', // format: PNG (100) - let Kitty auto-detect
|
|
134
|
+
't=d', // transmission: direct (data follows)
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
if (width) {
|
|
138
|
+
kvPairs.push(`c=${width}`) // columns
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (height) {
|
|
142
|
+
kvPairs.push(`r=${height}`) // rows
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (id) {
|
|
146
|
+
kvPairs.push(`i=${id}`) // image id
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const controlData = kvPairs.join(',')
|
|
150
|
+
|
|
151
|
+
// Kitty requires chunked transmission for large images
|
|
152
|
+
// For simplicity, we'll send in one chunk if small enough
|
|
153
|
+
const CHUNK_SIZE = 4096
|
|
154
|
+
|
|
155
|
+
if (base64Data.length <= CHUNK_SIZE) {
|
|
156
|
+
// Single chunk: ESC _ G <control> ; <data> ESC \
|
|
157
|
+
return `\x1b_G${controlData};${base64Data}\x1b\\`
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Multi-chunk transmission
|
|
161
|
+
const chunks: string[] = []
|
|
162
|
+
for (let i = 0; i < base64Data.length; i += CHUNK_SIZE) {
|
|
163
|
+
const chunk = base64Data.slice(i, i + CHUNK_SIZE)
|
|
164
|
+
const isLast = i + CHUNK_SIZE >= base64Data.length
|
|
165
|
+
const chunkControl = isLast ? controlData : `${controlData},m=1` // m=1 means more chunks coming
|
|
166
|
+
chunks.push(`\x1b_G${chunkControl};${chunk}\x1b\\`)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return chunks.join('')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Render an image inline in the terminal
|
|
174
|
+
* @param base64Data - Base64 encoded image data
|
|
175
|
+
* @param options - Display options
|
|
176
|
+
* @returns The escape sequence string, or null if not supported
|
|
177
|
+
*/
|
|
178
|
+
export function renderInlineImage(
|
|
179
|
+
base64Data: string,
|
|
180
|
+
options: {
|
|
181
|
+
width?: number
|
|
182
|
+
height?: number
|
|
183
|
+
filename?: string
|
|
184
|
+
} = {},
|
|
185
|
+
): string | null {
|
|
186
|
+
const protocol = detectTerminalImageSupport()
|
|
187
|
+
|
|
188
|
+
switch (protocol) {
|
|
189
|
+
case 'iterm2':
|
|
190
|
+
return generateITerm2ImageSequence(base64Data, {
|
|
191
|
+
width: options.width,
|
|
192
|
+
height: options.height,
|
|
193
|
+
name: options.filename,
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
case 'kitty':
|
|
197
|
+
return generateKittyImageSequence(base64Data, {
|
|
198
|
+
width: options.width,
|
|
199
|
+
height: options.height,
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
case 'sixel':
|
|
203
|
+
// Sixel is more complex and requires actual image decoding
|
|
204
|
+
// For now, return null and fall back to metadata display
|
|
205
|
+
return null
|
|
206
|
+
|
|
207
|
+
case 'none':
|
|
208
|
+
default:
|
|
209
|
+
return null
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get a user-friendly description of the terminal image support
|
|
215
|
+
*/
|
|
216
|
+
export function getImageSupportDescription(): string {
|
|
217
|
+
const protocol = detectTerminalImageSupport()
|
|
218
|
+
|
|
219
|
+
switch (protocol) {
|
|
220
|
+
case 'iterm2':
|
|
221
|
+
return 'iTerm2 inline images'
|
|
222
|
+
case 'kitty':
|
|
223
|
+
return 'Kitty graphics protocol'
|
|
224
|
+
case 'sixel':
|
|
225
|
+
return 'Sixel graphics'
|
|
226
|
+
case 'none':
|
|
227
|
+
return 'No inline image support'
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { closeSync, constants, openSync, writeSync } from 'fs'
|
|
2
|
+
|
|
3
|
+
/** Write every byte to an already-open file descriptor before returning. */
|
|
4
|
+
export function writeFileDescriptorSync(fd: number, value: string): boolean {
|
|
5
|
+
try {
|
|
6
|
+
const bytes = Buffer.from(value)
|
|
7
|
+
let offset = 0
|
|
8
|
+
while (offset < bytes.length) {
|
|
9
|
+
const written = writeSync(fd, bytes, offset, bytes.length - offset)
|
|
10
|
+
if (written === 0) return false
|
|
11
|
+
offset += written
|
|
12
|
+
}
|
|
13
|
+
return true
|
|
14
|
+
} catch {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Report a fatal error without relying on async logging or console capture. */
|
|
20
|
+
export function reportFatalErrorSync(
|
|
21
|
+
label: string,
|
|
22
|
+
error: unknown,
|
|
23
|
+
stderrFd: number = process.stderr.fd,
|
|
24
|
+
): void {
|
|
25
|
+
const detail = formatFatalError(error)
|
|
26
|
+
if (writeFileDescriptorSync(stderrFd, `${label}: ${detail}\n`)) return
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
console.error(`${label}:`, error)
|
|
30
|
+
} catch {
|
|
31
|
+
// The process is already failing and stderr may be closed.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Fatal boundaries must also survive values whose inspection itself throws. */
|
|
36
|
+
function formatFatalError(error: unknown): string {
|
|
37
|
+
try {
|
|
38
|
+
if (error instanceof Error) {
|
|
39
|
+
for (const property of ['stack', 'message', 'name'] as const) {
|
|
40
|
+
try {
|
|
41
|
+
const value = error[property]
|
|
42
|
+
if (!value) continue
|
|
43
|
+
const formatted = stringifyFatalValue(value)
|
|
44
|
+
if (formatted !== undefined) return formatted
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch {}
|
|
49
|
+
return stringifyFatalValue(error) ?? '<unprintable error>'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function stringifyFatalValue(value: unknown): string | undefined {
|
|
53
|
+
try {
|
|
54
|
+
return String(value)
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Write terminal control bytes synchronously to the controlling terminal.
|
|
62
|
+
* OpenTUI may capture stdout, so control sequences use /dev/tty (or CON on
|
|
63
|
+
* Windows) instead of the process.stdout stream.
|
|
64
|
+
*/
|
|
65
|
+
export function writeTerminalControlSync(
|
|
66
|
+
value: string,
|
|
67
|
+
ttyPath = process.platform === 'win32' ? 'CON' : '/dev/tty',
|
|
68
|
+
): boolean {
|
|
69
|
+
let fd: number | null = null
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
fd = openSync(ttyPath, constants.O_WRONLY)
|
|
73
|
+
return writeFileDescriptorSync(fd, value)
|
|
74
|
+
} catch {
|
|
75
|
+
return false
|
|
76
|
+
} finally {
|
|
77
|
+
if (fd !== null) {
|
|
78
|
+
try {
|
|
79
|
+
closeSync(fd)
|
|
80
|
+
} catch {
|
|
81
|
+
// The process is exiting; there is nothing useful to recover here.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { writeTerminalControlSync } from './terminal-io'
|
|
2
|
+
|
|
3
|
+
import type { CliRenderer } from '@opentui/core'
|
|
4
|
+
|
|
5
|
+
const ENABLE_FOCUS_REPORTING = '\x1b[?1004h'
|
|
6
|
+
const DISABLE_FOCUS_REPORTING = '\x1b[?1004l'
|
|
7
|
+
const FOCUS_EVENT_RE = /\x1b\[(I|O)/g
|
|
8
|
+
type TerminalProtocolRenderer = Pick<
|
|
9
|
+
CliRenderer,
|
|
10
|
+
'prependInputHandler' | 'removeInputHandler'
|
|
11
|
+
>
|
|
12
|
+
|
|
13
|
+
type FocusSubscriber = {
|
|
14
|
+
onFocusChange: (focused: boolean) => void
|
|
15
|
+
onSupportDetected?: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TerminalProtocolControllerOptions {
|
|
19
|
+
writeControl?: (sequence: string) => boolean
|
|
20
|
+
onError?: (error: unknown) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Return the last complete focus report in a terminal input sequence. */
|
|
24
|
+
export function parseFocusState(data: string): boolean | null {
|
|
25
|
+
if (!data.includes('\x1b[')) return null
|
|
26
|
+
|
|
27
|
+
let focused: boolean | null = null
|
|
28
|
+
FOCUS_EVENT_RE.lastIndex = 0
|
|
29
|
+
let match: RegExpExecArray | null
|
|
30
|
+
while ((match = FOCUS_EVENT_RE.exec(data)) !== null) {
|
|
31
|
+
focused = match[1] === 'I'
|
|
32
|
+
}
|
|
33
|
+
return focused
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Single owner for terminal protocols that can emit unsolicited input.
|
|
38
|
+
*
|
|
39
|
+
* OpenTUI owns normal rendering and mouse input. This controller only enables,
|
|
40
|
+
* parses, and disables focus reports for subscribers.
|
|
41
|
+
*/
|
|
42
|
+
export class TerminalProtocolController {
|
|
43
|
+
private readonly writeControl: (sequence: string) => boolean
|
|
44
|
+
private readonly onError: (error: unknown) => void
|
|
45
|
+
private readonly focusSubscribers = new Set<FocusSubscriber>()
|
|
46
|
+
private focusSupported = false
|
|
47
|
+
private lastFocusState: boolean | null = null
|
|
48
|
+
private disposed = false
|
|
49
|
+
|
|
50
|
+
constructor(
|
|
51
|
+
private readonly renderer: TerminalProtocolRenderer,
|
|
52
|
+
options: TerminalProtocolControllerOptions = {},
|
|
53
|
+
) {
|
|
54
|
+
this.writeControl = options.writeControl ?? writeTerminalControlSync
|
|
55
|
+
this.onError = options.onError ?? (() => {})
|
|
56
|
+
renderer.prependInputHandler(this.handleInput)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private reportError(error: unknown): void {
|
|
60
|
+
try {
|
|
61
|
+
this.onError(error)
|
|
62
|
+
} catch {
|
|
63
|
+
// Diagnostics must never turn a recoverable terminal transition into an
|
|
64
|
+
// input-handler or command-lifecycle failure.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private setFocusReporting(enabled: boolean, failureMessage: string): boolean {
|
|
69
|
+
try {
|
|
70
|
+
if (
|
|
71
|
+
this.writeControl(
|
|
72
|
+
enabled ? ENABLE_FOCUS_REPORTING : DISABLE_FOCUS_REPORTING,
|
|
73
|
+
)
|
|
74
|
+
) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
this.reportError(new Error(failureMessage))
|
|
78
|
+
} catch (error) {
|
|
79
|
+
this.reportError(error)
|
|
80
|
+
}
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private readonly handleInput = (sequence: string): boolean => {
|
|
85
|
+
const focused = parseFocusState(sequence)
|
|
86
|
+
if (focused === null) return false
|
|
87
|
+
|
|
88
|
+
if (!this.focusSupported) {
|
|
89
|
+
this.focusSupported = true
|
|
90
|
+
for (const subscriber of this.focusSubscribers) {
|
|
91
|
+
try {
|
|
92
|
+
subscriber.onSupportDetected?.()
|
|
93
|
+
} catch (error) {
|
|
94
|
+
this.reportError(error)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (focused !== this.lastFocusState) {
|
|
100
|
+
this.lastFocusState = focused
|
|
101
|
+
for (const subscriber of this.focusSubscribers) {
|
|
102
|
+
try {
|
|
103
|
+
subscriber.onFocusChange(focused)
|
|
104
|
+
} catch (error) {
|
|
105
|
+
this.reportError(error)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
subscribeToFocus(subscriber: FocusSubscriber): () => void {
|
|
114
|
+
if (this.disposed) return () => {}
|
|
115
|
+
const isFirstSubscriber = this.focusSubscribers.size === 0
|
|
116
|
+
this.focusSubscribers.add(subscriber)
|
|
117
|
+
|
|
118
|
+
if (isFirstSubscriber) {
|
|
119
|
+
this.setFocusReporting(true, 'Could not enable terminal focus reporting')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (this.focusSupported) {
|
|
123
|
+
try {
|
|
124
|
+
subscriber.onSupportDetected?.()
|
|
125
|
+
if (this.lastFocusState !== null) {
|
|
126
|
+
subscriber.onFocusChange(this.lastFocusState)
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
this.reportError(error)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let unsubscribed = false
|
|
134
|
+
return () => {
|
|
135
|
+
if (unsubscribed) return
|
|
136
|
+
unsubscribed = true
|
|
137
|
+
this.focusSubscribers.delete(subscriber)
|
|
138
|
+
if (this.disposed) return
|
|
139
|
+
if (this.focusSubscribers.size === 0) {
|
|
140
|
+
this.setFocusReporting(
|
|
141
|
+
false,
|
|
142
|
+
'Could not disable terminal focus reporting',
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
dispose(): void {
|
|
149
|
+
if (this.disposed) return
|
|
150
|
+
this.disposed = true
|
|
151
|
+
this.focusSubscribers.clear()
|
|
152
|
+
this.renderer.removeInputHandler(this.handleInput)
|
|
153
|
+
if (activeController === this) activeController = null
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let activeController: TerminalProtocolController | null = null
|
|
158
|
+
|
|
159
|
+
export function installTerminalProtocolController(
|
|
160
|
+
renderer: TerminalProtocolRenderer,
|
|
161
|
+
options: TerminalProtocolControllerOptions = {},
|
|
162
|
+
): TerminalProtocolController {
|
|
163
|
+
if (activeController) {
|
|
164
|
+
throw new Error('terminal protocol controller is already installed')
|
|
165
|
+
}
|
|
166
|
+
const controller = new TerminalProtocolController(renderer, options)
|
|
167
|
+
activeController = controller
|
|
168
|
+
return controller
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getTerminalProtocolController(): TerminalProtocolController | null {
|
|
172
|
+
return activeController
|
|
173
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal escape sequences to reset terminal state.
|
|
3
|
+
* Shared by the in-process cleanup handlers (renderer-cleanup.ts) and the
|
|
4
|
+
* sacrificial watchdog process (terminal-watchdog.ts).
|
|
5
|
+
*
|
|
6
|
+
* Keep this list in sync with the modes OpenTUI enables (see the enable
|
|
7
|
+
* sequences in @opentui/core's native lib) and with
|
|
8
|
+
* SAFE_TERMINAL_RESET_SEQUENCES in the npm release launcher
|
|
9
|
+
* (cli/release-core/launcher.js).
|
|
10
|
+
*
|
|
11
|
+
* Sequences:
|
|
12
|
+
* - \x1b[?1049l: Exit alternate screen buffer (restores main screen)
|
|
13
|
+
* - \x1b[?1000l: Disable X10 mouse mode
|
|
14
|
+
* - \x1b[?1002l: Disable button event mouse mode
|
|
15
|
+
* - \x1b[?1003l: Disable any-event mouse mode (all motion tracking)
|
|
16
|
+
* - \x1b[?1006l: Disable SGR extended mouse mode
|
|
17
|
+
* - \x1b[?1004l: Disable focus reporting
|
|
18
|
+
* - \x1b[?2004l: Disable bracketed paste mode
|
|
19
|
+
* - \x1b[<u: Pop the kitty keyboard protocol flags OpenTUI pushes
|
|
20
|
+
* - \x1b[>4;0m: Reset xterm modifyOtherKeys (OpenTUI sets [>4;1m)
|
|
21
|
+
* - \x1b[?25h: Show cursor (safety measure)
|
|
22
|
+
*/
|
|
23
|
+
export const TERMINAL_RESET_SEQUENCES =
|
|
24
|
+
'\x1b[?1049l' + // Exit alternate screen buffer
|
|
25
|
+
'\x1b[?1000l' + // Disable X10 mouse mode
|
|
26
|
+
'\x1b[?1002l' + // Disable button event mouse mode
|
|
27
|
+
'\x1b[?1003l' + // Disable any-event mouse mode (all motion)
|
|
28
|
+
'\x1b[?1006l' + // Disable SGR extended mouse mode
|
|
29
|
+
'\x1b[?1004l' + // Disable focus reporting
|
|
30
|
+
'\x1b[?2004l' + // Disable bracketed paste mode
|
|
31
|
+
'\x1b[<u' + // Pop kitty keyboard protocol flags
|
|
32
|
+
'\x1b[>4;0m' + // Reset modifyOtherKeys
|
|
33
|
+
'\x1b[?25h' // Show cursor
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal title utilities using OSC (Operating System Command) escape sequences.
|
|
3
|
+
*
|
|
4
|
+
* OSC sequence format for setting title:
|
|
5
|
+
* - `\x1b]0;${title}\x07` - Sets both window title and icon name
|
|
6
|
+
* - `\x1b` is ESC, `]0;` starts the title command, `\x07` (BEL) ends it
|
|
7
|
+
*
|
|
8
|
+
* We write directly to /dev/tty to bypass OpenTUI's stdout capture,
|
|
9
|
+
* similar to how clipboard.ts handles OSC52 sequences.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { IS_FREEBUFF } from './constants'
|
|
13
|
+
import { getCliEnv } from './env'
|
|
14
|
+
import { writeTerminalControlSync } from './terminal-io'
|
|
15
|
+
|
|
16
|
+
const MAX_TITLE_LENGTH = 60
|
|
17
|
+
const TITLE_PREFIX = IS_FREEBUFF ? 'Freebuff: ' : 'Anvil: '
|
|
18
|
+
const OSC_TERMINATOR = '\x07' // BEL
|
|
19
|
+
|
|
20
|
+
function isInTmux(env: ReturnType<typeof getCliEnv>): boolean {
|
|
21
|
+
return Boolean(env.TMUX)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isInScreen(env: ReturnType<typeof getCliEnv>): boolean {
|
|
25
|
+
if (env.STY) return true
|
|
26
|
+
const term = env.TERM ?? ''
|
|
27
|
+
return term.startsWith('screen') && !isInTmux(env)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build the OSC title sequence with tmux/screen passthrough if needed
|
|
32
|
+
*/
|
|
33
|
+
function buildTitleSequence(title: string, env: ReturnType<typeof getCliEnv>): string {
|
|
34
|
+
const osc = `\x1b]0;${title}${OSC_TERMINATOR}`
|
|
35
|
+
|
|
36
|
+
// tmux passthrough: wrap in DCS and double ESC characters
|
|
37
|
+
if (isInTmux(env)) {
|
|
38
|
+
const escaped = osc.replace(/\x1b/g, '\x1b\x1b')
|
|
39
|
+
return `\x1bPtmux;${escaped}\x1b\\`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// GNU screen passthrough: wrap in DCS
|
|
43
|
+
if (isInScreen(env)) {
|
|
44
|
+
return `\x1bP${osc}\x1b\\`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return osc
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Set the terminal window title.
|
|
52
|
+
* Works on most modern terminal emulators, including through tmux and screen.
|
|
53
|
+
*
|
|
54
|
+
* @param title - The title to set (will be truncated if too long)
|
|
55
|
+
*/
|
|
56
|
+
export function setTerminalTitle(title: string): void {
|
|
57
|
+
// Sanitize: remove control characters and newlines
|
|
58
|
+
const sanitized = title.replace(/[\x00-\x1f\x7f]/g, ' ').trim()
|
|
59
|
+
if (!sanitized) return
|
|
60
|
+
|
|
61
|
+
// Truncate to reasonable length
|
|
62
|
+
const maxInputLength = MAX_TITLE_LENGTH - TITLE_PREFIX.length
|
|
63
|
+
const truncated =
|
|
64
|
+
sanitized.length > maxInputLength
|
|
65
|
+
? sanitized.slice(0, maxInputLength - 1) + '…'
|
|
66
|
+
: sanitized
|
|
67
|
+
|
|
68
|
+
const fullTitle = `${TITLE_PREFIX}${truncated}`
|
|
69
|
+
const env = getCliEnv()
|
|
70
|
+
const sequence = buildTitleSequence(fullTitle, env)
|
|
71
|
+
|
|
72
|
+
writeTerminalControlSync(sequence)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Reset the terminal title to the default.
|
|
77
|
+
* Call this when the CLI exits to restore the terminal to a clean state.
|
|
78
|
+
*/
|
|
79
|
+
export function resetTerminalTitle(): void {
|
|
80
|
+
// Empty title resets to terminal's default behavior
|
|
81
|
+
const env = getCliEnv()
|
|
82
|
+
const sequence = buildTitleSequence('', env)
|
|
83
|
+
writeTerminalControlSync(sequence)
|
|
84
|
+
}
|