@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,73 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
import { computeSmartColumns, MIN_COLUMN_WIDTH } from '../utils/layout-helpers'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Terminal column width thresholds for responsive grid layout.
|
|
7
|
+
* These are character counts (not pixels) representing terminal width breakpoints:
|
|
8
|
+
* - Below 100 cols: 1 column (narrow terminal)
|
|
9
|
+
* - 100-149 cols: up to 2 columns (medium terminal)
|
|
10
|
+
* - 150-199 cols: up to 3 columns (large terminal)
|
|
11
|
+
* - 200+ cols: up to 4 columns (extra large terminal)
|
|
12
|
+
*/
|
|
13
|
+
export const WIDTH_MD_THRESHOLD = 100
|
|
14
|
+
export const WIDTH_LG_THRESHOLD = 150
|
|
15
|
+
export const WIDTH_XL_THRESHOLD = 200
|
|
16
|
+
|
|
17
|
+
/** Ordered thresholds for determining max columns based on terminal width */
|
|
18
|
+
const WIDTH_THRESHOLDS = [WIDTH_MD_THRESHOLD, WIDTH_LG_THRESHOLD, WIDTH_XL_THRESHOLD] as const
|
|
19
|
+
|
|
20
|
+
export interface GridLayoutResult<T> {
|
|
21
|
+
columns: number
|
|
22
|
+
columnWidth: number
|
|
23
|
+
columnGroups: T[][]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Gap between columns in multi-column layout */
|
|
27
|
+
const COLUMN_GAP = 1
|
|
28
|
+
|
|
29
|
+
export function computeGridLayout<T>(
|
|
30
|
+
items: T[],
|
|
31
|
+
availableWidth: number,
|
|
32
|
+
): GridLayoutResult<T> {
|
|
33
|
+
// Force single column for very narrow terminals where multi-column wouldn't fit
|
|
34
|
+
const minWidthForTwoColumns = MIN_COLUMN_WIDTH * 2 + COLUMN_GAP
|
|
35
|
+
if (availableWidth < minWidthForTwoColumns) {
|
|
36
|
+
return {
|
|
37
|
+
columns: 1,
|
|
38
|
+
columnWidth: Math.max(1, availableWidth),
|
|
39
|
+
columnGroups: [items],
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Determine max columns from width thresholds
|
|
44
|
+
const maxColumns = WIDTH_THRESHOLDS.filter(t => availableWidth >= t).length + 1
|
|
45
|
+
|
|
46
|
+
const columns = computeSmartColumns(items.length, maxColumns)
|
|
47
|
+
|
|
48
|
+
let columnWidth: number
|
|
49
|
+
if (columns === 1) {
|
|
50
|
+
columnWidth = availableWidth
|
|
51
|
+
} else {
|
|
52
|
+
const totalGap = columns - 1
|
|
53
|
+
const rawWidth = Math.floor((availableWidth - totalGap) / columns)
|
|
54
|
+
columnWidth = Math.max(MIN_COLUMN_WIDTH, rawWidth)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const columnGroups: T[][] = Array.from({ length: columns }, () => [])
|
|
58
|
+
items.forEach((item, idx) => {
|
|
59
|
+
columnGroups[idx % columns].push(item)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return { columns, columnWidth, columnGroups }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function useGridLayout<T>(
|
|
66
|
+
items: T[],
|
|
67
|
+
availableWidth: number,
|
|
68
|
+
): GridLayoutResult<T> {
|
|
69
|
+
return useMemo(
|
|
70
|
+
() => computeGridLayout(items, availableWidth),
|
|
71
|
+
[items, availableWidth],
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { useRef, useCallback, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
loadMessageHistory,
|
|
5
|
+
saveMessageHistory,
|
|
6
|
+
} from '../utils/message-history'
|
|
7
|
+
|
|
8
|
+
import type { InputValue } from '../types/store'
|
|
9
|
+
import type { InputMode } from '../utils/input-modes'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Determine the appropriate input mode and display text for a history item.
|
|
13
|
+
* Bash commands are stored with '!' prefix, so we detect that and return
|
|
14
|
+
* the appropriate mode and text to display.
|
|
15
|
+
*/
|
|
16
|
+
function parseHistoryItem(item: string): {
|
|
17
|
+
mode: InputMode
|
|
18
|
+
displayText: string
|
|
19
|
+
} {
|
|
20
|
+
if (item.startsWith('!') && item.length > 1) {
|
|
21
|
+
// It's a bash command - strip the '!' prefix for display
|
|
22
|
+
return { mode: 'bash', displayText: item.slice(1) }
|
|
23
|
+
}
|
|
24
|
+
// Regular prompt
|
|
25
|
+
return { mode: 'default', displayText: item }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const useInputHistory = (
|
|
29
|
+
inputValue: string,
|
|
30
|
+
setInputValue: (value: InputValue) => void,
|
|
31
|
+
options?: {
|
|
32
|
+
inputMode?: InputMode
|
|
33
|
+
setInputMode?: (mode: InputMode) => void
|
|
34
|
+
},
|
|
35
|
+
) => {
|
|
36
|
+
const { inputMode, setInputMode } = options ?? {}
|
|
37
|
+
const messageHistoryRef = useRef<string[]>([])
|
|
38
|
+
const historyIndexRef = useRef<number>(-1)
|
|
39
|
+
const currentDraftRef = useRef<string>('')
|
|
40
|
+
const currentDraftModeRef = useRef<InputMode>('default')
|
|
41
|
+
const isInitializedRef = useRef<boolean>(false)
|
|
42
|
+
const isNavigatingRef = useRef<boolean>(false)
|
|
43
|
+
|
|
44
|
+
// Load history from disk on mount
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!isInitializedRef.current) {
|
|
47
|
+
isInitializedRef.current = true
|
|
48
|
+
const savedHistory = loadMessageHistory()
|
|
49
|
+
messageHistoryRef.current = savedHistory
|
|
50
|
+
}
|
|
51
|
+
}, [])
|
|
52
|
+
|
|
53
|
+
const resetHistoryNavigation = useCallback(() => {
|
|
54
|
+
historyIndexRef.current = -1
|
|
55
|
+
currentDraftRef.current = ''
|
|
56
|
+
currentDraftModeRef.current = 'default'
|
|
57
|
+
}, [])
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!isNavigatingRef.current) {
|
|
61
|
+
resetHistoryNavigation()
|
|
62
|
+
}
|
|
63
|
+
}, [inputMode, resetHistoryNavigation])
|
|
64
|
+
|
|
65
|
+
const saveToHistory = useCallback((message: string) => {
|
|
66
|
+
// Re-read from disk to pick up messages from other terminals
|
|
67
|
+
const diskHistory = loadMessageHistory()
|
|
68
|
+
const newHistory = [...diskHistory, message]
|
|
69
|
+
messageHistoryRef.current = newHistory
|
|
70
|
+
historyIndexRef.current = -1
|
|
71
|
+
currentDraftRef.current = ''
|
|
72
|
+
currentDraftModeRef.current = 'default'
|
|
73
|
+
|
|
74
|
+
// Persist to disk
|
|
75
|
+
saveMessageHistory(newHistory)
|
|
76
|
+
}, [])
|
|
77
|
+
|
|
78
|
+
const navigateUp = useCallback(() => {
|
|
79
|
+
const history = messageHistoryRef.current
|
|
80
|
+
if (history.length === 0) return
|
|
81
|
+
|
|
82
|
+
isNavigatingRef.current = true
|
|
83
|
+
|
|
84
|
+
if (historyIndexRef.current === -1) {
|
|
85
|
+
// Save current draft and mode before navigating
|
|
86
|
+
currentDraftRef.current =
|
|
87
|
+
inputMode === 'bash' ? '!' + inputValue : inputValue
|
|
88
|
+
currentDraftModeRef.current = inputMode ?? 'default'
|
|
89
|
+
historyIndexRef.current = history.length - 1
|
|
90
|
+
} else if (historyIndexRef.current > 0) {
|
|
91
|
+
historyIndexRef.current -= 1
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const historyMessage = history[historyIndexRef.current]
|
|
95
|
+
if (historyMessage === undefined) {
|
|
96
|
+
isNavigatingRef.current = false
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { mode, displayText } = parseHistoryItem(historyMessage)
|
|
101
|
+
|
|
102
|
+
// Switch mode if needed
|
|
103
|
+
if (setInputMode && mode !== inputMode) {
|
|
104
|
+
setInputMode(mode)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setInputValue({
|
|
108
|
+
text: displayText,
|
|
109
|
+
cursorPosition: displayText.length,
|
|
110
|
+
lastEditDueToNav: true,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
isNavigatingRef.current = false
|
|
115
|
+
}, 0)
|
|
116
|
+
}, [inputValue, inputMode, setInputValue, setInputMode])
|
|
117
|
+
|
|
118
|
+
const navigateDown = useCallback(() => {
|
|
119
|
+
const history = messageHistoryRef.current
|
|
120
|
+
if (history.length === 0) return
|
|
121
|
+
if (historyIndexRef.current === -1) return
|
|
122
|
+
|
|
123
|
+
isNavigatingRef.current = true
|
|
124
|
+
|
|
125
|
+
if (historyIndexRef.current < history.length - 1) {
|
|
126
|
+
historyIndexRef.current += 1
|
|
127
|
+
const historyMessage = history[historyIndexRef.current]
|
|
128
|
+
if (historyMessage === undefined) {
|
|
129
|
+
isNavigatingRef.current = false
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { mode, displayText } = parseHistoryItem(historyMessage)
|
|
134
|
+
|
|
135
|
+
// Switch mode if needed
|
|
136
|
+
if (setInputMode && mode !== inputMode) {
|
|
137
|
+
setInputMode(mode)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setInputValue({
|
|
141
|
+
text: displayText,
|
|
142
|
+
cursorPosition: displayText.length,
|
|
143
|
+
lastEditDueToNav: true,
|
|
144
|
+
})
|
|
145
|
+
} else {
|
|
146
|
+
// Return to draft
|
|
147
|
+
historyIndexRef.current = -1
|
|
148
|
+
const draft = currentDraftRef.current
|
|
149
|
+
const draftMode = currentDraftModeRef.current
|
|
150
|
+
|
|
151
|
+
// Restore the mode we were in when we started navigating
|
|
152
|
+
if (setInputMode && draftMode !== inputMode) {
|
|
153
|
+
setInputMode(draftMode)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// If draft was in bash mode, it was stored with '!' prefix, so strip it
|
|
157
|
+
const textToShow =
|
|
158
|
+
draftMode === 'bash' && draft.startsWith('!') ? draft.slice(1) : draft
|
|
159
|
+
|
|
160
|
+
setInputValue({
|
|
161
|
+
text: textToShow,
|
|
162
|
+
cursorPosition: textToShow.length,
|
|
163
|
+
lastEditDueToNav: true,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
isNavigatingRef.current = false
|
|
169
|
+
}, 0)
|
|
170
|
+
}, [inputMode, setInputValue, setInputMode])
|
|
171
|
+
|
|
172
|
+
return { saveToHistory, navigateUp, navigateDown, resetHistoryNavigation }
|
|
173
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useKeyboard } from '@opentui/react'
|
|
2
|
+
import { useCallback } from 'react'
|
|
3
|
+
|
|
4
|
+
import { exitCliCleanly } from '../utils/exit-cleanly'
|
|
5
|
+
import { isPlainEnterKey } from '../utils/terminal-enter-detection'
|
|
6
|
+
|
|
7
|
+
import type { KeyEvent } from '@opentui/core'
|
|
8
|
+
|
|
9
|
+
interface UseLoginKeyboardHandlersParams {
|
|
10
|
+
loginUrl: string | null
|
|
11
|
+
hasOpenedBrowser: boolean
|
|
12
|
+
loading: boolean
|
|
13
|
+
onFetchLoginUrl: () => void
|
|
14
|
+
onCopyUrl: (url: string) => Promise<void> | void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Custom hook that handles keyboard input for the login modal
|
|
19
|
+
* - Enter key: fetch login URL and open browser
|
|
20
|
+
* - 'c' key: copy URL to clipboard
|
|
21
|
+
* - Ctrl+C: exit the application
|
|
22
|
+
*/
|
|
23
|
+
export function useLoginKeyboardHandlers({
|
|
24
|
+
loginUrl,
|
|
25
|
+
hasOpenedBrowser,
|
|
26
|
+
loading,
|
|
27
|
+
onFetchLoginUrl,
|
|
28
|
+
onCopyUrl,
|
|
29
|
+
}: UseLoginKeyboardHandlersParams) {
|
|
30
|
+
useKeyboard(
|
|
31
|
+
useCallback(
|
|
32
|
+
(key: KeyEvent) => {
|
|
33
|
+
const isEnter = isPlainEnterKey(key)
|
|
34
|
+
|
|
35
|
+
const isCKey = key.name === 'c' && !key.ctrl && !key.meta && !key.shift
|
|
36
|
+
const isCtrlC = key.ctrl && key.name === 'c'
|
|
37
|
+
|
|
38
|
+
if (isCtrlC) {
|
|
39
|
+
if (
|
|
40
|
+
'preventDefault' in key &&
|
|
41
|
+
typeof key.preventDefault === 'function'
|
|
42
|
+
) {
|
|
43
|
+
key.preventDefault()
|
|
44
|
+
}
|
|
45
|
+
void exitCliCleanly()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (isEnter && !hasOpenedBrowser && !loading) {
|
|
49
|
+
if (
|
|
50
|
+
'preventDefault' in key &&
|
|
51
|
+
typeof key.preventDefault === 'function'
|
|
52
|
+
) {
|
|
53
|
+
key.preventDefault()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onFetchLoginUrl()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isCKey && loginUrl && hasOpenedBrowser) {
|
|
60
|
+
if (
|
|
61
|
+
'preventDefault' in key &&
|
|
62
|
+
typeof key.preventDefault === 'function'
|
|
63
|
+
) {
|
|
64
|
+
key.preventDefault()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Fire-and-forget the async copy function with .catch() to prevent
|
|
68
|
+
// unhandled promise rejections if the implementation changes
|
|
69
|
+
void Promise.resolve(onCopyUrl(loginUrl)).catch(() => {})
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
[loginUrl, hasOpenedBrowser, loading, onCopyUrl, onFetchLoginUrl],
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { LOGIN_WEBSITE_URL } from '../login/constants'
|
|
4
|
+
import { pollLoginStatus } from '../login/login-flow'
|
|
5
|
+
import { trackEvent } from '../utils/analytics'
|
|
6
|
+
import { logger } from '../utils/logger'
|
|
7
|
+
|
|
8
|
+
import type { User } from '../utils/auth'
|
|
9
|
+
|
|
10
|
+
interface UseLoginPollingParams {
|
|
11
|
+
loginUrl: string | null
|
|
12
|
+
fingerprintId: string | null
|
|
13
|
+
fingerprintHash: string | null
|
|
14
|
+
expiresAt: string | null
|
|
15
|
+
isWaitingForEnter: boolean
|
|
16
|
+
onSuccess: (user: User) => void
|
|
17
|
+
onTimeout: () => void
|
|
18
|
+
onError: (error: string) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Custom hook that handles polling for login status
|
|
23
|
+
* Extracts the 109-line polling effect from login-modal.tsx
|
|
24
|
+
*/
|
|
25
|
+
export function useLoginPolling({
|
|
26
|
+
loginUrl,
|
|
27
|
+
fingerprintId,
|
|
28
|
+
fingerprintHash,
|
|
29
|
+
expiresAt,
|
|
30
|
+
isWaitingForEnter,
|
|
31
|
+
onSuccess,
|
|
32
|
+
onTimeout,
|
|
33
|
+
onError,
|
|
34
|
+
}: UseLoginPollingParams) {
|
|
35
|
+
// Store callbacks in refs to prevent effect re-runs
|
|
36
|
+
const onSuccessRef = useRef(onSuccess)
|
|
37
|
+
const onTimeoutRef = useRef(onTimeout)
|
|
38
|
+
const onErrorRef = useRef(onError)
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
onSuccessRef.current = onSuccess
|
|
42
|
+
}, [onSuccess])
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
onTimeoutRef.current = onTimeout
|
|
46
|
+
}, [onTimeout])
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
onErrorRef.current = onError
|
|
50
|
+
}, [onError])
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
// fingerprintHash only becomes non-null after the login-URL mutation
|
|
54
|
+
// succeeds, and that path always sets fingerprintId first — so gating
|
|
55
|
+
// on fingerprintHash implicitly gates on fingerprintId.
|
|
56
|
+
if (!loginUrl || !fingerprintId || !fingerprintHash || !expiresAt || !isWaitingForEnter) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let active = true
|
|
61
|
+
|
|
62
|
+
const sleep = (ms: number) =>
|
|
63
|
+
new Promise<void>((resolve) => {
|
|
64
|
+
setTimeout(resolve, ms)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
pollLoginStatus(
|
|
68
|
+
{
|
|
69
|
+
sleep,
|
|
70
|
+
logger,
|
|
71
|
+
trackEvent,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
baseUrl: LOGIN_WEBSITE_URL,
|
|
75
|
+
fingerprintId: fingerprintId!,
|
|
76
|
+
fingerprintHash,
|
|
77
|
+
expiresAt,
|
|
78
|
+
shouldContinue: () => active,
|
|
79
|
+
via: 'modal',
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
.then((result) => {
|
|
83
|
+
if (!active) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (result.status === 'success') {
|
|
88
|
+
const user = result.user as User
|
|
89
|
+
onSuccessRef.current(user)
|
|
90
|
+
} else if (result.status === 'timeout') {
|
|
91
|
+
logger.warn('Login polling timed out after configured limit')
|
|
92
|
+
onTimeoutRef.current()
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.catch((error) => {
|
|
96
|
+
if (!active) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
logger.error(
|
|
100
|
+
{
|
|
101
|
+
error: error instanceof Error ? error.message : String(error),
|
|
102
|
+
},
|
|
103
|
+
'💥 Unexpected error while polling login status',
|
|
104
|
+
)
|
|
105
|
+
onErrorRef.current(
|
|
106
|
+
error instanceof Error ? error.message : 'Failed to complete login',
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
active = false
|
|
112
|
+
}
|
|
113
|
+
}, [loginUrl, fingerprintHash, expiresAt, isWaitingForEnter, fingerprintId])
|
|
114
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
import { LOGO, LOGO_SMALL, SHADOW_CHARS } from '../login/constants'
|
|
4
|
+
import { parseLogoLines } from '../login/utils'
|
|
5
|
+
import { IS_FREEBUFF } from '../utils/constants'
|
|
6
|
+
|
|
7
|
+
interface UseLogoOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Available width for rendering the logo
|
|
10
|
+
*/
|
|
11
|
+
availableWidth: number
|
|
12
|
+
/**
|
|
13
|
+
* Optional function to apply styling to each character (e.g., for sheen animation)
|
|
14
|
+
* If not provided, default coloring is applied (white blocks, accent shadows)
|
|
15
|
+
*/
|
|
16
|
+
applySheenToChar?: (char: string, charIndex: number, lineIndex: number) => React.ReactNode
|
|
17
|
+
/**
|
|
18
|
+
* Color to apply to the text variant
|
|
19
|
+
*/
|
|
20
|
+
textColor?: string
|
|
21
|
+
/**
|
|
22
|
+
* Accent color for shadow/border characters (defaults to acid green #9EFC62)
|
|
23
|
+
*/
|
|
24
|
+
accentColor?: string
|
|
25
|
+
/**
|
|
26
|
+
* Block color for solid block characters (white for dark mode, black for light mode)
|
|
27
|
+
*/
|
|
28
|
+
blockColor?: string
|
|
29
|
+
/**
|
|
30
|
+
* Optional vertical budget (in rows) for the logo. When fewer than the
|
|
31
|
+
* ASCII art's 6 lines are available, the hook downgrades to the single-line
|
|
32
|
+
* text variant so callers on short terminals don't have to special-case it.
|
|
33
|
+
*/
|
|
34
|
+
maxHeight?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface LogoResult {
|
|
38
|
+
/**
|
|
39
|
+
* The formatted logo as a React component ready to render in UI
|
|
40
|
+
*/
|
|
41
|
+
component: React.ReactNode
|
|
42
|
+
/**
|
|
43
|
+
* The formatted logo string for plain text contexts (e.g., chat messages)
|
|
44
|
+
* Empty string for narrow widths, formatted ASCII art otherwise
|
|
45
|
+
*/
|
|
46
|
+
textBlock: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hook to render a logo based on available width
|
|
51
|
+
* Returns a fully formatted React component and text block that "just work"
|
|
52
|
+
*
|
|
53
|
+
* Returns:
|
|
54
|
+
* - Full ASCII logo for width >= 70
|
|
55
|
+
* - Small ASCII logo for width >= 40
|
|
56
|
+
* - Text variant "ANVIL" or "Anvil CLI" for narrow widths
|
|
57
|
+
*
|
|
58
|
+
* The hook handles ALL formatting internally including:
|
|
59
|
+
* - Line parsing and width limiting
|
|
60
|
+
* - Optional character-level styling (sheen animation) for React component
|
|
61
|
+
* - Text wrapping and block formatting for plain text contexts
|
|
62
|
+
* - No consumer needs to know about parseLogoLines, split, join, etc.
|
|
63
|
+
*/
|
|
64
|
+
export const useLogo = ({
|
|
65
|
+
availableWidth,
|
|
66
|
+
applySheenToChar,
|
|
67
|
+
textColor,
|
|
68
|
+
accentColor = '#9EFC62',
|
|
69
|
+
blockColor = '#ffffff',
|
|
70
|
+
maxHeight,
|
|
71
|
+
}: UseLogoOptions): LogoResult => {
|
|
72
|
+
// The ASCII art (full and small) is 6 lines tall. If the caller can't spare
|
|
73
|
+
// that many rows, collapse straight to the single-line text variant.
|
|
74
|
+
const ASCII_LOGO_LINES = 6
|
|
75
|
+
const rawLogoString = useMemo(() => {
|
|
76
|
+
if (maxHeight != null && maxHeight < ASCII_LOGO_LINES) {
|
|
77
|
+
return IS_FREEBUFF ? 'FREEBUFF' : 'ANVIL'
|
|
78
|
+
}
|
|
79
|
+
if (availableWidth >= 70) return LOGO
|
|
80
|
+
if (availableWidth >= 20) return LOGO_SMALL
|
|
81
|
+
return IS_FREEBUFF ? 'FREEBUFF' : 'ANVIL'
|
|
82
|
+
}, [availableWidth, maxHeight])
|
|
83
|
+
|
|
84
|
+
// Format text block for plain text contexts (chat messages, etc.)
|
|
85
|
+
const textBlock = useMemo(() => {
|
|
86
|
+
if (rawLogoString === 'ANVIL' || rawLogoString === 'FREEBUFF') {
|
|
87
|
+
return '' // Don't show ASCII art for text-only variant in plain text contexts
|
|
88
|
+
}
|
|
89
|
+
// Parse and format for plain text display
|
|
90
|
+
return parseLogoLines(rawLogoString)
|
|
91
|
+
.map((line) => line.slice(0, availableWidth))
|
|
92
|
+
.join('\n')
|
|
93
|
+
}, [rawLogoString, availableWidth])
|
|
94
|
+
|
|
95
|
+
// Format component for React contexts (login modal, etc.)
|
|
96
|
+
const component = useMemo(() => {
|
|
97
|
+
// Text-only variant for very narrow widths
|
|
98
|
+
if (rawLogoString === 'ANVIL' || rawLogoString === 'FREEBUFF') {
|
|
99
|
+
const brandName = IS_FREEBUFF ? 'Freebuff' : 'Anvil'
|
|
100
|
+
// When we collapsed to text purely to fit a short terminal (not because
|
|
101
|
+
// the terminal is narrow), keep it to the bare brand name — "Anvil CLI"
|
|
102
|
+
// reads as filler in that already-cramped space.
|
|
103
|
+
const forcedByHeight = maxHeight != null && maxHeight < ASCII_LOGO_LINES
|
|
104
|
+
const displayText =
|
|
105
|
+
availableWidth < 30 || forcedByHeight
|
|
106
|
+
? brandName
|
|
107
|
+
: `${brandName} CLI`
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<text style={{ wrapMode: 'none' }}>
|
|
111
|
+
<b>
|
|
112
|
+
{textColor ? (
|
|
113
|
+
<span fg={textColor}>{displayText}</span>
|
|
114
|
+
) : (
|
|
115
|
+
<>{displayText}</>
|
|
116
|
+
)}
|
|
117
|
+
</b>
|
|
118
|
+
</text>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ASCII art variant
|
|
123
|
+
const logoLines = parseLogoLines(rawLogoString)
|
|
124
|
+
const displayLines = logoLines.map((line) => line.slice(0, availableWidth))
|
|
125
|
+
|
|
126
|
+
// Default coloring function: blockColor for blocks, accent color for shadows
|
|
127
|
+
const defaultColorChar = (char: string, charIndex: number) => {
|
|
128
|
+
if (char === ' ' || char === '\n') {
|
|
129
|
+
return <span key={charIndex}>{char}</span>
|
|
130
|
+
}
|
|
131
|
+
// Block characters use blockColor (white in dark mode, black in light mode)
|
|
132
|
+
if (char === '█') {
|
|
133
|
+
return <span key={charIndex} fg={blockColor}>{char}</span>
|
|
134
|
+
}
|
|
135
|
+
// Shadow/border characters get accent color
|
|
136
|
+
if (SHADOW_CHARS.has(char)) {
|
|
137
|
+
return <span key={charIndex} fg={accentColor}>{char}</span>
|
|
138
|
+
}
|
|
139
|
+
// Other characters use accent color
|
|
140
|
+
return <span key={charIndex} fg={accentColor}>{char}</span>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
{displayLines.map((line, lineIndex) => (
|
|
146
|
+
<text key={`logo-line-${lineIndex}`} style={{ wrapMode: 'none' }}>
|
|
147
|
+
{line
|
|
148
|
+
.split('')
|
|
149
|
+
.map((char, charIndex) =>
|
|
150
|
+
applySheenToChar
|
|
151
|
+
? applySheenToChar(char, charIndex, lineIndex)
|
|
152
|
+
: defaultColorChar(char, charIndex),
|
|
153
|
+
)}
|
|
154
|
+
</text>
|
|
155
|
+
))}
|
|
156
|
+
</>
|
|
157
|
+
)
|
|
158
|
+
}, [rawLogoString, availableWidth, applySheenToChar, textColor, accentColor, blockColor, maxHeight])
|
|
159
|
+
|
|
160
|
+
return { component, textBlock }
|
|
161
|
+
}
|