@alumbwe/anvil 1.0.19 → 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 -123508
- package/dist/bundled.js.map +0 -828
- 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,313 @@
|
|
|
1
|
+
import { getAnvilModel } from '@anvil/common/constants/anvil-models'
|
|
2
|
+
import { TextAttributes } from '@opentui/core'
|
|
3
|
+
import React, { useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { Button } from './button'
|
|
6
|
+
import { ScrollToBottomButton } from './scroll-to-bottom-button'
|
|
7
|
+
import { ShimmerText } from './shimmer-text'
|
|
8
|
+
import { TerminalThinkingOrb } from './terminal-thinking-orb'
|
|
9
|
+
|
|
10
|
+
import { useAnvilSessionProgress } from '../hooks/use-anvil-session-progress'
|
|
11
|
+
import { useTheme } from '../hooks/use-theme'
|
|
12
|
+
import { formatElapsedTime } from '../utils/format-elapsed-time'
|
|
13
|
+
import {
|
|
14
|
+
ANVIL_COUNTDOWN_VISIBLE_MS,
|
|
15
|
+
formatAnvilSessionCountdown,
|
|
16
|
+
formatAnvilSessionRemaining,
|
|
17
|
+
} from '../utils/anvil-session-display'
|
|
18
|
+
|
|
19
|
+
import type { AnvilSessionResponse } from '../types/anvil-session'
|
|
20
|
+
import type { StatusIndicatorState } from '../utils/status-indicator-state'
|
|
21
|
+
|
|
22
|
+
/** A small status-bar action button with hover-bold styling. */
|
|
23
|
+
const StatusActionButton = ({
|
|
24
|
+
children,
|
|
25
|
+
onClick,
|
|
26
|
+
}: {
|
|
27
|
+
children: React.ReactNode
|
|
28
|
+
onClick: () => void
|
|
29
|
+
}) => {
|
|
30
|
+
const theme = useTheme()
|
|
31
|
+
const [hovered, setHovered] = useState(false)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Button
|
|
35
|
+
style={{ paddingLeft: 1, paddingRight: 1 }}
|
|
36
|
+
onClick={onClick}
|
|
37
|
+
onMouseOver={() => setHovered(true)}
|
|
38
|
+
onMouseOut={() => setHovered(false)}
|
|
39
|
+
>
|
|
40
|
+
<text>
|
|
41
|
+
<span
|
|
42
|
+
fg={theme.secondary}
|
|
43
|
+
attributes={hovered ? TextAttributes.BOLD : TextAttributes.NONE}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</span>
|
|
47
|
+
</text>
|
|
48
|
+
</Button>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const SHIMMER_INTERVAL_MS = 160
|
|
53
|
+
|
|
54
|
+
interface StatusBarProps {
|
|
55
|
+
timerStartTime: number | null
|
|
56
|
+
isAtBottom: boolean
|
|
57
|
+
scrollToLatest: () => void
|
|
58
|
+
statusIndicatorState: StatusIndicatorState
|
|
59
|
+
onStop?: () => void
|
|
60
|
+
onEndSession?: () => void
|
|
61
|
+
anvilSession: AnvilSessionResponse | null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const StatusBar = ({
|
|
65
|
+
timerStartTime,
|
|
66
|
+
isAtBottom,
|
|
67
|
+
scrollToLatest,
|
|
68
|
+
statusIndicatorState,
|
|
69
|
+
onStop,
|
|
70
|
+
onEndSession,
|
|
71
|
+
anvilSession,
|
|
72
|
+
}: StatusBarProps) => {
|
|
73
|
+
const theme = useTheme()
|
|
74
|
+
const [elapsedSeconds, setElapsedSeconds] = useState(0)
|
|
75
|
+
|
|
76
|
+
// Show timer when actively working (streaming or waiting for response) or paused (ask_user)
|
|
77
|
+
// This uses statusIndicatorState as the single source of truth for "is the LLM working?"
|
|
78
|
+
const shouldShowTimer =
|
|
79
|
+
statusIndicatorState?.kind === 'waiting' ||
|
|
80
|
+
statusIndicatorState?.kind === 'streaming' ||
|
|
81
|
+
statusIndicatorState?.kind === 'paused'
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!timerStartTime || !shouldShowTimer) {
|
|
85
|
+
setElapsedSeconds(0)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// When paused, don't update the timer - just keep the frozen value
|
|
90
|
+
if (statusIndicatorState?.kind === 'paused') {
|
|
91
|
+
// Calculate current elapsed time once and freeze it
|
|
92
|
+
const now = Date.now()
|
|
93
|
+
const elapsed = Math.floor((now - timerStartTime) / 1000)
|
|
94
|
+
setElapsedSeconds(elapsed)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const updateElapsed = () => {
|
|
99
|
+
const now = Date.now()
|
|
100
|
+
const elapsed = Math.floor((now - timerStartTime) / 1000)
|
|
101
|
+
setElapsedSeconds(elapsed)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
updateElapsed()
|
|
105
|
+
const interval = setInterval(updateElapsed, 1000)
|
|
106
|
+
|
|
107
|
+
return () => clearInterval(interval)
|
|
108
|
+
}, [timerStartTime, shouldShowTimer, statusIndicatorState?.kind])
|
|
109
|
+
|
|
110
|
+
const sessionProgress = useAnvilSessionProgress(anvilSession)
|
|
111
|
+
const isUnlimited =
|
|
112
|
+
anvilSession?.status === 'active' && !anvilSession.rateLimit
|
|
113
|
+
|
|
114
|
+
const renderStatusIndicator = () => {
|
|
115
|
+
switch (statusIndicatorState.kind) {
|
|
116
|
+
case 'ctrlC':
|
|
117
|
+
return <span fg={theme.secondary}>Press Ctrl-C again to exit</span>
|
|
118
|
+
|
|
119
|
+
case 'clipboard':
|
|
120
|
+
// Use green color for feedback success messages
|
|
121
|
+
const isFeedbackSuccess =
|
|
122
|
+
statusIndicatorState.message.includes('Feedback sent')
|
|
123
|
+
return (
|
|
124
|
+
<span fg={isFeedbackSuccess ? theme.success : theme.primary}>
|
|
125
|
+
{statusIndicatorState.message}
|
|
126
|
+
</span>
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
case 'reconnected':
|
|
130
|
+
return <span fg={theme.success}>Back online</span>
|
|
131
|
+
|
|
132
|
+
case 'retrying':
|
|
133
|
+
return (
|
|
134
|
+
<span>
|
|
135
|
+
<TerminalThinkingOrb state="retrying" variant="spark" color={theme.warning} />{' '}
|
|
136
|
+
<ShimmerText text="hiccup — retrying..." primaryColor={theme.warning} />
|
|
137
|
+
</span>
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
case 'capacityWait':
|
|
141
|
+
return (
|
|
142
|
+
<span>
|
|
143
|
+
<TerminalThinkingOrb state="waiting" variant="spark" color={theme.warning} />{' '}
|
|
144
|
+
<ShimmerText
|
|
145
|
+
text="peak traffic — you're in line, starting soon..."
|
|
146
|
+
primaryColor={theme.warning}
|
|
147
|
+
/>
|
|
148
|
+
</span>
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
case 'connecting':
|
|
152
|
+
return (
|
|
153
|
+
<span>
|
|
154
|
+
<TerminalThinkingOrb state="connecting" variant="spark" />{' '}
|
|
155
|
+
<ShimmerText text="dialing in..." />
|
|
156
|
+
</span>
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
case 'waiting':
|
|
160
|
+
return (
|
|
161
|
+
<span>
|
|
162
|
+
<TerminalThinkingOrb state="waiting" variant="spark" />{' '}
|
|
163
|
+
<ShimmerText
|
|
164
|
+
text="warming up..."
|
|
165
|
+
interval={SHIMMER_INTERVAL_MS}
|
|
166
|
+
primaryColor={theme.secondary}
|
|
167
|
+
/>
|
|
168
|
+
</span>
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
case 'streaming':
|
|
172
|
+
return (
|
|
173
|
+
<span>
|
|
174
|
+
<TerminalThinkingOrb state="working" variant="spark" />{' '}
|
|
175
|
+
<ShimmerText
|
|
176
|
+
text="on it..."
|
|
177
|
+
interval={SHIMMER_INTERVAL_MS}
|
|
178
|
+
primaryColor={theme.secondary}
|
|
179
|
+
/>
|
|
180
|
+
</span>
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
case 'paused':
|
|
184
|
+
return null
|
|
185
|
+
|
|
186
|
+
case 'idle':
|
|
187
|
+
if (sessionProgress !== null) {
|
|
188
|
+
const isUrgent =
|
|
189
|
+
sessionProgress.remainingMs < ANVIL_COUNTDOWN_VISIBLE_MS
|
|
190
|
+
const modelName =
|
|
191
|
+
anvilSession?.status === 'active'
|
|
192
|
+
? getAnvilModel(anvilSession.model).displayName
|
|
193
|
+
: null
|
|
194
|
+
return (
|
|
195
|
+
<span
|
|
196
|
+
fg={
|
|
197
|
+
isUnlimited
|
|
198
|
+
? theme.secondary
|
|
199
|
+
: isUrgent
|
|
200
|
+
? theme.warning
|
|
201
|
+
: theme.secondary
|
|
202
|
+
}
|
|
203
|
+
>
|
|
204
|
+
{modelName ? `${modelName} · ` : ''}
|
|
205
|
+
{isUnlimited
|
|
206
|
+
? 'unlimited'
|
|
207
|
+
: formatAnvilSessionRemaining(sessionProgress.remainingMs)}
|
|
208
|
+
</span>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
return null
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const renderElapsedTime = () => {
|
|
216
|
+
if (!shouldShowTimer || elapsedSeconds === 0) {
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<span fg={theme.secondary}>
|
|
222
|
+
worked for {formatElapsedTime(elapsedSeconds)}
|
|
223
|
+
</span>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const statusIndicatorContent = renderStatusIndicator()
|
|
228
|
+
const elapsedTimeContent = renderElapsedTime()
|
|
229
|
+
|
|
230
|
+
// Show gray background when there's status indicator, timer, or when the
|
|
231
|
+
// anvil session fill is visible (otherwise the fill would float over
|
|
232
|
+
// transparent space).
|
|
233
|
+
const hasContent =
|
|
234
|
+
statusIndicatorContent || elapsedTimeContent || sessionProgress !== null
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<box
|
|
238
|
+
style={{
|
|
239
|
+
width: '100%',
|
|
240
|
+
flexDirection: 'row',
|
|
241
|
+
alignItems: 'center',
|
|
242
|
+
paddingLeft: 1,
|
|
243
|
+
paddingRight: 1,
|
|
244
|
+
gap: 1,
|
|
245
|
+
backgroundColor: hasContent ? theme.surface : 'transparent',
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
{sessionProgress !== null && (
|
|
249
|
+
<box
|
|
250
|
+
style={{
|
|
251
|
+
position: 'absolute',
|
|
252
|
+
left: 0,
|
|
253
|
+
top: 0,
|
|
254
|
+
bottom: 0,
|
|
255
|
+
// Fill anchors left and shrinks as time passes — the draining
|
|
256
|
+
// bar is the countdown; no separate numeric readout needed.
|
|
257
|
+
width: `${sessionProgress.fraction * 100}%`,
|
|
258
|
+
backgroundColor: theme.surfaceHover,
|
|
259
|
+
}}
|
|
260
|
+
/>
|
|
261
|
+
)}
|
|
262
|
+
<box
|
|
263
|
+
style={{
|
|
264
|
+
flexGrow: 1,
|
|
265
|
+
flexShrink: 1,
|
|
266
|
+
flexBasis: 0,
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
<text style={{ wrapMode: 'none' }}>{statusIndicatorContent}</text>
|
|
270
|
+
</box>
|
|
271
|
+
|
|
272
|
+
<box style={{ flexShrink: 0 }}>
|
|
273
|
+
{!isAtBottom && <ScrollToBottomButton onClick={scrollToLatest} />}
|
|
274
|
+
</box>
|
|
275
|
+
|
|
276
|
+
<box
|
|
277
|
+
style={{
|
|
278
|
+
flexGrow: 1,
|
|
279
|
+
flexShrink: 1,
|
|
280
|
+
flexBasis: 0,
|
|
281
|
+
flexDirection: 'row',
|
|
282
|
+
justifyContent: 'flex-end',
|
|
283
|
+
alignItems: 'center',
|
|
284
|
+
gap: 1,
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
287
|
+
<text style={{ wrapMode: 'none' }}>{elapsedTimeContent}</text>
|
|
288
|
+
{onStop &&
|
|
289
|
+
(statusIndicatorState.kind === 'waiting' ||
|
|
290
|
+
statusIndicatorState.kind === 'streaming') && (
|
|
291
|
+
<StatusActionButton onClick={onStop}>■ Esc</StatusActionButton>
|
|
292
|
+
)}
|
|
293
|
+
{onEndSession &&
|
|
294
|
+
statusIndicatorState.kind === 'idle' &&
|
|
295
|
+
anvilSession?.status === 'active' && (
|
|
296
|
+
<StatusActionButton onClick={onEndSession}>
|
|
297
|
+
✕ End session
|
|
298
|
+
</StatusActionButton>
|
|
299
|
+
)}
|
|
300
|
+
{sessionProgress !== null &&
|
|
301
|
+
sessionProgress.remainingMs < ANVIL_COUNTDOWN_VISIBLE_MS &&
|
|
302
|
+
statusIndicatorState.kind !== 'idle' &&
|
|
303
|
+
!isUnlimited && (
|
|
304
|
+
<text style={{ wrapMode: 'none' }}>
|
|
305
|
+
<span fg={theme.warning} attributes={TextAttributes.BOLD}>
|
|
306
|
+
{formatAnvilSessionCountdown(sessionProgress.remainingMs)}
|
|
307
|
+
</span>
|
|
308
|
+
</text>
|
|
309
|
+
)}
|
|
310
|
+
</box>
|
|
311
|
+
</box>
|
|
312
|
+
)
|
|
313
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { SUBSCRIPTION_TIERS } from '@anvil/common/constants/subscription-plans'
|
|
2
|
+
import { IS_ANVIL } from '../utils/constants'
|
|
3
|
+
import { safeOpen } from '../utils/open-url'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
import { Button } from './button'
|
|
7
|
+
import { ProgressBar } from './progress-bar'
|
|
8
|
+
import { useSubscriptionQuery } from '../hooks/use-subscription-query'
|
|
9
|
+
import { useTheme } from '../hooks/use-theme'
|
|
10
|
+
import { useUpdatePreference } from '../hooks/use-update-preference'
|
|
11
|
+
import { useUsageQuery } from '../hooks/use-usage-query'
|
|
12
|
+
import { WEBSITE_URL } from '../login/constants'
|
|
13
|
+
import { useChatStore } from '../state/chat-store'
|
|
14
|
+
import { formatResetTime } from '../utils/time-format'
|
|
15
|
+
import { BORDER_CHARS } from '../utils/ui-constants'
|
|
16
|
+
|
|
17
|
+
export const SubscriptionLimitBanner = () => {
|
|
18
|
+
if (IS_ANVIL) return null
|
|
19
|
+
|
|
20
|
+
const setInputMode = useChatStore((state) => state.setInputMode)
|
|
21
|
+
const theme = useTheme()
|
|
22
|
+
|
|
23
|
+
const { data: subscriptionData } = useSubscriptionQuery({
|
|
24
|
+
refetchInterval: 15 * 1000,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const { data: usageData } = useUsageQuery({
|
|
28
|
+
enabled: true,
|
|
29
|
+
refetchInterval: 30 * 1000,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const rateLimit = subscriptionData?.hasSubscription ? subscriptionData.rateLimit : undefined
|
|
33
|
+
const remainingBalance = usageData?.remainingBalance ?? 0
|
|
34
|
+
const hasAlaCarteCredits = remainingBalance > 0
|
|
35
|
+
|
|
36
|
+
// Determine if user can upgrade (not on highest tier)
|
|
37
|
+
const maxTier = Math.max(...Object.keys(SUBSCRIPTION_TIERS).map(Number))
|
|
38
|
+
const currentTier = subscriptionData?.hasSubscription ? subscriptionData.subscription.tier : 0
|
|
39
|
+
const canUpgrade = currentTier < maxTier
|
|
40
|
+
|
|
41
|
+
const fallbackToALaCarte = subscriptionData?.fallbackToALaCarte ?? false
|
|
42
|
+
const updatePreference = useUpdatePreference()
|
|
43
|
+
|
|
44
|
+
const handleToggleFallbackToALaCarte = () => {
|
|
45
|
+
updatePreference.mutate({ fallbackToALaCarte: !fallbackToALaCarte })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!subscriptionData || !rateLimit?.limited) {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { reason, weeklyPercentUsed, weeklyResetsAt: weeklyResetsAtStr, blockResetsAt: blockResetsAtStr } = rateLimit
|
|
53
|
+
const isWeeklyLimit = reason === 'weekly_limit'
|
|
54
|
+
const isBlockExhausted = reason === 'block_exhausted'
|
|
55
|
+
const weeklyRemaining = 100 - weeklyPercentUsed
|
|
56
|
+
const weeklyResetsAt = weeklyResetsAtStr ? new Date(weeklyResetsAtStr) : null
|
|
57
|
+
const blockResetsAt = blockResetsAtStr ? new Date(blockResetsAtStr) : null
|
|
58
|
+
|
|
59
|
+
const handleContinueWithCredits = () => {
|
|
60
|
+
setInputMode('default')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const handleBuyCredits = () => {
|
|
64
|
+
safeOpen(WEBSITE_URL + '/usage')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handleUpgrade = () => {
|
|
68
|
+
safeOpen(WEBSITE_URL + '/subscribe')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const borderColor = isWeeklyLimit ? theme.error : theme.warning
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<box
|
|
75
|
+
style={{
|
|
76
|
+
width: '100%',
|
|
77
|
+
borderStyle: 'single',
|
|
78
|
+
borderColor,
|
|
79
|
+
customBorderChars: BORDER_CHARS,
|
|
80
|
+
paddingLeft: 1,
|
|
81
|
+
paddingRight: 1,
|
|
82
|
+
paddingTop: 0,
|
|
83
|
+
paddingBottom: 0,
|
|
84
|
+
flexDirection: 'column',
|
|
85
|
+
gap: 0,
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<box
|
|
89
|
+
style={{
|
|
90
|
+
flexDirection: 'column',
|
|
91
|
+
justifyContent: 'center',
|
|
92
|
+
minHeight: 3,
|
|
93
|
+
gap: 0,
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
{isWeeklyLimit ? (
|
|
97
|
+
<>
|
|
98
|
+
<text style={{ fg: theme.error, marginBottom: 1 }}>
|
|
99
|
+
🛑 Weekly limit reached
|
|
100
|
+
</text>
|
|
101
|
+
<text style={{ fg: theme.muted }}>
|
|
102
|
+
You've used all {rateLimit.weeklyLimit.toLocaleString()} credits for this week.
|
|
103
|
+
</text>
|
|
104
|
+
{weeklyResetsAt && (
|
|
105
|
+
<text style={{ fg: theme.muted }}>
|
|
106
|
+
Weekly usage resets in {formatResetTime(weeklyResetsAt)}
|
|
107
|
+
</text>
|
|
108
|
+
)}
|
|
109
|
+
</>
|
|
110
|
+
) : isBlockExhausted ? (
|
|
111
|
+
<>
|
|
112
|
+
<text style={{ fg: theme.warning, marginBottom: 1 }}>
|
|
113
|
+
5 hour limit reached
|
|
114
|
+
</text>
|
|
115
|
+
{blockResetsAt && (
|
|
116
|
+
<text style={{ fg: theme.muted }}>
|
|
117
|
+
New session starts in {formatResetTime(blockResetsAt)}
|
|
118
|
+
</text>
|
|
119
|
+
)}
|
|
120
|
+
</>
|
|
121
|
+
) : (
|
|
122
|
+
<text style={{ fg: theme.warning }}>
|
|
123
|
+
Subscription limit reached
|
|
124
|
+
</text>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
<box style={{ flexDirection: 'row', alignItems: 'center', gap: 1, marginTop: 0 }}>
|
|
128
|
+
<text style={{ fg: theme.muted }}>Weekly:</text>
|
|
129
|
+
<ProgressBar value={weeklyRemaining} width={12} showPercentage={false} />
|
|
130
|
+
<text style={{ fg: theme.muted }}>{weeklyPercentUsed}% used</text>
|
|
131
|
+
</box>
|
|
132
|
+
|
|
133
|
+
{hasAlaCarteCredits ? (
|
|
134
|
+
<box style={{ flexDirection: 'column', gap: 1, marginTop: 1 }}>
|
|
135
|
+
{fallbackToALaCarte ? (
|
|
136
|
+
<>
|
|
137
|
+
<text style={{ fg: theme.muted }}>
|
|
138
|
+
✓ Credit spending enabled. You can continue using your credits.
|
|
139
|
+
</text>
|
|
140
|
+
<box style={{ flexDirection: 'row', gap: 2 }}>
|
|
141
|
+
<Button onClick={handleContinueWithCredits}>
|
|
142
|
+
<text style={{ fg: theme.background, bg: theme.foreground }}>
|
|
143
|
+
{' '}Continue with credits ({remainingBalance.toLocaleString()}){' '}
|
|
144
|
+
</text>
|
|
145
|
+
</Button>
|
|
146
|
+
{canUpgrade ? (
|
|
147
|
+
<Button onClick={handleUpgrade}>
|
|
148
|
+
<text style={{ fg: theme.background, bg: theme.foreground }}>{' '}Upgrade Plan ↗{' '}</text>
|
|
149
|
+
</Button>
|
|
150
|
+
) : (
|
|
151
|
+
<Button onClick={handleBuyCredits}>
|
|
152
|
+
<text style={{ fg: theme.background, bg: theme.muted }}>{' '}Buy Credits ↗{' '}</text>
|
|
153
|
+
</Button>
|
|
154
|
+
)}
|
|
155
|
+
</box>
|
|
156
|
+
<Button onClick={handleToggleFallbackToALaCarte} disabled={updatePreference.isPending}>
|
|
157
|
+
<text style={{ fg: theme.muted }}>
|
|
158
|
+
{updatePreference.isPending ? '[updating...]' : '[disable credit spending]'}
|
|
159
|
+
</text>
|
|
160
|
+
</Button>
|
|
161
|
+
</>
|
|
162
|
+
) : (
|
|
163
|
+
<>
|
|
164
|
+
<text style={{ fg: theme.warning }}>
|
|
165
|
+
Credit spending is disabled. Enable it to continue.
|
|
166
|
+
</text>
|
|
167
|
+
<box style={{ flexDirection: 'row', gap: 2 }}>
|
|
168
|
+
<Button onClick={handleToggleFallbackToALaCarte} disabled={updatePreference.isPending}>
|
|
169
|
+
<text style={{ fg: theme.background, bg: theme.foreground }}>
|
|
170
|
+
{updatePreference.isPending ? ' Enabling... ' : ' Enable Credit Spending '}
|
|
171
|
+
</text>
|
|
172
|
+
</Button>
|
|
173
|
+
{canUpgrade ? (
|
|
174
|
+
<Button onClick={handleUpgrade}>
|
|
175
|
+
<text style={{ fg: theme.background, bg: theme.muted }}>{' '}Upgrade Plan ↗{' '}</text>
|
|
176
|
+
</Button>
|
|
177
|
+
) : (
|
|
178
|
+
<Button onClick={handleBuyCredits}>
|
|
179
|
+
<text style={{ fg: theme.background, bg: theme.muted }}>{' '}Buy Credits ↗{' '}</text>
|
|
180
|
+
</Button>
|
|
181
|
+
)}
|
|
182
|
+
</box>
|
|
183
|
+
<text style={{ fg: theme.muted }}>
|
|
184
|
+
You have {remainingBalance.toLocaleString()} credits available.
|
|
185
|
+
</text>
|
|
186
|
+
</>
|
|
187
|
+
)}
|
|
188
|
+
</box>
|
|
189
|
+
) : (
|
|
190
|
+
<box style={{ flexDirection: 'row', gap: 2, marginTop: 1 }}>
|
|
191
|
+
<text style={{ fg: theme.muted }}>No a-la-carte credits available.</text>
|
|
192
|
+
{canUpgrade ? (
|
|
193
|
+
<Button onClick={handleUpgrade}>
|
|
194
|
+
<text style={{ fg: theme.background, bg: theme.muted }}>{' '}Upgrade Plan ↗{' '}</text>
|
|
195
|
+
</Button>
|
|
196
|
+
) : (
|
|
197
|
+
<Button onClick={handleBuyCredits}>
|
|
198
|
+
<text style={{ fg: theme.background, bg: theme.muted }}>{' '}Buy Credits ↗{' '}</text>
|
|
199
|
+
</Button>
|
|
200
|
+
)}
|
|
201
|
+
</box>
|
|
202
|
+
)}
|
|
203
|
+
</box>
|
|
204
|
+
</box>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { TextAttributes } from '@opentui/core'
|
|
2
|
+
import { useCallback, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Button } from './button'
|
|
5
|
+
import { useTerminalDimensions } from '../hooks/use-terminal-dimensions'
|
|
6
|
+
import { useTheme } from '../hooks/use-theme'
|
|
7
|
+
|
|
8
|
+
/** Minimum terminal width to reveal the full prompt preview on hover */
|
|
9
|
+
const MIN_WIDTH_FOR_DESCRIPTION = 80
|
|
10
|
+
/** Gap between the label column and the hover preview so they never touch */
|
|
11
|
+
const LABEL_DESCRIPTION_GAP = 2
|
|
12
|
+
|
|
13
|
+
export interface SuggestedPrompt {
|
|
14
|
+
/** Short text shown on the chip */
|
|
15
|
+
label: string
|
|
16
|
+
/** Full prompt submitted to the agent when clicked */
|
|
17
|
+
prompt: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Metadata passed alongside the prompt when a suggestion is chosen */
|
|
21
|
+
export interface SuggestedPromptSelection {
|
|
22
|
+
label: string
|
|
23
|
+
index: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* First-time onboarding prompts. Kept short and broadly useful so a brand-new
|
|
28
|
+
* user can get a feel for what the agent does with a single click.
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_SUGGESTED_PROMPTS: SuggestedPrompt[] = [
|
|
31
|
+
{
|
|
32
|
+
label: 'Explain this codebase',
|
|
33
|
+
prompt:
|
|
34
|
+
'Give me a high-level overview of how this codebase is structured and what the main parts do.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: 'Find opportunities to refactor',
|
|
38
|
+
prompt:
|
|
39
|
+
'Look through my codebase for opportunities to refactor and simplify, and suggest the highest-impact ones.',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: 'Improve my test coverage',
|
|
43
|
+
prompt:
|
|
44
|
+
'Analyze my test coverage and tell me where adding tests would have the most impact.',
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
interface SuggestedPromptLineProps {
|
|
49
|
+
prompt: SuggestedPrompt
|
|
50
|
+
index: number
|
|
51
|
+
isHovered: boolean
|
|
52
|
+
onSelect: (prompt: string, selection: SuggestedPromptSelection) => void
|
|
53
|
+
onHover: (label: string | null) => void
|
|
54
|
+
/** Fixed width of the label column so hover previews align across rows */
|
|
55
|
+
labelColumnWidth: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const SuggestedPromptLine = ({
|
|
59
|
+
prompt,
|
|
60
|
+
index,
|
|
61
|
+
isHovered,
|
|
62
|
+
onSelect,
|
|
63
|
+
onHover,
|
|
64
|
+
labelColumnWidth,
|
|
65
|
+
}: SuggestedPromptLineProps) => {
|
|
66
|
+
const theme = useTheme()
|
|
67
|
+
const { terminalWidth } = useTerminalDimensions()
|
|
68
|
+
|
|
69
|
+
const handleClick = useCallback(
|
|
70
|
+
() => onSelect(prompt.prompt, { label: prompt.label, index }),
|
|
71
|
+
[onSelect, prompt.prompt, prompt.label, index],
|
|
72
|
+
)
|
|
73
|
+
const handleMouseOver = useCallback(
|
|
74
|
+
() => onHover(prompt.label),
|
|
75
|
+
[onHover, prompt.label],
|
|
76
|
+
)
|
|
77
|
+
const handleMouseOut = useCallback(() => onHover(null), [onHover])
|
|
78
|
+
|
|
79
|
+
const iconColor = isHovered ? theme.primary : theme.muted
|
|
80
|
+
const labelColor = isHovered ? theme.primary : theme.foreground
|
|
81
|
+
|
|
82
|
+
// On hover, reveal the full prompt that will be sent so the short chip label
|
|
83
|
+
// doesn't hide what actually happens. Only when there's room for it.
|
|
84
|
+
const showDescription =
|
|
85
|
+
isHovered && terminalWidth >= MIN_WIDTH_FOR_DESCRIPTION
|
|
86
|
+
const labelLength = '→ '.length + prompt.label.length
|
|
87
|
+
const paddingSpaces = showDescription
|
|
88
|
+
? ' '.repeat(Math.max(0, labelColumnWidth - labelLength))
|
|
89
|
+
: ''
|
|
90
|
+
const truncatedPrompt = showDescription
|
|
91
|
+
? (() => {
|
|
92
|
+
const availableWidth = Math.max(0, terminalWidth - labelColumnWidth - 4)
|
|
93
|
+
return prompt.prompt.length > availableWidth
|
|
94
|
+
? prompt.prompt.slice(0, availableWidth - 1) + '…'
|
|
95
|
+
: prompt.prompt
|
|
96
|
+
})()
|
|
97
|
+
: ''
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<box style={{ flexDirection: 'row', width: '100%' }}>
|
|
101
|
+
{/* Only the label itself is clickable */}
|
|
102
|
+
<Button
|
|
103
|
+
onClick={handleClick}
|
|
104
|
+
onMouseOver={handleMouseOver}
|
|
105
|
+
onMouseOut={handleMouseOut}
|
|
106
|
+
style={{
|
|
107
|
+
flexShrink: 0,
|
|
108
|
+
flexGrow: 0,
|
|
109
|
+
backgroundColor: isHovered ? theme.surface : undefined,
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<text style={{ wrapMode: 'none' }}>
|
|
113
|
+
<span fg={iconColor}>→</span>
|
|
114
|
+
<span
|
|
115
|
+
fg={labelColor}
|
|
116
|
+
attributes={isHovered ? TextAttributes.BOLD : undefined}
|
|
117
|
+
>
|
|
118
|
+
{' '}
|
|
119
|
+
{prompt.label}
|
|
120
|
+
</span>
|
|
121
|
+
</text>
|
|
122
|
+
</Button>
|
|
123
|
+
{/* Non-clickable preview of the full prompt, aligned via padding */}
|
|
124
|
+
{showDescription && (
|
|
125
|
+
<box style={{ flexGrow: 1 }}>
|
|
126
|
+
<text style={{ wrapMode: 'none' }}>
|
|
127
|
+
<span fg={theme.muted} attributes={TextAttributes.ITALIC}>
|
|
128
|
+
{paddingSpaces}
|
|
129
|
+
{truncatedPrompt}
|
|
130
|
+
</span>
|
|
131
|
+
</text>
|
|
132
|
+
</box>
|
|
133
|
+
)}
|
|
134
|
+
</box>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface SuggestedPromptsProps {
|
|
139
|
+
onSelect: (prompt: string, selection: SuggestedPromptSelection) => void
|
|
140
|
+
/** Limit the number of suggestions shown (e.g. on short terminals) */
|
|
141
|
+
maxItems?: number
|
|
142
|
+
prompts?: SuggestedPrompt[]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A small block of clickable starter prompts shown to first-time users on an
|
|
147
|
+
* empty chat, just above the input box. Clicking one submits it immediately.
|
|
148
|
+
*/
|
|
149
|
+
export const SuggestedPrompts = ({
|
|
150
|
+
onSelect,
|
|
151
|
+
maxItems,
|
|
152
|
+
prompts = DEFAULT_SUGGESTED_PROMPTS,
|
|
153
|
+
}: SuggestedPromptsProps) => {
|
|
154
|
+
const theme = useTheme()
|
|
155
|
+
const [hoveredLabel, setHoveredLabel] = useState<string | null>(null)
|
|
156
|
+
|
|
157
|
+
const items =
|
|
158
|
+
maxItems != null ? prompts.slice(0, Math.max(0, maxItems)) : prompts
|
|
159
|
+
|
|
160
|
+
if (items.length === 0) return null
|
|
161
|
+
|
|
162
|
+
// Shared label-column width so the hover previews line up across rows, plus
|
|
163
|
+
// a gap so the preview never touches even the longest label.
|
|
164
|
+
const labelColumnWidth =
|
|
165
|
+
Math.max(...items.map((p) => '→ '.length + p.label.length)) +
|
|
166
|
+
LABEL_DESCRIPTION_GAP
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<box style={{ flexDirection: 'column', paddingLeft: 1, paddingBottom: 1 }}>
|
|
170
|
+
<text style={{ fg: theme.muted }}>Try one of these:</text>
|
|
171
|
+
{items.map((prompt, index) => (
|
|
172
|
+
<SuggestedPromptLine
|
|
173
|
+
key={prompt.label}
|
|
174
|
+
prompt={prompt}
|
|
175
|
+
index={index}
|
|
176
|
+
isHovered={hoveredLabel === prompt.label}
|
|
177
|
+
onSelect={onSelect}
|
|
178
|
+
onHover={setHoveredLabel}
|
|
179
|
+
labelColumnWidth={labelColumnWidth}
|
|
180
|
+
/>
|
|
181
|
+
))}
|
|
182
|
+
</box>
|
|
183
|
+
)
|
|
184
|
+
}
|