@vellumai/assistant 0.6.2 → 0.6.4
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/ARCHITECTURE.md +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +41 -49
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +1111 -86
- package/package.json +40 -42
- package/scripts/generate-openapi.ts +0 -2
- package/scripts/test.sh +73 -18
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +240 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +1000 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +1248 -224
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
- package/src/__tests__/conversation-agent-loop.test.ts +99 -3
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +290 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +12 -4
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +946 -62
- package/src/__tests__/conversation-routes-disk-view.test.ts +275 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +324 -46
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +6 -3
- package/src/__tests__/credential-vault-unit.test.ts +383 -7
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +42 -18
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +222 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +386 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +2 -0
- package/src/__tests__/gemini-provider.test.ts +66 -2
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +738 -359
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +528 -49
- package/src/__tests__/headless-browser-read-tools.test.ts +274 -100
- package/src/__tests__/headless-browser-snapshot.test.ts +250 -77
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +145 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +596 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +423 -0
- package/src/__tests__/host-cu-proxy.test.ts +166 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +67 -8
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +109 -7
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +18 -12
- package/src/__tests__/oauth-cli.test.ts +709 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +118 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +147 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +52 -14
- package/src/__tests__/oauth-store.test.ts +1465 -176
- package/src/__tests__/oauth2-gateway-transport.test.ts +460 -26
- package/src/__tests__/onboarding-template-contract.test.ts +81 -70
- package/src/__tests__/openai-provider.test.ts +178 -2
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +32 -3
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +343 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/require-fresh-approval.test.ts +40 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +8 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +801 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +738 -0
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +576 -16
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +184 -27
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +25 -5
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +14 -29
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +55 -9
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +69 -3
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +3 -4
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +273 -16
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +56 -44
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +32 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +330 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +117 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +6 -3
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +215 -36
- package/src/cli/commands/oauth/shared.ts +7 -6
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +254 -0
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +141 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +5 -2
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +54 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +9 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +38 -0
- package/src/config/env.ts +49 -4
- package/src/config/feature-flag-registry.json +85 -14
- package/src/config/loader.ts +82 -13
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +81 -15
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +112 -0
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +52 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -1
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +177 -0
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +61 -10
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +195 -0
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +23 -2
- package/src/daemon/conversation-agent-loop.ts +153 -42
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +622 -13
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +128 -36
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +131 -40
- package/src/daemon/conversation-tool-setup.ts +99 -8
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +292 -16
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +13 -141
- package/src/daemon/handlers/shared.ts +80 -0
- package/src/daemon/handlers/skills.ts +483 -44
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +192 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +179 -28
- package/src/daemon/message-protocol.ts +13 -0
- package/src/daemon/message-types/conversations.ts +89 -14
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +19 -5
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +26 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +439 -14
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +30 -20
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/mcp/client.ts +59 -24
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +31 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +122 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +18 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +176 -17
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +67 -40
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +137 -60
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +12 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/oauth.ts +21 -13
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +25 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +26 -9
- package/src/oauth/byo-connection.ts +10 -8
- package/src/oauth/connect-orchestrator.ts +25 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +22 -18
- package/src/oauth/connection.ts +3 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +223 -100
- package/src/oauth/platform-connection.test.ts +101 -3
- package/src/oauth/platform-connection.ts +56 -35
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +133 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +76 -38
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -60
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +10 -1
- package/src/runtime/AGENTS.md +65 -0
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +48 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +33 -9
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +368 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +815 -75
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +198 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +7 -0
- package/src/runtime/migrations/migration-wizard.ts +23 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +96 -1
- package/src/runtime/migrations/vbundle-importer.ts +89 -5
- package/src/runtime/pending-interactions.ts +18 -13
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +556 -0
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -141
- package/src/runtime/routes/conversation-management-routes.ts +223 -0
- package/src/runtime/routes/conversation-routes.ts +598 -103
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +268 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +262 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +122 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +31 -102
- package/src/runtime/routes/skills-routes.ts +128 -9
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +38 -9
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +57 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +68 -29
- package/src/security/secure-keys.ts +143 -27
- package/src/security/token-manager.ts +31 -10
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +554 -0
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +169 -40
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +1787 -342
- package/src/tools/browser/browser-manager.ts +81 -12
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1263 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +359 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1993 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +1007 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +744 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +868 -0
- package/src/tools/browser/cdp-client/errors.ts +49 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +148 -0
- package/src/tools/browser/cdp-client/factory.ts +914 -0
- package/src/tools/browser/cdp-client/index.ts +28 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +120 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/permission-checker.ts +77 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +15 -0
- package/src/tools/terminal/shell.ts +36 -20
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +19 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +63 -24
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +31 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/cli/commands/browser-relay.ts +0 -536
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -0,0 +1,993 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vellum backup` — manage automated backups, on-demand snapshots, restore, and verify.
|
|
3
|
+
*
|
|
4
|
+
* All subcommands run in-process (they do not call the daemon HTTP port).
|
|
5
|
+
* Config mutations go through `loadRawConfig` / `setNestedValue` / `saveRawConfig`
|
|
6
|
+
* so the on-disk `config.json` is the single source of truth and the daemon's
|
|
7
|
+
* config cache is invalidated via `saveRawConfig`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { stat } from "node:fs/promises";
|
|
11
|
+
import { dirname } from "node:path";
|
|
12
|
+
|
|
13
|
+
import type { Command } from "commander";
|
|
14
|
+
|
|
15
|
+
import { readBackupKey } from "../../backup/backup-key.js";
|
|
16
|
+
import { createSnapshotNow } from "../../backup/backup-worker.js";
|
|
17
|
+
import {
|
|
18
|
+
listSnapshotsInDir,
|
|
19
|
+
type SnapshotEntry,
|
|
20
|
+
} from "../../backup/list-snapshots.js";
|
|
21
|
+
import {
|
|
22
|
+
getBackupKeyPath,
|
|
23
|
+
getLocalBackupsDir,
|
|
24
|
+
resolveOffsiteDestinations,
|
|
25
|
+
} from "../../backup/paths.js";
|
|
26
|
+
import { restoreFromSnapshot, verifySnapshot } from "../../backup/restore.js";
|
|
27
|
+
import {
|
|
28
|
+
getConfig,
|
|
29
|
+
invalidateConfigCache,
|
|
30
|
+
loadRawConfig,
|
|
31
|
+
saveRawConfig,
|
|
32
|
+
setNestedValue,
|
|
33
|
+
} from "../../config/loader.js";
|
|
34
|
+
import type { BackupDestination } from "../../config/schema.js";
|
|
35
|
+
import { isDaemonRunning } from "../../daemon/daemon-control.js";
|
|
36
|
+
import { getMemoryCheckpoint } from "../../memory/checkpoints.js";
|
|
37
|
+
import { resetDb } from "../../memory/db-connection.js";
|
|
38
|
+
import { clearCache as clearTrustCache } from "../../permissions/trust-store.js";
|
|
39
|
+
import { DefaultPathResolver } from "../../runtime/migrations/vbundle-import-analyzer.js";
|
|
40
|
+
import {
|
|
41
|
+
getWorkspaceDir,
|
|
42
|
+
getWorkspaceHooksDir,
|
|
43
|
+
} from "../../util/platform.js";
|
|
44
|
+
import { log } from "../logger.js";
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Small formatting helpers
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/** Format a byte count as a human-readable string (B / KB / MB / GB). */
|
|
51
|
+
function formatBytes(bytes: number): string {
|
|
52
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
53
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
54
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
55
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
56
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Format a Date as `YYYY-MM-DD HH:MM UTC`. */
|
|
60
|
+
function formatDate(date: Date): string {
|
|
61
|
+
const y = date.getUTCFullYear().toString().padStart(4, "0");
|
|
62
|
+
const mo = (date.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
63
|
+
const d = date.getUTCDate().toString().padStart(2, "0");
|
|
64
|
+
const h = date.getUTCHours().toString().padStart(2, "0");
|
|
65
|
+
const mi = date.getUTCMinutes().toString().padStart(2, "0");
|
|
66
|
+
return `${y}-${mo}-${d} ${h}:${mi} UTC`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Format a duration (milliseconds) as a short human string: "3h 12m",
|
|
71
|
+
* "12m", "45s", or "just now".
|
|
72
|
+
*/
|
|
73
|
+
function formatDurationShort(ms: number): string {
|
|
74
|
+
if (ms < 0) ms = 0;
|
|
75
|
+
const seconds = Math.floor(ms / 1000);
|
|
76
|
+
if (seconds < 30) return "just now";
|
|
77
|
+
const minutes = Math.floor(seconds / 60);
|
|
78
|
+
if (minutes < 1) return `${seconds}s`;
|
|
79
|
+
const hours = Math.floor(minutes / 60);
|
|
80
|
+
const remMinutes = minutes - hours * 60;
|
|
81
|
+
if (hours < 1) return `${minutes}m`;
|
|
82
|
+
const days = Math.floor(hours / 24);
|
|
83
|
+
const remHours = hours - days * 24;
|
|
84
|
+
if (days < 1) return `${hours}h ${remMinutes}m`;
|
|
85
|
+
return `${days}d ${remHours}h`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Reachability probe
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check whether an offsite destination's parent directory exists. Mirrors the
|
|
94
|
+
* reachability check in `offsite-writer.ts` — if the parent is missing (e.g.
|
|
95
|
+
* iCloud Drive not enabled, external SSD unplugged) the destination is
|
|
96
|
+
* considered unreachable and we skip it at runtime.
|
|
97
|
+
*/
|
|
98
|
+
async function isDestinationReachable(destPath: string): Promise<boolean> {
|
|
99
|
+
try {
|
|
100
|
+
await stat(dirname(destPath));
|
|
101
|
+
return true;
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Exported handlers — exported so tests can drive them directly.
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
export interface EnableOptions {
|
|
112
|
+
interval?: string;
|
|
113
|
+
retention?: string;
|
|
114
|
+
offsite?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function handleEnable(opts: EnableOptions): void {
|
|
118
|
+
const raw = loadRawConfig();
|
|
119
|
+
setNestedValue(raw, "backup.enabled", true);
|
|
120
|
+
|
|
121
|
+
if (opts.interval !== undefined) {
|
|
122
|
+
const hours = Number.parseInt(opts.interval, 10);
|
|
123
|
+
if (!Number.isFinite(hours) || hours < 1) {
|
|
124
|
+
log.error(
|
|
125
|
+
`Invalid --interval "${opts.interval}". Must be a positive integer (hours). ` +
|
|
126
|
+
`Run 'vellum backup enable --help' for usage.`,
|
|
127
|
+
);
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
setNestedValue(raw, "backup.intervalHours", hours);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (opts.retention !== undefined) {
|
|
135
|
+
const count = Number.parseInt(opts.retention, 10);
|
|
136
|
+
if (!Number.isFinite(count) || count < 1) {
|
|
137
|
+
log.error(
|
|
138
|
+
`Invalid --retention "${opts.retention}". Must be a positive integer. ` +
|
|
139
|
+
`Run 'vellum backup enable --help' for usage.`,
|
|
140
|
+
);
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
setNestedValue(raw, "backup.retention", count);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// commander's `.option("--no-offsite", ...)` sets `opts.offsite = false`
|
|
148
|
+
// when the flag is present and leaves it `undefined` otherwise. Only a
|
|
149
|
+
// literal `false` flips the offsite switch — we never touch `destinations`.
|
|
150
|
+
if (opts.offsite === false) {
|
|
151
|
+
setNestedValue(raw, "backup.offsite.enabled", false);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
saveRawConfig(raw);
|
|
155
|
+
|
|
156
|
+
const cfg = getConfig().backup;
|
|
157
|
+
log.info(
|
|
158
|
+
`Automatic backups enabled (interval=${cfg.intervalHours}h, retention=${cfg.retention}, offsite=${cfg.offsite.enabled ? "on" : "off"})`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function handleDisable(): void {
|
|
163
|
+
const raw = loadRawConfig();
|
|
164
|
+
setNestedValue(raw, "backup.enabled", false);
|
|
165
|
+
saveRawConfig(raw);
|
|
166
|
+
log.info("Automatic backups disabled");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// destinations subgroup handlers
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Load the raw destinations array, materializing the iCloud default on first
|
|
175
|
+
* touch. Returns the array plus the raw config so callers can mutate and
|
|
176
|
+
* re-persist.
|
|
177
|
+
*
|
|
178
|
+
* When `backup.offsite.destinations` is `null` in config, the runtime uses the
|
|
179
|
+
* iCloud default — but that default is implicit. On first `add`/`remove`/
|
|
180
|
+
* `set-encrypt`, we need to make it explicit so subsequent mutations have
|
|
181
|
+
* something to mutate.
|
|
182
|
+
*/
|
|
183
|
+
function loadDestinationsForMutation(): {
|
|
184
|
+
raw: Record<string, unknown>;
|
|
185
|
+
destinations: BackupDestination[];
|
|
186
|
+
} {
|
|
187
|
+
const raw = loadRawConfig();
|
|
188
|
+
const current = getConfig().backup.offsite.destinations;
|
|
189
|
+
const destinations = resolveOffsiteDestinations(current);
|
|
190
|
+
return { raw, destinations };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function handleDestinationsList(): Promise<void> {
|
|
194
|
+
const cfg = getConfig().backup;
|
|
195
|
+
const destinations = resolveOffsiteDestinations(cfg.offsite.destinations);
|
|
196
|
+
|
|
197
|
+
if (destinations.length === 0) {
|
|
198
|
+
log.info("No offsite destinations configured");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const pathW = Math.max(
|
|
203
|
+
4,
|
|
204
|
+
...destinations.map((d) => d.path.length),
|
|
205
|
+
);
|
|
206
|
+
log.info(
|
|
207
|
+
"Path".padEnd(pathW) + " " + "Encrypted",
|
|
208
|
+
);
|
|
209
|
+
log.info("-".repeat(pathW + 2 + 9));
|
|
210
|
+
for (const d of destinations) {
|
|
211
|
+
log.info(d.path.padEnd(pathW) + " " + (d.encrypt ? "yes" : "no"));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface DestinationAddOptions {
|
|
216
|
+
plaintext?: boolean;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function handleDestinationsAdd(
|
|
220
|
+
path: string,
|
|
221
|
+
opts: DestinationAddOptions,
|
|
222
|
+
): void {
|
|
223
|
+
const { raw, destinations } = loadDestinationsForMutation();
|
|
224
|
+
|
|
225
|
+
if (destinations.some((d) => d.path === path)) {
|
|
226
|
+
log.error(
|
|
227
|
+
`Destination "${path}" already exists. Run 'vellum backup destinations list' to see configured destinations.`,
|
|
228
|
+
);
|
|
229
|
+
process.exitCode = 1;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const next: BackupDestination[] = [
|
|
234
|
+
...destinations,
|
|
235
|
+
{ path, encrypt: !opts.plaintext },
|
|
236
|
+
];
|
|
237
|
+
setNestedValue(raw, "backup.offsite.destinations", next);
|
|
238
|
+
saveRawConfig(raw);
|
|
239
|
+
log.info(
|
|
240
|
+
`Added destination ${path} (${opts.plaintext ? "plaintext" : "encrypted"})`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function handleDestinationsRemove(path: string): void {
|
|
245
|
+
const { raw, destinations } = loadDestinationsForMutation();
|
|
246
|
+
|
|
247
|
+
const filtered = destinations.filter((d) => d.path !== path);
|
|
248
|
+
if (filtered.length === destinations.length) {
|
|
249
|
+
log.error(
|
|
250
|
+
`Destination "${path}" not found. Run 'vellum backup destinations list' to see configured destinations.`,
|
|
251
|
+
);
|
|
252
|
+
process.exitCode = 1;
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
setNestedValue(raw, "backup.offsite.destinations", filtered);
|
|
257
|
+
saveRawConfig(raw);
|
|
258
|
+
log.info(`Removed destination ${path}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function handleDestinationsSetEncrypt(
|
|
262
|
+
path: string,
|
|
263
|
+
value: string,
|
|
264
|
+
): void {
|
|
265
|
+
const normalized = value.toLowerCase();
|
|
266
|
+
if (normalized !== "true" && normalized !== "false") {
|
|
267
|
+
log.error(
|
|
268
|
+
`Invalid encrypt value "${value}". Must be "true" or "false". ` +
|
|
269
|
+
`Run 'vellum backup destinations set-encrypt --help' for usage.`,
|
|
270
|
+
);
|
|
271
|
+
process.exitCode = 1;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const encrypt = normalized === "true";
|
|
275
|
+
|
|
276
|
+
const { raw, destinations } = loadDestinationsForMutation();
|
|
277
|
+
const idx = destinations.findIndex((d) => d.path === path);
|
|
278
|
+
if (idx === -1) {
|
|
279
|
+
log.error(
|
|
280
|
+
`Destination "${path}" not found. Run 'vellum backup destinations list' to see configured destinations.`,
|
|
281
|
+
);
|
|
282
|
+
process.exitCode = 1;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const next = destinations.map((d, i) =>
|
|
287
|
+
i === idx ? { ...d, encrypt } : d,
|
|
288
|
+
);
|
|
289
|
+
setNestedValue(raw, "backup.offsite.destinations", next);
|
|
290
|
+
saveRawConfig(raw);
|
|
291
|
+
log.info(
|
|
292
|
+
`Set ${path} encrypt=${encrypt ? "true" : "false"}`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
// status
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
export async function handleStatus(): Promise<void> {
|
|
301
|
+
const cfg = getConfig().backup;
|
|
302
|
+
|
|
303
|
+
log.info(
|
|
304
|
+
`Automatic backups: ${cfg.enabled ? "enabled" : "disabled"}`,
|
|
305
|
+
);
|
|
306
|
+
log.info(`Interval: every ${cfg.intervalHours}h`);
|
|
307
|
+
log.info(
|
|
308
|
+
`Retention: ${cfg.retention} snapshots per destination`,
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// Last / next run — both gated on a valid checkpoint. The daemon records
|
|
312
|
+
// `backup:last_run_at` as a unix-millis string.
|
|
313
|
+
const lastRunRaw = getMemoryCheckpoint("backup:last_run_at");
|
|
314
|
+
const lastRunMs = lastRunRaw ? Number.parseInt(lastRunRaw, 10) : NaN;
|
|
315
|
+
const now = Date.now();
|
|
316
|
+
if (!Number.isNaN(lastRunMs)) {
|
|
317
|
+
const lastRunDate = new Date(lastRunMs);
|
|
318
|
+
log.info(
|
|
319
|
+
`Last run: ${formatDate(lastRunDate)} (${formatDurationShort(now - lastRunMs)} ago)`,
|
|
320
|
+
);
|
|
321
|
+
if (cfg.enabled) {
|
|
322
|
+
const intervalMs = cfg.intervalHours * 3600 * 1000;
|
|
323
|
+
const nextMs = lastRunMs + intervalMs;
|
|
324
|
+
const delta = nextMs - now;
|
|
325
|
+
if (delta <= 0) {
|
|
326
|
+
log.info(`Next run: due now`);
|
|
327
|
+
} else {
|
|
328
|
+
log.info(`Next run: in ${formatDurationShort(delta)}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
log.info(`Last run: never`);
|
|
333
|
+
if (cfg.enabled) {
|
|
334
|
+
log.info(`Next run: on next tick`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Local directory line — include snapshot count so users can confirm the
|
|
339
|
+
// pool size matches retention.
|
|
340
|
+
const localDir = getLocalBackupsDir(cfg.localDirectory);
|
|
341
|
+
const localSnapshots = await listSnapshotsInDir(localDir);
|
|
342
|
+
log.info(
|
|
343
|
+
`Local directory: ${localDir} (${localSnapshots.length} snapshots)`,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Offsite destinations — resolve the iCloud default, probe reachability
|
|
347
|
+
// for each, and report snapshot counts.
|
|
348
|
+
log.info(`Offsite:`);
|
|
349
|
+
if (!cfg.offsite.enabled) {
|
|
350
|
+
log.info(` (disabled)`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const destinations = resolveOffsiteDestinations(cfg.offsite.destinations);
|
|
354
|
+
if (destinations.length === 0) {
|
|
355
|
+
log.info(` (no destinations configured)`);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
for (const dest of destinations) {
|
|
359
|
+
const reachable = await isDestinationReachable(dest.path);
|
|
360
|
+
const tag = reachable ? "[OK]" : "[unreachable]";
|
|
361
|
+
const enc = dest.encrypt ? "encrypted" : "plaintext";
|
|
362
|
+
const snapshots = reachable
|
|
363
|
+
? await listSnapshotsInDir(dest.path)
|
|
364
|
+
: [];
|
|
365
|
+
const suffix = reachable
|
|
366
|
+
? ""
|
|
367
|
+
: " -- parent directory not reachable";
|
|
368
|
+
log.info(
|
|
369
|
+
` ${tag} ${dest.path} (${enc}, ${snapshots.length} snapshots)${suffix}`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// list
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
/** Print a snapshot table for a group of entries. */
|
|
379
|
+
function printSnapshotGroup(
|
|
380
|
+
heading: string,
|
|
381
|
+
entries: SnapshotEntry[],
|
|
382
|
+
): void {
|
|
383
|
+
log.info(heading);
|
|
384
|
+
if (entries.length === 0) {
|
|
385
|
+
log.info(" (none)");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const tsW = 19;
|
|
389
|
+
const sizeW = 10;
|
|
390
|
+
const encW = 9;
|
|
391
|
+
log.info(
|
|
392
|
+
" " +
|
|
393
|
+
"Timestamp".padEnd(tsW) +
|
|
394
|
+
" " +
|
|
395
|
+
"Size".padEnd(sizeW) +
|
|
396
|
+
" " +
|
|
397
|
+
"Encrypted".padEnd(encW) +
|
|
398
|
+
" " +
|
|
399
|
+
"Filename",
|
|
400
|
+
);
|
|
401
|
+
for (const e of entries) {
|
|
402
|
+
log.info(
|
|
403
|
+
" " +
|
|
404
|
+
formatDate(e.createdAt).padEnd(tsW) +
|
|
405
|
+
" " +
|
|
406
|
+
formatBytes(e.sizeBytes).padEnd(sizeW) +
|
|
407
|
+
" " +
|
|
408
|
+
(e.encrypted ? "yes" : "no").padEnd(encW) +
|
|
409
|
+
" " +
|
|
410
|
+
e.filename,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export async function handleList(): Promise<void> {
|
|
416
|
+
const cfg = getConfig().backup;
|
|
417
|
+
const localDir = getLocalBackupsDir(cfg.localDirectory);
|
|
418
|
+
const localSnapshots = await listSnapshotsInDir(localDir);
|
|
419
|
+
printSnapshotGroup(`Local: ${localDir}`, localSnapshots);
|
|
420
|
+
|
|
421
|
+
if (!cfg.offsite.enabled) return;
|
|
422
|
+
const destinations = resolveOffsiteDestinations(cfg.offsite.destinations);
|
|
423
|
+
for (const dest of destinations) {
|
|
424
|
+
const entries = await listSnapshotsInDir(dest.path);
|
|
425
|
+
const tag = dest.encrypt ? "encrypted" : "plaintext";
|
|
426
|
+
log.info("");
|
|
427
|
+
printSnapshotGroup(`Offsite: ${dest.path} (${tag})`, entries);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
// create
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
export async function handleCreate(): Promise<void> {
|
|
436
|
+
const cfg = getConfig().backup;
|
|
437
|
+
try {
|
|
438
|
+
const result = await createSnapshotNow(cfg, new Date());
|
|
439
|
+
log.info(`Created snapshot: ${result.local.path}`);
|
|
440
|
+
log.info(` size: ${formatBytes(result.local.sizeBytes)}`);
|
|
441
|
+
log.info(` duration: ${result.durationMs}ms`);
|
|
442
|
+
if (result.offsite.length === 0) {
|
|
443
|
+
log.info(` offsite: (none)`);
|
|
444
|
+
} else {
|
|
445
|
+
log.info(` offsite:`);
|
|
446
|
+
for (const r of result.offsite) {
|
|
447
|
+
if (r.entry) {
|
|
448
|
+
log.info(
|
|
449
|
+
` ok ${r.destination.path} -> ${r.entry.filename}`,
|
|
450
|
+
);
|
|
451
|
+
} else if (r.skipped) {
|
|
452
|
+
log.info(
|
|
453
|
+
` skipped ${r.destination.path} (${r.skipped})`,
|
|
454
|
+
);
|
|
455
|
+
} else {
|
|
456
|
+
log.info(
|
|
457
|
+
` error ${r.destination.path} (${r.error ?? "unknown"})`,
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} catch (err) {
|
|
463
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
464
|
+
if (message.toLowerCase().includes("snapshot in progress")) {
|
|
465
|
+
log.error(
|
|
466
|
+
"Another snapshot is already running. Wait for it to finish, then retry.",
|
|
467
|
+
);
|
|
468
|
+
} else {
|
|
469
|
+
log.error(`Snapshot failed: ${message}`);
|
|
470
|
+
}
|
|
471
|
+
process.exitCode = 1;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
// restore / verify helpers
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
|
|
479
|
+
/** True when a snapshot path ends in `.vbundle.enc`. */
|
|
480
|
+
function isEncryptedPath(path: string): boolean {
|
|
481
|
+
return path.endsWith(".vbundle.enc");
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Load the backup key when the snapshot is encrypted. Throws a user-facing
|
|
486
|
+
* error when the key file is missing or corrupt.
|
|
487
|
+
*/
|
|
488
|
+
async function loadKeyForEncryptedSnapshot(
|
|
489
|
+
snapshotPath: string,
|
|
490
|
+
): Promise<Buffer | undefined> {
|
|
491
|
+
if (!isEncryptedPath(snapshotPath)) return undefined;
|
|
492
|
+
const keyPath = getBackupKeyPath();
|
|
493
|
+
const key = await readBackupKey(keyPath);
|
|
494
|
+
if (!key) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
`Encrypted snapshot requires backup key at ${keyPath}, but none was found. ` +
|
|
497
|
+
`The key is generated the first time automatic backup runs against an encrypted ` +
|
|
498
|
+
`destination.`,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
return key;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Prompt for y/N confirmation. Defaults to `false` on empty input, EOF, or
|
|
506
|
+
* anything other than `y` / `yes` (case-insensitive).
|
|
507
|
+
*/
|
|
508
|
+
async function promptConfirm(question: string): Promise<boolean> {
|
|
509
|
+
const readline = await import("node:readline");
|
|
510
|
+
const rl = readline.createInterface({
|
|
511
|
+
input: process.stdin,
|
|
512
|
+
output: process.stdout,
|
|
513
|
+
});
|
|
514
|
+
const answer = await new Promise<string>((resolve) => {
|
|
515
|
+
rl.question(question, resolve);
|
|
516
|
+
});
|
|
517
|
+
rl.close();
|
|
518
|
+
const normalized = answer.trim().toLowerCase();
|
|
519
|
+
return normalized === "y" || normalized === "yes";
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ---------------------------------------------------------------------------
|
|
523
|
+
// restore
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
|
|
526
|
+
export interface RestoreOptions {
|
|
527
|
+
path?: string;
|
|
528
|
+
latest?: boolean;
|
|
529
|
+
yes?: boolean;
|
|
530
|
+
force?: boolean;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export async function handleRestore(opts: RestoreOptions): Promise<void> {
|
|
534
|
+
if (!opts.path && !opts.latest) {
|
|
535
|
+
log.error(
|
|
536
|
+
"Must specify --path <snapshot> or --latest. " +
|
|
537
|
+
"Run 'vellum backup list' to see available snapshots.",
|
|
538
|
+
);
|
|
539
|
+
process.exitCode = 1;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (opts.path && opts.latest) {
|
|
543
|
+
log.error(
|
|
544
|
+
"Cannot combine --path and --latest. Drop one.",
|
|
545
|
+
);
|
|
546
|
+
process.exitCode = 1;
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Safety gate: a restore while the assistant is running is dangerous.
|
|
551
|
+
// The assistant holds an open SQLite handle (referencing the old inode on
|
|
552
|
+
// Unix), a cached config, and cached trust rules. Overwriting the files
|
|
553
|
+
// under a running process corrupts state. Refuse unless `--force` says the
|
|
554
|
+
// caller knows what they're doing.
|
|
555
|
+
if (!opts.force && isDaemonRunning()) {
|
|
556
|
+
log.error(
|
|
557
|
+
"Assistant is running — stop it first with 'vellum sleep' before restoring " +
|
|
558
|
+
"(safe restore requires an idle assistant). Pass --force to override.",
|
|
559
|
+
);
|
|
560
|
+
process.exitCode = 1;
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
let snapshotPath: string;
|
|
565
|
+
if (opts.path) {
|
|
566
|
+
snapshotPath = opts.path;
|
|
567
|
+
} else {
|
|
568
|
+
// `--latest` is explicitly scoped to local snapshots — offsite files may
|
|
569
|
+
// not exist after a machine swap (per the plan), so we keep the selection
|
|
570
|
+
// rule predictable.
|
|
571
|
+
const cfg = getConfig().backup;
|
|
572
|
+
const localDir = getLocalBackupsDir(cfg.localDirectory);
|
|
573
|
+
const entries = await listSnapshotsInDir(localDir);
|
|
574
|
+
if (entries.length === 0) {
|
|
575
|
+
log.error(
|
|
576
|
+
`No local snapshots found in ${localDir}. ` +
|
|
577
|
+
`Run 'vellum backup create' to make one, or pass --path with an explicit file.`,
|
|
578
|
+
);
|
|
579
|
+
process.exitCode = 1;
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
snapshotPath = entries[0]!.path;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (!opts.yes) {
|
|
586
|
+
const confirmed = await promptConfirm(
|
|
587
|
+
`Restore from ${snapshotPath}? This will overwrite workspace files. (y/N) `,
|
|
588
|
+
);
|
|
589
|
+
if (!confirmed) {
|
|
590
|
+
log.info("Restore cancelled");
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
let key: Buffer | undefined;
|
|
596
|
+
try {
|
|
597
|
+
key = await loadKeyForEncryptedSnapshot(snapshotPath);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
600
|
+
process.exitCode = 1;
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const workspaceDir = getWorkspaceDir();
|
|
606
|
+
const hooksDir = getWorkspaceHooksDir();
|
|
607
|
+
const pathResolver = new DefaultPathResolver(workspaceDir, hooksDir);
|
|
608
|
+
|
|
609
|
+
// Close the SQLite singleton before the bundle is written. If the
|
|
610
|
+
// assistant process was running in-process (tests, `--force`) the
|
|
611
|
+
// singleton may still reference the old file; resetting closes the
|
|
612
|
+
// handle so the restored DB file is picked up cleanly on the next
|
|
613
|
+
// getDb() call.
|
|
614
|
+
resetDb();
|
|
615
|
+
|
|
616
|
+
const result = await restoreFromSnapshot(snapshotPath, {
|
|
617
|
+
key,
|
|
618
|
+
pathResolver,
|
|
619
|
+
workspaceDir,
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Invalidate in-process caches so the restored settings.json and
|
|
623
|
+
// trust.json take effect (matches the HTTP handler's recovery sequence
|
|
624
|
+
// and the migration importer).
|
|
625
|
+
invalidateConfigCache();
|
|
626
|
+
clearTrustCache();
|
|
627
|
+
|
|
628
|
+
log.info(`Restored from ${snapshotPath}`);
|
|
629
|
+
log.info(` source: ${result.manifest.source ?? "unknown"}`);
|
|
630
|
+
log.info(` schema_version: ${result.manifest.schema_version}`);
|
|
631
|
+
log.info(` files restored: ${result.restoredFiles}`);
|
|
632
|
+
} catch (err) {
|
|
633
|
+
log.error(
|
|
634
|
+
`Restore failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
635
|
+
);
|
|
636
|
+
process.exitCode = 1;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// ---------------------------------------------------------------------------
|
|
641
|
+
// verify
|
|
642
|
+
// ---------------------------------------------------------------------------
|
|
643
|
+
|
|
644
|
+
export async function handleVerify(path: string): Promise<void> {
|
|
645
|
+
let key: Buffer | undefined;
|
|
646
|
+
try {
|
|
647
|
+
key = await loadKeyForEncryptedSnapshot(path);
|
|
648
|
+
} catch (err) {
|
|
649
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
650
|
+
process.exitCode = 1;
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
const result = await verifySnapshot(path, { key });
|
|
656
|
+
if (result.valid) {
|
|
657
|
+
log.info(`OK: ${path}`);
|
|
658
|
+
if (result.manifest) {
|
|
659
|
+
log.info(` schema_version: ${result.manifest.schema_version}`);
|
|
660
|
+
log.info(` source: ${result.manifest.source ?? "unknown"}`);
|
|
661
|
+
}
|
|
662
|
+
} else {
|
|
663
|
+
log.error(`Invalid: ${path}`);
|
|
664
|
+
if (result.error) log.error(` ${result.error}`);
|
|
665
|
+
process.exitCode = 1;
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
log.error(
|
|
669
|
+
`Verify failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
670
|
+
);
|
|
671
|
+
process.exitCode = 1;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ---------------------------------------------------------------------------
|
|
676
|
+
// Command wiring
|
|
677
|
+
// ---------------------------------------------------------------------------
|
|
678
|
+
|
|
679
|
+
export function registerBackupCommand(program: Command): void {
|
|
680
|
+
const backup = program
|
|
681
|
+
.command("backup")
|
|
682
|
+
.description(
|
|
683
|
+
"Manage automated backups, on-demand snapshots, restore, and verify",
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
backup.addHelpText(
|
|
687
|
+
"after",
|
|
688
|
+
`
|
|
689
|
+
Backups capture a snapshot of the assistant workspace (config, conversations,
|
|
690
|
+
trust rules, hooks, the SQLite database) as a .vbundle file. Credentials are
|
|
691
|
+
NOT included — they live in the OS keychain / CES and users re-authenticate
|
|
692
|
+
integrations after a restore. The automated worker runs on a configurable
|
|
693
|
+
interval and writes to a local pool under ~/.vellum/backups/local/, optionally
|
|
694
|
+
mirroring each snapshot to one or more offsite destinations (iCloud Drive by
|
|
695
|
+
default).
|
|
696
|
+
|
|
697
|
+
Offsite destinations can be per-destination encrypted (AES-256-GCM) or
|
|
698
|
+
plaintext — plaintext only makes sense when the user owns physical access to
|
|
699
|
+
the medium (e.g. an external SSD).
|
|
700
|
+
|
|
701
|
+
Examples:
|
|
702
|
+
$ vellum backup enable --interval 6 --retention 7
|
|
703
|
+
$ vellum backup destinations add /Volumes/BackupSSD/vellum --plaintext
|
|
704
|
+
$ vellum backup status
|
|
705
|
+
$ vellum backup list
|
|
706
|
+
$ vellum backup create
|
|
707
|
+
$ vellum backup restore --latest --yes
|
|
708
|
+
$ vellum backup verify ~/.vellum/backups/local/backup-20260411-093000.vbundle`,
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
backup
|
|
712
|
+
.command("enable")
|
|
713
|
+
.description("Enable automated backups")
|
|
714
|
+
.option(
|
|
715
|
+
"--interval <hours>",
|
|
716
|
+
"Hours between automated backups (1-168). Defaults to 6.",
|
|
717
|
+
)
|
|
718
|
+
.option(
|
|
719
|
+
"--retention <n>",
|
|
720
|
+
"Snapshots to retain per destination (1-100). Defaults to 7.",
|
|
721
|
+
)
|
|
722
|
+
.option(
|
|
723
|
+
"--no-offsite",
|
|
724
|
+
"Disable offsite backup (local only). Does not touch the destinations list.",
|
|
725
|
+
)
|
|
726
|
+
.addHelpText(
|
|
727
|
+
"after",
|
|
728
|
+
`
|
|
729
|
+
Sets backup.enabled = true in config.json. Optionally overrides intervalHours,
|
|
730
|
+
retention, and the offsite.enabled flag. Does NOT modify
|
|
731
|
+
backup.offsite.destinations — use 'vellum backup destinations add/remove' to
|
|
732
|
+
manage those.
|
|
733
|
+
|
|
734
|
+
Examples:
|
|
735
|
+
$ vellum backup enable
|
|
736
|
+
$ vellum backup enable --interval 12 --retention 14
|
|
737
|
+
$ vellum backup enable --no-offsite`,
|
|
738
|
+
)
|
|
739
|
+
.action((opts: EnableOptions) => {
|
|
740
|
+
handleEnable(opts);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
backup
|
|
744
|
+
.command("disable")
|
|
745
|
+
.description("Disable automated backups")
|
|
746
|
+
.addHelpText(
|
|
747
|
+
"after",
|
|
748
|
+
`
|
|
749
|
+
Sets backup.enabled = false in config.json. Existing snapshots are untouched;
|
|
750
|
+
only the automated worker stops creating new ones.
|
|
751
|
+
|
|
752
|
+
Examples:
|
|
753
|
+
$ vellum backup disable`,
|
|
754
|
+
)
|
|
755
|
+
.action(() => {
|
|
756
|
+
handleDisable();
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// ---------------------------------------------------------------------------
|
|
760
|
+
// destinations — subgroup
|
|
761
|
+
// ---------------------------------------------------------------------------
|
|
762
|
+
|
|
763
|
+
const destinations = backup
|
|
764
|
+
.command("destinations")
|
|
765
|
+
.description("Manage offsite backup destinations");
|
|
766
|
+
|
|
767
|
+
destinations.addHelpText(
|
|
768
|
+
"after",
|
|
769
|
+
`
|
|
770
|
+
Offsite destinations are absolute paths the backup worker writes a copy of
|
|
771
|
+
each snapshot to after the local write succeeds. The default destination is
|
|
772
|
+
the iCloud Drive VellumAssistant folder, and it is used implicitly until an
|
|
773
|
+
explicit destinations array is configured. The first 'destinations add' or
|
|
774
|
+
'destinations remove' materializes the iCloud default before applying the
|
|
775
|
+
change, so the default is never lost on an accidental "clear all".
|
|
776
|
+
|
|
777
|
+
Each destination has an 'encrypt' flag. When true (the default), snapshots
|
|
778
|
+
are written as .vbundle.enc (AES-256-GCM). When false, snapshots are copied
|
|
779
|
+
as plaintext .vbundle — only use this for media you control physically.
|
|
780
|
+
|
|
781
|
+
Examples:
|
|
782
|
+
$ vellum backup destinations list
|
|
783
|
+
$ vellum backup destinations add /Volumes/BackupSSD/vellum --plaintext
|
|
784
|
+
$ vellum backup destinations remove /Volumes/BackupSSD/vellum
|
|
785
|
+
$ vellum backup destinations set-encrypt /Volumes/BackupSSD/vellum false`,
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
destinations
|
|
789
|
+
.command("list")
|
|
790
|
+
.description("List configured offsite destinations")
|
|
791
|
+
.addHelpText(
|
|
792
|
+
"after",
|
|
793
|
+
`
|
|
794
|
+
Resolves the current destinations array (materializing the iCloud default if
|
|
795
|
+
no explicit array is configured) and prints a table with the path and
|
|
796
|
+
encryption flag per row.
|
|
797
|
+
|
|
798
|
+
Examples:
|
|
799
|
+
$ vellum backup destinations list`,
|
|
800
|
+
)
|
|
801
|
+
.action(async () => {
|
|
802
|
+
await handleDestinationsList();
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
destinations
|
|
806
|
+
.command("add <path>")
|
|
807
|
+
.description("Add an offsite backup destination")
|
|
808
|
+
.option(
|
|
809
|
+
"--plaintext",
|
|
810
|
+
"Write snapshots as plaintext .vbundle (default is AES-256-GCM encrypted .vbundle.enc)",
|
|
811
|
+
)
|
|
812
|
+
.addHelpText(
|
|
813
|
+
"after",
|
|
814
|
+
`
|
|
815
|
+
Arguments:
|
|
816
|
+
path Absolute path to the destination directory. Must be on a mount the
|
|
817
|
+
caller controls; the backup worker writes files inside this
|
|
818
|
+
directory, not the directory itself.
|
|
819
|
+
|
|
820
|
+
If backup.offsite.destinations is currently null (the implicit iCloud default),
|
|
821
|
+
the iCloud default is materialized first so the new entry appends to a
|
|
822
|
+
2-element array rather than replacing the default.
|
|
823
|
+
|
|
824
|
+
Examples:
|
|
825
|
+
$ vellum backup destinations add /Volumes/BackupSSD/vellum --plaintext
|
|
826
|
+
$ vellum backup destinations add ~/Dropbox/VellumAssistant/backups`,
|
|
827
|
+
)
|
|
828
|
+
.action((path: string, opts: DestinationAddOptions) => {
|
|
829
|
+
handleDestinationsAdd(path, opts);
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
destinations
|
|
833
|
+
.command("remove <path>")
|
|
834
|
+
.description("Remove an offsite backup destination by path")
|
|
835
|
+
.addHelpText(
|
|
836
|
+
"after",
|
|
837
|
+
`
|
|
838
|
+
Arguments:
|
|
839
|
+
path Exact path match of the destination to remove. Run
|
|
840
|
+
'vellum backup destinations list' to see configured paths.
|
|
841
|
+
|
|
842
|
+
Errors if no destination with the given path exists.
|
|
843
|
+
|
|
844
|
+
Examples:
|
|
845
|
+
$ vellum backup destinations remove /Volumes/BackupSSD/vellum`,
|
|
846
|
+
)
|
|
847
|
+
.action((path: string) => {
|
|
848
|
+
handleDestinationsRemove(path);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
destinations
|
|
852
|
+
.command("set-encrypt <path> <value>")
|
|
853
|
+
.description("Toggle encryption for an existing destination")
|
|
854
|
+
.addHelpText(
|
|
855
|
+
"after",
|
|
856
|
+
`
|
|
857
|
+
Arguments:
|
|
858
|
+
path Exact path match of an existing destination. Run
|
|
859
|
+
'vellum backup destinations list' to see configured paths.
|
|
860
|
+
value "true" to encrypt, "false" for plaintext writes.
|
|
861
|
+
|
|
862
|
+
Errors if no destination with the given path exists. Existing snapshot files
|
|
863
|
+
are not modified; only future writes honour the new setting.
|
|
864
|
+
|
|
865
|
+
Examples:
|
|
866
|
+
$ vellum backup destinations set-encrypt /Volumes/BackupSSD/vellum false
|
|
867
|
+
$ vellum backup destinations set-encrypt /Volumes/BackupSSD/vellum true`,
|
|
868
|
+
)
|
|
869
|
+
.action((path: string, value: string) => {
|
|
870
|
+
handleDestinationsSetEncrypt(path, value);
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
// ---------------------------------------------------------------------------
|
|
874
|
+
// status / list / create / restore / verify
|
|
875
|
+
// ---------------------------------------------------------------------------
|
|
876
|
+
|
|
877
|
+
backup
|
|
878
|
+
.command("status")
|
|
879
|
+
.description("Show backup status and next-run timing")
|
|
880
|
+
.addHelpText(
|
|
881
|
+
"after",
|
|
882
|
+
`
|
|
883
|
+
Reports enabled/disabled state, interval and retention, last-run and next-run
|
|
884
|
+
timing (from the backup:last_run_at memory checkpoint), and a per-destination
|
|
885
|
+
reachability probe. Unreachable destinations (parent directory missing, e.g.
|
|
886
|
+
iCloud Drive not enabled or external volume unplugged) are flagged
|
|
887
|
+
[unreachable] and skipped by the worker.
|
|
888
|
+
|
|
889
|
+
Examples:
|
|
890
|
+
$ vellum backup status`,
|
|
891
|
+
)
|
|
892
|
+
.action(async () => {
|
|
893
|
+
await handleStatus();
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
backup
|
|
897
|
+
.command("list")
|
|
898
|
+
.description("List all backup snapshots, grouped by destination")
|
|
899
|
+
.addHelpText(
|
|
900
|
+
"after",
|
|
901
|
+
`
|
|
902
|
+
Prints a per-destination table of snapshots with timestamp, size, and
|
|
903
|
+
encryption flag. Local destination is listed first, followed by each offsite
|
|
904
|
+
destination. Unreachable destinations are listed with an empty snapshot set.
|
|
905
|
+
|
|
906
|
+
Examples:
|
|
907
|
+
$ vellum backup list`,
|
|
908
|
+
)
|
|
909
|
+
.action(async () => {
|
|
910
|
+
await handleList();
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
backup
|
|
914
|
+
.command("create")
|
|
915
|
+
.description("Create a backup snapshot immediately (ignores interval)")
|
|
916
|
+
.addHelpText(
|
|
917
|
+
"after",
|
|
918
|
+
`
|
|
919
|
+
Triggers an on-demand snapshot. Bypasses the interval gate so it will run even
|
|
920
|
+
if the automated worker just ran, but still honours the concurrency mutex --
|
|
921
|
+
a second concurrent caller errors with "snapshot in progress". Does NOT update
|
|
922
|
+
the last-run checkpoint (manual snapshots should not reset the cadence).
|
|
923
|
+
|
|
924
|
+
Examples:
|
|
925
|
+
$ vellum backup create`,
|
|
926
|
+
)
|
|
927
|
+
.action(async () => {
|
|
928
|
+
await handleCreate();
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
backup
|
|
932
|
+
.command("restore")
|
|
933
|
+
.description("Restore a backup snapshot into the workspace")
|
|
934
|
+
.option(
|
|
935
|
+
"--path <path>",
|
|
936
|
+
"Absolute path to the .vbundle or .vbundle.enc file to restore",
|
|
937
|
+
)
|
|
938
|
+
.option(
|
|
939
|
+
"--latest",
|
|
940
|
+
"Restore the newest local snapshot (offsite files are not considered)",
|
|
941
|
+
)
|
|
942
|
+
.option("--yes", "Skip the confirmation prompt")
|
|
943
|
+
.option(
|
|
944
|
+
"--force",
|
|
945
|
+
"Restore even when the assistant is running (unsafe — only use if you know what you're doing)",
|
|
946
|
+
)
|
|
947
|
+
.addHelpText(
|
|
948
|
+
"after",
|
|
949
|
+
`
|
|
950
|
+
Restores a snapshot by writing its contents back into the workspace.
|
|
951
|
+
Encryption is auto-detected from the file extension; encrypted snapshots
|
|
952
|
+
(.vbundle.enc) require the backup key at ~/.vellum/protected/backup.key.
|
|
953
|
+
|
|
954
|
+
Prompts for confirmation unless --yes is passed.
|
|
955
|
+
|
|
956
|
+
--latest selects the newest local snapshot only. Offsite files may not exist
|
|
957
|
+
on a new machine after a workspace migration, so --latest refuses to dig into
|
|
958
|
+
them on purpose.
|
|
959
|
+
|
|
960
|
+
Safety: refuses to run while the assistant is running, because the live
|
|
961
|
+
SQLite handle and cached config/trust rules can corrupt the restored state.
|
|
962
|
+
Stop the assistant first with 'vellum sleep'. Pass --force to override (only
|
|
963
|
+
use this if you understand the risk).
|
|
964
|
+
|
|
965
|
+
Examples:
|
|
966
|
+
$ vellum backup restore --latest --yes
|
|
967
|
+
$ vellum backup restore --path ~/.vellum/backups/local/backup-20260411-093000.vbundle`,
|
|
968
|
+
)
|
|
969
|
+
.action(async (opts: RestoreOptions) => {
|
|
970
|
+
await handleRestore(opts);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
backup
|
|
974
|
+
.command("verify <path>")
|
|
975
|
+
.description("Verify a backup snapshot without restoring it")
|
|
976
|
+
.addHelpText(
|
|
977
|
+
"after",
|
|
978
|
+
`
|
|
979
|
+
Arguments:
|
|
980
|
+
path Absolute path to a .vbundle or .vbundle.enc snapshot file.
|
|
981
|
+
|
|
982
|
+
Runs the same validation the importer would run but never touches the
|
|
983
|
+
workspace. Encryption is auto-detected from the file extension; encrypted
|
|
984
|
+
snapshots require the backup key at ~/.vellum/protected/backup.key.
|
|
985
|
+
|
|
986
|
+
Examples:
|
|
987
|
+
$ vellum backup verify ~/.vellum/backups/local/backup-20260411-093000.vbundle
|
|
988
|
+
$ vellum backup verify /Volumes/BackupSSD/vellum/backup-20260411-093000.vbundle.enc`,
|
|
989
|
+
)
|
|
990
|
+
.action(async (path: string) => {
|
|
991
|
+
await handleVerify(path);
|
|
992
|
+
});
|
|
993
|
+
}
|