@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,86 @@
|
|
|
1
|
+
import { flushAnalytics } from './analytics'
|
|
2
|
+
import { IS_FREEBUFF } from './constants'
|
|
3
|
+
import { stopEngagementTracking } from './engagement'
|
|
4
|
+
import { endFreebuffSessionBestEffort } from './freebuff-session-api'
|
|
5
|
+
import { drainClientLogs } from './log-shipper'
|
|
6
|
+
import { withTimeout } from './terminal-color-detection'
|
|
7
|
+
|
|
8
|
+
const EXIT_CLEANUP_TIMEOUT_MS = 1_000
|
|
9
|
+
|
|
10
|
+
type ExitCliDependencies = {
|
|
11
|
+
isFreebuff: boolean
|
|
12
|
+
cleanupLocal: () => void
|
|
13
|
+
stopEngagementTracking: () => void
|
|
14
|
+
flushAnalytics: () => Promise<void>
|
|
15
|
+
drainClientLogs: () => Promise<void>
|
|
16
|
+
endFreebuffSession: () => Promise<void>
|
|
17
|
+
waitForRemoteCleanup: (tasks: Promise<void>[]) => Promise<void>
|
|
18
|
+
exit: (code: number) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let localExitCleanup: (() => void) | undefined
|
|
22
|
+
|
|
23
|
+
/** Register the synchronous renderer/terminal finalizer once it is available. */
|
|
24
|
+
export function registerExitCleanup(cleanup: () => void): void {
|
|
25
|
+
localExitCleanup = cleanup
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build an idempotent exit request. Exported for dependency-injected tests;
|
|
30
|
+
* production uses the singleton below so competing exit triggers converge.
|
|
31
|
+
*/
|
|
32
|
+
export function createExitCliCleanly(deps: ExitCliDependencies) {
|
|
33
|
+
let exitPromise: Promise<void> | undefined
|
|
34
|
+
|
|
35
|
+
return (exitCode = 0): Promise<void> => {
|
|
36
|
+
if (exitPromise) return exitPromise
|
|
37
|
+
|
|
38
|
+
// Start on the next microtask so exitPromise is assigned before any cleanup
|
|
39
|
+
// callback can re-enter this function.
|
|
40
|
+
exitPromise = Promise.resolve().then(async () => {
|
|
41
|
+
try {
|
|
42
|
+
deps.cleanupLocal()
|
|
43
|
+
} catch {
|
|
44
|
+
// Cleanup is best-effort; never strand the process in a half-exited UI.
|
|
45
|
+
}
|
|
46
|
+
if (deps.isFreebuff) {
|
|
47
|
+
try {
|
|
48
|
+
deps.stopEngagementTracking()
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const remoteTasks = [
|
|
53
|
+
Promise.resolve().then(deps.flushAnalytics),
|
|
54
|
+
Promise.resolve().then(deps.drainClientLogs),
|
|
55
|
+
]
|
|
56
|
+
if (deps.isFreebuff) {
|
|
57
|
+
remoteTasks.push(Promise.resolve().then(deps.endFreebuffSession))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await deps.waitForRemoteCleanup(remoteTasks)
|
|
62
|
+
} finally {
|
|
63
|
+
deps.exit(exitCode)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return exitPromise
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const exitCliCleanly = createExitCliCleanly({
|
|
72
|
+
isFreebuff: IS_FREEBUFF,
|
|
73
|
+
cleanupLocal: () => localExitCleanup?.(),
|
|
74
|
+
stopEngagementTracking,
|
|
75
|
+
flushAnalytics,
|
|
76
|
+
drainClientLogs,
|
|
77
|
+
endFreebuffSession: endFreebuffSessionBestEffort,
|
|
78
|
+
waitForRemoteCleanup: async (tasks) => {
|
|
79
|
+
await withTimeout(
|
|
80
|
+
Promise.allSettled(tasks),
|
|
81
|
+
EXIT_CLEANUP_TIMEOUT_MS,
|
|
82
|
+
undefined,
|
|
83
|
+
)
|
|
84
|
+
},
|
|
85
|
+
exit: (code) => process.exit(code),
|
|
86
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_ERROR_ID_LENGTH,
|
|
3
|
+
MAX_ERROR_MESSAGE_LENGTH,
|
|
4
|
+
MAX_ERRORS,
|
|
5
|
+
MAX_RECENT_MESSAGES,
|
|
6
|
+
} from '@anvil/common/constants/feedback'
|
|
7
|
+
|
|
8
|
+
import type { ChatMessage } from '../types/chat'
|
|
9
|
+
import type { FeedbackCategory } from '@anvil/common/constants/feedback'
|
|
10
|
+
|
|
11
|
+
import type { FeedbackRequest } from '@anvil/common/schemas/feedback'
|
|
12
|
+
|
|
13
|
+
export type RecentMessageSummary = NonNullable<
|
|
14
|
+
FeedbackRequest['recentMessages']
|
|
15
|
+
>[number]
|
|
16
|
+
|
|
17
|
+
function toRecentMessageSummary(m: ChatMessage): RecentMessageSummary {
|
|
18
|
+
return {
|
|
19
|
+
type: m.variant,
|
|
20
|
+
id: m.id,
|
|
21
|
+
...(m.completionTime != null && { completionTime: m.completionTime }),
|
|
22
|
+
...(m.credits != null && { credits: m.credits }),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildMessageContext(
|
|
27
|
+
messages: ChatMessage[],
|
|
28
|
+
targetMessageId: string | null,
|
|
29
|
+
): {
|
|
30
|
+
target: ChatMessage | null
|
|
31
|
+
recentMessages: RecentMessageSummary[]
|
|
32
|
+
} {
|
|
33
|
+
if (!targetMessageId) {
|
|
34
|
+
const startIndex = Math.max(0, messages.length - MAX_RECENT_MESSAGES)
|
|
35
|
+
return { target: null, recentMessages: messages.slice(startIndex).map(toRecentMessageSummary) }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const target = messages.find((m: ChatMessage) => m.id === targetMessageId) ?? null
|
|
39
|
+
|
|
40
|
+
if (!target) {
|
|
41
|
+
return { target: null, recentMessages: [] }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const targetIndex = messages.indexOf(target)
|
|
45
|
+
const startIndex = Math.max(0, targetIndex - (MAX_RECENT_MESSAGES - 1))
|
|
46
|
+
return { target, recentMessages: messages.slice(startIndex, targetIndex + 1).map(toRecentMessageSummary) }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface BuildFeedbackPayloadParams {
|
|
50
|
+
text: string
|
|
51
|
+
feedbackCategory: FeedbackCategory
|
|
52
|
+
feedbackMessageId: string | null
|
|
53
|
+
target: ChatMessage | null
|
|
54
|
+
recentMessages: RecentMessageSummary[]
|
|
55
|
+
agentMode: string | null
|
|
56
|
+
sessionCreditsUsed: number | null
|
|
57
|
+
errors: Array<{ id: string; message: string }> | null
|
|
58
|
+
clientFeedbackId: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildFeedbackPayload(
|
|
62
|
+
params: BuildFeedbackPayloadParams,
|
|
63
|
+
): FeedbackRequest {
|
|
64
|
+
const {
|
|
65
|
+
text,
|
|
66
|
+
feedbackCategory,
|
|
67
|
+
feedbackMessageId,
|
|
68
|
+
target,
|
|
69
|
+
recentMessages,
|
|
70
|
+
agentMode,
|
|
71
|
+
sessionCreditsUsed,
|
|
72
|
+
errors,
|
|
73
|
+
clientFeedbackId,
|
|
74
|
+
} = params
|
|
75
|
+
|
|
76
|
+
const hasMessageId = feedbackMessageId != null && feedbackMessageId !== ''
|
|
77
|
+
const feedbackType: 'message' | 'general' = hasMessageId ? 'message' : 'general'
|
|
78
|
+
|
|
79
|
+
const truncatedErrors = errors
|
|
80
|
+
? errors.slice(0, MAX_ERRORS).map((e) => ({
|
|
81
|
+
id: e.id.slice(0, MAX_ERROR_ID_LENGTH),
|
|
82
|
+
message: e.message.slice(0, MAX_ERROR_MESSAGE_LENGTH),
|
|
83
|
+
}))
|
|
84
|
+
: null
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
text,
|
|
88
|
+
category: feedbackCategory,
|
|
89
|
+
type: feedbackType,
|
|
90
|
+
clientFeedbackId,
|
|
91
|
+
source: 'cli',
|
|
92
|
+
...(hasMessageId && { messageId: feedbackMessageId }),
|
|
93
|
+
...(target?.variant != null && { messageVariant: target.variant }),
|
|
94
|
+
...(target?.completionTime != null && target.completionTime !== '' && {
|
|
95
|
+
completionTime: target.completionTime,
|
|
96
|
+
}),
|
|
97
|
+
...(target?.credits != null && { credits: target.credits }),
|
|
98
|
+
...(agentMode != null && agentMode !== '' && { agentMode }),
|
|
99
|
+
...(sessionCreditsUsed != null && { sessionCreditsUsed }),
|
|
100
|
+
...(recentMessages.length > 0 && { recentMessages }),
|
|
101
|
+
...(truncatedErrors && truncatedErrors.length > 0 && { errors: truncatedErrors }),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface FeedbackSubmissionResolution {
|
|
2
|
+
isCurrentSubmission: boolean
|
|
3
|
+
shouldSettleSubmission: boolean
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Decide whether an async feedback result should update local state.
|
|
8
|
+
*
|
|
9
|
+
* - current submission id => settle and apply full success path
|
|
10
|
+
* - null active id => feedback was closed while request was in-flight; still settle
|
|
11
|
+
* - different active id => a newer feedback session exists; ignore stale result
|
|
12
|
+
*/
|
|
13
|
+
export function resolveFeedbackSubmission(
|
|
14
|
+
activeClientFeedbackId: string | null,
|
|
15
|
+
submittedClientFeedbackId: string,
|
|
16
|
+
): FeedbackSubmissionResolution {
|
|
17
|
+
const isCurrentSubmission = activeClientFeedbackId === submittedClientFeedbackId
|
|
18
|
+
return {
|
|
19
|
+
isCurrentSubmission,
|
|
20
|
+
shouldSettleSubmission: isCurrentSubmission || activeClientFeedbackId === null,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getAuthToken } from './auth'
|
|
2
|
+
import { getApiClient } from './anvil-api'
|
|
3
|
+
import { logger } from './logger'
|
|
4
|
+
import { useChatStore } from '../state/chat-store'
|
|
5
|
+
|
|
6
|
+
import type { AnvilApiClient } from './anvil-api'
|
|
7
|
+
import type { Logger } from '@anvil/common/types/contracts/logger'
|
|
8
|
+
|
|
9
|
+
export interface FetchAndUpdateUsageParams {
|
|
10
|
+
showBanner?: boolean
|
|
11
|
+
getAuthToken?: () => string | undefined
|
|
12
|
+
getChatStore?: () => {
|
|
13
|
+
sessionCreditsUsed: number
|
|
14
|
+
setInputMode: (mode: 'usage' | 'default') => void
|
|
15
|
+
}
|
|
16
|
+
logger?: Logger
|
|
17
|
+
apiClient?: AnvilApiClient
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Fetches current usage data from the API and updates the chat store.
|
|
22
|
+
* If `showBanner` is true, makes the usage banner visible after updating.
|
|
23
|
+
* Returns true if successful, false otherwise.
|
|
24
|
+
*/
|
|
25
|
+
export async function fetchAndUpdateUsage(
|
|
26
|
+
params: FetchAndUpdateUsageParams = {},
|
|
27
|
+
): Promise<boolean> {
|
|
28
|
+
const {
|
|
29
|
+
showBanner = false,
|
|
30
|
+
getAuthToken: getAuthTokenFn = getAuthToken,
|
|
31
|
+
getChatStore = () => useChatStore.getState(),
|
|
32
|
+
logger: loggerInstance = logger,
|
|
33
|
+
apiClient: providedApiClient,
|
|
34
|
+
} = params
|
|
35
|
+
|
|
36
|
+
const authToken = getAuthTokenFn()
|
|
37
|
+
const chatStore = getChatStore()
|
|
38
|
+
|
|
39
|
+
if (!authToken) {
|
|
40
|
+
loggerInstance.debug('Cannot fetch usage: not authenticated')
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const apiClient =
|
|
45
|
+
providedApiClient ?? getApiClient()
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const response = await apiClient.usage()
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
loggerInstance.error(
|
|
52
|
+
{ status: response.status, errorText: response.error },
|
|
53
|
+
'Usage request failed',
|
|
54
|
+
)
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Note: This function is deprecated. Use useUsageQuery hook instead.
|
|
59
|
+
// We no longer update the store here since usage data is managed by TanStack Query.
|
|
60
|
+
|
|
61
|
+
if (showBanner) {
|
|
62
|
+
chatStore.setInputMode('usage')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return true
|
|
66
|
+
} catch (error) {
|
|
67
|
+
loggerInstance.error(
|
|
68
|
+
{
|
|
69
|
+
error: error instanceof Error ? error.message : String(error),
|
|
70
|
+
errorStack: error instanceof Error ? error.stack : undefined,
|
|
71
|
+
},
|
|
72
|
+
'Error fetching usage',
|
|
73
|
+
)
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced fingerprinting for CLI authentication.
|
|
3
|
+
*
|
|
4
|
+
* Uses hardware-based identifiers to create deterministic fingerprints,
|
|
5
|
+
* making it harder for users to game the system by creating multiple accounts.
|
|
6
|
+
*
|
|
7
|
+
* Falls back to legacy random fingerprints if enhanced fingerprinting fails.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash, randomBytes } from 'node:crypto'
|
|
11
|
+
import { cpus, networkInterfaces } from 'node:os'
|
|
12
|
+
|
|
13
|
+
import { AnalyticsEvent } from '@anvil/common/constants/analytics-events'
|
|
14
|
+
|
|
15
|
+
import { trackEvent } from './analytics'
|
|
16
|
+
import { detectShell } from './detect-shell'
|
|
17
|
+
import { logger } from './logger'
|
|
18
|
+
|
|
19
|
+
// Lazy imports for optional dependencies
|
|
20
|
+
let machineIdModule: typeof import('node-machine-id') | null = null
|
|
21
|
+
let systeminformationModule: typeof import('systeminformation') | null = null
|
|
22
|
+
|
|
23
|
+
async function getMachineId(): Promise<string> {
|
|
24
|
+
if (!machineIdModule) {
|
|
25
|
+
machineIdModule = await import('node-machine-id')
|
|
26
|
+
}
|
|
27
|
+
const id = await machineIdModule.machineId()
|
|
28
|
+
// Validate that we got a real machine ID, not an empty or placeholder value.
|
|
29
|
+
// Throwing here triggers the legacy fallback in calculateFingerprint().
|
|
30
|
+
if (!id || id === 'unknown' || id.length < 8) {
|
|
31
|
+
throw new Error('Invalid machine ID returned')
|
|
32
|
+
}
|
|
33
|
+
return id
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function getSystemInfo(): Promise<{
|
|
37
|
+
system: { manufacturer: string; model: string; serial: string; uuid: string }
|
|
38
|
+
cpu: { manufacturer: string; brand: string; cores: number; physicalCores: number }
|
|
39
|
+
os: { platform: string; distro: string; arch: string; hostname: string }
|
|
40
|
+
}> {
|
|
41
|
+
try {
|
|
42
|
+
if (!systeminformationModule) {
|
|
43
|
+
systeminformationModule = await import('systeminformation')
|
|
44
|
+
}
|
|
45
|
+
const [systemInfo, cpuInfo, osInfo] = await Promise.all([
|
|
46
|
+
systeminformationModule.system(),
|
|
47
|
+
systeminformationModule.cpu(),
|
|
48
|
+
systeminformationModule.osInfo(),
|
|
49
|
+
])
|
|
50
|
+
return {
|
|
51
|
+
system: {
|
|
52
|
+
manufacturer: systemInfo.manufacturer,
|
|
53
|
+
model: systemInfo.model,
|
|
54
|
+
serial: systemInfo.serial,
|
|
55
|
+
uuid: systemInfo.uuid,
|
|
56
|
+
},
|
|
57
|
+
cpu: {
|
|
58
|
+
manufacturer: cpuInfo.manufacturer,
|
|
59
|
+
brand: cpuInfo.brand,
|
|
60
|
+
cores: cpuInfo.cores,
|
|
61
|
+
physicalCores: cpuInfo.physicalCores,
|
|
62
|
+
},
|
|
63
|
+
os: {
|
|
64
|
+
platform: osInfo.platform,
|
|
65
|
+
distro: osInfo.distro,
|
|
66
|
+
arch: osInfo.arch,
|
|
67
|
+
hostname: osInfo.hostname,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
return {
|
|
72
|
+
system: { manufacturer: '', model: '', serial: '', uuid: '' },
|
|
73
|
+
cpu: { manufacturer: '', brand: '', cores: 0, physicalCores: 0 },
|
|
74
|
+
os: { platform: process.platform, distro: '', arch: process.arch, hostname: '' },
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generates an enhanced CLI fingerprint using hardware identifiers.
|
|
81
|
+
* This is deterministic - the same machine will always produce the same fingerprint.
|
|
82
|
+
* Throws if machine ID cannot be obtained (to trigger legacy fallback).
|
|
83
|
+
*/
|
|
84
|
+
async function calculateEnhancedFingerprint(): Promise<string> {
|
|
85
|
+
// getMachineId will throw if it can't get a valid machine ID
|
|
86
|
+
const machineIdValue = await getMachineId()
|
|
87
|
+
|
|
88
|
+
const [sysInfo, shell, networkInfo] = await Promise.all([
|
|
89
|
+
getSystemInfo(),
|
|
90
|
+
Promise.resolve(detectShell()),
|
|
91
|
+
Promise.resolve(networkInterfaces()),
|
|
92
|
+
])
|
|
93
|
+
|
|
94
|
+
// Extract MAC addresses for additional uniqueness
|
|
95
|
+
const macAddresses = Object.values(networkInfo)
|
|
96
|
+
.flat()
|
|
97
|
+
.filter(
|
|
98
|
+
(iface) =>
|
|
99
|
+
iface && !iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00',
|
|
100
|
+
)
|
|
101
|
+
.map((iface) => iface!.mac)
|
|
102
|
+
.sort()
|
|
103
|
+
|
|
104
|
+
const fingerprintInfo = {
|
|
105
|
+
system: sysInfo.system,
|
|
106
|
+
cpu: sysInfo.cpu,
|
|
107
|
+
os: sysInfo.os,
|
|
108
|
+
runtime: {
|
|
109
|
+
nodeVersion: process.version,
|
|
110
|
+
platform: process.platform,
|
|
111
|
+
arch: process.arch,
|
|
112
|
+
shell,
|
|
113
|
+
cpuCount: cpus().length,
|
|
114
|
+
},
|
|
115
|
+
network: {
|
|
116
|
+
macAddresses,
|
|
117
|
+
interfaceCount: Object.keys(networkInfo).length,
|
|
118
|
+
},
|
|
119
|
+
machineId: machineIdValue,
|
|
120
|
+
fingerprintVersion: '2.0',
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const fingerprintString = JSON.stringify(fingerprintInfo)
|
|
124
|
+
const fingerprintHash = createHash('sha256')
|
|
125
|
+
.update(fingerprintString)
|
|
126
|
+
.digest('base64url')
|
|
127
|
+
|
|
128
|
+
return `enhanced-${fingerprintHash}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generates a legacy fingerprint with a random suffix.
|
|
133
|
+
* Used as a fallback when enhanced fingerprinting fails.
|
|
134
|
+
*/
|
|
135
|
+
function calculateLegacyFingerprint(): string {
|
|
136
|
+
const randomSuffix = randomBytes(6).toString('base64url').substring(0, 8)
|
|
137
|
+
return `anvil-cli-${randomSuffix}`
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Cached fingerprint promise. Populated on first call and reused for the
|
|
142
|
+
* process lifetime so every auth step in a session ships the same fingerprint
|
|
143
|
+
* to the server.
|
|
144
|
+
*/
|
|
145
|
+
let cachedFingerprintPromise: Promise<string> | null = null
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Returns the process-wide CLI fingerprint, computing it on first call.
|
|
149
|
+
* Safe to call from multiple places — the first caller wins and the rest
|
|
150
|
+
* await the same promise.
|
|
151
|
+
*/
|
|
152
|
+
export function getFingerprintId(): Promise<string> {
|
|
153
|
+
if (!cachedFingerprintPromise) {
|
|
154
|
+
cachedFingerprintPromise = calculateFingerprint()
|
|
155
|
+
}
|
|
156
|
+
return cachedFingerprintPromise
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Main fingerprint function.
|
|
161
|
+
* Tries enhanced fingerprinting first, falls back to legacy if it fails.
|
|
162
|
+
*/
|
|
163
|
+
export async function calculateFingerprint(): Promise<string> {
|
|
164
|
+
try {
|
|
165
|
+
const fingerprint = await calculateEnhancedFingerprint()
|
|
166
|
+
logger.debug(
|
|
167
|
+
{
|
|
168
|
+
fingerprintType: 'enhanced_cli',
|
|
169
|
+
fingerprintId: fingerprint.substring(0, 20) + '...',
|
|
170
|
+
},
|
|
171
|
+
'Enhanced CLI fingerprint generated successfully',
|
|
172
|
+
)
|
|
173
|
+
trackEvent(AnalyticsEvent.FINGERPRINT_GENERATED, {
|
|
174
|
+
fingerprintType: 'enhanced_cli',
|
|
175
|
+
success: true,
|
|
176
|
+
})
|
|
177
|
+
return fingerprint
|
|
178
|
+
} catch (enhancedError) {
|
|
179
|
+
logger.info(
|
|
180
|
+
{
|
|
181
|
+
errorMessage:
|
|
182
|
+
enhancedError instanceof Error ? enhancedError.message : String(enhancedError),
|
|
183
|
+
fingerprintType: 'enhanced_failed_fallback',
|
|
184
|
+
},
|
|
185
|
+
'Enhanced CLI fingerprinting failed, using legacy fallback',
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const fingerprint = calculateLegacyFingerprint()
|
|
190
|
+
logger.debug(
|
|
191
|
+
{
|
|
192
|
+
fingerprintType: 'legacy_fallback',
|
|
193
|
+
fingerprintId: fingerprint,
|
|
194
|
+
},
|
|
195
|
+
'Legacy fingerprint generated successfully as fallback',
|
|
196
|
+
)
|
|
197
|
+
trackEvent(AnalyticsEvent.FINGERPRINT_GENERATED, {
|
|
198
|
+
fingerprintType: 'legacy',
|
|
199
|
+
success: true,
|
|
200
|
+
fallbackReason:
|
|
201
|
+
enhancedError instanceof Error ? enhancedError.message : 'unknown',
|
|
202
|
+
})
|
|
203
|
+
return fingerprint
|
|
204
|
+
} catch (legacyError) {
|
|
205
|
+
logger.error(
|
|
206
|
+
{
|
|
207
|
+
errorMessage:
|
|
208
|
+
legacyError instanceof Error ? legacyError.message : String(legacyError),
|
|
209
|
+
fingerprintType: 'failed',
|
|
210
|
+
},
|
|
211
|
+
'Both enhanced and legacy fingerprint generation failed',
|
|
212
|
+
)
|
|
213
|
+
throw new Error('Fingerprint generation failed')
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Synchronous fingerprint generation (legacy only).
|
|
220
|
+
* Use this only when async is not possible (e.g., initial state).
|
|
221
|
+
* @deprecated Prefer calculateFingerprint() for hardware-based fingerprinting
|
|
222
|
+
*/
|
|
223
|
+
export function generateFingerprintIdSync(): string {
|
|
224
|
+
return calculateLegacyFingerprint()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Detects the fingerprint type from a fingerprint ID.
|
|
229
|
+
*/
|
|
230
|
+
export function getFingerprintType(
|
|
231
|
+
fingerprintId: string,
|
|
232
|
+
): 'enhanced_cli' | 'legacy' | 'unknown' {
|
|
233
|
+
if (fingerprintId.startsWith('enhanced-')) {
|
|
234
|
+
return 'enhanced_cli'
|
|
235
|
+
}
|
|
236
|
+
// `codebuff-cli-` is kept for fingerprints issued by pre-Anvil installs.
|
|
237
|
+
if (
|
|
238
|
+
fingerprintId.startsWith('anvil-cli-') ||
|
|
239
|
+
fingerprintId.startsWith('codebuff-cli-') ||
|
|
240
|
+
fingerprintId.startsWith('legacy-')
|
|
241
|
+
) {
|
|
242
|
+
return 'legacy'
|
|
243
|
+
}
|
|
244
|
+
return 'unknown'
|
|
245
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format elapsed seconds into a human-readable string.
|
|
3
|
+
*
|
|
4
|
+
* @param elapsedSeconds - Number of seconds elapsed (should be non-negative)
|
|
5
|
+
* @returns Formatted time string
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* formatElapsedTime(30) // "30s"
|
|
9
|
+
* formatElapsedTime(60) // "1m"
|
|
10
|
+
* formatElapsedTime(90) // "1m 30s"
|
|
11
|
+
* formatElapsedTime(3600) // "1h"
|
|
12
|
+
* formatElapsedTime(3700) // "1h 1m"
|
|
13
|
+
*
|
|
14
|
+
* Format rules:
|
|
15
|
+
* - Under 60 seconds: "Xs" (e.g., "45s")
|
|
16
|
+
* - 60-3599 seconds (1-59 minutes): "Xm" or "Xm Ys" (e.g., "1m", "12m 5s")
|
|
17
|
+
* - 3600+ seconds (1+ hours): "Xh" or "Xh Ym" (e.g., "1h", "2h 15m")
|
|
18
|
+
*/
|
|
19
|
+
export const formatElapsedTime = (elapsedSeconds: number): string => {
|
|
20
|
+
if (elapsedSeconds < 60) {
|
|
21
|
+
return `${elapsedSeconds}s`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const hours = Math.floor(elapsedSeconds / 3600)
|
|
25
|
+
const minutes = Math.floor((elapsedSeconds % 3600) / 60)
|
|
26
|
+
const seconds = elapsedSeconds % 60
|
|
27
|
+
|
|
28
|
+
if (hours > 0) {
|
|
29
|
+
return `${hours}h` + (minutes > 0 ? ` ${minutes}m` : '')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `${minutes}m` + (seconds > 0 ? ` ${seconds}s` : '')
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Premium-session counts come back from the server as `recentCount` units
|
|
2
|
+
* that may be fractional (a long agent run can consume 1.3 sessions). Render
|
|
3
|
+
* integers without a trailing `.0`, fractionals at one decimal — matches the
|
|
4
|
+
* `limit` field which is always integer. */
|
|
5
|
+
export const formatSessionUnits = (units: number): string =>
|
|
6
|
+
Number.isInteger(units) ? String(units) : units.toFixed(1)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a timeout value for display.
|
|
3
|
+
* - Returns "no timeout" for non-finite values (NaN, Infinity, -Infinity)
|
|
4
|
+
* - Returns "no timeout" for negative values (including -1)
|
|
5
|
+
* - Returns hours (e.g., "1h timeout") for values >= 3600 that are evenly divisible by 3600
|
|
6
|
+
* - Returns minutes (e.g., "2m timeout") for values >= 60 that are evenly divisible by 60
|
|
7
|
+
* - Returns seconds (e.g., "90s timeout") otherwise
|
|
8
|
+
* - Rounds floating point values to nearest integer
|
|
9
|
+
*/
|
|
10
|
+
export function formatTimeout(timeoutSeconds: number): string {
|
|
11
|
+
// Handle NaN, Infinity, -Infinity
|
|
12
|
+
if (!Number.isFinite(timeoutSeconds)) {
|
|
13
|
+
return 'no timeout'
|
|
14
|
+
}
|
|
15
|
+
// Handle all negative values (including -1)
|
|
16
|
+
if (timeoutSeconds < 0) {
|
|
17
|
+
return 'no timeout'
|
|
18
|
+
}
|
|
19
|
+
// Round floating point values
|
|
20
|
+
const rounded = Math.round(timeoutSeconds)
|
|
21
|
+
if (rounded >= 3600 && rounded % 3600 === 0) {
|
|
22
|
+
return `${rounded / 3600}h timeout`
|
|
23
|
+
}
|
|
24
|
+
if (rounded >= 60 && rounded % 60 === 0) {
|
|
25
|
+
return `${rounded / 60}m timeout`
|
|
26
|
+
}
|
|
27
|
+
return `${rounded}s timeout`
|
|
28
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { formatValidationError } from './validation-error-formatting'
|
|
4
|
+
|
|
5
|
+
import type { LocalAgentInfo } from './local-agent-registry'
|
|
6
|
+
|
|
7
|
+
export type ValidationErrorForMessage = {
|
|
8
|
+
id: string
|
|
9
|
+
message: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FormatValidationErrorsOptions {
|
|
13
|
+
errors: ValidationErrorForMessage[]
|
|
14
|
+
loadedAgentsData?: {
|
|
15
|
+
agents: Array<{ id: string; displayName: string; filePath?: string }>
|
|
16
|
+
agentsDir: string
|
|
17
|
+
} | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FormattedValidationErrors {
|
|
21
|
+
text: string
|
|
22
|
+
filePathByAgentId: Map<string, string>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Formats validation errors for display in chat messages.
|
|
27
|
+
* Matches the formatting from the validation banner.
|
|
28
|
+
* Returns formatted text and a map of agent IDs to file paths for creating clickable links.
|
|
29
|
+
*/
|
|
30
|
+
export function formatValidationErrorsForMessage(
|
|
31
|
+
options: FormatValidationErrorsOptions,
|
|
32
|
+
): FormattedValidationErrors {
|
|
33
|
+
const { errors, loadedAgentsData } = options
|
|
34
|
+
|
|
35
|
+
const filePathByAgentId = new Map<string, string>()
|
|
36
|
+
|
|
37
|
+
// Helper to normalize relative path (matching banner format)
|
|
38
|
+
const normalizeRelativePath = (filePath: string): string => {
|
|
39
|
+
if (!loadedAgentsData) return filePath
|
|
40
|
+
const relativeToAgentsDir = path.relative(
|
|
41
|
+
loadedAgentsData.agentsDir,
|
|
42
|
+
filePath,
|
|
43
|
+
)
|
|
44
|
+
const normalized = relativeToAgentsDir.replace(/\\/g, '/')
|
|
45
|
+
return `.agents/${normalized}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Get agent info by ID
|
|
49
|
+
const agentInfoById = new Map<string, LocalAgentInfo>(
|
|
50
|
+
(loadedAgentsData?.agents.map((agent) => [
|
|
51
|
+
agent.id,
|
|
52
|
+
agent as LocalAgentInfo,
|
|
53
|
+
]) || []) as [string, LocalAgentInfo][],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const text = errors
|
|
57
|
+
.map((error, index) => {
|
|
58
|
+
const agentId = error.id.replace(/_\d+$/, '')
|
|
59
|
+
const agentInfo = agentInfoById.get(agentId)
|
|
60
|
+
const relativePath = agentInfo?.filePath
|
|
61
|
+
? normalizeRelativePath(agentInfo.filePath)
|
|
62
|
+
: null
|
|
63
|
+
|
|
64
|
+
// Store file path for this agent ID to enable clickable links
|
|
65
|
+
if (agentInfo?.filePath) {
|
|
66
|
+
filePathByAgentId.set(agentId, agentInfo.filePath)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { fieldName, message } = formatValidationError(error.message)
|
|
70
|
+
const errorMsg = fieldName ? `${fieldName}: ${message}` : message
|
|
71
|
+
const truncatedMsg = errorMsg.length > 68 ? errorMsg.substring(0, 65) + '...' : errorMsg
|
|
72
|
+
|
|
73
|
+
let output = index === 0 ? '' : '\n\n'
|
|
74
|
+
output += agentId
|
|
75
|
+
if (relativePath) {
|
|
76
|
+
output += ` (${relativePath})`
|
|
77
|
+
}
|
|
78
|
+
output += '\n ' + truncatedMsg
|
|
79
|
+
return output
|
|
80
|
+
})
|
|
81
|
+
.join('')
|
|
82
|
+
|
|
83
|
+
return { text, filePathByAgentId }
|
|
84
|
+
}
|