@alumbwe/anvil 1.0.18 → 1.0.20
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 +28 -30
- package/bin/anvil +2 -0
- package/package.json +71 -75
- package/src/agents/bundled-agents.generated.d.ts +14 -0
- package/src/agents/bundled-agents.generated.ts +3760 -0
- package/src/app.tsx +399 -0
- package/src/chat.tsx +1698 -0
- package/src/cli-args.ts +134 -0
- package/src/commands/__tests__/anvil-command-aliases.test.ts +56 -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__/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__/anvil-landing-heading-row.test.tsx +91 -0
- package/src/components/__tests__/anvil-model-selector.test.tsx +519 -0
- package/src/components/__tests__/anvil-offer-invariants.test.ts +54 -0
- package/src/components/__tests__/anvil-takeover-prompt.test.tsx +212 -0
- package/src/components/__tests__/chat-input-bar.test.tsx +130 -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-active-session-summary.tsx +63 -0
- package/src/components/anvil-landing-screen.tsx +865 -0
- package/src/components/anvil-mark.tsx +52 -0
- package/src/components/anvil-model-selector.tsx +1087 -0
- package/src/components/anvil-referral-banner.tsx +604 -0
- package/src/components/anvil-superseded-screen.tsx +62 -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/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-anvil-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-anvil-ctrl-c-exit.ts +23 -0
- package/src/hooks/use-anvil-session-progress.ts +34 -0
- package/src/hooks/use-anvil-session.ts +806 -0
- package/src/hooks/use-anvil-streak-query.ts +61 -0
- package/src/hooks/use-ask-user-bridge.ts +92 -0
- package/src/hooks/use-auth-query.ts +262 -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-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 +473 -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/setup/key-setup.ts +295 -0
- package/src/setup/load-env.ts +106 -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/anvil-model-store.ts +42 -0
- package/src/state/anvil-session-store.ts +53 -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/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/types/anvil-session.ts +17 -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/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__/anvil-instance-owner.test.ts +69 -0
- package/src/utils/__tests__/anvil-model-navigation.test.ts +102 -0
- package/src/utils/__tests__/anvil-premium-reset.test.ts +79 -0
- package/src/utils/__tests__/anvil-referral-cache.test.ts +116 -0
- package/src/utils/__tests__/anvil-session-api.test.ts +215 -0
- package/src/utils/__tests__/anvil-session-display.test.ts +23 -0
- package/src/utils/__tests__/anvil-streak-line.test.ts +217 -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__/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-agent-selection.ts +33 -0
- package/src/utils/anvil-api.ts +612 -0
- package/src/utils/anvil-client.ts +217 -0
- package/src/utils/anvil-instance-owner.ts +66 -0
- package/src/utils/anvil-model-navigation.ts +50 -0
- package/src/utils/anvil-premium-reset.ts +55 -0
- package/src/utils/anvil-referral-cache.ts +51 -0
- package/src/utils/anvil-session-api.ts +243 -0
- package/src/utils/anvil-session-display.ts +21 -0
- package/src/utils/anvil-streak-line.ts +93 -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/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
- package/bin/anvil.cjs +0 -40
- package/dist/bundled.js +0 -123450
- package/dist/bundled.js.map +0 -827
- package/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/vendor/ripgrep/arm64-linux/rg +0 -0
- package/dist/vendor/ripgrep/x64-darwin/rg +0 -0
- package/dist/vendor/ripgrep/x64-linux/rg +0 -0
- package/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { AnalyticsEvent } from '@anvil/common/constants/analytics-events'
|
|
2
|
+
import { runTerminalCommand } from '@anvil/sdk'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
findCommand,
|
|
7
|
+
type RouterParams,
|
|
8
|
+
type CommandResult,
|
|
9
|
+
} from './command-registry'
|
|
10
|
+
import {
|
|
11
|
+
isSlashCommand,
|
|
12
|
+
parseCommandInput,
|
|
13
|
+
} from './router-utils'
|
|
14
|
+
import { buildInterviewPrompt, buildPlanPrompt, buildReviewPrompt } from './prompt-builders'
|
|
15
|
+
import { getProjectRoot } from '../project-files'
|
|
16
|
+
import { useChatStore } from '../state/chat-store'
|
|
17
|
+
import { useAnvilSessionStore } from '../state/anvil-session-store'
|
|
18
|
+
import { trackEvent } from '../utils/analytics'
|
|
19
|
+
import {
|
|
20
|
+
buildBashHistoryMessages,
|
|
21
|
+
createRunTerminalToolResult,
|
|
22
|
+
} from '../utils/bash-messages'
|
|
23
|
+
import { showClipboardMessage } from '../utils/clipboard'
|
|
24
|
+
import { IS_ANVIL } from '../utils/constants'
|
|
25
|
+
import { getSystemProcessEnv } from '../utils/env'
|
|
26
|
+
import { terminalCommandBroker } from '../utils/terminal-command-broker'
|
|
27
|
+
import { getSystemMessage, getUserMessage } from '../utils/message-history'
|
|
28
|
+
import {
|
|
29
|
+
capturePendingAttachments,
|
|
30
|
+
hasProcessingFiles,
|
|
31
|
+
hasProcessingImages,
|
|
32
|
+
validateAndAddImage,
|
|
33
|
+
} from '../utils/pending-attachments'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run a bash command with automatic ghost/direct mode selection.
|
|
37
|
+
* Uses ghost mode when streaming or chain in progress, otherwise adds directly to chat history.
|
|
38
|
+
*/
|
|
39
|
+
export function runBashCommand(command: string) {
|
|
40
|
+
const {
|
|
41
|
+
streamingAgents,
|
|
42
|
+
isChainInProgress,
|
|
43
|
+
setMessages,
|
|
44
|
+
addPendingBashMessage,
|
|
45
|
+
updatePendingBashMessage,
|
|
46
|
+
} = useChatStore.getState()
|
|
47
|
+
|
|
48
|
+
const ghost = streamingAgents.size > 0 || isChainInProgress
|
|
49
|
+
const id = crypto.randomUUID()
|
|
50
|
+
const commandCwd = process.cwd()
|
|
51
|
+
const startTime = Date.now()
|
|
52
|
+
|
|
53
|
+
if (ghost) {
|
|
54
|
+
// Ghost mode: add to pending messages
|
|
55
|
+
addPendingBashMessage({
|
|
56
|
+
id,
|
|
57
|
+
command,
|
|
58
|
+
stdout: '',
|
|
59
|
+
stderr: '',
|
|
60
|
+
exitCode: 0,
|
|
61
|
+
isRunning: true,
|
|
62
|
+
startTime: Date.now(),
|
|
63
|
+
cwd: commandCwd,
|
|
64
|
+
})
|
|
65
|
+
} else {
|
|
66
|
+
// Direct mode: add to chat history with placeholder output (user + assistant)
|
|
67
|
+
const { assistantMessage } = buildBashHistoryMessages({
|
|
68
|
+
command,
|
|
69
|
+
cwd: commandCwd,
|
|
70
|
+
toolCallId: id,
|
|
71
|
+
output: '...',
|
|
72
|
+
})
|
|
73
|
+
setMessages((prev) => [...prev, assistantMessage])
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
runTerminalCommand({
|
|
77
|
+
command,
|
|
78
|
+
process_type: 'SYNC',
|
|
79
|
+
cwd: commandCwd,
|
|
80
|
+
timeout_seconds: -1,
|
|
81
|
+
env: getSystemProcessEnv(),
|
|
82
|
+
terminalCommandBroker,
|
|
83
|
+
})
|
|
84
|
+
.then(([{ value }]) => {
|
|
85
|
+
const stdout = 'stdout' in value ? value.stdout || '' : ''
|
|
86
|
+
const stderr = 'stderr' in value ? value.stderr || '' : ''
|
|
87
|
+
const exitCode = 'exitCode' in value ? value.exitCode ?? 0 : 0
|
|
88
|
+
|
|
89
|
+
// Track terminal command completion
|
|
90
|
+
const durationMs = Date.now() - startTime
|
|
91
|
+
trackEvent(AnalyticsEvent.TERMINAL_COMMAND_COMPLETED, {
|
|
92
|
+
command: command.split(' ')[0], // Just the command name, not args
|
|
93
|
+
exitCode,
|
|
94
|
+
success: exitCode === 0,
|
|
95
|
+
ghost,
|
|
96
|
+
durationMs,
|
|
97
|
+
hasStdout: stdout.length > 0,
|
|
98
|
+
hasStderr: stderr.length > 0,
|
|
99
|
+
stdoutLength: stdout.length,
|
|
100
|
+
stderrLength: stderr.length,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if (ghost) {
|
|
104
|
+
updatePendingBashMessage(id, {
|
|
105
|
+
stdout,
|
|
106
|
+
stderr,
|
|
107
|
+
exitCode,
|
|
108
|
+
isRunning: false,
|
|
109
|
+
})
|
|
110
|
+
} else {
|
|
111
|
+
const toolResultOutput = createRunTerminalToolResult({
|
|
112
|
+
command,
|
|
113
|
+
cwd: commandCwd,
|
|
114
|
+
stdout: stdout || null,
|
|
115
|
+
stderr: stderr || null,
|
|
116
|
+
exitCode,
|
|
117
|
+
})
|
|
118
|
+
const outputJson = JSON.stringify(toolResultOutput)
|
|
119
|
+
|
|
120
|
+
setMessages((prev) =>
|
|
121
|
+
prev.map((msg) => {
|
|
122
|
+
if (!msg.blocks) return msg
|
|
123
|
+
let didUpdate = false
|
|
124
|
+
const blocks = msg.blocks.map((block) => {
|
|
125
|
+
if ('toolCallId' in block && block.toolCallId === id) {
|
|
126
|
+
didUpdate = true
|
|
127
|
+
return { ...block, output: outputJson }
|
|
128
|
+
}
|
|
129
|
+
return block
|
|
130
|
+
})
|
|
131
|
+
return didUpdate ? { ...msg, blocks, isComplete: true } : msg
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
// Also add to pending bash messages so the next user message includes this context for the LLM
|
|
136
|
+
// Mark as already added to history to avoid duplicate UI entries
|
|
137
|
+
addPendingBashMessage({
|
|
138
|
+
id,
|
|
139
|
+
command,
|
|
140
|
+
stdout,
|
|
141
|
+
stderr,
|
|
142
|
+
exitCode,
|
|
143
|
+
isRunning: false,
|
|
144
|
+
cwd: commandCwd,
|
|
145
|
+
addedToHistory: true,
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
.catch((error) => {
|
|
150
|
+
const errorMessage =
|
|
151
|
+
error instanceof Error ? error.message : String(error)
|
|
152
|
+
|
|
153
|
+
// Track terminal command completion with error
|
|
154
|
+
const durationMs = Date.now() - startTime
|
|
155
|
+
trackEvent(AnalyticsEvent.TERMINAL_COMMAND_COMPLETED, {
|
|
156
|
+
command: command.split(' ')[0], // Just the command name, not args
|
|
157
|
+
exitCode: 1,
|
|
158
|
+
success: false,
|
|
159
|
+
ghost,
|
|
160
|
+
durationMs,
|
|
161
|
+
hasStdout: false,
|
|
162
|
+
hasStderr: true,
|
|
163
|
+
stdoutLength: 0,
|
|
164
|
+
stderrLength: errorMessage.length,
|
|
165
|
+
isException: true,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
if (ghost) {
|
|
169
|
+
updatePendingBashMessage(id, {
|
|
170
|
+
stdout: '',
|
|
171
|
+
stderr: errorMessage,
|
|
172
|
+
exitCode: 1,
|
|
173
|
+
isRunning: false,
|
|
174
|
+
})
|
|
175
|
+
} else {
|
|
176
|
+
const errorToolResultOutput = createRunTerminalToolResult({
|
|
177
|
+
command,
|
|
178
|
+
cwd: commandCwd,
|
|
179
|
+
stdout: null,
|
|
180
|
+
stderr: null,
|
|
181
|
+
exitCode: 1,
|
|
182
|
+
errorMessage,
|
|
183
|
+
})
|
|
184
|
+
const errorOutputJson = JSON.stringify(errorToolResultOutput)
|
|
185
|
+
|
|
186
|
+
setMessages((prev) =>
|
|
187
|
+
prev.map((msg) => {
|
|
188
|
+
if (!msg.blocks) return msg
|
|
189
|
+
let didUpdate = false
|
|
190
|
+
const blocks = msg.blocks.map((block) => {
|
|
191
|
+
if ('toolCallId' in block && block.toolCallId === id) {
|
|
192
|
+
didUpdate = true
|
|
193
|
+
return { ...block, output: errorOutputJson }
|
|
194
|
+
}
|
|
195
|
+
return block
|
|
196
|
+
})
|
|
197
|
+
return didUpdate ? { ...msg, blocks, isComplete: true } : msg
|
|
198
|
+
}),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
// Also add to pending bash messages so the next user message includes this context for the LLM
|
|
202
|
+
// Mark as already added to history to avoid duplicate UI entries
|
|
203
|
+
addPendingBashMessage({
|
|
204
|
+
id,
|
|
205
|
+
command,
|
|
206
|
+
stdout: '',
|
|
207
|
+
stderr: errorMessage,
|
|
208
|
+
exitCode: 1,
|
|
209
|
+
isRunning: false,
|
|
210
|
+
cwd: commandCwd,
|
|
211
|
+
addedToHistory: true,
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Add a completed bash command result to the chat message history.
|
|
219
|
+
* Note: This is UI-only; we no longer send these commands to the AI context.
|
|
220
|
+
*/
|
|
221
|
+
export function addBashMessageToHistory(params: {
|
|
222
|
+
command: string
|
|
223
|
+
stdout: string
|
|
224
|
+
stderr: string | null
|
|
225
|
+
exitCode: number
|
|
226
|
+
cwd: string
|
|
227
|
+
setMessages: RouterParams['setMessages']
|
|
228
|
+
}) {
|
|
229
|
+
const { command, stdout, stderr, exitCode, cwd, setMessages } = params
|
|
230
|
+
const toolResultOutput = createRunTerminalToolResult({
|
|
231
|
+
command,
|
|
232
|
+
cwd,
|
|
233
|
+
stdout: stdout || null,
|
|
234
|
+
stderr: stderr ?? null,
|
|
235
|
+
exitCode,
|
|
236
|
+
})
|
|
237
|
+
const toolCallId = crypto.randomUUID()
|
|
238
|
+
const outputJson = JSON.stringify(toolResultOutput)
|
|
239
|
+
const { assistantMessage } = buildBashHistoryMessages({
|
|
240
|
+
command,
|
|
241
|
+
cwd,
|
|
242
|
+
toolCallId,
|
|
243
|
+
output: outputJson,
|
|
244
|
+
isComplete: true,
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
setMessages((prev) => [...prev, assistantMessage])
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function routeUserPrompt(
|
|
251
|
+
params: RouterParams,
|
|
252
|
+
): Promise<CommandResult> {
|
|
253
|
+
const {
|
|
254
|
+
agentMode,
|
|
255
|
+
inputRef,
|
|
256
|
+
inputValue,
|
|
257
|
+
isChainInProgressRef,
|
|
258
|
+
isStreaming,
|
|
259
|
+
streamMessageIdRef,
|
|
260
|
+
addToQueue,
|
|
261
|
+
saveToHistory,
|
|
262
|
+
scrollToLatest,
|
|
263
|
+
sendMessage,
|
|
264
|
+
setInputFocused,
|
|
265
|
+
setInputValue,
|
|
266
|
+
setMessages,
|
|
267
|
+
} = params
|
|
268
|
+
|
|
269
|
+
const inputMode = useChatStore.getState().inputMode
|
|
270
|
+
const setInputMode = useChatStore.getState().setInputMode
|
|
271
|
+
const pendingAttachments = useChatStore.getState().pendingAttachments
|
|
272
|
+
const pendingImages = pendingAttachments.filter((a) => a.kind === 'image')
|
|
273
|
+
const pendingTextAttachments = pendingAttachments.filter(
|
|
274
|
+
(a) => a.kind === 'text',
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
const trimmed = inputValue.trim()
|
|
278
|
+
// Allow empty messages if there are pending attachments (images or text)
|
|
279
|
+
const hasAttachments = pendingAttachments.length > 0
|
|
280
|
+
if (!trimmed && !hasAttachments) return
|
|
281
|
+
|
|
282
|
+
// Track user input complete
|
|
283
|
+
// Count @ mentions (simple pattern match - more accurate than nothing)
|
|
284
|
+
const mentionMatches = trimmed.match(/@\S+/g) || []
|
|
285
|
+
trackEvent(AnalyticsEvent.USER_INPUT_COMPLETE, {
|
|
286
|
+
inputLength: trimmed.length,
|
|
287
|
+
mode: agentMode,
|
|
288
|
+
inputMode,
|
|
289
|
+
hasImages: pendingImages.length > 0,
|
|
290
|
+
imageCount: pendingImages.length,
|
|
291
|
+
hasTextAttachments: pendingTextAttachments.length > 0,
|
|
292
|
+
textAttachmentCount: pendingTextAttachments.length,
|
|
293
|
+
isSlashCommand: isSlashCommand(trimmed),
|
|
294
|
+
isBashCommand: trimmed.startsWith('!'),
|
|
295
|
+
hasMentions: mentionMatches.length > 0,
|
|
296
|
+
mentionCount: mentionMatches.length,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
// DAU signal: one un-sampled event per user-submitted prompt. The CLI's
|
|
300
|
+
// distinct id resolves to the canonical anvil user id (anonymous id is
|
|
301
|
+
// aliased to the real user id on login), matching the web and chat surfaces
|
|
302
|
+
// so combined DAU is a single unique-users query. Anvil-only: anvil
|
|
303
|
+
// CLI usage is intentionally excluded.
|
|
304
|
+
if (IS_ANVIL) {
|
|
305
|
+
const anvilSession = useAnvilSessionStore.getState().session
|
|
306
|
+
trackEvent(AnalyticsEvent.MESSAGE_SENT, {
|
|
307
|
+
surface: 'cli',
|
|
308
|
+
accessTier:
|
|
309
|
+
anvilSession && 'accessTier' in anvilSession
|
|
310
|
+
? anvilSession.accessTier
|
|
311
|
+
: 'unknown',
|
|
312
|
+
mode: agentMode,
|
|
313
|
+
inputMode,
|
|
314
|
+
inputLength: trimmed.length,
|
|
315
|
+
isSlashCommand: isSlashCommand(trimmed),
|
|
316
|
+
isBashCommand: trimmed.startsWith('!'),
|
|
317
|
+
hasImages: pendingImages.length > 0,
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Handle bash mode commands
|
|
322
|
+
if (inputMode === 'bash') {
|
|
323
|
+
const commandWithBang = '!' + trimmed
|
|
324
|
+
saveToHistory(commandWithBang)
|
|
325
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
326
|
+
setInputMode('default')
|
|
327
|
+
setInputFocused(true)
|
|
328
|
+
inputRef.current?.focus()
|
|
329
|
+
|
|
330
|
+
runBashCommand(trimmed)
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Handle plan mode input
|
|
335
|
+
if (inputMode === 'plan') {
|
|
336
|
+
if (!trimmed) return
|
|
337
|
+
saveToHistory(trimmed)
|
|
338
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
339
|
+
setInputMode('default')
|
|
340
|
+
setInputFocused(true)
|
|
341
|
+
inputRef.current?.focus()
|
|
342
|
+
|
|
343
|
+
sendMessage({ content: buildPlanPrompt(trimmed), agentMode })
|
|
344
|
+
setTimeout(() => {
|
|
345
|
+
scrollToLatest()
|
|
346
|
+
}, 0)
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Handle interview mode input
|
|
351
|
+
if (inputMode === 'interview') {
|
|
352
|
+
if (!trimmed) return
|
|
353
|
+
saveToHistory(trimmed)
|
|
354
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
355
|
+
setInputMode('default')
|
|
356
|
+
setInputFocused(true)
|
|
357
|
+
inputRef.current?.focus()
|
|
358
|
+
|
|
359
|
+
sendMessage({ content: buildInterviewPrompt(trimmed), agentMode })
|
|
360
|
+
setTimeout(() => {
|
|
361
|
+
scrollToLatest()
|
|
362
|
+
}, 0)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Handle review mode input
|
|
367
|
+
if (inputMode === 'review') {
|
|
368
|
+
if (!trimmed) return
|
|
369
|
+
saveToHistory(trimmed)
|
|
370
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
371
|
+
setInputMode('default')
|
|
372
|
+
setInputFocused(true)
|
|
373
|
+
inputRef.current?.focus()
|
|
374
|
+
|
|
375
|
+
sendMessage({ content: buildReviewPrompt('custom', trimmed), agentMode })
|
|
376
|
+
setTimeout(() => {
|
|
377
|
+
scrollToLatest()
|
|
378
|
+
}, 0)
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Handle bash commands from queue (starts with '!')
|
|
383
|
+
if (trimmed.startsWith('!') && trimmed.length > 1) {
|
|
384
|
+
const command = trimmed.slice(1)
|
|
385
|
+
saveToHistory(trimmed)
|
|
386
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
387
|
+
runBashCommand(command)
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Handle image mode input
|
|
392
|
+
if (inputMode === 'image') {
|
|
393
|
+
const imagePath = trimmed
|
|
394
|
+
const projectRoot = getProjectRoot()
|
|
395
|
+
|
|
396
|
+
// Validate and add the image (handles path resolution, format check, and processing)
|
|
397
|
+
const result = await validateAndAddImage(imagePath, projectRoot)
|
|
398
|
+
if (!result.success) {
|
|
399
|
+
setMessages((prev) => [
|
|
400
|
+
...prev,
|
|
401
|
+
getUserMessage(trimmed),
|
|
402
|
+
getSystemMessage(`❌ ${result.error}`),
|
|
403
|
+
])
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Note: No system message added here - the PendingImagesBanner shows attached images
|
|
407
|
+
saveToHistory(trimmed)
|
|
408
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
409
|
+
setInputMode('default')
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Handle slash commands or configured slashless exact commands.
|
|
414
|
+
const parsedCommand = parseCommandInput(trimmed)
|
|
415
|
+
if (parsedCommand) {
|
|
416
|
+
const commandDef = findCommand(parsedCommand.command)
|
|
417
|
+
if (commandDef) {
|
|
418
|
+
const argsLength = parsedCommand.args.length
|
|
419
|
+
const analyticsPayload = {
|
|
420
|
+
command: commandDef.name,
|
|
421
|
+
hasArgs: argsLength > 0,
|
|
422
|
+
argsLength,
|
|
423
|
+
agentMode,
|
|
424
|
+
...(parsedCommand.implicitCommand ? { implicitCommand: true } : {}),
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
trackEvent(AnalyticsEvent.SLASH_COMMAND_USED, analyticsPayload)
|
|
428
|
+
|
|
429
|
+
// The command handler (via defineCommand/defineCommandWithArgs factories)
|
|
430
|
+
// is responsible for validating and handling args
|
|
431
|
+
return await commandDef.handler(params, parsedCommand.args)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Regular message or unknown slash command - send to agent
|
|
436
|
+
|
|
437
|
+
// Block sending if attachments are still processing
|
|
438
|
+
if (hasProcessingImages() || hasProcessingFiles()) {
|
|
439
|
+
showClipboardMessage('processing attachments...', {
|
|
440
|
+
durationMs: 2000,
|
|
441
|
+
})
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
saveToHistory(trimmed)
|
|
446
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
447
|
+
|
|
448
|
+
if (
|
|
449
|
+
isStreaming ||
|
|
450
|
+
streamMessageIdRef.current ||
|
|
451
|
+
isChainInProgressRef.current
|
|
452
|
+
) {
|
|
453
|
+
const pendingAttachmentsForQueue = capturePendingAttachments()
|
|
454
|
+
// Pass a copy of pending attachments to the queue
|
|
455
|
+
addToQueue(trimmed, pendingAttachmentsForQueue)
|
|
456
|
+
|
|
457
|
+
setInputFocused(true)
|
|
458
|
+
inputRef.current?.focus()
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Unknown slash command - show error
|
|
463
|
+
if (isSlashCommand(trimmed)) {
|
|
464
|
+
// Track invalid/unknown command (only log command name, not full input for privacy)
|
|
465
|
+
const attemptedCmd = trimmed.slice(1).split(/\s+/)[0]?.toLowerCase() || ''
|
|
466
|
+
trackEvent(AnalyticsEvent.INVALID_COMMAND, {
|
|
467
|
+
attemptedCommand: attemptedCmd,
|
|
468
|
+
inputLength: trimmed.length,
|
|
469
|
+
agentMode,
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
setMessages((prev) => [
|
|
473
|
+
...prev,
|
|
474
|
+
getUserMessage(trimmed),
|
|
475
|
+
getSystemMessage(`Command not found: ${JSON.stringify(trimmed)}`),
|
|
476
|
+
])
|
|
477
|
+
return
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
sendMessage({ content: trimmed, agentMode })
|
|
481
|
+
|
|
482
|
+
setTimeout(() => {
|
|
483
|
+
scrollToLatest()
|
|
484
|
+
}, 0)
|
|
485
|
+
|
|
486
|
+
return
|
|
487
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useChatStore } from '../state/chat-store'
|
|
2
|
+
import { getAuthToken } from '../utils/auth'
|
|
3
|
+
import { getSystemMessage } from '../utils/message-history'
|
|
4
|
+
|
|
5
|
+
import type { PostUserMessageFn } from '../types/contracts/send-message'
|
|
6
|
+
|
|
7
|
+
export async function handleUsageCommand(): Promise<{
|
|
8
|
+
postUserMessage: PostUserMessageFn
|
|
9
|
+
}> {
|
|
10
|
+
const authToken = getAuthToken()
|
|
11
|
+
|
|
12
|
+
if (!authToken) {
|
|
13
|
+
const postUserMessage: PostUserMessageFn = (prev) => [
|
|
14
|
+
...prev,
|
|
15
|
+
getSystemMessage('Please log in first to view your usage.'),
|
|
16
|
+
]
|
|
17
|
+
return { postUserMessage }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Show the usage banner - the useUsageQuery hook will automatically fetch
|
|
21
|
+
// the data when the banner becomes visible
|
|
22
|
+
useChatStore.getState().setInputMode('usage')
|
|
23
|
+
|
|
24
|
+
const postUserMessage: PostUserMessageFn = (prev) => prev
|
|
25
|
+
return { postUserMessage }
|
|
26
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { getAdDisplayLabel, getInlineAdLayout } from '../ad-banner'
|
|
4
|
+
|
|
5
|
+
describe('ad banner display label', () => {
|
|
6
|
+
test('uses the display domain when the ad has a URL', () => {
|
|
7
|
+
expect(
|
|
8
|
+
getAdDisplayLabel({
|
|
9
|
+
title: 'Example Sponsor',
|
|
10
|
+
url: 'https://www.example.com/path',
|
|
11
|
+
}),
|
|
12
|
+
).toEqual({ text: 'example.com', variant: 'domain' })
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('uses the ad title when the ad has no URL', () => {
|
|
16
|
+
expect(
|
|
17
|
+
getAdDisplayLabel({
|
|
18
|
+
title: 'Example Sponsor',
|
|
19
|
+
url: '',
|
|
20
|
+
}),
|
|
21
|
+
).toEqual({ text: 'Example Sponsor', variant: 'title' })
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('inline ad layout', () => {
|
|
26
|
+
const ad = {
|
|
27
|
+
adText:
|
|
28
|
+
'Deploy frontends globally with zero config and preview every pull request.',
|
|
29
|
+
title: 'Vercel',
|
|
30
|
+
url: 'https://www.vercel.com/products',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('fits the compact copy and sponsor within the card interior', () => {
|
|
34
|
+
const width = 60
|
|
35
|
+
const layout = getInlineAdLayout(ad, width)
|
|
36
|
+
const header = `${layout.title} Ad`
|
|
37
|
+
const detail = `${layout.description} ${layout.label} ↗`
|
|
38
|
+
|
|
39
|
+
expect(header.length).toBeLessThanOrEqual(width - 4)
|
|
40
|
+
expect(detail.length).toBeLessThanOrEqual(width - 4)
|
|
41
|
+
expect(layout.title).toBe('Vercel')
|
|
42
|
+
expect(layout.label).toBe('vercel.com')
|
|
43
|
+
expect(layout.description.endsWith('…')).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('truncates long labels without starving narrow cards', () => {
|
|
47
|
+
const layout = getInlineAdLayout(
|
|
48
|
+
{
|
|
49
|
+
...ad,
|
|
50
|
+
url: 'https://www.extraordinarily-long-sponsor-domain.example',
|
|
51
|
+
},
|
|
52
|
+
48,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
expect(layout.label).toBe('extraordinari…')
|
|
56
|
+
expect(layout.description.length).toBeGreaterThan(0)
|
|
57
|
+
expect(`${layout.title} Ad`.length).toBeLessThanOrEqual(44)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('prioritizes copy over the destination on very narrow cards', () => {
|
|
61
|
+
const width = 47
|
|
62
|
+
const layout = getInlineAdLayout(ad, width)
|
|
63
|
+
|
|
64
|
+
expect(layout.label).toBe('')
|
|
65
|
+
expect(layout.description.length).toBe(width - 4)
|
|
66
|
+
expect(layout.description.endsWith('…')).toBe(true)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('uses the full detail row when no destination domain is available', () => {
|
|
70
|
+
const layout = getInlineAdLayout(
|
|
71
|
+
{
|
|
72
|
+
adText: 'A Carbon ad whose tracked destination is intentionally hidden.',
|
|
73
|
+
title: 'Example Sponsor',
|
|
74
|
+
url: '',
|
|
75
|
+
},
|
|
76
|
+
40,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
expect(layout.title).toBe('Example Sponsor')
|
|
80
|
+
expect(layout.label).toBe('')
|
|
81
|
+
expect(layout.description.length).toBe(36)
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { afterEach, beforeAll, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { createTestRenderer } from '@opentui/core/testing'
|
|
3
|
+
import { createRoot, flushSync } from '@opentui/react'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
import { LandingHeadingRow } from '../anvil-landing-screen'
|
|
7
|
+
import { initializeThemeStore } from '../../hooks/use-theme'
|
|
8
|
+
import { getAnvilStreakLine } from '../../utils/anvil-streak-line'
|
|
9
|
+
|
|
10
|
+
let cleanupRenderer: (() => void) | undefined
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
initializeThemeStore()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
cleanupRenderer?.()
|
|
18
|
+
cleanupRenderer = undefined
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Mirrors the landing screen's containers around the heading row: a
|
|
23
|
+
* shrink-to-fit column inside a centered, maxWidth-capped column. That shape
|
|
24
|
+
* is what broke the row — with nothing wider on screen the column collapses
|
|
25
|
+
* to the row's own width, leaving space-between no space to hand out.
|
|
26
|
+
*/
|
|
27
|
+
const mountHeadingRow = async (streak: number, contentMaxWidth: number) => {
|
|
28
|
+
const setup = await createTestRenderer({ width: contentMaxWidth + 4, height: 6 })
|
|
29
|
+
const root = createRoot(setup.renderer)
|
|
30
|
+
cleanupRenderer = () => {
|
|
31
|
+
flushSync(() => root.unmount())
|
|
32
|
+
setup.renderer.destroy()
|
|
33
|
+
}
|
|
34
|
+
flushSync(() =>
|
|
35
|
+
root.render(
|
|
36
|
+
<box style={{ flexDirection: 'column', alignItems: 'center', maxWidth: contentMaxWidth }}>
|
|
37
|
+
<box style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 0 }}>
|
|
38
|
+
<LandingHeadingRow
|
|
39
|
+
streakLine={getAnvilStreakLine(streak)}
|
|
40
|
+
marginBottom={1}
|
|
41
|
+
/>
|
|
42
|
+
</box>
|
|
43
|
+
</box>,
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
await setup.renderOnce()
|
|
47
|
+
return setup
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const renderHeadingRow = async (streak: number, contentMaxWidth: number) =>
|
|
51
|
+
(await mountHeadingRow(streak, contentMaxWidth)).captureCharFrame()
|
|
52
|
+
|
|
53
|
+
describe('LandingHeadingRow', () => {
|
|
54
|
+
test('keeps the streak clear of the heading when nothing wider is on screen', async () => {
|
|
55
|
+
const frame = await renderHeadingRow(18, 60)
|
|
56
|
+
const line = frame
|
|
57
|
+
.split('\n')
|
|
58
|
+
.find((row) => row.includes('Start coding for free'))!
|
|
59
|
+
|
|
60
|
+
// The bug: "Start coding for free18 day streak".
|
|
61
|
+
expect(line).not.toContain('free18')
|
|
62
|
+
expect(line).toMatch(/Start coding for free {3,}18 day streak/)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('draws the progress dots as filled/hollow circles', async () => {
|
|
66
|
+
const frame = await renderHeadingRow(18, 60)
|
|
67
|
+
|
|
68
|
+
expect(frame).toContain('●●●●●●●+')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('leaves the row bare for a user with no streak yet', async () => {
|
|
72
|
+
const frame = await renderHeadingRow(0, 60)
|
|
73
|
+
|
|
74
|
+
expect(frame).toContain('Start coding for free')
|
|
75
|
+
expect(frame).not.toContain('day streak')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// The landing screen only hands over a streak that fits, but a resize lays
|
|
79
|
+
// the current tree out against the new width before React re-renders, so the
|
|
80
|
+
// row does get measured too narrow for a frame. As a space-between flex row
|
|
81
|
+
// that overflow segfaulted the native renderer — it has to survive as text.
|
|
82
|
+
test('survives being laid out narrower than its content', async () => {
|
|
83
|
+
const setup = await mountHeadingRow(18, 60)
|
|
84
|
+
|
|
85
|
+
for (const width of [34, 20, 12]) {
|
|
86
|
+
setup.resize(width, 6)
|
|
87
|
+
await setup.renderOnce()
|
|
88
|
+
expect(setup.captureCharFrame()).toContain('Start')
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
})
|