@alumbwe/anvil 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/bin/anvil +2 -0
- package/bin/tree-sitter.wasm +0 -0
- package/package.json +67 -0
- package/src/__tests__/README.md +256 -0
- package/src/__tests__/bash-mode.test.ts +442 -0
- package/src/__tests__/cli-args.test.ts +181 -0
- package/src/__tests__/cli-harness.test.ts +52 -0
- package/src/__tests__/e2e/first-time-login.test.ts +173 -0
- package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
- package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
- package/src/__tests__/e2e-cli.test.ts +183 -0
- package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
- package/src/__tests__/helpers/mock-api-client.ts +64 -0
- package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
- package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
- package/src/__tests__/home-directory-detection.test.ts +213 -0
- package/src/__tests__/integration/api-integration.test.ts +288 -0
- package/src/__tests__/integration/credentials-storage.test.ts +486 -0
- package/src/__tests__/integration/local-agents.test.ts +1187 -0
- package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
- package/src/__tests__/integration/login-polling-working.test.ts +285 -0
- package/src/__tests__/integration/queue-editing.test.tsx +261 -0
- package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
- package/src/__tests__/integration-tmux.test.ts +188 -0
- package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
- package/src/__tests__/launcher-disconnect.test.ts +104 -0
- package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
- package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
- package/src/__tests__/path-completion.test.ts +243 -0
- package/src/__tests__/project-files-chat-id.test.ts +34 -0
- package/src/__tests__/release/proxy-http-get.test.ts +667 -0
- package/src/__tests__/release/wrapper-safety.test.ts +412 -0
- package/src/__tests__/renderer-cleanup.test.ts +174 -0
- package/src/__tests__/rerender-perf.integration.test.ts +283 -0
- package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
- package/src/__tests__/terminal-watchdog.test.ts +388 -0
- package/src/__tests__/test-utils.ts +271 -0
- package/src/__tests__/tmux-poc.ts +150 -0
- package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
- package/src/__tests__/unit/copy-button.test.ts +157 -0
- package/src/__tests__/unit/create-run-config.test.ts +66 -0
- package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
- package/src/__tests__/unit/segmented-control.test.ts +152 -0
- package/src/__tests__/utils/env.test.ts +169 -0
- package/src/__tests__/utils/project-picker.test.ts +39 -0
- package/src/agents/bundled-agents.generated.d.ts +14 -0
- package/src/agents/bundled-agents.generated.ts +3692 -0
- package/src/app.tsx +399 -0
- package/src/chat.tsx +1698 -0
- package/src/cli-args.ts +134 -0
- package/src/commands/__tests__/bash-command.test.ts +445 -0
- package/src/commands/__tests__/command-args.test.ts +349 -0
- package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
- package/src/commands/__tests__/copy-conversation.test.ts +226 -0
- package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
- package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
- package/src/commands/__tests__/image.test.ts +99 -0
- package/src/commands/__tests__/init.test.ts +456 -0
- package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
- package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
- package/src/commands/__tests__/prompt-builders.test.ts +28 -0
- package/src/commands/__tests__/router-input.test.ts +298 -0
- package/src/commands/ads.ts +44 -0
- package/src/commands/command-registry.ts +726 -0
- package/src/commands/copy-conversation.ts +392 -0
- package/src/commands/help.ts +13 -0
- package/src/commands/image.ts +20 -0
- package/src/commands/init.ts +121 -0
- package/src/commands/process-diagnostics.ts +120 -0
- package/src/commands/prompt-builders.ts +100 -0
- package/src/commands/publish.ts +203 -0
- package/src/commands/router-utils.ts +80 -0
- package/src/commands/router.ts +487 -0
- package/src/commands/usage.ts +26 -0
- package/src/components/__tests__/ad-banner.test.tsx +83 -0
- package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
- package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
- package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
- package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
- package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
- package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
- package/src/components/__tests__/grid-layout.test.tsx +1051 -0
- package/src/components/__tests__/message-block.completion.test.tsx +104 -0
- package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
- package/src/components/__tests__/message-with-agents.test.tsx +579 -0
- package/src/components/__tests__/multiline-input.test.tsx +1132 -0
- package/src/components/__tests__/queue-panel.test.tsx +398 -0
- package/src/components/__tests__/selectable-list.test.ts +232 -0
- package/src/components/__tests__/status-bar.test.tsx +44 -0
- package/src/components/__tests__/status-indicator.test.tsx +204 -0
- package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
- package/src/components/__tests__/user-error-banner.test.tsx +102 -0
- package/src/components/ad-banner.tsx +323 -0
- package/src/components/agent-checklist.tsx +350 -0
- package/src/components/agent-mode-toggle.tsx +241 -0
- package/src/components/anvil-mark.tsx +52 -0
- package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
- package/src/components/ask-user/__tests__/validation.test.ts +213 -0
- package/src/components/ask-user/components/accordion-question.tsx +153 -0
- package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
- package/src/components/ask-user/components/options-list.tsx +133 -0
- package/src/components/ask-user/components/question-header.tsx +80 -0
- package/src/components/ask-user/components/question-option.tsx +89 -0
- package/src/components/ask-user/constants.ts +38 -0
- package/src/components/ask-user/index.tsx +631 -0
- package/src/components/ask-user/utils/validation.ts +85 -0
- package/src/components/attachment-card.tsx +65 -0
- package/src/components/blocks/agent-block-grid.tsx +58 -0
- package/src/components/blocks/agent-branch-item.tsx +311 -0
- package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
- package/src/components/blocks/agent-list-branch.tsx +77 -0
- package/src/components/blocks/ask-user-branch.tsx +81 -0
- package/src/components/blocks/block-helpers.ts +11 -0
- package/src/components/blocks/blocks-renderer.tsx +289 -0
- package/src/components/blocks/content-with-markdown.tsx +33 -0
- package/src/components/blocks/image-block.tsx +128 -0
- package/src/components/blocks/implementor-row.tsx +429 -0
- package/src/components/blocks/single-block.tsx +201 -0
- package/src/components/blocks/thinking-block.tsx +67 -0
- package/src/components/blocks/tool-block-group.tsx +57 -0
- package/src/components/blocks/tool-branch.tsx +206 -0
- package/src/components/blocks/user-content-copy.tsx +157 -0
- package/src/components/bottom-banner.tsx +136 -0
- package/src/components/build-mode-buttons.tsx +110 -0
- package/src/components/button.tsx +66 -0
- package/src/components/chat-header.tsx +77 -0
- package/src/components/chat-history-screen.tsx +413 -0
- package/src/components/chat-input-bar.tsx +523 -0
- package/src/components/clickable-title-box.tsx +66 -0
- package/src/components/clickable.tsx +119 -0
- package/src/components/collapse-button.tsx +34 -0
- package/src/components/copy-button.tsx +203 -0
- package/src/components/elapsed-timer.tsx +55 -0
- package/src/components/error-boundary.tsx +55 -0
- package/src/components/feedback-container.tsx +181 -0
- package/src/components/feedback-icon-button.tsx +89 -0
- package/src/components/feedback-input-mode.tsx +344 -0
- package/src/components/file-attachment-card.tsx +98 -0
- package/src/components/freebuff-active-session-summary.tsx +63 -0
- package/src/components/freebuff-landing-screen.tsx +865 -0
- package/src/components/freebuff-model-selector.tsx +1087 -0
- package/src/components/freebuff-referral-banner.tsx +604 -0
- package/src/components/freebuff-superseded-screen.tsx +62 -0
- package/src/components/grid-layout.tsx +80 -0
- package/src/components/help-banner.tsx +127 -0
- package/src/components/highlighted-text.tsx +52 -0
- package/src/components/image-card.tsx +167 -0
- package/src/components/image-thumbnail.tsx +114 -0
- package/src/components/input-cursor.tsx +77 -0
- package/src/components/input-mode-banner.tsx +57 -0
- package/src/components/load-previous-button.tsx +48 -0
- package/src/components/login-modal.tsx +475 -0
- package/src/components/message-block.tsx +346 -0
- package/src/components/message-footer.tsx +258 -0
- package/src/components/message-with-agents.tsx +506 -0
- package/src/components/mode-divider.tsx +43 -0
- package/src/components/multiline-input.tsx +1241 -0
- package/src/components/out-of-credits-banner.tsx +160 -0
- package/src/components/pending-attachments-banner.tsx +116 -0
- package/src/components/pending-bash-message.tsx +59 -0
- package/src/components/progress-bar.tsx +81 -0
- package/src/components/project-picker-screen.tsx +485 -0
- package/src/components/publish-confirmation.tsx +493 -0
- package/src/components/publish-container.tsx +687 -0
- package/src/components/queue-panel.tsx +310 -0
- package/src/components/raised-pill.tsx +95 -0
- package/src/components/renderers/plan-box.tsx +59 -0
- package/src/components/review-screen.tsx +112 -0
- package/src/components/scroll-to-bottom-button.tsx +34 -0
- package/src/components/segmented-control.tsx +187 -0
- package/src/components/selectable-list.tsx +248 -0
- package/src/components/selected-chips.tsx +80 -0
- package/src/components/separator.tsx +40 -0
- package/src/components/session-ended-banner.tsx +226 -0
- package/src/components/shimmer-text.tsx +248 -0
- package/src/components/status-bar.tsx +313 -0
- package/src/components/subscription-limit-banner.tsx +206 -0
- package/src/components/suggested-prompts.tsx +184 -0
- package/src/components/suggestion-menu.tsx +215 -0
- package/src/components/terminal-command-display.tsx +151 -0
- package/src/components/terminal-link.tsx +97 -0
- package/src/components/terminal-thinking-orb.tsx +95 -0
- package/src/components/text-attachment-card.tsx +77 -0
- package/src/components/thinking.tsx +109 -0
- package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
- package/src/components/tools/__tests__/code-search.test.tsx +45 -0
- package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
- package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
- package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
- package/src/components/tools/apply-patch.tsx +95 -0
- package/src/components/tools/code-search.tsx +45 -0
- package/src/components/tools/composio.tsx +152 -0
- package/src/components/tools/diff-viewer.tsx +76 -0
- package/src/components/tools/glob.tsx +61 -0
- package/src/components/tools/gravity-index.tsx +77 -0
- package/src/components/tools/list-directory.tsx +61 -0
- package/src/components/tools/read-docs.tsx +37 -0
- package/src/components/tools/read-files.tsx +91 -0
- package/src/components/tools/read-subtree.tsx +44 -0
- package/src/components/tools/read-url.tsx +33 -0
- package/src/components/tools/registry.ts +126 -0
- package/src/components/tools/render-ui.tsx +122 -0
- package/src/components/tools/run-terminal-command.tsx +79 -0
- package/src/components/tools/skill.tsx +29 -0
- package/src/components/tools/str-replace.tsx +77 -0
- package/src/components/tools/suggest-followups.tsx +369 -0
- package/src/components/tools/task-completed.tsx +16 -0
- package/src/components/tools/tool-call-item.tsx +273 -0
- package/src/components/tools/types.ts +55 -0
- package/src/components/tools/web-search.tsx +33 -0
- package/src/components/tools/write-file.tsx +16 -0
- package/src/components/tools/write-todos.tsx +95 -0
- package/src/components/top-banner.tsx +178 -0
- package/src/components/usage-banner.tsx +244 -0
- package/src/components/user-error-banner.tsx +56 -0
- package/src/components/validation-error-popover.tsx +192 -0
- package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
- package/src/contexts/chat-runtime-context.tsx +240 -0
- package/src/data/slash-commands.test.ts +24 -0
- package/src/data/slash-commands.ts +249 -0
- package/src/entry.ts +12 -0
- package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
- package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
- package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
- package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
- package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
- package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
- package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
- package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
- package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
- package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
- package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
- package/src/hooks/__tests__/use-input-history.test.ts +699 -0
- package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
- package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
- package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
- package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
- package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
- package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
- package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
- package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
- package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
- package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
- package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
- package/src/hooks/__tests__/use-timeout.test.ts +330 -0
- package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
- package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
- package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
- package/src/hooks/helpers/send-message.ts +654 -0
- package/src/hooks/stream-state.ts +83 -0
- package/src/hooks/use-activity-query.ts +668 -0
- package/src/hooks/use-agent-validation.ts +74 -0
- package/src/hooks/use-ask-user-bridge.ts +92 -0
- package/src/hooks/use-auth-query.ts +253 -0
- package/src/hooks/use-auth-state.ts +152 -0
- package/src/hooks/use-chat-input.ts +105 -0
- package/src/hooks/use-chat-keyboard.ts +321 -0
- package/src/hooks/use-chat-messages.ts +254 -0
- package/src/hooks/use-chat-state.ts +165 -0
- package/src/hooks/use-chat-streaming.ts +180 -0
- package/src/hooks/use-chat-ui.ts +131 -0
- package/src/hooks/use-clipboard.ts +151 -0
- package/src/hooks/use-connection-status.ts +142 -0
- package/src/hooks/use-directory-browser.ts +88 -0
- package/src/hooks/use-elapsed-time.ts +116 -0
- package/src/hooks/use-event.ts +34 -0
- package/src/hooks/use-exit-handler.ts +66 -0
- package/src/hooks/use-fetch-login-url.ts +65 -0
- package/src/hooks/use-fingerprint.ts +61 -0
- package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
- package/src/hooks/use-freebuff-session-progress.ts +34 -0
- package/src/hooks/use-freebuff-session.ts +806 -0
- package/src/hooks/use-freebuff-streak-query.ts +61 -0
- package/src/hooks/use-gravity-ad.ts +656 -0
- package/src/hooks/use-grid-layout.ts +73 -0
- package/src/hooks/use-input-history.ts +173 -0
- package/src/hooks/use-login-keyboard-handlers.ts +75 -0
- package/src/hooks/use-login-polling.ts +114 -0
- package/src/hooks/use-logo.tsx +161 -0
- package/src/hooks/use-message-queue.ts +387 -0
- package/src/hooks/use-now.ts +20 -0
- package/src/hooks/use-path-tab-completion.ts +88 -0
- package/src/hooks/use-publish-mutation.ts +60 -0
- package/src/hooks/use-queue-controls.ts +68 -0
- package/src/hooks/use-queue-ui.ts +74 -0
- package/src/hooks/use-scroll-management.ts +184 -0
- package/src/hooks/use-searchable-list.ts +98 -0
- package/src/hooks/use-send-message.ts +773 -0
- package/src/hooks/use-sheen-animation.tsx +88 -0
- package/src/hooks/use-subscription-query.ts +71 -0
- package/src/hooks/use-suggestion-engine.ts +783 -0
- package/src/hooks/use-terminal-breakpoints.ts +77 -0
- package/src/hooks/use-terminal-dimensions.ts +46 -0
- package/src/hooks/use-terminal-focus.ts +29 -0
- package/src/hooks/use-terminal-layout.ts +189 -0
- package/src/hooks/use-theme.tsx +140 -0
- package/src/hooks/use-timeout.ts +85 -0
- package/src/hooks/use-update-preference.ts +66 -0
- package/src/hooks/use-usage-monitor.ts +55 -0
- package/src/hooks/use-usage-query.ts +124 -0
- package/src/hooks/use-user-details-query.ts +93 -0
- package/src/hooks/use-why-did-you-update.ts +177 -0
- package/src/index.tsx +441 -0
- package/src/init/__tests__/init-direnv.test.ts +526 -0
- package/src/init/init-app.ts +34 -0
- package/src/init/init-direnv.ts +133 -0
- package/src/login/constants.ts +60 -0
- package/src/login/login-flow.ts +204 -0
- package/src/login/plain-login.ts +108 -0
- package/src/login/utils.ts +183 -0
- package/src/native/ripgrep.ts +80 -0
- package/src/polyfills/bun-strip-ansi.ts +12 -0
- package/src/pre-init/tree-sitter-wasm.ts +96 -0
- package/src/project-files.ts +102 -0
- package/src/smoke/terminal-command-broker.ts +439 -0
- package/src/state/__tests__/chat-store-focus.test.ts +22 -0
- package/src/state/__tests__/feedback-store.test.ts +257 -0
- package/src/state/chat-history-store.ts +30 -0
- package/src/state/chat-store.ts +544 -0
- package/src/state/feedback-store.ts +160 -0
- package/src/state/freebuff-model-store.ts +42 -0
- package/src/state/freebuff-session-store.ts +53 -0
- package/src/state/login-store.ts +150 -0
- package/src/state/message-block-store.ts +149 -0
- package/src/state/publish-store.ts +146 -0
- package/src/state/queue-panel-store.ts +26 -0
- package/src/state/review-store.ts +24 -0
- package/src/testing/env.ts +46 -0
- package/src/types/chat-state.ts +15 -0
- package/src/types/chat.ts +241 -0
- package/src/types/contracts/send-message.ts +12 -0
- package/src/types/env.ts +98 -0
- package/src/types/freebuff-session.ts +17 -0
- package/src/types/function-params.ts +28 -0
- package/src/types/react19-compat.d.ts +19 -0
- package/src/types/store.ts +111 -0
- package/src/types/theme-system.ts +144 -0
- package/src/types/utils.ts +3 -0
- package/src/utils/__tests__/active-run.test.ts +140 -0
- package/src/utils/__tests__/activity-tracker.test.ts +440 -0
- package/src/utils/__tests__/agent-display.test.ts +139 -0
- package/src/utils/__tests__/analytics-client.test.ts +262 -0
- package/src/utils/__tests__/anonymous-id.test.ts +74 -0
- package/src/utils/__tests__/anvil-api.test.ts +578 -0
- package/src/utils/__tests__/arrays.test.ts +264 -0
- package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
- package/src/utils/__tests__/block-processor.test.ts +810 -0
- package/src/utils/__tests__/chat-history.test.ts +203 -0
- package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
- package/src/utils/__tests__/chat-meta.test.ts +130 -0
- package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
- package/src/utils/__tests__/clipboard.test.ts +910 -0
- package/src/utils/__tests__/code-search-summary.test.ts +84 -0
- package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
- package/src/utils/__tests__/error-handling.test.ts +658 -0
- package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
- package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
- package/src/utils/__tests__/feedback-submission.test.ts +26 -0
- package/src/utils/__tests__/fetch-usage.test.ts +355 -0
- package/src/utils/__tests__/fingerprint.test.ts +144 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
- package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
- package/src/utils/__tests__/format-timeout.test.ts +87 -0
- package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
- package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
- package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
- package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
- package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
- package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
- package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
- package/src/utils/__tests__/image-dimensions.test.ts +249 -0
- package/src/utils/__tests__/image-processor.test.ts +209 -0
- package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
- package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
- package/src/utils/__tests__/layout-helpers.test.ts +36 -0
- package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
- package/src/utils/__tests__/log-shipper.test.ts +53 -0
- package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
- package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
- package/src/utils/__tests__/message-updater.test.ts +645 -0
- package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
- package/src/utils/__tests__/pending-attachments.test.ts +277 -0
- package/src/utils/__tests__/polling-backoff.test.ts +51 -0
- package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
- package/src/utils/__tests__/run-state-storage.test.ts +919 -0
- package/src/utils/__tests__/safe-json.test.ts +77 -0
- package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
- package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
- package/src/utils/__tests__/send-message-timer.test.ts +64 -0
- package/src/utils/__tests__/settings.test.ts +99 -0
- package/src/utils/__tests__/strings.test.ts +192 -0
- package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
- package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
- package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
- package/src/utils/__tests__/terminal-io.test.ts +137 -0
- package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
- package/src/utils/__tests__/text-layout.test.ts +82 -0
- package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
- package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
- package/src/utils/__tests__/trace-writer.test.ts +223 -0
- package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
- package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
- package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
- package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
- package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
- package/src/utils/active-run.ts +131 -0
- package/src/utils/activity-tracker.ts +66 -0
- package/src/utils/agent-display.ts +87 -0
- package/src/utils/agent-helpers.ts +27 -0
- package/src/utils/agent-id-utils.ts +14 -0
- package/src/utils/ai-message-id.ts +12 -0
- package/src/utils/analytics.ts +361 -0
- package/src/utils/anonymous-id.ts +87 -0
- package/src/utils/anvil-api.ts +612 -0
- package/src/utils/anvil-client.ts +217 -0
- package/src/utils/arrays.ts +117 -0
- package/src/utils/auth.ts +258 -0
- package/src/utils/bash-context-processor.ts +47 -0
- package/src/utils/bash-messages.ts +108 -0
- package/src/utils/block-margins.ts +35 -0
- package/src/utils/block-operations.ts +523 -0
- package/src/utils/block-processor.ts +213 -0
- package/src/utils/chat-history.ts +253 -0
- package/src/utils/chat-input-key-intercept.ts +52 -0
- package/src/utils/chat-meta.ts +90 -0
- package/src/utils/chat-scroll-accel.ts +151 -0
- package/src/utils/clipboard-image.ts +588 -0
- package/src/utils/clipboard.ts +397 -0
- package/src/utils/code-search-summary.ts +70 -0
- package/src/utils/collapse-helpers.ts +256 -0
- package/src/utils/config-dir.ts +24 -0
- package/src/utils/constants.ts +190 -0
- package/src/utils/create-event-handler-state.ts +73 -0
- package/src/utils/create-run-config.ts +121 -0
- package/src/utils/detect-shell.ts +116 -0
- package/src/utils/directory-browser.ts +72 -0
- package/src/utils/engagement.ts +55 -0
- package/src/utils/env.ts +92 -0
- package/src/utils/error-handling.ts +255 -0
- package/src/utils/error-messages.ts +62 -0
- package/src/utils/exit-cleanly.ts +86 -0
- package/src/utils/feedback-helpers.ts +103 -0
- package/src/utils/feedback-submission.ts +22 -0
- package/src/utils/fetch-usage.ts +76 -0
- package/src/utils/fingerprint.ts +245 -0
- package/src/utils/format-elapsed-time.ts +33 -0
- package/src/utils/format-session-units.ts +6 -0
- package/src/utils/format-timeout.ts +28 -0
- package/src/utils/format-validation-errors-for-message.ts +84 -0
- package/src/utils/freebuff-agent-selection.ts +33 -0
- package/src/utils/freebuff-instance-owner.ts +66 -0
- package/src/utils/freebuff-model-navigation.ts +50 -0
- package/src/utils/freebuff-premium-reset.ts +55 -0
- package/src/utils/freebuff-referral-cache.ts +51 -0
- package/src/utils/freebuff-session-api.ts +243 -0
- package/src/utils/freebuff-session-display.ts +21 -0
- package/src/utils/freebuff-streak-line.ts +93 -0
- package/src/utils/git.ts +17 -0
- package/src/utils/helpers.ts +47 -0
- package/src/utils/image-display.ts +67 -0
- package/src/utils/image-handler.ts +325 -0
- package/src/utils/image-processor.ts +133 -0
- package/src/utils/image-thumbnail.ts +78 -0
- package/src/utils/implementor-helpers.ts +787 -0
- package/src/utils/input-modes.ts +174 -0
- package/src/utils/keyboard-actions.ts +392 -0
- package/src/utils/keypad-keys.ts +47 -0
- package/src/utils/layout-helpers.ts +33 -0
- package/src/utils/lazy-response-ads.ts +1 -0
- package/src/utils/local-agent-registry.ts +474 -0
- package/src/utils/log-shipper.ts +126 -0
- package/src/utils/logger.ts +327 -0
- package/src/utils/markdown-renderer.tsx +1090 -0
- package/src/utils/math.ts +3 -0
- package/src/utils/message-block-helpers.ts +684 -0
- package/src/utils/message-history.ts +143 -0
- package/src/utils/message-tree-utils.ts +67 -0
- package/src/utils/message-updater.ts +279 -0
- package/src/utils/open-file.ts +146 -0
- package/src/utils/open-url.ts +63 -0
- package/src/utils/path-completion.ts +111 -0
- package/src/utils/path-helpers.ts +38 -0
- package/src/utils/pending-attachments.ts +360 -0
- package/src/utils/polling-backoff.ts +59 -0
- package/src/utils/project-picker.ts +12 -0
- package/src/utils/queue-panel-actions.ts +70 -0
- package/src/utils/recent-projects.ts +156 -0
- package/src/utils/renderer-cleanup.ts +233 -0
- package/src/utils/response-ad-positions.ts +1 -0
- package/src/utils/run-state-storage.ts +603 -0
- package/src/utils/safe-json.ts +153 -0
- package/src/utils/sdk-event-handlers.ts +571 -0
- package/src/utils/send-message-helpers.ts +210 -0
- package/src/utils/send-message-timer.ts +110 -0
- package/src/utils/settings.ts +226 -0
- package/src/utils/skill-registry.ts +94 -0
- package/src/utils/spawn-agent-matcher.ts +55 -0
- package/src/utils/status-indicator-state.ts +107 -0
- package/src/utils/stream-chunk-processor.ts +57 -0
- package/src/utils/strings.ts +271 -0
- package/src/utils/subscription.ts +31 -0
- package/src/utils/syntax-highlighter.tsx +19 -0
- package/src/utils/terminal-color-detection.ts +474 -0
- package/src/utils/terminal-command-broker.ts +467 -0
- package/src/utils/terminal-enter-detection.ts +63 -0
- package/src/utils/terminal-images.ts +229 -0
- package/src/utils/terminal-io.ts +85 -0
- package/src/utils/terminal-protocol-controller.ts +173 -0
- package/src/utils/terminal-reset-sequences.ts +33 -0
- package/src/utils/terminal-title.ts +84 -0
- package/src/utils/terminal-watchdog.ts +366 -0
- package/src/utils/text-layout.ts +149 -0
- package/src/utils/theme-config.ts +141 -0
- package/src/utils/theme-system.ts +1206 -0
- package/src/utils/think-tag-parser.ts +95 -0
- package/src/utils/time-format.ts +21 -0
- package/src/utils/trace-writer.ts +162 -0
- package/src/utils/ui-constants.ts +69 -0
- package/src/utils/update-checker.ts +110 -0
- package/src/utils/usage-banner-state.ts +146 -0
- package/src/utils/validation-error-formatting.ts +86 -0
- package/src/utils/validation-error-helpers.ts +19 -0
- package/src/utils/windows-terminal-health.ts +124 -0
- package/src/utils/word-wrap-utils.ts +53 -0
- package/src/utils/write-file-atomic.ts +54 -0
- package/src/utils/yield-to-event-loop.ts +9 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import { WEBSITE_URL } from '@anvil/sdk'
|
|
2
|
+
import type {
|
|
3
|
+
PublishAgentsResponse,
|
|
4
|
+
} from '@anvil/common/types/api/agents/publish'
|
|
5
|
+
import type { FeedbackRequest } from '@anvil/common/schemas/feedback'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* API response types for consistent error handling.
|
|
9
|
+
*
|
|
10
|
+
* When `ok` is true, `data` may be undefined for responses with no body (e.g., 204 No Content).
|
|
11
|
+
* Callers should check for `response.data` when they expect data from the endpoint.
|
|
12
|
+
*/
|
|
13
|
+
export type ApiResponse<T> =
|
|
14
|
+
| { ok: true; status: number; data?: T }
|
|
15
|
+
| { ok: false; status: number; error?: string; errorData?: Record<string, unknown> }
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Type-safe endpoint request/response types
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/** User fields that can be fetched from /api/v1/me */
|
|
22
|
+
export type UserField = 'id' | 'email' | 'discord_id'
|
|
23
|
+
|
|
24
|
+
export type UserDetails<T extends UserField = UserField> = {
|
|
25
|
+
[K in T]: K extends 'discord_id' ? string | null : string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UsageRequest {
|
|
29
|
+
fingerprintId?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UsageResponse {
|
|
33
|
+
type: 'usage-response'
|
|
34
|
+
usage: number
|
|
35
|
+
remainingBalance: number | null
|
|
36
|
+
balanceBreakdown?: Record<string, number>
|
|
37
|
+
next_quota_reset: string | null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LoginCodeRequest {
|
|
41
|
+
fingerprintId: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface LoginCodeResponse {
|
|
45
|
+
loginUrl: string
|
|
46
|
+
fingerprintHash: string
|
|
47
|
+
expiresAt: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface LoginStatusRequest {
|
|
51
|
+
fingerprintId: string
|
|
52
|
+
fingerprintHash: string
|
|
53
|
+
expiresAt: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface LoginStatusResponse {
|
|
57
|
+
user?: Record<string, unknown>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface LogoutRequest {
|
|
61
|
+
userId?: string
|
|
62
|
+
fingerprintId?: string
|
|
63
|
+
fingerprintHash?: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface FeedbackResponse {
|
|
67
|
+
success: boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Retry configuration
|
|
72
|
+
*/
|
|
73
|
+
export interface RetryConfig {
|
|
74
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
75
|
+
maxRetries?: number
|
|
76
|
+
/** Initial delay in ms before first retry (default: 1000) */
|
|
77
|
+
initialDelayMs?: number
|
|
78
|
+
/** Maximum delay in ms between retries (default: 10000) */
|
|
79
|
+
maxDelayMs?: number
|
|
80
|
+
/** HTTP status codes to retry on (default: [408, 429, 500, 502, 503, 504]) */
|
|
81
|
+
retryableStatusCodes?: number[]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {
|
|
85
|
+
maxRetries: 3,
|
|
86
|
+
initialDelayMs: 1000,
|
|
87
|
+
maxDelayMs: 10000,
|
|
88
|
+
retryableStatusCodes: [408, 429, 500, 502, 503, 504],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Configuration for creating a Anvil API client
|
|
93
|
+
*/
|
|
94
|
+
export interface AnvilApiClientConfig {
|
|
95
|
+
/** Base URL for API requests (defaults to WEBSITE_URL from SDK) */
|
|
96
|
+
baseUrl?: string
|
|
97
|
+
/** Auth token for Bearer authentication */
|
|
98
|
+
authToken?: string
|
|
99
|
+
/** Custom fetch implementation (for testing) */
|
|
100
|
+
fetch?: typeof fetch
|
|
101
|
+
/** Default timeout in ms for all requests (default: 30000) */
|
|
102
|
+
defaultTimeoutMs?: number
|
|
103
|
+
/** Default retry configuration */
|
|
104
|
+
retry?: RetryConfig
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Options for individual requests
|
|
109
|
+
*/
|
|
110
|
+
export interface RequestOptions {
|
|
111
|
+
/** Query parameters to append to URL */
|
|
112
|
+
query?: Record<string, string>
|
|
113
|
+
/** Include Authorization header (default: true when authToken is set) */
|
|
114
|
+
includeAuth?: boolean
|
|
115
|
+
/** Include session token as Cookie header (for legacy endpoints) */
|
|
116
|
+
includeCookie?: boolean
|
|
117
|
+
/** Request timeout in ms (overrides default) */
|
|
118
|
+
timeoutMs?: number
|
|
119
|
+
/** Retry configuration (overrides default) */
|
|
120
|
+
retry?: RetryConfig | false
|
|
121
|
+
/** Custom headers */
|
|
122
|
+
headers?: Record<string, string>
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface AnvilApiClient {
|
|
126
|
+
readonly baseUrl: string
|
|
127
|
+
readonly authToken?: string
|
|
128
|
+
|
|
129
|
+
/** Make a raw HTTP request */
|
|
130
|
+
request<T>(
|
|
131
|
+
method: string,
|
|
132
|
+
path: string,
|
|
133
|
+
body?: unknown,
|
|
134
|
+
options?: RequestOptions,
|
|
135
|
+
): Promise<ApiResponse<T>>
|
|
136
|
+
|
|
137
|
+
/** Make a GET request */
|
|
138
|
+
get<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>
|
|
139
|
+
|
|
140
|
+
/** Make a POST request */
|
|
141
|
+
post<T>(
|
|
142
|
+
path: string,
|
|
143
|
+
body?: Record<string, unknown>,
|
|
144
|
+
options?: RequestOptions,
|
|
145
|
+
): Promise<ApiResponse<T>>
|
|
146
|
+
|
|
147
|
+
/** Make a PUT request */
|
|
148
|
+
put<T>(
|
|
149
|
+
path: string,
|
|
150
|
+
body?: Record<string, unknown>,
|
|
151
|
+
options?: RequestOptions,
|
|
152
|
+
): Promise<ApiResponse<T>>
|
|
153
|
+
|
|
154
|
+
/** Make a PATCH request */
|
|
155
|
+
patch<T>(
|
|
156
|
+
path: string,
|
|
157
|
+
body?: Record<string, unknown>,
|
|
158
|
+
options?: RequestOptions,
|
|
159
|
+
): Promise<ApiResponse<T>>
|
|
160
|
+
|
|
161
|
+
/** Make a DELETE request */
|
|
162
|
+
delete<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// Type-safe endpoint methods
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
/** Fetch user details from /api/v1/me */
|
|
169
|
+
me<T extends UserField>(
|
|
170
|
+
fields: readonly T[],
|
|
171
|
+
): Promise<ApiResponse<UserDetails<T>>>
|
|
172
|
+
|
|
173
|
+
/** Fetch usage data from /api/v1/usage */
|
|
174
|
+
usage(req?: UsageRequest): Promise<ApiResponse<UsageResponse>>
|
|
175
|
+
|
|
176
|
+
/** Request a login code from /api/auth/cli/code */
|
|
177
|
+
loginCode(req: LoginCodeRequest): Promise<ApiResponse<LoginCodeResponse>>
|
|
178
|
+
|
|
179
|
+
/** Check login status from /api/auth/cli/status */
|
|
180
|
+
loginStatus(
|
|
181
|
+
req: LoginStatusRequest,
|
|
182
|
+
): Promise<ApiResponse<LoginStatusResponse>>
|
|
183
|
+
|
|
184
|
+
/** Publish agents via /api/agents/publish */
|
|
185
|
+
publish(
|
|
186
|
+
data: Record<string, unknown>[],
|
|
187
|
+
allLocalAgentIds?: string[],
|
|
188
|
+
): Promise<ApiResponse<PublishAgentsResponse>>
|
|
189
|
+
|
|
190
|
+
/** Logout via /api/auth/cli/logout */
|
|
191
|
+
logout(req?: LogoutRequest): Promise<ApiResponse<void>>
|
|
192
|
+
|
|
193
|
+
/** Submit feedback via /api/v1/feedback */
|
|
194
|
+
feedback(req: FeedbackRequest): Promise<ApiResponse<FeedbackResponse>>
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const TLS_CERTIFICATE_ERROR_CODES = new Set([
|
|
198
|
+
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
|
199
|
+
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
200
|
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
|
201
|
+
'ERR_TLS_CERT_ALTNAME_INVALID',
|
|
202
|
+
'CERT_HAS_EXPIRED',
|
|
203
|
+
])
|
|
204
|
+
|
|
205
|
+
function getTlsCertificateError(error: Error, depth = 0): Error | null {
|
|
206
|
+
const code =
|
|
207
|
+
error &&
|
|
208
|
+
typeof error === 'object' &&
|
|
209
|
+
'code' in error &&
|
|
210
|
+
typeof error.code === 'string'
|
|
211
|
+
? error.code
|
|
212
|
+
: undefined
|
|
213
|
+
const message = error.message.toLowerCase()
|
|
214
|
+
if (
|
|
215
|
+
(code && TLS_CERTIFICATE_ERROR_CODES.has(code)) ||
|
|
216
|
+
message.includes('self signed certificate') ||
|
|
217
|
+
message.includes('unable to verify the first certificate') ||
|
|
218
|
+
message.includes('certificate has expired') ||
|
|
219
|
+
message.includes('certificate verify failed')
|
|
220
|
+
) {
|
|
221
|
+
return error
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (depth >= 2 || !(error.cause instanceof Error)) {
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return getTlsCertificateError(error.cause, depth + 1)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function formatNetworkErrorMessage(error: Error, method: string, url: string) {
|
|
232
|
+
const requestUrl = new URL(url)
|
|
233
|
+
const tlsCertificateError = getTlsCertificateError(error)
|
|
234
|
+
|
|
235
|
+
if (tlsCertificateError) {
|
|
236
|
+
return [
|
|
237
|
+
`TLS certificate verification failed for ${requestUrl.origin}.`,
|
|
238
|
+
'If your network intercepts HTTPS traffic, install its root certificate into your system trust store or use a network path that does not intercept TLS.',
|
|
239
|
+
`Original error: ${tlsCertificateError.message} (${method} ${url})`,
|
|
240
|
+
].join(' ')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return `${error.message} (${method} ${url})`
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Sleep for a given duration
|
|
248
|
+
*/
|
|
249
|
+
const sleep = (ms: number): Promise<void> =>
|
|
250
|
+
new Promise((resolve) => setTimeout(resolve, ms))
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Calculate delay with exponential backoff and jitter
|
|
254
|
+
*/
|
|
255
|
+
const calculateBackoffDelay = (
|
|
256
|
+
attempt: number,
|
|
257
|
+
initialDelayMs: number,
|
|
258
|
+
maxDelayMs: number,
|
|
259
|
+
): number => {
|
|
260
|
+
const exponentialDelay = initialDelayMs * Math.pow(2, attempt)
|
|
261
|
+
const jitter = Math.random() * 0.3 * exponentialDelay // 0-30% jitter
|
|
262
|
+
return Math.min(exponentialDelay + jitter, maxDelayMs)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check if an error is retryable (network errors).
|
|
267
|
+
*
|
|
268
|
+
* Note: AbortError is NOT retryable because it indicates intentional cancellation
|
|
269
|
+
* (e.g., user cancelled the request or our timeout was exceeded).
|
|
270
|
+
*/
|
|
271
|
+
const isRetryableError = (error: unknown): boolean => {
|
|
272
|
+
if (error instanceof Error) {
|
|
273
|
+
const name = error.name.toLowerCase()
|
|
274
|
+
const message = error.message.toLowerCase()
|
|
275
|
+
|
|
276
|
+
// Don't retry abort errors - they indicate intentional cancellation
|
|
277
|
+
if (name === 'aborterror') {
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
if (getTlsCertificateError(error)) {
|
|
281
|
+
return false
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
name === 'timeouterror' ||
|
|
286
|
+
message.includes('network') ||
|
|
287
|
+
message.includes('fetch') ||
|
|
288
|
+
message.includes('econnreset') ||
|
|
289
|
+
message.includes('econnrefused')
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
return false
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Create a Anvil API client for making authenticated requests to the Anvil API
|
|
297
|
+
*/
|
|
298
|
+
export function createAnvilApiClient(
|
|
299
|
+
config: AnvilApiClientConfig = {},
|
|
300
|
+
): AnvilApiClient {
|
|
301
|
+
const {
|
|
302
|
+
baseUrl = WEBSITE_URL,
|
|
303
|
+
authToken,
|
|
304
|
+
fetch: fetchFn = fetch,
|
|
305
|
+
defaultTimeoutMs = 30000,
|
|
306
|
+
retry: defaultRetryConfig = {},
|
|
307
|
+
} = config
|
|
308
|
+
|
|
309
|
+
const mergedDefaultRetry: Required<RetryConfig> = {
|
|
310
|
+
...DEFAULT_RETRY_CONFIG,
|
|
311
|
+
...defaultRetryConfig,
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function request<T>(
|
|
315
|
+
method: string,
|
|
316
|
+
path: string,
|
|
317
|
+
body?: unknown,
|
|
318
|
+
options: RequestOptions = {},
|
|
319
|
+
): Promise<ApiResponse<T>> {
|
|
320
|
+
const {
|
|
321
|
+
query,
|
|
322
|
+
includeAuth = true,
|
|
323
|
+
includeCookie = false,
|
|
324
|
+
timeoutMs = defaultTimeoutMs,
|
|
325
|
+
retry: retryConfig = mergedDefaultRetry,
|
|
326
|
+
headers: customHeaders = {},
|
|
327
|
+
} = options
|
|
328
|
+
|
|
329
|
+
// Build URL with query parameters
|
|
330
|
+
let url = `${baseUrl}${path}`
|
|
331
|
+
if (query && Object.keys(query).length > 0) {
|
|
332
|
+
const params = new URLSearchParams(query)
|
|
333
|
+
url += `?${params.toString()}`
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Build headers
|
|
337
|
+
const headers: Record<string, string> = { ...customHeaders }
|
|
338
|
+
if (authToken && includeAuth) {
|
|
339
|
+
headers['Authorization'] = `Bearer ${authToken}`
|
|
340
|
+
}
|
|
341
|
+
if (authToken && includeCookie) {
|
|
342
|
+
headers['Cookie'] = `next-auth.session-token=${authToken};`
|
|
343
|
+
}
|
|
344
|
+
if (body !== undefined) {
|
|
345
|
+
headers['Content-Type'] = 'application/json'
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Build fetch options
|
|
349
|
+
const fetchOptions: RequestInit = {
|
|
350
|
+
method,
|
|
351
|
+
headers,
|
|
352
|
+
}
|
|
353
|
+
if (body !== undefined) {
|
|
354
|
+
fetchOptions.body = JSON.stringify(body)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Determine retry config
|
|
358
|
+
const shouldRetry = retryConfig !== false
|
|
359
|
+
const retryOpts = shouldRetry
|
|
360
|
+
? { ...mergedDefaultRetry, ...retryConfig }
|
|
361
|
+
: null
|
|
362
|
+
|
|
363
|
+
let lastError: unknown
|
|
364
|
+
const maxAttempts = shouldRetry ? (retryOpts?.maxRetries ?? 0) + 1 : 1
|
|
365
|
+
|
|
366
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
367
|
+
// Create abort controller for timeout
|
|
368
|
+
const controller = new AbortController()
|
|
369
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
const response = await fetchFn(url, {
|
|
373
|
+
...fetchOptions,
|
|
374
|
+
signal: controller.signal,
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
clearTimeout(timeoutId)
|
|
378
|
+
|
|
379
|
+
if (response.ok) {
|
|
380
|
+
try {
|
|
381
|
+
const responseBody = await response.json()
|
|
382
|
+
const data = responseBody as T
|
|
383
|
+
return { ok: true, status: response.status, data }
|
|
384
|
+
} catch {
|
|
385
|
+
// Response was OK but no JSON body (e.g., 204 No Content)
|
|
386
|
+
return { ok: true, status: response.status }
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Check if we should retry on this status code
|
|
391
|
+
if (
|
|
392
|
+
shouldRetry &&
|
|
393
|
+
retryOpts &&
|
|
394
|
+
retryOpts.retryableStatusCodes.includes(response.status) &&
|
|
395
|
+
attempt < maxAttempts - 1
|
|
396
|
+
) {
|
|
397
|
+
const delay = calculateBackoffDelay(
|
|
398
|
+
attempt,
|
|
399
|
+
retryOpts.initialDelayMs,
|
|
400
|
+
retryOpts.maxDelayMs,
|
|
401
|
+
)
|
|
402
|
+
await sleep(delay)
|
|
403
|
+
continue
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Parse error response
|
|
407
|
+
let errorMessage: string | undefined
|
|
408
|
+
let errorData: unknown
|
|
409
|
+
try {
|
|
410
|
+
const errorBody = await response.json()
|
|
411
|
+
errorData = errorBody
|
|
412
|
+
errorMessage =
|
|
413
|
+
errorBody?.error || errorBody?.message || response.statusText
|
|
414
|
+
} catch {
|
|
415
|
+
try {
|
|
416
|
+
errorMessage = await response.text()
|
|
417
|
+
} catch {
|
|
418
|
+
errorMessage = response.statusText
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return { ok: false, status: response.status, error: errorMessage, errorData: errorData as Record<string, unknown> | undefined }
|
|
423
|
+
} catch (error) {
|
|
424
|
+
clearTimeout(timeoutId)
|
|
425
|
+
lastError = error
|
|
426
|
+
|
|
427
|
+
// Check if we should retry on this error
|
|
428
|
+
if (
|
|
429
|
+
shouldRetry &&
|
|
430
|
+
retryOpts &&
|
|
431
|
+
isRetryableError(error) &&
|
|
432
|
+
attempt < maxAttempts - 1
|
|
433
|
+
) {
|
|
434
|
+
const delay = calculateBackoffDelay(
|
|
435
|
+
attempt,
|
|
436
|
+
retryOpts.initialDelayMs,
|
|
437
|
+
retryOpts.maxDelayMs,
|
|
438
|
+
)
|
|
439
|
+
await sleep(delay)
|
|
440
|
+
continue
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Don't retry, throw the error with URL context
|
|
444
|
+
if (error instanceof Error) {
|
|
445
|
+
const enhancedError = new Error(
|
|
446
|
+
formatNetworkErrorMessage(error, method, url),
|
|
447
|
+
)
|
|
448
|
+
enhancedError.name = error.name
|
|
449
|
+
enhancedError.cause = error
|
|
450
|
+
throw enhancedError
|
|
451
|
+
}
|
|
452
|
+
throw error
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Should not reach here, but just in case
|
|
457
|
+
throw lastError ?? new Error('Request failed after all retries')
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
baseUrl,
|
|
462
|
+
authToken,
|
|
463
|
+
request,
|
|
464
|
+
|
|
465
|
+
get<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>> {
|
|
466
|
+
return request<T>('GET', path, undefined, options)
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
post<T>(
|
|
470
|
+
path: string,
|
|
471
|
+
body?: Record<string, unknown>,
|
|
472
|
+
options?: RequestOptions,
|
|
473
|
+
): Promise<ApiResponse<T>> {
|
|
474
|
+
return request<T>('POST', path, body, options)
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
put<T>(
|
|
478
|
+
path: string,
|
|
479
|
+
body?: Record<string, unknown>,
|
|
480
|
+
options?: RequestOptions,
|
|
481
|
+
): Promise<ApiResponse<T>> {
|
|
482
|
+
return request<T>('PUT', path, body, options)
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
patch<T>(
|
|
486
|
+
path: string,
|
|
487
|
+
body?: Record<string, unknown>,
|
|
488
|
+
options?: RequestOptions,
|
|
489
|
+
): Promise<ApiResponse<T>> {
|
|
490
|
+
return request<T>('PATCH', path, body, options)
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
delete<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>> {
|
|
494
|
+
return request<T>('DELETE', path, undefined, options)
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
// ============================================================================
|
|
498
|
+
// Type-safe endpoint methods
|
|
499
|
+
// ============================================================================
|
|
500
|
+
|
|
501
|
+
me<T extends UserField>(
|
|
502
|
+
fields: readonly T[],
|
|
503
|
+
): Promise<ApiResponse<UserDetails<T>>> {
|
|
504
|
+
return request<UserDetails<T>>('GET', '/api/v1/me', undefined, {
|
|
505
|
+
query: { fields: fields.join(',') },
|
|
506
|
+
})
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
usage(req: UsageRequest = {}): Promise<ApiResponse<UsageResponse>> {
|
|
510
|
+
// Auth is sent via Authorization header (includeAuth defaults to true)
|
|
511
|
+
return request<UsageResponse>('POST', '/api/v1/usage', {
|
|
512
|
+
fingerprintId: req.fingerprintId ?? 'cli-usage',
|
|
513
|
+
})
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
loginCode(req: LoginCodeRequest): Promise<ApiResponse<LoginCodeResponse>> {
|
|
517
|
+
return request<LoginCodeResponse>(
|
|
518
|
+
'POST',
|
|
519
|
+
'/api/auth/cli/code',
|
|
520
|
+
{ fingerprintId: req.fingerprintId },
|
|
521
|
+
{ includeAuth: false },
|
|
522
|
+
)
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
loginStatus(
|
|
526
|
+
req: LoginStatusRequest,
|
|
527
|
+
): Promise<ApiResponse<LoginStatusResponse>> {
|
|
528
|
+
return request<LoginStatusResponse>('GET', '/api/auth/cli/status', undefined, {
|
|
529
|
+
query: {
|
|
530
|
+
fingerprintId: req.fingerprintId,
|
|
531
|
+
fingerprintHash: req.fingerprintHash,
|
|
532
|
+
expiresAt: req.expiresAt,
|
|
533
|
+
},
|
|
534
|
+
includeAuth: false,
|
|
535
|
+
})
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
publish(
|
|
539
|
+
data: Record<string, unknown>[],
|
|
540
|
+
allLocalAgentIds?: string[],
|
|
541
|
+
): Promise<ApiResponse<PublishAgentsResponse>> {
|
|
542
|
+
// Auth is sent via Authorization header (includeAuth defaults to true)
|
|
543
|
+
return request<PublishAgentsResponse>('POST', '/api/agents/publish', {
|
|
544
|
+
data,
|
|
545
|
+
allLocalAgentIds,
|
|
546
|
+
})
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
logout(req: LogoutRequest = {}): Promise<ApiResponse<void>> {
|
|
550
|
+
// Auth is sent via Authorization header (includeAuth defaults to true)
|
|
551
|
+
return request<void>('POST', '/api/auth/cli/logout', {
|
|
552
|
+
userId: req.userId,
|
|
553
|
+
fingerprintId: req.fingerprintId,
|
|
554
|
+
fingerprintHash: req.fingerprintHash,
|
|
555
|
+
})
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
feedback(req: FeedbackRequest): Promise<ApiResponse<FeedbackResponse>> {
|
|
559
|
+
return request<FeedbackResponse>('POST', '/api/v1/feedback', req, {
|
|
560
|
+
// Feedback submissions are not idempotent server-side yet, so avoid automatic retries.
|
|
561
|
+
retry: false,
|
|
562
|
+
})
|
|
563
|
+
},
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ============================================================================
|
|
568
|
+
// Shared singleton client
|
|
569
|
+
// ============================================================================
|
|
570
|
+
|
|
571
|
+
let sharedClient: AnvilApiClient | null = null
|
|
572
|
+
let sharedAuthToken: string | undefined
|
|
573
|
+
// Track the token that was used to create the current client instance
|
|
574
|
+
let clientCreatedWithToken: string | undefined
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Get or create the shared API client singleton.
|
|
578
|
+
* The client is lazily created and reused across the application.
|
|
579
|
+
*
|
|
580
|
+
* Note: Always call setApiClientAuthToken() before getApiClient() when you need
|
|
581
|
+
* to ensure a specific auth token is used. The client is recreated whenever
|
|
582
|
+
* the auth token changes.
|
|
583
|
+
*/
|
|
584
|
+
export function getApiClient(): AnvilApiClient {
|
|
585
|
+
// Recreate client if it doesn't exist or if the token has changed since creation
|
|
586
|
+
if (!sharedClient || clientCreatedWithToken !== sharedAuthToken) {
|
|
587
|
+
sharedClient = createAnvilApiClient({ authToken: sharedAuthToken })
|
|
588
|
+
clientCreatedWithToken = sharedAuthToken
|
|
589
|
+
}
|
|
590
|
+
return sharedClient
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Set the auth token for the shared API client.
|
|
595
|
+
* This will cause the next call to getApiClient() to create a new client
|
|
596
|
+
* with the updated token.
|
|
597
|
+
*/
|
|
598
|
+
export function setApiClientAuthToken(authToken: string | undefined): void {
|
|
599
|
+
sharedAuthToken = authToken
|
|
600
|
+
// Note: We don't eagerly invalidate the client here. Instead, getApiClient()
|
|
601
|
+
// checks if the token has changed and recreates the client if needed.
|
|
602
|
+
// This avoids race conditions where the client is nullified but not yet recreated.
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Reset the shared client (mainly for testing)
|
|
607
|
+
*/
|
|
608
|
+
export function resetApiClient(): void {
|
|
609
|
+
sharedClient = null
|
|
610
|
+
sharedAuthToken = undefined
|
|
611
|
+
clientCreatedWithToken = undefined
|
|
612
|
+
}
|