@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,96 @@
|
|
|
1
|
+
// Find tree-sitter.wasm so the SDK's tree-sitter parser singleton can load
|
|
2
|
+
// it at runtime. Must be the very first import in `index.tsx`: subsequent
|
|
3
|
+
// imports (the SDK / code-map) eagerly construct the parser, and its init
|
|
4
|
+
// reads what we publish here on `globalThis` and via the env var.
|
|
5
|
+
//
|
|
6
|
+
// Final approach after several attempts to embed the wasm into the bun
|
|
7
|
+
// --compile binary all failed on Windows (the bytes ended up in the
|
|
8
|
+
// binary, but every JS-level retrieval mechanism — `with { type: 'file' }`
|
|
9
|
+
// import binding, base64 string literals, chunked base64 in a generated
|
|
10
|
+
// module, function-export wrappers — was either tree-shaken, transformed
|
|
11
|
+
// by the minifier, or otherwise stripped):
|
|
12
|
+
//
|
|
13
|
+
// ship tree-sitter.wasm as a sibling file next to the binary.
|
|
14
|
+
//
|
|
15
|
+
// It's 200KB, the npm tarball already contains the binary; adding one
|
|
16
|
+
// more file is trivial. The build script copies the wasm into `cli/bin/`
|
|
17
|
+
// after compile, the release workflow tarballs both, and the anvil /
|
|
18
|
+
// anvil downloader extracts both into the same directory. At runtime,
|
|
19
|
+
// `process.execPath` plus a relative file lookup gets us the wasm with
|
|
20
|
+
// zero bundler involvement.
|
|
21
|
+
|
|
22
|
+
import { existsSync, readFileSync } from 'fs'
|
|
23
|
+
import { dirname, isAbsolute, join, resolve } from 'path'
|
|
24
|
+
|
|
25
|
+
// Where to look for the sibling tree-sitter.wasm. We can't just use
|
|
26
|
+
// `dirname(process.execPath)`: at pre-init time inside a bun --compile
|
|
27
|
+
// binary on Windows, `process.execPath` returns the *bunfs* internal
|
|
28
|
+
// path (`B:\~BUN\root\<binary>.exe`) rather than the on-disk path of
|
|
29
|
+
// the .exe the user invoked. By the time main() runs it switches to
|
|
30
|
+
// the disk path, but pre-init has long since bailed out.
|
|
31
|
+
//
|
|
32
|
+
// Try several sources in order; the first whose sibling .wasm exists
|
|
33
|
+
// wins. argv[0] is normally the path the binary was invoked with —
|
|
34
|
+
// always a real disk path, never bunfs. execPath is kept as a fallback
|
|
35
|
+
// for environments where argv[0] is something exotic.
|
|
36
|
+
const candidates = (
|
|
37
|
+
[process.argv[0], process.execPath] as Array<string | undefined>
|
|
38
|
+
)
|
|
39
|
+
.filter((p): p is string => typeof p === 'string' && p.length > 0)
|
|
40
|
+
.map((p) => (isAbsolute(p) ? p : resolve(p)))
|
|
41
|
+
.map((p) => join(dirname(p), 'tree-sitter.wasm'))
|
|
42
|
+
|
|
43
|
+
const siblingPath = candidates.find((p) => existsSync(p))
|
|
44
|
+
|
|
45
|
+
// Pre-init diagnostic — only fires when --smoke-tree-sitter is set so we
|
|
46
|
+
// don't spam every run. We need to see what argv[0] / execPath looked
|
|
47
|
+
// like at this exact phase on Windows: the round-7 main() diag showed
|
|
48
|
+
// disk paths, but pre-init silently bailed, meaning module-init time
|
|
49
|
+
// gives different values. argv[0] alone wasn't enough to fix it.
|
|
50
|
+
if (process.argv.includes('--smoke-tree-sitter')) {
|
|
51
|
+
console.error(
|
|
52
|
+
`[pre-init diag] argv[0]=${process.argv[0]}\n` +
|
|
53
|
+
`[pre-init diag] execPath=${process.execPath}\n` +
|
|
54
|
+
`[pre-init diag] candidates=${JSON.stringify(candidates)}\n` +
|
|
55
|
+
`[pre-init diag] resolved siblingPath=${siblingPath ?? '<none>'}\n`,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (siblingPath) {
|
|
60
|
+
// Tell init-node.ts (in code-map / the SDK bundle) where the wasm
|
|
61
|
+
// is. The locateFile callback there will hand this path to
|
|
62
|
+
// emscripten, which fs.readFile's it.
|
|
63
|
+
process.env.ANVIL_TREE_SITTER_WASM_PATH = siblingPath
|
|
64
|
+
|
|
65
|
+
// Also publish on globalThis so the smoke handler in index.tsx can
|
|
66
|
+
// read it without touching process.env (which is gated by the env
|
|
67
|
+
// architecture check outside the allowlisted pre-init files).
|
|
68
|
+
;(
|
|
69
|
+
globalThis as { __ANVIL_TREE_SITTER_WASM_PATH__?: string }
|
|
70
|
+
).__ANVIL_TREE_SITTER_WASM_PATH__ = siblingPath
|
|
71
|
+
|
|
72
|
+
// Also try the synchronous-bytes path: hand the bytes straight to
|
|
73
|
+
// Parser.init({ wasmBinary }) so the SDK doesn't need to round-trip
|
|
74
|
+
// through emscripten's path resolution. Both channels feed the same
|
|
75
|
+
// tree-sitter init; whichever one trips first wins.
|
|
76
|
+
try {
|
|
77
|
+
const buf = readFileSync(siblingPath)
|
|
78
|
+
;(
|
|
79
|
+
globalThis as { __ANVIL_TREE_SITTER_WASM_BINARY__?: Uint8Array }
|
|
80
|
+
).__ANVIL_TREE_SITTER_WASM_BINARY__ = new Uint8Array(
|
|
81
|
+
buf.buffer,
|
|
82
|
+
buf.byteOffset,
|
|
83
|
+
buf.byteLength,
|
|
84
|
+
)
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error(
|
|
87
|
+
'[tree-sitter pre-init] readFileSync failed for sibling wasm at',
|
|
88
|
+
siblingPath,
|
|
89
|
+
'—',
|
|
90
|
+
err instanceof Error ? err.message : String(err),
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// `--smoke-tree-sitter` is the deterministic CI gate. The handler lives at
|
|
96
|
+
// the top of main() in cli/src/index.tsx (before parseArgs).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { mkdirSync, readdirSync, statSync } from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
import { getConfigDir } from './utils/auth'
|
|
5
|
+
|
|
6
|
+
let projectRoot: string | undefined
|
|
7
|
+
let currentChatId: string | undefined
|
|
8
|
+
|
|
9
|
+
function ensureChatDirectory(dir: string) {
|
|
10
|
+
mkdirSync(dir, { recursive: true })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function setProjectRoot(dir: string) {
|
|
14
|
+
projectRoot = dir
|
|
15
|
+
return projectRoot
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getProjectRoot() {
|
|
19
|
+
if (!projectRoot) {
|
|
20
|
+
throw new Error('Project root not set')
|
|
21
|
+
}
|
|
22
|
+
return projectRoot
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function tryGetProjectRoot() {
|
|
26
|
+
return projectRoot
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getCurrentChatId() {
|
|
30
|
+
if (!currentChatId) {
|
|
31
|
+
currentChatId = new Date().toISOString().replace(/:/g, '-')
|
|
32
|
+
}
|
|
33
|
+
return currentChatId
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function setCurrentChatId(chatId: string) {
|
|
37
|
+
currentChatId = chatId
|
|
38
|
+
return currentChatId
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function startNewChat() {
|
|
42
|
+
currentChatId = new Date().toISOString().replace(/:/g, '-')
|
|
43
|
+
return currentChatId
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get the project-specific data directory
|
|
47
|
+
export function getProjectDataDir(): string {
|
|
48
|
+
const root = getProjectRoot()
|
|
49
|
+
if (!root) {
|
|
50
|
+
throw new Error('Project root not set')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const baseName = path.basename(root)
|
|
54
|
+
const baseDir = path.join(getConfigDir(), 'projects', baseName)
|
|
55
|
+
|
|
56
|
+
return baseDir
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Find the most recent chat directory based on modification time
|
|
61
|
+
* Returns null if no chat directories exist
|
|
62
|
+
*/
|
|
63
|
+
export function getMostRecentChatDir(): string | null {
|
|
64
|
+
try {
|
|
65
|
+
const chatsDir = path.join(getProjectDataDir(), 'chats')
|
|
66
|
+
if (!statSync(chatsDir, { throwIfNoEntry: false })) {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const chatDirs = readdirSync(chatsDir)
|
|
71
|
+
.map((name) => {
|
|
72
|
+
const fullPath = path.join(chatsDir, name)
|
|
73
|
+
try {
|
|
74
|
+
const stat = statSync(fullPath)
|
|
75
|
+
return { name, fullPath, mtime: stat.mtime }
|
|
76
|
+
} catch {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.filter(
|
|
81
|
+
(item): item is { name: string; fullPath: string; mtime: Date } =>
|
|
82
|
+
item !== null && statSync(item.fullPath).isDirectory(),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if (chatDirs.length === 0) {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Sort by modification time, most recent first
|
|
90
|
+
chatDirs.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
|
|
91
|
+
return chatDirs[0].fullPath
|
|
92
|
+
} catch {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getCurrentChatDir(): string {
|
|
98
|
+
const chatId = getCurrentChatId()
|
|
99
|
+
const dir = path.join(getProjectDataDir(), 'chats', chatId)
|
|
100
|
+
ensureChatDirectory(dir)
|
|
101
|
+
return dir
|
|
102
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive key-setup wizard for first-time CLI boot.
|
|
3
|
+
*
|
|
4
|
+
* Prompts the user to paste API keys for DeepSeek (required), Gemini
|
|
5
|
+
* (required), and Serper (optional). Saves them to .env.local so they
|
|
6
|
+
* persist across sessions.
|
|
7
|
+
*
|
|
8
|
+
* Runs BEFORE the TUI renderer starts, using plain console I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs'
|
|
12
|
+
import os from 'os'
|
|
13
|
+
import path from 'path'
|
|
14
|
+
import { createInterface } from 'readline'
|
|
15
|
+
|
|
16
|
+
import { cyan, green, yellow, bold, dim } from 'picocolors'
|
|
17
|
+
|
|
18
|
+
// ─── constants ───────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const REQUIRED_KEYS = [
|
|
21
|
+
{
|
|
22
|
+
envVar: 'DEEPSEEK_API_KEY',
|
|
23
|
+
label: 'DeepSeek API Key',
|
|
24
|
+
helpUrl: 'https://platform.deepseek.com/api_keys',
|
|
25
|
+
description: 'Powers the main coding agent (DeepSeek V4).',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
envVar: 'GEMINI_API_KEY',
|
|
29
|
+
label: 'Gemini API Key',
|
|
30
|
+
helpUrl: 'https://aistudio.google.com/apikey',
|
|
31
|
+
description: 'Powers vision analysis, file search, and helper sub-agents.',
|
|
32
|
+
},
|
|
33
|
+
] as const
|
|
34
|
+
|
|
35
|
+
const OPTIONAL_KEYS = [
|
|
36
|
+
{
|
|
37
|
+
envVar: 'SERPER_API_KEY',
|
|
38
|
+
label: 'Serper API Key',
|
|
39
|
+
helpUrl: 'https://serper.dev',
|
|
40
|
+
description: 'Enables Google Search (web_search tool). Without it, web search is disabled.',
|
|
41
|
+
},
|
|
42
|
+
] as const
|
|
43
|
+
|
|
44
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function askQuestion(
|
|
47
|
+
rl: ReturnType<typeof createInterface>,
|
|
48
|
+
question: string,
|
|
49
|
+
): Promise<string> {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
rl.question(question, (answer) => {
|
|
52
|
+
resolve(answer.trim())
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isValidApiKey(key: string): boolean {
|
|
58
|
+
// Loose check: non-empty, no whitespace, reasonable length
|
|
59
|
+
return key.length > 5 && !/\s/.test(key)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function maskKey(key: string): string {
|
|
63
|
+
if (key.length <= 8) return '••••••••'
|
|
64
|
+
return key.slice(0, 4) + '•'.repeat(Math.min(key.length - 8, 20)) + key.slice(-4)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── .env.local writer ───────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function getGlobalEnvPath(): string {
|
|
70
|
+
return path.join(os.homedir(), '.env.local')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readExistingEnv(filePath: string): Record<string, string> {
|
|
74
|
+
const result: Record<string, string> = {}
|
|
75
|
+
if (!fs.existsSync(filePath)) return result
|
|
76
|
+
|
|
77
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
78
|
+
for (const rawLine of content.split('\n')) {
|
|
79
|
+
const line = rawLine.trim()
|
|
80
|
+
if (!line || line.startsWith('#') || line.startsWith('//')) continue
|
|
81
|
+
const eqIndex = line.indexOf('=')
|
|
82
|
+
if (eqIndex === -1) continue
|
|
83
|
+
const key = line.slice(0, eqIndex).trim()
|
|
84
|
+
let value = line.slice(eqIndex + 1).trim()
|
|
85
|
+
if (
|
|
86
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
87
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
88
|
+
) {
|
|
89
|
+
value = value.slice(1, -1)
|
|
90
|
+
}
|
|
91
|
+
if (key) result[key] = value
|
|
92
|
+
}
|
|
93
|
+
return result
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function writeEnvFile(
|
|
97
|
+
filePath: string,
|
|
98
|
+
vars: Record<string, string>,
|
|
99
|
+
): void {
|
|
100
|
+
const lines: string[] = [
|
|
101
|
+
'# API keys for Anvil / Freebuff CLI',
|
|
102
|
+
'# Generated by the setup wizard — do not commit this file.',
|
|
103
|
+
'',
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
// Group: required keys first, then optional, then anything else
|
|
107
|
+
const allKeyOrder = [
|
|
108
|
+
...REQUIRED_KEYS.map((k) => k.envVar),
|
|
109
|
+
...OPTIONAL_KEYS.map((k) => k.envVar),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
const written = new Set<string>()
|
|
113
|
+
|
|
114
|
+
for (const key of allKeyOrder) {
|
|
115
|
+
if (key in vars) {
|
|
116
|
+
lines.push(`${key}=${vars[key]}`)
|
|
117
|
+
written.add(key)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Append any extra keys not in our schema
|
|
122
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
123
|
+
if (!written.has(key)) {
|
|
124
|
+
lines.push(`${key}=${value}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
lines.push('')
|
|
129
|
+
fs.writeFileSync(filePath, lines.join('\n'), 'utf-8')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─── main wizard ─────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
export interface KeySetupResult {
|
|
135
|
+
/** true if the wizard ran and saved keys */
|
|
136
|
+
completed: boolean
|
|
137
|
+
/** true if all required keys are present (either pre-existing or entered) */
|
|
138
|
+
allRequired: boolean
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Run the interactive key-setup wizard.
|
|
143
|
+
*
|
|
144
|
+
* @param cwd Project root (where .env.local is written)
|
|
145
|
+
* @param force If true, re-prompt even if keys already exist (for `setup` command)
|
|
146
|
+
*/
|
|
147
|
+
export async function runKeySetup(
|
|
148
|
+
_cwd: string,
|
|
149
|
+
force = false,
|
|
150
|
+
): Promise<KeySetupResult> {
|
|
151
|
+
const envPath = getGlobalEnvPath()
|
|
152
|
+
const existing = readExistingEnv(envPath)
|
|
153
|
+
|
|
154
|
+
// Check which keys are already set (in existing .env.local OR in process.env)
|
|
155
|
+
const hasKey = (envVar: string): boolean =>
|
|
156
|
+
Boolean(existing[envVar] || process.env[envVar])
|
|
157
|
+
|
|
158
|
+
const missingRequired = REQUIRED_KEYS.filter((k) => !hasKey(k.envVar))
|
|
159
|
+
const missingOptional = OPTIONAL_KEYS.filter((k) => !hasKey(k.envVar))
|
|
160
|
+
|
|
161
|
+
// If nothing is missing and not forced, skip the wizard
|
|
162
|
+
if (missingRequired.length === 0 && missingOptional.length === 0 && !force) {
|
|
163
|
+
return { completed: false, allRequired: true }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log()
|
|
167
|
+
console.log(bold(' ╔══════════════════════════════════════════════╗'))
|
|
168
|
+
console.log(bold(' ║ 🔑 API Key Setup Wizard ║'))
|
|
169
|
+
console.log(bold(' ╚══════════════════════════════════════════════╝'))
|
|
170
|
+
console.log()
|
|
171
|
+
|
|
172
|
+
if (missingRequired.length > 0) {
|
|
173
|
+
console.log(cyan(' Required keys to get started:'))
|
|
174
|
+
console.log()
|
|
175
|
+
for (const k of missingRequired) {
|
|
176
|
+
console.log(` ${bold(k.label)}`)
|
|
177
|
+
console.log(` ${dim(k.description)}`)
|
|
178
|
+
console.log(` Get one at: ${cyan(k.helpUrl)}`)
|
|
179
|
+
console.log()
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (missingOptional.length > 0 && !force) {
|
|
184
|
+
console.log(yellow(' Optional keys (can be added later):'))
|
|
185
|
+
console.log()
|
|
186
|
+
for (const k of missingOptional) {
|
|
187
|
+
console.log(` ${bold(k.label)}`)
|
|
188
|
+
console.log(` ${dim(k.description)}`)
|
|
189
|
+
console.log(` Get one at: ${cyan(k.helpUrl)}`)
|
|
190
|
+
console.log()
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const rl = createInterface({
|
|
195
|
+
input: process.stdin,
|
|
196
|
+
output: process.stdout,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const newVars: Record<string, string> = { ...existing }
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// ── Required keys ──────────────────────────────────────────────────
|
|
203
|
+
for (const k of REQUIRED_KEYS) {
|
|
204
|
+
if (hasKey(k.envVar) && !force) continue
|
|
205
|
+
|
|
206
|
+
const existingVal = existing[k.envVar] || process.env[k.envVar]
|
|
207
|
+
const suffix = existingVal ? ` ${dim(`(currently: ${maskKey(existingVal)})`)}` : ''
|
|
208
|
+
|
|
209
|
+
let attempts = 0
|
|
210
|
+
while (attempts < 3) {
|
|
211
|
+
const answer = await askQuestion(
|
|
212
|
+
rl,
|
|
213
|
+
` Paste your ${k.label}${suffix}\n → `,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
// Allow skipping if there's already a value (force mode)
|
|
217
|
+
if (!answer && existingVal) {
|
|
218
|
+
newVars[k.envVar] = existingVal
|
|
219
|
+
break
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!answer) {
|
|
223
|
+
console.log(yellow(` ⚠ ${k.label} is required. Press Ctrl+C to abort.`))
|
|
224
|
+
attempts++
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!isValidApiKey(answer)) {
|
|
229
|
+
console.log(yellow(' ⚠ That doesn\'t look like a valid API key. Please try again.'))
|
|
230
|
+
attempts++
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
newVars[k.envVar] = answer
|
|
235
|
+
console.log(green(` ✓ ${k.label} saved`))
|
|
236
|
+
console.log()
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (attempts >= 3) {
|
|
241
|
+
console.log()
|
|
242
|
+
console.log(yellow(' Too many attempts. Please set the key manually in .env.local'))
|
|
243
|
+
console.log(yellow(` and re-run the CLI.`))
|
|
244
|
+
console.log()
|
|
245
|
+
rl.close()
|
|
246
|
+
return { completed: false, allRequired: false }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Optional keys ──────────────────────────────────────────────────
|
|
251
|
+
for (const k of OPTIONAL_KEYS) {
|
|
252
|
+
if (hasKey(k.envVar) && !force) continue
|
|
253
|
+
|
|
254
|
+
const existingVal = existing[k.envVar] || process.env[k.envVar]
|
|
255
|
+
const suffix = existingVal ? ` ${dim(`(currently: ${maskKey(existingVal)})`)}` : ''
|
|
256
|
+
|
|
257
|
+
const answer = await askQuestion(
|
|
258
|
+
rl,
|
|
259
|
+
` Paste your ${k.label} ${dim('(optional, press Enter to skip)')}${suffix}\n → `,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if (answer && isValidApiKey(answer)) {
|
|
263
|
+
newVars[k.envVar] = answer
|
|
264
|
+
console.log(green(` ✓ ${k.label} saved`))
|
|
265
|
+
console.log()
|
|
266
|
+
} else if (answer && !isValidApiKey(answer)) {
|
|
267
|
+
console.log(yellow(' ⚠ Invalid format, skipping.'))
|
|
268
|
+
console.log()
|
|
269
|
+
} else {
|
|
270
|
+
console.log(dim(' Skipped.'))
|
|
271
|
+
console.log()
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} finally {
|
|
275
|
+
rl.close()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Save to .env.local ────────────────────────────────────────────────
|
|
279
|
+
writeEnvFile(envPath, newVars)
|
|
280
|
+
|
|
281
|
+
// Also load into process.env so the current session works immediately
|
|
282
|
+
for (const [key, value] of Object.entries(newVars)) {
|
|
283
|
+
if (!process.env[key]) {
|
|
284
|
+
process.env[key] = value
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log(green(` ✓ Keys saved to ${envPath}`))
|
|
289
|
+
console.log()
|
|
290
|
+
console.log(dim(' Tip: Add .env.local to your .gitignore to keep keys out of git.'))
|
|
291
|
+
console.log()
|
|
292
|
+
|
|
293
|
+
const allRequired = REQUIRED_KEYS.every((k) => Boolean(newVars[k.envVar]))
|
|
294
|
+
return { completed: true, allRequired }
|
|
295
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load environment variables from .env.local at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Keys live in the user's HOME directory (~/.env.local) so they persist
|
|
5
|
+
* across every project and working directory. Bun does NOT auto-load
|
|
6
|
+
* .env.local for compiled binaries, so this module parses the simple
|
|
7
|
+
* KEY=VALUE format and applies values to process.env.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs'
|
|
11
|
+
import os from 'os'
|
|
12
|
+
import path from 'path'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the global .env.local path in the user's home directory.
|
|
16
|
+
*/
|
|
17
|
+
function getGlobalEnvPath(): string {
|
|
18
|
+
return path.join(os.homedir(), '.env.local')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse a .env.local file into a record of key → value.
|
|
23
|
+
* Handles:
|
|
24
|
+
* - # and // comments
|
|
25
|
+
* - KEY=value, KEY="value", KEY='value'
|
|
26
|
+
* - Trims whitespace around keys and values
|
|
27
|
+
*/
|
|
28
|
+
function parseEnvFile(content: string): Record<string, string> {
|
|
29
|
+
const result: Record<string, string> = {}
|
|
30
|
+
|
|
31
|
+
for (const rawLine of content.split('\n')) {
|
|
32
|
+
const line = rawLine.trim()
|
|
33
|
+
|
|
34
|
+
// Skip empty lines and comments
|
|
35
|
+
if (!line || line.startsWith('#') || line.startsWith('//')) continue
|
|
36
|
+
|
|
37
|
+
const eqIndex = line.indexOf('=')
|
|
38
|
+
if (eqIndex === -1) continue
|
|
39
|
+
|
|
40
|
+
const key = line.slice(0, eqIndex).trim()
|
|
41
|
+
let value = line.slice(eqIndex + 1).trim()
|
|
42
|
+
|
|
43
|
+
// Strip surrounding quotes (single or double)
|
|
44
|
+
if (
|
|
45
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
46
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
47
|
+
) {
|
|
48
|
+
value = value.slice(1, -1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (key) {
|
|
52
|
+
result[key] = value
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Load .env.local into process.env. Existing env vars are NOT overwritten.
|
|
61
|
+
* Looks in ~/ .env.local first, then walks up from cwd.
|
|
62
|
+
*
|
|
63
|
+
* @returns The path to the loaded .env.local, or null if none found.
|
|
64
|
+
*/
|
|
65
|
+
export function loadEnvLocal(cwd?: string): string | null {
|
|
66
|
+
// Always check the global home-dir location first.
|
|
67
|
+
const globalPath = getGlobalEnvPath()
|
|
68
|
+
let envPath: string | null = null
|
|
69
|
+
|
|
70
|
+
if (fs.existsSync(globalPath)) {
|
|
71
|
+
envPath = globalPath
|
|
72
|
+
} else if (cwd) {
|
|
73
|
+
// Fallback: walk up from cwd (legacy behavior).
|
|
74
|
+
let dir = path.resolve(cwd)
|
|
75
|
+
const root = path.parse(dir).root
|
|
76
|
+
while (dir !== root) {
|
|
77
|
+
const candidate = path.join(dir, '.env.local')
|
|
78
|
+
if (fs.existsSync(candidate)) {
|
|
79
|
+
envPath = candidate
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
const parent = path.dirname(dir)
|
|
83
|
+
if (parent === dir) break
|
|
84
|
+
dir = parent
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!envPath) return null
|
|
89
|
+
|
|
90
|
+
const content = fs.readFileSync(envPath, 'utf-8')
|
|
91
|
+
const vars = parseEnvFile(content)
|
|
92
|
+
|
|
93
|
+
let loaded = 0
|
|
94
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
95
|
+
if (!(key in process.env)) {
|
|
96
|
+
process.env[key] = value
|
|
97
|
+
loaded++
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (loaded > 0) {
|
|
102
|
+
console.log(` Loaded ${loaded} env var(s) from ${envPath}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return envPath
|
|
106
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { useChatStore } from '../chat-store'
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
const store = useChatStore.getState()
|
|
7
|
+
store.setInputFocused(true)
|
|
8
|
+
store.setIsFocusSupported(false)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe('chat store focus state', () => {
|
|
12
|
+
test('chat reset preserves terminal focus and detected capabilities', () => {
|
|
13
|
+
const store = useChatStore.getState()
|
|
14
|
+
store.setInputFocused(false)
|
|
15
|
+
store.setIsFocusSupported(true)
|
|
16
|
+
|
|
17
|
+
store.reset()
|
|
18
|
+
|
|
19
|
+
expect(useChatStore.getState().inputFocused).toBe(false)
|
|
20
|
+
expect(useChatStore.getState().isFocusSupported).toBe(true)
|
|
21
|
+
})
|
|
22
|
+
})
|