@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,142 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { getAnvilClient } from '../utils/anvil-client'
|
|
4
|
+
import { logger } from '../utils/logger'
|
|
5
|
+
import {
|
|
6
|
+
failedPollDelayMs,
|
|
7
|
+
jitterPollIntervalMs,
|
|
8
|
+
} from '../utils/polling-backoff'
|
|
9
|
+
|
|
10
|
+
// Adaptive health check interval configuration
|
|
11
|
+
// Progressively increases polling interval based on consecutive successful checks
|
|
12
|
+
const HEALTH_CHECK_CONFIG = {
|
|
13
|
+
// Healthy startup cadence (ms).
|
|
14
|
+
INITIAL_INTERVAL: 10_000, // 10 seconds
|
|
15
|
+
// Interval thresholds based on consecutive successful checks
|
|
16
|
+
INTERVALS: [
|
|
17
|
+
{ successCount: 3, interval: 30_000 }, // 30 seconds after 3 successes
|
|
18
|
+
{ successCount: 6, interval: 60_000 }, // 1 minute after 6 successes
|
|
19
|
+
{ successCount: 10, interval: 120_000 }, // 2 minutes after 10 successes
|
|
20
|
+
{ successCount: 15, interval: 300_000 }, // 5 minutes after 15 successes
|
|
21
|
+
{ successCount: 20, interval: 600_000 }, // 10 minutes after 20 successes
|
|
22
|
+
],
|
|
23
|
+
} as const
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calculates the next health check interval based on consecutive successful checks
|
|
27
|
+
* Exported for testing purposes
|
|
28
|
+
*/
|
|
29
|
+
export function getNextInterval(consecutiveSuccesses: number): number {
|
|
30
|
+
// Find the highest threshold that we've passed
|
|
31
|
+
for (let i = HEALTH_CHECK_CONFIG.INTERVALS.length - 1; i >= 0; i--) {
|
|
32
|
+
const { successCount, interval } = HEALTH_CHECK_CONFIG.INTERVALS[i]
|
|
33
|
+
if (consecutiveSuccesses >= successCount) {
|
|
34
|
+
return interval
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return HEALTH_CHECK_CONFIG.INITIAL_INTERVAL
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Hook to monitor connection status to the Anvil backend.
|
|
42
|
+
* Jitters the adaptive healthy cadence and exponentially backs off failures so
|
|
43
|
+
* a shared outage cannot synchronize every CLI into a fixed retry wave.
|
|
44
|
+
*
|
|
45
|
+
* When the connection transitions from disconnected to connected, the optional
|
|
46
|
+
* onReconnect callback is invoked with a boolean indicating whether this was
|
|
47
|
+
* the initial connection (true) or a subsequent reconnection (false).
|
|
48
|
+
*/
|
|
49
|
+
export const useConnectionStatus = (
|
|
50
|
+
onReconnect?: (isInitialConnection: boolean) => void,
|
|
51
|
+
) => {
|
|
52
|
+
const [isConnected, setIsConnected] = useState(true)
|
|
53
|
+
// null = never connected, false = was disconnected, true = was connected
|
|
54
|
+
const previousConnectedRef = useRef<boolean | null>(null)
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
let isMounted = true
|
|
58
|
+
let timeoutId: NodeJS.Timeout | null = null
|
|
59
|
+
let consecutiveSuccesses = 0
|
|
60
|
+
let consecutiveFailures = 0
|
|
61
|
+
|
|
62
|
+
const scheduleNextCheck = (interval: number) => {
|
|
63
|
+
if (!isMounted) return
|
|
64
|
+
timeoutId = setTimeout(() => checkConnection(), interval)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const scheduleFailedCheck = (message: string, error?: unknown): void => {
|
|
68
|
+
if (!isMounted) return
|
|
69
|
+
setIsConnected(false)
|
|
70
|
+
previousConnectedRef.current = false
|
|
71
|
+
consecutiveSuccesses = 0
|
|
72
|
+
consecutiveFailures++
|
|
73
|
+
const delayMs = failedPollDelayMs({
|
|
74
|
+
consecutiveFailures,
|
|
75
|
+
})
|
|
76
|
+
logger.debug(
|
|
77
|
+
{
|
|
78
|
+
...(error === undefined ? {} : { error }),
|
|
79
|
+
delayMs,
|
|
80
|
+
consecutiveFailures,
|
|
81
|
+
},
|
|
82
|
+
message,
|
|
83
|
+
)
|
|
84
|
+
scheduleNextCheck(delayMs)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const checkConnection = async () => {
|
|
88
|
+
try {
|
|
89
|
+
const client = await getAnvilClient()
|
|
90
|
+
if (!client) {
|
|
91
|
+
scheduleFailedCheck('Health check: No client, backing off')
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const connected = await client.checkConnection()
|
|
96
|
+
if (!isMounted) return
|
|
97
|
+
|
|
98
|
+
const prevConnected = previousConnectedRef.current
|
|
99
|
+
setIsConnected(connected)
|
|
100
|
+
previousConnectedRef.current = connected
|
|
101
|
+
|
|
102
|
+
if (connected) {
|
|
103
|
+
consecutiveFailures = 0
|
|
104
|
+
// Determine if this is the initial connection (null) or a reconnection (false)
|
|
105
|
+
const isInitialConnection = prevConnected === null
|
|
106
|
+
const shouldFireReconnectCallback =
|
|
107
|
+
typeof onReconnect === 'function' && prevConnected !== true
|
|
108
|
+
|
|
109
|
+
if (shouldFireReconnectCallback) {
|
|
110
|
+
logger.info(
|
|
111
|
+
{ isInitialConnection },
|
|
112
|
+
'Reconnection detected, firing onReconnect callback',
|
|
113
|
+
)
|
|
114
|
+
onReconnect(isInitialConnection)
|
|
115
|
+
}
|
|
116
|
+
consecutiveSuccesses++
|
|
117
|
+
scheduleNextCheck(
|
|
118
|
+
jitterPollIntervalMs({
|
|
119
|
+
intervalMs: getNextInterval(consecutiveSuccesses),
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
} else {
|
|
123
|
+
scheduleFailedCheck('Health check failed, backing off')
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
scheduleFailedCheck('Connection check failed; backing off', error)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Start first check immediately
|
|
131
|
+
checkConnection()
|
|
132
|
+
|
|
133
|
+
return () => {
|
|
134
|
+
isMounted = false
|
|
135
|
+
if (timeoutId) {
|
|
136
|
+
clearTimeout(timeoutId)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}, [])
|
|
140
|
+
|
|
141
|
+
return isConnected
|
|
142
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'fs'
|
|
2
|
+
import os from 'os'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
import { useCallback, useMemo, useState } from 'react'
|
|
6
|
+
|
|
7
|
+
import { getDirectories, hasGitDirectory } from '../utils/directory-browser'
|
|
8
|
+
|
|
9
|
+
import type { DirectoryEntry } from '../utils/directory-browser'
|
|
10
|
+
|
|
11
|
+
export interface UseDirectoryBrowserOptions {
|
|
12
|
+
/** Initial path to browse from (defaults to home directory) */
|
|
13
|
+
initialPath?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseDirectoryBrowserReturn {
|
|
17
|
+
/** Current directory path */
|
|
18
|
+
currentPath: string
|
|
19
|
+
/** Set the current directory path */
|
|
20
|
+
setCurrentPath: (path: string) => void
|
|
21
|
+
/** List of directory entries in the current path */
|
|
22
|
+
directories: DirectoryEntry[]
|
|
23
|
+
/** Whether current directory is a git repository */
|
|
24
|
+
isGitRepo: boolean
|
|
25
|
+
/** Expand ~ to home directory */
|
|
26
|
+
expandPath: (inputPath: string) => string
|
|
27
|
+
/** Try to navigate to a typed path, returns true if successful */
|
|
28
|
+
tryNavigateToPath: (inputPath: string) => boolean
|
|
29
|
+
/** Navigate to a directory entry */
|
|
30
|
+
navigateToDirectory: (entry: DirectoryEntry) => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook for directory browsing state and navigation.
|
|
35
|
+
* Provides current path state, directory listing, git detection,
|
|
36
|
+
* and navigation utilities.
|
|
37
|
+
*/
|
|
38
|
+
export function useDirectoryBrowser({
|
|
39
|
+
initialPath,
|
|
40
|
+
}: UseDirectoryBrowserOptions = {}): UseDirectoryBrowserReturn {
|
|
41
|
+
const [currentPath, setCurrentPath] = useState(initialPath ?? os.homedir())
|
|
42
|
+
|
|
43
|
+
// Get directories for current path
|
|
44
|
+
const directories = useMemo(() => getDirectories(currentPath), [currentPath])
|
|
45
|
+
|
|
46
|
+
// Check if current directory has .git
|
|
47
|
+
const isGitRepo = useMemo(() => hasGitDirectory(currentPath), [currentPath])
|
|
48
|
+
|
|
49
|
+
// Expand ~ to home directory
|
|
50
|
+
const expandPath = useCallback((inputPath: string): string => {
|
|
51
|
+
if (inputPath.startsWith('~')) {
|
|
52
|
+
return path.join(os.homedir(), inputPath.slice(1))
|
|
53
|
+
}
|
|
54
|
+
return inputPath
|
|
55
|
+
}, [])
|
|
56
|
+
|
|
57
|
+
// Try to navigate to a typed path
|
|
58
|
+
const tryNavigateToPath = useCallback(
|
|
59
|
+
(inputPath: string): boolean => {
|
|
60
|
+
const expandedPath = expandPath(inputPath.trim())
|
|
61
|
+
try {
|
|
62
|
+
if (existsSync(expandedPath) && statSync(expandedPath).isDirectory()) {
|
|
63
|
+
setCurrentPath(expandedPath)
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Path doesn't exist or can't be accessed
|
|
68
|
+
}
|
|
69
|
+
return false
|
|
70
|
+
},
|
|
71
|
+
[expandPath],
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
// Navigate to a directory entry
|
|
75
|
+
const navigateToDirectory = useCallback((entry: DirectoryEntry) => {
|
|
76
|
+
setCurrentPath(entry.path)
|
|
77
|
+
}, [])
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
currentPath,
|
|
81
|
+
setCurrentPath,
|
|
82
|
+
directories,
|
|
83
|
+
isGitRepo,
|
|
84
|
+
expandPath,
|
|
85
|
+
tryNavigateToPath,
|
|
86
|
+
navigateToDirectory,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface ElapsedTimeTracker {
|
|
4
|
+
/**
|
|
5
|
+
* Start tracking elapsed time from now
|
|
6
|
+
*/
|
|
7
|
+
start: () => void
|
|
8
|
+
/**
|
|
9
|
+
* Stop tracking and reset to 0
|
|
10
|
+
*/
|
|
11
|
+
stop: () => void
|
|
12
|
+
/**
|
|
13
|
+
* Pause tracking, freezing the current elapsed time
|
|
14
|
+
*/
|
|
15
|
+
pause: () => void
|
|
16
|
+
/**
|
|
17
|
+
* Resume tracking from the frozen elapsed time
|
|
18
|
+
*/
|
|
19
|
+
resume: () => void
|
|
20
|
+
/**
|
|
21
|
+
* Get the current elapsed seconds
|
|
22
|
+
*/
|
|
23
|
+
elapsedSeconds: number
|
|
24
|
+
/**
|
|
25
|
+
* Get the start time timestamp (null if not started)
|
|
26
|
+
*/
|
|
27
|
+
startTime: number | null
|
|
28
|
+
/**
|
|
29
|
+
* Whether the timer is currently paused
|
|
30
|
+
*/
|
|
31
|
+
isPaused: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hook to track elapsed time with manual start/stop control
|
|
36
|
+
* Updates every second while active
|
|
37
|
+
*
|
|
38
|
+
* @returns ElapsedTimeTracker - Object with start/stop methods and current elapsed time
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const timer = useElapsedTime()
|
|
42
|
+
* timer.start() // Start timing
|
|
43
|
+
* timer.stop() // Stop and reset
|
|
44
|
+
*
|
|
45
|
+
* // Pass the timer object to components that need to display elapsed time
|
|
46
|
+
* <StatusIndicator timer={timer} />
|
|
47
|
+
* <MessageBlock timer={timer} />
|
|
48
|
+
*/
|
|
49
|
+
export const useElapsedTime = (): ElapsedTimeTracker => {
|
|
50
|
+
const [startTime, setStartTime] = useState<number | null>(null)
|
|
51
|
+
const [elapsedSeconds, setElapsedSeconds] = useState<number>(0)
|
|
52
|
+
const [isPaused, setIsPaused] = useState(false)
|
|
53
|
+
// Track accumulated time from previous pause/resume cycles
|
|
54
|
+
const [accumulatedSeconds, setAccumulatedSeconds] = useState(0)
|
|
55
|
+
|
|
56
|
+
const start = useCallback(() => {
|
|
57
|
+
setStartTime(Date.now())
|
|
58
|
+
setAccumulatedSeconds(0)
|
|
59
|
+
setIsPaused(false)
|
|
60
|
+
}, [])
|
|
61
|
+
|
|
62
|
+
const stop = useCallback(() => {
|
|
63
|
+
setStartTime(null)
|
|
64
|
+
setElapsedSeconds(0)
|
|
65
|
+
setAccumulatedSeconds(0)
|
|
66
|
+
setIsPaused(false)
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
const pause = useCallback(() => {
|
|
70
|
+
if (startTime && !isPaused) {
|
|
71
|
+
// Capture current elapsed time before pausing
|
|
72
|
+
const currentElapsed = Math.floor((Date.now() - startTime) / 1000)
|
|
73
|
+
setAccumulatedSeconds(currentElapsed)
|
|
74
|
+
setElapsedSeconds(currentElapsed)
|
|
75
|
+
setIsPaused(true)
|
|
76
|
+
}
|
|
77
|
+
}, [startTime, isPaused])
|
|
78
|
+
|
|
79
|
+
const resume = useCallback(() => {
|
|
80
|
+
if (isPaused) {
|
|
81
|
+
// Set a new start time adjusted for accumulated time
|
|
82
|
+
setStartTime(Date.now() - accumulatedSeconds * 1000)
|
|
83
|
+
setIsPaused(false)
|
|
84
|
+
}
|
|
85
|
+
}, [isPaused, accumulatedSeconds])
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!startTime || isPaused) {
|
|
89
|
+
// When paused, keep showing the frozen elapsed time (don't reset)
|
|
90
|
+
if (!isPaused && !startTime) {
|
|
91
|
+
setElapsedSeconds(0)
|
|
92
|
+
}
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const updateElapsed = () => {
|
|
97
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000)
|
|
98
|
+
setElapsedSeconds(elapsed)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Update immediately
|
|
102
|
+
updateElapsed()
|
|
103
|
+
|
|
104
|
+
// Then update every second
|
|
105
|
+
const interval = setInterval(updateElapsed, 1000)
|
|
106
|
+
|
|
107
|
+
return () => clearInterval(interval)
|
|
108
|
+
}, [startTime, isPaused])
|
|
109
|
+
|
|
110
|
+
const timer = useMemo(
|
|
111
|
+
() => ({ start, stop, pause, resume, elapsedSeconds, startTime, isPaused }),
|
|
112
|
+
[start, stop, pause, resume, elapsedSeconds, startTime, isPaused],
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return timer
|
|
116
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a stable function reference that always calls the latest version of the provided callback.
|
|
5
|
+
* Similar to React's proposed useEvent RFC.
|
|
6
|
+
*
|
|
7
|
+
* The returned function has a stable identity across renders, but always calls the most recent
|
|
8
|
+
* version of the callback you pass in. This is useful for event handlers and callbacks that
|
|
9
|
+
* need to access the latest props/state without causing re-renders of child components.
|
|
10
|
+
*
|
|
11
|
+
* @param callback - The function to wrap
|
|
12
|
+
* @returns A stable function reference that calls the latest version of the callback
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const handleClick = useEvent(() => {
|
|
16
|
+
* console.log(count) // Always logs the latest count
|
|
17
|
+
* })
|
|
18
|
+
*
|
|
19
|
+
* // handleClick has a stable reference, so it won't cause child re-renders
|
|
20
|
+
* <ChildComponent onClick={handleClick} />
|
|
21
|
+
*/
|
|
22
|
+
export function useEvent<T extends (...args: any[]) => any>(callback: T): T {
|
|
23
|
+
const callbackRef = useRef<T>(callback)
|
|
24
|
+
|
|
25
|
+
// Update the ref to the latest callback on every render
|
|
26
|
+
// This ensures the ref is always in sync with the current render
|
|
27
|
+
callbackRef.current = callback
|
|
28
|
+
|
|
29
|
+
// Return a stable function that calls the latest callback
|
|
30
|
+
return useCallback(
|
|
31
|
+
((...args: any[]) => callbackRef.current(...args)) as T,
|
|
32
|
+
[], // Empty deps array ensures the function identity is stable
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { getCurrentChatId } from '../project-files'
|
|
4
|
+
import { IS_FREEBUFF } from '../utils/constants'
|
|
5
|
+
import { exitCliCleanly } from '../utils/exit-cleanly'
|
|
6
|
+
|
|
7
|
+
import type { InputValue } from '../types/store'
|
|
8
|
+
|
|
9
|
+
interface UseExitHandlerOptions {
|
|
10
|
+
inputValue: string
|
|
11
|
+
setInputValue: (value: InputValue) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let exitHandlerRegistered = false
|
|
15
|
+
|
|
16
|
+
function setupExitMessageHandler() {
|
|
17
|
+
if (exitHandlerRegistered) return
|
|
18
|
+
exitHandlerRegistered = true
|
|
19
|
+
|
|
20
|
+
process.on('exit', () => {
|
|
21
|
+
try {
|
|
22
|
+
const chatId = getCurrentChatId()
|
|
23
|
+
if (chatId) {
|
|
24
|
+
// This runs synchronously during the exit phase
|
|
25
|
+
// OpenTUI has already cleaned up by this point
|
|
26
|
+
const cliName = IS_FREEBUFF ? 'freebuff' : 'anvil'
|
|
27
|
+
process.stdout.write(
|
|
28
|
+
`\nTo continue this session later, run:\n${cliName} --continue ${chatId}\n`,
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// Silent fail - don't block exit
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const useExitHandler = ({
|
|
38
|
+
inputValue,
|
|
39
|
+
setInputValue,
|
|
40
|
+
}: UseExitHandlerOptions) => {
|
|
41
|
+
const [nextCtrlCWillExit, setNextCtrlCWillExit] = useState(false)
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
setupExitMessageHandler()
|
|
45
|
+
}, [])
|
|
46
|
+
|
|
47
|
+
const handleCtrlC = useCallback(() => {
|
|
48
|
+
if (inputValue) {
|
|
49
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!nextCtrlCWillExit) {
|
|
54
|
+
setNextCtrlCWillExit(true)
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
setNextCtrlCWillExit(false)
|
|
57
|
+
}, 2000)
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void exitCliCleanly()
|
|
62
|
+
return true
|
|
63
|
+
}, [inputValue, setInputValue, nextCtrlCWillExit])
|
|
64
|
+
|
|
65
|
+
return { handleCtrlC, nextCtrlCWillExit }
|
|
66
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query'
|
|
2
|
+
import { safeOpen } from '../utils/open-url'
|
|
3
|
+
|
|
4
|
+
import { LOGIN_WEBSITE_URL } from '../login/constants'
|
|
5
|
+
import { generateLoginUrl } from '../login/login-flow'
|
|
6
|
+
import { trackEvent } from '../utils/analytics'
|
|
7
|
+
import { logger } from '../utils/logger'
|
|
8
|
+
|
|
9
|
+
interface UseFetchLoginUrlParams {
|
|
10
|
+
setLoginUrl: (url: string | null) => void
|
|
11
|
+
setFingerprintHash: (hash: string | null) => void
|
|
12
|
+
setExpiresAt: (expiresAt: string | null) => void
|
|
13
|
+
setIsWaitingForEnter: (waiting: boolean) => void
|
|
14
|
+
setHasOpenedBrowser: (opened: boolean) => void
|
|
15
|
+
setError: (error: string | null) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Custom hook that handles fetching the login URL and opening the browser
|
|
20
|
+
*/
|
|
21
|
+
export function useFetchLoginUrl({
|
|
22
|
+
setLoginUrl,
|
|
23
|
+
setFingerprintHash,
|
|
24
|
+
setExpiresAt,
|
|
25
|
+
setIsWaitingForEnter,
|
|
26
|
+
setHasOpenedBrowser,
|
|
27
|
+
setError,
|
|
28
|
+
}: UseFetchLoginUrlParams) {
|
|
29
|
+
const fetchLoginUrlMutation = useMutation({
|
|
30
|
+
mutationFn: async (fingerprintId: string) => {
|
|
31
|
+
return generateLoginUrl(
|
|
32
|
+
{
|
|
33
|
+
logger,
|
|
34
|
+
trackEvent,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
baseUrl: LOGIN_WEBSITE_URL,
|
|
38
|
+
fingerprintId,
|
|
39
|
+
via: 'modal',
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
},
|
|
43
|
+
onSuccess: async (data) => {
|
|
44
|
+
setLoginUrl(data.loginUrl)
|
|
45
|
+
setFingerprintHash(data.fingerprintHash)
|
|
46
|
+
setExpiresAt(data.expiresAt)
|
|
47
|
+
setIsWaitingForEnter(true)
|
|
48
|
+
setHasOpenedBrowser(true)
|
|
49
|
+
|
|
50
|
+
// Open browser after fetching URL
|
|
51
|
+
await safeOpen(data.loginUrl)
|
|
52
|
+
},
|
|
53
|
+
onError: (err) => {
|
|
54
|
+
setError(err instanceof Error ? err.message : 'Failed to get login URL')
|
|
55
|
+
logger.error(
|
|
56
|
+
{
|
|
57
|
+
error: err instanceof Error ? err.message : String(err),
|
|
58
|
+
},
|
|
59
|
+
'Failed to get login URL',
|
|
60
|
+
)
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return fetchLoginUrlMutation
|
|
65
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { calculateFingerprint, generateFingerprintIdSync } from '../utils/fingerprint'
|
|
4
|
+
import { logger } from '../utils/logger'
|
|
5
|
+
|
|
6
|
+
interface UseFingerprintResult {
|
|
7
|
+
fingerprintId: string
|
|
8
|
+
isEnhanced: boolean
|
|
9
|
+
isLoading: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* React hook for generating a hardware-based fingerprint.
|
|
14
|
+
*
|
|
15
|
+
* Immediately provides a legacy fingerprint for responsiveness,
|
|
16
|
+
* then asynchronously generates an enhanced fingerprint if possible.
|
|
17
|
+
*
|
|
18
|
+
* The fingerprint is stable across re-renders (generated once on mount).
|
|
19
|
+
*/
|
|
20
|
+
export function useFingerprint(): UseFingerprintResult {
|
|
21
|
+
// Start with a sync legacy fingerprint for immediate availability
|
|
22
|
+
const [state, setState] = useState<UseFingerprintResult>(() => ({
|
|
23
|
+
fingerprintId: generateFingerprintIdSync(),
|
|
24
|
+
isEnhanced: false,
|
|
25
|
+
isLoading: true,
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let cancelled = false
|
|
30
|
+
|
|
31
|
+
const generateEnhanced = async () => {
|
|
32
|
+
try {
|
|
33
|
+
const enhancedFingerprint = await calculateFingerprint()
|
|
34
|
+
if (!cancelled) {
|
|
35
|
+
setState({
|
|
36
|
+
fingerprintId: enhancedFingerprint,
|
|
37
|
+
isEnhanced: enhancedFingerprint.startsWith('enhanced-'),
|
|
38
|
+
isLoading: false,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
logger.error(error, 'Failed to generate enhanced fingerprint')
|
|
43
|
+
if (!cancelled) {
|
|
44
|
+
// Keep the legacy fingerprint we already have
|
|
45
|
+
setState((prev) => ({
|
|
46
|
+
...prev,
|
|
47
|
+
isLoading: false,
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
generateEnhanced()
|
|
54
|
+
|
|
55
|
+
return () => {
|
|
56
|
+
cancelled = true
|
|
57
|
+
}
|
|
58
|
+
}, [])
|
|
59
|
+
|
|
60
|
+
return state
|
|
61
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useKeyboard } from '@opentui/react'
|
|
2
|
+
import { useCallback } from 'react'
|
|
3
|
+
|
|
4
|
+
import { exitCliCleanly } from '../utils/exit-cleanly'
|
|
5
|
+
|
|
6
|
+
import type { KeyEvent } from '@opentui/core'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Bind Ctrl+C on a full-screen freebuff view to `exitCliCleanly`. Stdin
|
|
10
|
+
* is in raw mode, so SIGINT never fires — the key arrives as a normal OpenTUI
|
|
11
|
+
* key event and we route it through the shared cleanup path (flush analytics,
|
|
12
|
+
* release the session seat, then process.exit).
|
|
13
|
+
*/
|
|
14
|
+
export function useFreebuffCtrlCExit(): void {
|
|
15
|
+
useKeyboard(
|
|
16
|
+
useCallback((key: KeyEvent) => {
|
|
17
|
+
if (key.ctrl && key.name === 'c') {
|
|
18
|
+
key.preventDefault?.()
|
|
19
|
+
void exitCliCleanly()
|
|
20
|
+
}
|
|
21
|
+
}, []),
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useNow } from './use-now'
|
|
2
|
+
import { IS_FREEBUFF } from '../utils/constants'
|
|
3
|
+
|
|
4
|
+
import type { FreebuffSessionResponse } from '../types/freebuff-session'
|
|
5
|
+
|
|
6
|
+
export interface FreebuffSessionProgress {
|
|
7
|
+
/** 0..1, fraction of the session remaining. 1 at admission, 0 at expiry. */
|
|
8
|
+
fraction: number
|
|
9
|
+
remainingMs: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Computes a live progress value for the active freebuff session, ticking at
|
|
14
|
+
* 1Hz. Returns null outside of active state or in non-freebuff builds, so
|
|
15
|
+
* callers can short-circuit their rendering.
|
|
16
|
+
*/
|
|
17
|
+
export function useFreebuffSessionProgress(
|
|
18
|
+
session: FreebuffSessionResponse | null,
|
|
19
|
+
): FreebuffSessionProgress | null {
|
|
20
|
+
const expiresAtMs =
|
|
21
|
+
session?.status === 'active' ? Date.parse(session.expiresAt) : null
|
|
22
|
+
const admittedAtMs =
|
|
23
|
+
session?.status === 'active' ? Date.parse(session.admittedAt) : null
|
|
24
|
+
|
|
25
|
+
const nowMs = useNow(1000, expiresAtMs !== null)
|
|
26
|
+
|
|
27
|
+
if (!IS_FREEBUFF || !expiresAtMs || !admittedAtMs) return null
|
|
28
|
+
|
|
29
|
+
const totalMs = expiresAtMs - admittedAtMs
|
|
30
|
+
if (totalMs <= 0) return null
|
|
31
|
+
const remainingMs = Math.max(0, expiresAtMs - nowMs)
|
|
32
|
+
const fraction = Math.max(0, Math.min(1, remainingMs / totalMs))
|
|
33
|
+
return { fraction, remainingMs }
|
|
34
|
+
}
|