@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,271 @@
|
|
|
1
|
+
import { execSync } from 'child_process'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if tmux is available and usable on the system.
|
|
7
|
+
* This checks both that tmux is installed AND that it can actually run
|
|
8
|
+
* (e.g., the tmux server socket directory exists and is accessible).
|
|
9
|
+
*
|
|
10
|
+
* Returns false on CI unless ANVIL_RUN_TMUX_TESTS=1 explicitly opts into
|
|
11
|
+
* running deterministic tmux tests in a known terminal environment.
|
|
12
|
+
*/
|
|
13
|
+
export function isTmuxAvailable(): boolean {
|
|
14
|
+
// Skip on CI - tmux integration tests need a real terminal environment
|
|
15
|
+
if (
|
|
16
|
+
(process.env.CI === 'true' || process.env.CI === '1') &&
|
|
17
|
+
process.env.ANVIL_RUN_TMUX_TESTS !== '1'
|
|
18
|
+
) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// First check if tmux is installed
|
|
24
|
+
execSync('which tmux', { stdio: 'pipe' })
|
|
25
|
+
// Then verify tmux can actually run by creating and killing a test session
|
|
26
|
+
// This will fail if tmux server can't start (e.g., no socket directory on CI)
|
|
27
|
+
execSync('tmux new-session -d -s __anvil_tmux_check__ && tmux kill-session -t __anvil_tmux_check__', {
|
|
28
|
+
stdio: 'pipe',
|
|
29
|
+
timeout: 5000,
|
|
30
|
+
})
|
|
31
|
+
return true
|
|
32
|
+
} catch {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if the SDK is built by checking for the dist directory
|
|
39
|
+
*/
|
|
40
|
+
export function isSDKBuilt(): boolean {
|
|
41
|
+
try {
|
|
42
|
+
const sdkDistDir = path.join(__dirname, '../../../sdk/dist')
|
|
43
|
+
const possibleArtifacts = ['index.js', 'index.mjs', 'index.cjs']
|
|
44
|
+
return possibleArtifacts.some((file) =>
|
|
45
|
+
fs.existsSync(path.join(sdkDistDir, file)),
|
|
46
|
+
)
|
|
47
|
+
} catch {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Sleep utility for async delays
|
|
54
|
+
*/
|
|
55
|
+
export const sleep = (ms: number) =>
|
|
56
|
+
new Promise((resolve) => setTimeout(resolve, ms))
|
|
57
|
+
|
|
58
|
+
let cachedEnv: Record<string, string> | null = null
|
|
59
|
+
|
|
60
|
+
const TEST_CLIENT_ENV_DEFAULTS: Record<string, string> = {
|
|
61
|
+
NEXT_PUBLIC_CB_ENVIRONMENT: 'test',
|
|
62
|
+
NEXT_PUBLIC_ANVIL_APP_URL: 'http://localhost:3000',
|
|
63
|
+
NEXT_PUBLIC_SUPPORT_EMAIL: 'support@codebuff.com',
|
|
64
|
+
NEXT_PUBLIC_POSTHOG_API_KEY: 'test-posthog-key',
|
|
65
|
+
NEXT_PUBLIC_POSTHOG_HOST_URL: 'https://us.i.posthog.com',
|
|
66
|
+
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: 'pk_test_placeholder',
|
|
67
|
+
NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL:
|
|
68
|
+
'https://billing.stripe.com/p/login/test_placeholder',
|
|
69
|
+
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID: 'test-verification',
|
|
70
|
+
NEXT_PUBLIC_WEB_PORT: '3000',
|
|
71
|
+
}
|
|
72
|
+
const TEST_SERVER_ENV_DEFAULTS: Record<string, string> = {
|
|
73
|
+
OPEN_ROUTER_API_KEY: 'test',
|
|
74
|
+
OPENAI_API_KEY: 'test',
|
|
75
|
+
ANTHROPIC_API_KEY: 'test',
|
|
76
|
+
SERPER_API_KEY: 'test',
|
|
77
|
+
GRAVITY_API_KEY: 'test',
|
|
78
|
+
GRAVITY_CAPI_KEY: 'test',
|
|
79
|
+
PORT: '4242',
|
|
80
|
+
DATABASE_URL: 'postgres://user:pass@localhost:5432/db',
|
|
81
|
+
ANVIL_GITHUB_ID: 'test-id',
|
|
82
|
+
ANVIL_GITHUB_SECRET: 'test-secret',
|
|
83
|
+
NEXTAUTH_SECRET: 'test-secret',
|
|
84
|
+
STRIPE_SECRET_KEY: 'sk_test_dummy',
|
|
85
|
+
STRIPE_WEBHOOK_SECRET_KEY: 'whsec_dummy',
|
|
86
|
+
STRIPE_TEAM_FEE_PRICE_ID: 'price_test',
|
|
87
|
+
LOOPS_API_KEY: 'test',
|
|
88
|
+
DISCORD_PUBLIC_KEY: 'test',
|
|
89
|
+
DISCORD_BOT_TOKEN: 'test',
|
|
90
|
+
DISCORD_APPLICATION_ID: 'test',
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function ensureCliEnvDefaults(): void {
|
|
94
|
+
if (!process.env.NODE_ENV) {
|
|
95
|
+
process.env.NODE_ENV = 'test'
|
|
96
|
+
}
|
|
97
|
+
if (!process.env.BUN_ENV) {
|
|
98
|
+
process.env.BUN_ENV = 'test'
|
|
99
|
+
}
|
|
100
|
+
if (process.env.CI !== 'true' && process.env.CI !== '1') {
|
|
101
|
+
process.env.CI = 'true'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const [key, value] of Object.entries(TEST_CLIENT_ENV_DEFAULTS)) {
|
|
105
|
+
if (!process.env[key]) {
|
|
106
|
+
process.env[key] = value
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const [key, value] of Object.entries(TEST_SERVER_ENV_DEFAULTS)) {
|
|
111
|
+
if (!process.env[key]) {
|
|
112
|
+
process.env[key] = value
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function loadCliEnv(): Record<string, string> {
|
|
118
|
+
if (cachedEnv) {
|
|
119
|
+
return cachedEnv
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
ensureCliEnvDefaults()
|
|
124
|
+
// NOTE: Inline require() is used for lazy loading - the env module depends on
|
|
125
|
+
// Infisical secrets which may not be available at module load time in test environments
|
|
126
|
+
const { env } = require('../../../packages/internal/src/env') as {
|
|
127
|
+
env: Record<string, unknown>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
cachedEnv = Object.entries(env).reduce<Record<string, string>>(
|
|
131
|
+
(acc, [key, value]) => {
|
|
132
|
+
if (value !== undefined && value !== null) {
|
|
133
|
+
acc[key] = String(value)
|
|
134
|
+
}
|
|
135
|
+
return acc
|
|
136
|
+
},
|
|
137
|
+
{},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return cachedEnv
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const message =
|
|
143
|
+
error instanceof Error
|
|
144
|
+
? error.message
|
|
145
|
+
: 'unknown error loading environment'
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Failed to load CLI environment via packages/internal/src/env: ${message}. ` +
|
|
148
|
+
'Run commands via "infisical run -- bun …" or export the required variables.',
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function ensureCliTestEnv(): void {
|
|
154
|
+
loadCliEnv()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function getDefaultCliEnv(): Record<string, string> {
|
|
158
|
+
return { ...loadCliEnv() }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Parsed re-render log entry from useWhyDidYouUpdate hook
|
|
163
|
+
*/
|
|
164
|
+
export interface RerenderLogEntry {
|
|
165
|
+
timestamp: string
|
|
166
|
+
componentName: string
|
|
167
|
+
messageId: string
|
|
168
|
+
renderCount: number
|
|
169
|
+
changedProps: string[]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Aggregated re-render analysis results
|
|
174
|
+
*/
|
|
175
|
+
export interface RerenderAnalysis {
|
|
176
|
+
totalRerenders: number
|
|
177
|
+
rerendersByMessage: Map<string, number>
|
|
178
|
+
propChangeFrequency: Map<string, number>
|
|
179
|
+
maxRerenderPerMessage: number
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Parse re-render logs from the CLI debug log file
|
|
184
|
+
*/
|
|
185
|
+
export function parseRerenderLogs(logPath: string): RerenderLogEntry[] {
|
|
186
|
+
const entries: RerenderLogEntry[] = []
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const content = fs.readFileSync(logPath, 'utf-8')
|
|
190
|
+
const lines = content.split('\n').filter((line) => line.trim())
|
|
191
|
+
|
|
192
|
+
for (const line of lines) {
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(line)
|
|
195
|
+
|
|
196
|
+
// Check if this is a re-render log entry
|
|
197
|
+
if (
|
|
198
|
+
parsed.msg &&
|
|
199
|
+
typeof parsed.msg === 'string' &&
|
|
200
|
+
parsed.msg.includes('render #')
|
|
201
|
+
) {
|
|
202
|
+
// Extract component name from msg like "MessageBlock render #2 [user-123]: 2 props changed"
|
|
203
|
+
const msgMatch = parsed.msg.match(
|
|
204
|
+
/^(\w+) render #(\d+) \[([^\]]+)\]/,
|
|
205
|
+
)
|
|
206
|
+
if (msgMatch && parsed.data) {
|
|
207
|
+
entries.push({
|
|
208
|
+
timestamp: parsed.timestamp,
|
|
209
|
+
componentName: msgMatch[1],
|
|
210
|
+
messageId: parsed.data.id || msgMatch[3],
|
|
211
|
+
renderCount: parseInt(msgMatch[2], 10),
|
|
212
|
+
changedProps: parsed.data.changedProps || [],
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
// Skip malformed lines
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
// File doesn't exist or can't be read
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return entries
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Analyze re-render logs and return aggregated statistics
|
|
229
|
+
*/
|
|
230
|
+
export function analyzeRerenders(entries: RerenderLogEntry[]): RerenderAnalysis {
|
|
231
|
+
const rerendersByMessage = new Map<string, number>()
|
|
232
|
+
const propChangeFrequency = new Map<string, number>()
|
|
233
|
+
|
|
234
|
+
for (const entry of entries) {
|
|
235
|
+
// Count re-renders per message
|
|
236
|
+
const currentCount = rerendersByMessage.get(entry.messageId) || 0
|
|
237
|
+
rerendersByMessage.set(entry.messageId, currentCount + 1)
|
|
238
|
+
|
|
239
|
+
// Count how often each prop changes
|
|
240
|
+
for (const prop of entry.changedProps) {
|
|
241
|
+
const propCount = propChangeFrequency.get(prop) || 0
|
|
242
|
+
propChangeFrequency.set(prop, propCount + 1)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Find max re-renders for any single message
|
|
247
|
+
let maxRerenderPerMessage = 0
|
|
248
|
+
for (const count of rerendersByMessage.values()) {
|
|
249
|
+
if (count > maxRerenderPerMessage) {
|
|
250
|
+
maxRerenderPerMessage = count
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
totalRerenders: entries.length,
|
|
256
|
+
rerendersByMessage,
|
|
257
|
+
propChangeFrequency,
|
|
258
|
+
maxRerenderPerMessage,
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Clear the CLI debug log file
|
|
264
|
+
*/
|
|
265
|
+
export function clearCliDebugLog(logPath: string): void {
|
|
266
|
+
try {
|
|
267
|
+
fs.writeFileSync(logPath, '')
|
|
268
|
+
} catch {
|
|
269
|
+
// Ignore errors
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Proof of Concept: tmux-based CLI testing
|
|
5
|
+
*
|
|
6
|
+
* This script demonstrates how to:
|
|
7
|
+
* 1. Create a tmux session
|
|
8
|
+
* 2. Run the CLI in that session
|
|
9
|
+
* 3. Send commands to the CLI
|
|
10
|
+
* 4. Capture and verify output
|
|
11
|
+
* 5. Clean up the session
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn } from 'child_process'
|
|
15
|
+
|
|
16
|
+
import stripAnsi from 'strip-ansi'
|
|
17
|
+
|
|
18
|
+
import { isTmuxAvailable, sleep } from './test-utils'
|
|
19
|
+
|
|
20
|
+
// Utility to run tmux commands
|
|
21
|
+
function tmux(args: string[]): Promise<string> {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const proc = spawn('tmux', args, { stdio: 'pipe' })
|
|
24
|
+
let stdout = ''
|
|
25
|
+
let stderr = ''
|
|
26
|
+
|
|
27
|
+
proc.stdout?.on('data', (data) => {
|
|
28
|
+
stdout += data.toString()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
proc.stderr?.on('data', (data) => {
|
|
32
|
+
stderr += data.toString()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
proc.on('close', (code) => {
|
|
36
|
+
if (code === 0) {
|
|
37
|
+
resolve(stdout)
|
|
38
|
+
} else {
|
|
39
|
+
reject(new Error(`tmux command failed: ${stderr}`))
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Capture pane content
|
|
46
|
+
async function capturePane(sessionName: string): Promise<string> {
|
|
47
|
+
return await tmux(['capture-pane', '-t', sessionName, '-p'])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Main test function
|
|
51
|
+
async function testCLIWithTmux() {
|
|
52
|
+
const sessionName = 'anvil-test-' + Date.now()
|
|
53
|
+
|
|
54
|
+
console.log('🚀 Starting tmux-based CLI test...')
|
|
55
|
+
console.log(`📦 Session: ${sessionName}`)
|
|
56
|
+
|
|
57
|
+
// 1. Check if tmux is installed
|
|
58
|
+
if (!isTmuxAvailable()) {
|
|
59
|
+
console.error('❌ tmux not found')
|
|
60
|
+
console.error('\n📦 Installation:')
|
|
61
|
+
console.error(' macOS: brew install tmux')
|
|
62
|
+
console.error(' Ubuntu: sudo apt-get install tmux')
|
|
63
|
+
console.error(' Windows: Use WSL and run sudo apt-get install tmux')
|
|
64
|
+
console.error(
|
|
65
|
+
'\nℹ️ This is just a proof-of-concept. See the documentation for alternatives.',
|
|
66
|
+
)
|
|
67
|
+
process.exit(1)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const version = await tmux(['-V'])
|
|
72
|
+
console.log(`✅ tmux is installed: ${version.trim()}`)
|
|
73
|
+
|
|
74
|
+
// 2. Create new detached tmux session running the CLI
|
|
75
|
+
console.log('\n📺 Creating tmux session...')
|
|
76
|
+
await tmux([
|
|
77
|
+
'new-session',
|
|
78
|
+
'-d',
|
|
79
|
+
'-s',
|
|
80
|
+
sessionName,
|
|
81
|
+
'-x',
|
|
82
|
+
'120', // width
|
|
83
|
+
'-y',
|
|
84
|
+
'30', // height
|
|
85
|
+
'bun',
|
|
86
|
+
'run',
|
|
87
|
+
'src/entry.ts',
|
|
88
|
+
'--help',
|
|
89
|
+
])
|
|
90
|
+
console.log('✅ Session created')
|
|
91
|
+
|
|
92
|
+
// 3. Wait for CLI to start
|
|
93
|
+
await sleep(1000)
|
|
94
|
+
|
|
95
|
+
// 4. Capture initial output
|
|
96
|
+
console.log('\n📸 Capturing initial output...')
|
|
97
|
+
const initialOutput = await capturePane(sessionName)
|
|
98
|
+
const cleanOutput = stripAnsi(initialOutput)
|
|
99
|
+
|
|
100
|
+
console.log('\n--- Output ---')
|
|
101
|
+
console.log(cleanOutput)
|
|
102
|
+
console.log('--- End Output ---\n')
|
|
103
|
+
|
|
104
|
+
// 5. Verify output contains expected text
|
|
105
|
+
const checks = [
|
|
106
|
+
{ text: '--agent', pass: cleanOutput.includes('--agent') },
|
|
107
|
+
{ text: 'Usage:', pass: cleanOutput.includes('Usage:') },
|
|
108
|
+
{ text: '--help', pass: cleanOutput.includes('--help') },
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
console.log('🔍 Verification:')
|
|
112
|
+
checks.forEach(({ text, pass }) => {
|
|
113
|
+
console.log(
|
|
114
|
+
` ${pass ? '✅' : '❌'} Contains "${text}"${pass ? '' : ' - NOT FOUND'}`,
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const allPassed = checks.every((c) => c.pass)
|
|
119
|
+
console.log(
|
|
120
|
+
`\n${allPassed ? '🎉 All checks passed!' : '⚠️ Some checks failed'}`,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// 6. Example: Send interactive command (commented out for --help test)
|
|
124
|
+
/*
|
|
125
|
+
console.log('\n⌨️ Sending test command...')
|
|
126
|
+
await sendKeys(sessionName, 'hello world')
|
|
127
|
+
await sendKeys(sessionName, 'Enter')
|
|
128
|
+
await sleep(2000)
|
|
129
|
+
|
|
130
|
+
const responseOutput = await capturePane(sessionName)
|
|
131
|
+
console.log('\n--- Response ---')
|
|
132
|
+
console.log(stripAnsi(responseOutput))
|
|
133
|
+
console.log('--- End Response ---')
|
|
134
|
+
*/
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('\n❌ Test failed:', error)
|
|
137
|
+
} finally {
|
|
138
|
+
// 7. Cleanup: kill the tmux session
|
|
139
|
+
console.log('\n🧹 Cleaning up...')
|
|
140
|
+
try {
|
|
141
|
+
await tmux(['kill-session', '-t', sessionName])
|
|
142
|
+
console.log('✅ Session cleaned up')
|
|
143
|
+
} catch (e) {
|
|
144
|
+
console.log('⚠️ Session may have already exited')
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Run the test
|
|
150
|
+
testCLIWithTmux().catch(console.error)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildExpandedSegments,
|
|
5
|
+
resolveAgentModeClick,
|
|
6
|
+
OPEN_DELAY_MS,
|
|
7
|
+
CLOSE_DELAY_MS,
|
|
8
|
+
REOPEN_SUPPRESS_MS,
|
|
9
|
+
} from '../../components/agent-mode-toggle'
|
|
10
|
+
import { createHoverToggleControllerForTest } from '../mocks/hover-toggle-controller'
|
|
11
|
+
|
|
12
|
+
import type { AgentMode } from '../../utils/constants'
|
|
13
|
+
|
|
14
|
+
describe('AgentModeToggle - buildExpandedSegments', () => {
|
|
15
|
+
const modes: AgentMode[] = ['DEFAULT', 'MAX', 'PLAN']
|
|
16
|
+
|
|
17
|
+
for (const mode of modes) {
|
|
18
|
+
test(`returns segments with active indicator for ${mode}`, () => {
|
|
19
|
+
const segs = buildExpandedSegments(mode)
|
|
20
|
+
// 4 mode options (DEFAULT, LITE, MAX, PLAN) + 1 active indicator
|
|
21
|
+
expect(segs.length).toBe(5)
|
|
22
|
+
|
|
23
|
+
// Current mode is disabled among the choices
|
|
24
|
+
const current = segs.find((s) => s.id === mode)
|
|
25
|
+
expect(current?.disabled).toBe(true)
|
|
26
|
+
|
|
27
|
+
// Active indicator has expected id and flags
|
|
28
|
+
const active = segs.find((s) => s.id === `active-${mode}`)
|
|
29
|
+
expect(active).toBeTruthy()
|
|
30
|
+
expect(active?.isSelected).toBe(true)
|
|
31
|
+
expect(active?.defaultHighlighted).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('AgentModeToggle - resolveAgentModeClick', () => {
|
|
37
|
+
test('clicking active indicator returns closeActive', () => {
|
|
38
|
+
const action = resolveAgentModeClick('DEFAULT', 'active-DEFAULT', true)
|
|
39
|
+
expect(action).toEqual({ type: 'closeActive' })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('with onSelectMode provided, clicking different mode selects it', () => {
|
|
43
|
+
const action = resolveAgentModeClick('DEFAULT', 'MAX', true)
|
|
44
|
+
expect(action).toEqual({ type: 'selectMode', mode: 'MAX' })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('without onSelectMode, clicking different mode toggles', () => {
|
|
48
|
+
const action = resolveAgentModeClick('DEFAULT', 'PLAN', false)
|
|
49
|
+
expect(action).toEqual({ type: 'toggleMode', mode: 'PLAN' })
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Extended Date.now type with test helper method
|
|
54
|
+
interface MockDateNow {
|
|
55
|
+
(): number
|
|
56
|
+
set: (v: number) => void
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('useHoverToggle timing (controller)', () => {
|
|
60
|
+
let originalSetTimeout: typeof setTimeout
|
|
61
|
+
let originalClearTimeout: typeof clearTimeout
|
|
62
|
+
let originalNow: typeof Date.now
|
|
63
|
+
|
|
64
|
+
let timers: { id: number; ms: number; fn: () => void; active: boolean }[]
|
|
65
|
+
let nextId: number
|
|
66
|
+
|
|
67
|
+
const runAll = () => {
|
|
68
|
+
for (const t of timers) {
|
|
69
|
+
if (t.active) t.fn()
|
|
70
|
+
}
|
|
71
|
+
timers = []
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
timers = []
|
|
76
|
+
nextId = 1
|
|
77
|
+
originalSetTimeout = setTimeout
|
|
78
|
+
originalClearTimeout = clearTimeout
|
|
79
|
+
originalNow = Date.now
|
|
80
|
+
|
|
81
|
+
let now = 1_000
|
|
82
|
+
const mockDateNow: MockDateNow = Object.assign(
|
|
83
|
+
() => now,
|
|
84
|
+
{ set: (v: number) => { now = v } }
|
|
85
|
+
)
|
|
86
|
+
Date.now = mockDateNow
|
|
87
|
+
|
|
88
|
+
globalThis.setTimeout = ((fn: () => void, ms?: number) => {
|
|
89
|
+
const id = nextId++
|
|
90
|
+
timers.push({ id, ms: Number(ms ?? 0), fn, active: true })
|
|
91
|
+
return id as unknown as ReturnType<typeof setTimeout>
|
|
92
|
+
}) as typeof setTimeout
|
|
93
|
+
|
|
94
|
+
globalThis.clearTimeout = ((id?: ReturnType<typeof clearTimeout>) => {
|
|
95
|
+
const rec = timers.find((t) => t.id === (id as unknown as number))
|
|
96
|
+
if (rec) rec.active = false
|
|
97
|
+
}) as typeof clearTimeout
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
afterEach(() => {
|
|
101
|
+
globalThis.setTimeout = originalSetTimeout
|
|
102
|
+
globalThis.clearTimeout = originalClearTimeout
|
|
103
|
+
Date.now = originalNow
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('scheduleOpen waits OPEN_DELAY_MS then opens', () => {
|
|
107
|
+
const ctl = createHoverToggleControllerForTest()
|
|
108
|
+
expect(ctl.isOpen).toBe(false)
|
|
109
|
+
ctl.scheduleOpen()
|
|
110
|
+
expect(timers.length).toBe(1)
|
|
111
|
+
expect(timers[0].ms).toBe(OPEN_DELAY_MS)
|
|
112
|
+
runAll()
|
|
113
|
+
expect(ctl.isOpen).toBe(true)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('scheduleClose waits CLOSE_DELAY_MS then closes', () => {
|
|
117
|
+
const ctl = createHoverToggleControllerForTest()
|
|
118
|
+
ctl.openNow()
|
|
119
|
+
expect(ctl.isOpen).toBe(true)
|
|
120
|
+
ctl.scheduleClose()
|
|
121
|
+
expect(timers.length).toBe(1)
|
|
122
|
+
expect(timers[0].ms).toBe(CLOSE_DELAY_MS)
|
|
123
|
+
runAll()
|
|
124
|
+
expect(ctl.isOpen).toBe(false)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('closeNow(true) suppresses reopen until time passes', () => {
|
|
128
|
+
const ctl = createHoverToggleControllerForTest()
|
|
129
|
+
ctl.closeNow(true)
|
|
130
|
+
ctl.scheduleOpen()
|
|
131
|
+
expect(timers.length).toBe(0)
|
|
132
|
+
;(Date.now as MockDateNow).set(1_000 + REOPEN_SUPPRESS_MS + 1)
|
|
133
|
+
ctl.scheduleOpen()
|
|
134
|
+
expect(timers.length).toBe(1)
|
|
135
|
+
expect(timers[0].ms).toBe(OPEN_DELAY_MS)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { createMockTimers } from '@anvil/common/testing/mocks/timers'
|
|
2
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getCopyIconText,
|
|
6
|
+
copyButtonHandlers,
|
|
7
|
+
COPIED_RESET_DELAY_MS,
|
|
8
|
+
COPY_ICON_COLLAPSED,
|
|
9
|
+
COPY_ICON_EXPANDED,
|
|
10
|
+
COPY_ICON_COPIED,
|
|
11
|
+
} from '../../components/copy-button'
|
|
12
|
+
import { initializeThemeStore } from '../../hooks/use-theme'
|
|
13
|
+
|
|
14
|
+
import type { MockTimers } from '@anvil/common/testing/mocks/timers'
|
|
15
|
+
|
|
16
|
+
// Initialize theme before tests
|
|
17
|
+
initializeThemeStore()
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tests for CopyButton component logic.
|
|
21
|
+
*
|
|
22
|
+
* These tests use the exported utilities from copy-button.tsx:
|
|
23
|
+
* - getCopyIconText: determines what text to display
|
|
24
|
+
* - copyButtonHandlers: pure functions for state transitions
|
|
25
|
+
* - COPIED_RESET_DELAY_MS: the timeout constant
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
describe('CopyButton - CopyIcon text rendering', () => {
|
|
29
|
+
describe('with leadingSpace=true (default)', () => {
|
|
30
|
+
test('renders collapsed icon when not hovered or copied', () => {
|
|
31
|
+
const text = getCopyIconText(false, false, true)
|
|
32
|
+
expect(text).toBe(' ⎘')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('renders expanded text when hovered', () => {
|
|
36
|
+
const text = getCopyIconText(false, true, true)
|
|
37
|
+
expect(text).toBe(' [⎘ copy]')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('renders copied text when copied', () => {
|
|
41
|
+
const text = getCopyIconText(true, false, true)
|
|
42
|
+
expect(text).toBe(' [✔ copied]')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('renders copied text even when hovered (copied takes priority)', () => {
|
|
46
|
+
const text = getCopyIconText(true, true, true)
|
|
47
|
+
expect(text).toBe(' [✔ copied]')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('with leadingSpace=false', () => {
|
|
52
|
+
test('renders collapsed icon without leading space', () => {
|
|
53
|
+
const text = getCopyIconText(false, false, false)
|
|
54
|
+
expect(text).toBe('⎘')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('renders expanded text without leading space', () => {
|
|
58
|
+
const text = getCopyIconText(false, true, false)
|
|
59
|
+
expect(text).toBe('[⎘ copy]')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('renders copied text without leading space', () => {
|
|
63
|
+
const text = getCopyIconText(true, false, false)
|
|
64
|
+
expect(text).toBe('[✔ copied]')
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('CopyButton - copyButtonHandlers (from component)', () => {
|
|
70
|
+
describe('handleMouseOver', () => {
|
|
71
|
+
test('returns true (should hover) when not copied', () => {
|
|
72
|
+
expect(copyButtonHandlers.handleMouseOver(false)).toBe(true)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('returns false (block hover) when copied', () => {
|
|
76
|
+
expect(copyButtonHandlers.handleMouseOver(true)).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('handleMouseOut', () => {
|
|
81
|
+
test('always returns false to clear hover', () => {
|
|
82
|
+
expect(copyButtonHandlers.handleMouseOut()).toBe(false)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('handleCopy', () => {
|
|
87
|
+
test('returns copied=true and clears hover', () => {
|
|
88
|
+
const result = copyButtonHandlers.handleCopy()
|
|
89
|
+
expect(result).toEqual({ isCopied: true, isHovered: false })
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('CopyButton - exported constants', () => {
|
|
95
|
+
test('COPIED_RESET_DELAY_MS is 2000ms', () => {
|
|
96
|
+
expect(COPIED_RESET_DELAY_MS).toBe(2000)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('icon constants are defined', () => {
|
|
100
|
+
expect(COPY_ICON_COLLAPSED).toBe('⎘')
|
|
101
|
+
expect(COPY_ICON_EXPANDED).toBe('[⎘ copy]')
|
|
102
|
+
expect(COPY_ICON_COPIED).toBe('[✔ copied]')
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('CopyButton - copied state reset timing', () => {
|
|
107
|
+
let mockTimers: MockTimers
|
|
108
|
+
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
mockTimers = createMockTimers()
|
|
111
|
+
mockTimers.install()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
afterEach(() => {
|
|
115
|
+
mockTimers.restore()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('uses the exported COPIED_RESET_DELAY_MS constant (2000ms)', () => {
|
|
119
|
+
let isCopied = false
|
|
120
|
+
|
|
121
|
+
// Simulate handleCopy using the exported constant
|
|
122
|
+
const handleCopy = () => {
|
|
123
|
+
const newState = copyButtonHandlers.handleCopy()
|
|
124
|
+
isCopied = newState.isCopied
|
|
125
|
+
setTimeout(() => {
|
|
126
|
+
isCopied = false
|
|
127
|
+
}, COPIED_RESET_DELAY_MS)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
handleCopy()
|
|
131
|
+
expect(isCopied).toBe(true)
|
|
132
|
+
expect(mockTimers.getPendingCount()).toBe(1)
|
|
133
|
+
const nextTimer = mockTimers.getNext()
|
|
134
|
+
expect(nextTimer?.ms).toBe(COPIED_RESET_DELAY_MS)
|
|
135
|
+
|
|
136
|
+
mockTimers.runAll()
|
|
137
|
+
expect(isCopied).toBe(false)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('multiple rapid clicks only create one active timer', () => {
|
|
141
|
+
let currentTimerId: number | null = null
|
|
142
|
+
|
|
143
|
+
const handleCopy = () => {
|
|
144
|
+
if (currentTimerId !== null) {
|
|
145
|
+
clearTimeout(currentTimerId)
|
|
146
|
+
}
|
|
147
|
+
copyButtonHandlers.handleCopy()
|
|
148
|
+
currentTimerId = setTimeout(() => {}, COPIED_RESET_DELAY_MS) as unknown as number
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
handleCopy()
|
|
152
|
+
handleCopy()
|
|
153
|
+
handleCopy()
|
|
154
|
+
|
|
155
|
+
expect(mockTimers.getPendingCount()).toBe(1)
|
|
156
|
+
})
|
|
157
|
+
})
|