@vellumai/assistant 0.4.43 → 0.4.45

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.
Files changed (713) hide show
  1. package/.prettierignore +4 -0
  2. package/ARCHITECTURE.md +46 -44
  3. package/README.md +15 -16
  4. package/bun.lock +10 -35
  5. package/docs/architecture/integrations.md +102 -215
  6. package/docs/architecture/keychain-broker.md +1 -1
  7. package/docs/architecture/memory.md +2 -2
  8. package/docs/architecture/scheduling.md +1 -1
  9. package/docs/architecture/security.md +11 -11
  10. package/docs/error-handling.md +1 -1
  11. package/docs/trusted-contact-access.md +3 -3
  12. package/drizzle/meta/0000_snapshot.json +34 -100
  13. package/drizzle/meta/_journal.json +1 -1
  14. package/drizzle.config.ts +4 -4
  15. package/package.json +3 -2
  16. package/scripts/capture-x-graphql.ts +237 -141
  17. package/scripts/generate-bundled-tool-registry.ts +223 -0
  18. package/src/__tests__/access-request-decision.test.ts +0 -1
  19. package/src/__tests__/actor-token-service.test.ts +23 -24
  20. package/src/__tests__/agent-loop.test.ts +0 -131
  21. package/src/__tests__/always-loaded-tools-guard.test.ts +71 -0
  22. package/src/__tests__/amazon-cdp-integration.test.ts +11 -9
  23. package/src/__tests__/approval-primitive.test.ts +0 -1
  24. package/src/__tests__/approval-routes-http.test.ts +11 -3
  25. package/src/__tests__/asset-materialize-tool.test.ts +0 -1
  26. package/src/__tests__/asset-search-tool.test.ts +0 -1
  27. package/src/__tests__/assistant-attachment-directive.test.ts +1 -1
  28. package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
  29. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  30. package/src/__tests__/assistant-feature-flags-integration.test.ts +70 -18
  31. package/src/__tests__/assistant-id-boundary-guard.test.ts +6 -6
  32. package/src/__tests__/attachments-store.test.ts +0 -1
  33. package/src/__tests__/avatar-e2e.test.ts +74 -115
  34. package/src/__tests__/avatar-router.test.ts +25 -62
  35. package/src/__tests__/browser-manager.test.ts +24 -0
  36. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +4 -3
  37. package/src/__tests__/browser-skill-endstate.test.ts +8 -11
  38. package/src/__tests__/btw-routes.test.ts +326 -0
  39. package/src/__tests__/bundled-asset.test.ts +1 -1
  40. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +23 -9
  41. package/src/__tests__/call-controller.test.ts +0 -1
  42. package/src/__tests__/call-conversation-messages.test.ts +0 -1
  43. package/src/__tests__/call-domain.test.ts +0 -1
  44. package/src/__tests__/call-pointer-messages.test.ts +0 -1
  45. package/src/__tests__/call-recovery.test.ts +0 -1
  46. package/src/__tests__/call-routes-http.test.ts +0 -1
  47. package/src/__tests__/call-store.test.ts +0 -1
  48. package/src/__tests__/canonical-guardian-store.test.ts +0 -1
  49. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  50. package/src/__tests__/channel-approvals.test.ts +1 -1
  51. package/src/__tests__/channel-delivery-store.test.ts +0 -1
  52. package/src/__tests__/channel-guardian.test.ts +5 -7
  53. package/src/__tests__/channel-retry-sweep.test.ts +0 -1
  54. package/src/__tests__/checker.test.ts +32 -36
  55. package/src/__tests__/compaction.benchmark.test.ts +16 -14
  56. package/src/__tests__/computer-use-session-lifecycle.test.ts +10 -11
  57. package/src/__tests__/computer-use-session-working-dir.test.ts +2 -6
  58. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +2 -5
  59. package/src/__tests__/computer-use-tools.test.ts +35 -31
  60. package/src/__tests__/config-schema.test.ts +11 -15
  61. package/src/__tests__/config-watcher.test.ts +0 -1
  62. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  63. package/src/__tests__/conflict-store.test.ts +0 -1
  64. package/src/__tests__/connection-policy.test.ts +4 -7
  65. package/src/__tests__/contacts-tools.test.ts +0 -1
  66. package/src/__tests__/context-memory-e2e.test.ts +2 -4
  67. package/src/__tests__/context-overflow-reducer.test.ts +2 -4
  68. package/src/__tests__/context-window-manager.test.ts +147 -60
  69. package/src/__tests__/contradiction-checker.test.ts +0 -1
  70. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  71. package/src/__tests__/conversation-attention-telegram.test.ts +1 -1
  72. package/src/__tests__/conversation-pairing.test.ts +2 -2
  73. package/src/__tests__/conversation-routes-guardian-reply.test.ts +31 -7
  74. package/src/__tests__/conversation-routes-slash-commands.test.ts +381 -0
  75. package/src/__tests__/conversation-store.test.ts +0 -1
  76. package/src/__tests__/conversation-unread-route.test.ts +1 -2
  77. package/src/__tests__/credential-security-invariants.test.ts +8 -8
  78. package/src/__tests__/cross-provider-web-search.test.ts +353 -0
  79. package/src/__tests__/daemon-assistant-events.test.ts +6 -7
  80. package/src/__tests__/db-schedule-syntax-migration.test.ts +15 -3
  81. package/src/__tests__/delete-managed-skill-tool.test.ts +5 -9
  82. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
  83. package/src/__tests__/diagnostics-export.test.ts +189 -0
  84. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  85. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
  86. package/src/__tests__/entity-extractor.test.ts +0 -1
  87. package/src/__tests__/entity-search.test.ts +0 -1
  88. package/src/__tests__/ephemeral-permissions.test.ts +2 -4
  89. package/src/__tests__/error-handler-friendly-messages.test.ts +46 -0
  90. package/src/__tests__/file-read-tool.test.ts +86 -0
  91. package/src/__tests__/followup-tools.test.ts +0 -1
  92. package/src/__tests__/frontmatter.test.ts +77 -34
  93. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  94. package/src/__tests__/gateway-only-guard.test.ts +1 -1
  95. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -1
  96. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -1
  97. package/src/__tests__/guardian-action-followup-store.test.ts +0 -1
  98. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  99. package/src/__tests__/guardian-action-late-reply.test.ts +0 -1
  100. package/src/__tests__/guardian-action-store.test.ts +0 -1
  101. package/src/__tests__/guardian-action-sweep.test.ts +0 -1
  102. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  103. package/src/__tests__/guardian-dispatch.test.ts +1 -2
  104. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  105. package/src/__tests__/guardian-outbound-http.test.ts +0 -1
  106. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -1
  107. package/src/__tests__/guardian-routing-invariants.test.ts +1 -1
  108. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  109. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  110. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +3 -5
  111. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +28 -426
  112. package/src/__tests__/host-bash-proxy.test.ts +335 -0
  113. package/src/__tests__/host-file-proxy.test.ts +374 -0
  114. package/src/__tests__/host-shell-tool.test.ts +147 -1
  115. package/src/__tests__/http-user-message-parity.test.ts +361 -0
  116. package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
  117. package/src/__tests__/integration-status.test.ts +3 -8
  118. package/src/__tests__/intent-routing.test.ts +7 -46
  119. package/src/__tests__/invite-redemption-service.test.ts +0 -1
  120. package/src/__tests__/invite-routes-http.test.ts +0 -1
  121. package/src/__tests__/llm-usage-store.test.ts +0 -1
  122. package/src/__tests__/managed-avatar-client.test.ts +101 -55
  123. package/src/__tests__/managed-skill-lifecycle.test.ts +9 -18
  124. package/src/__tests__/managed-store.test.ts +94 -21
  125. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -1
  126. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +2 -4
  127. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -1
  128. package/src/__tests__/memory-recall-quality.test.ts +0 -1
  129. package/src/__tests__/memory-regressions.experimental.test.ts +0 -1
  130. package/src/__tests__/memory-regressions.test.ts +0 -1
  131. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -1
  132. package/src/__tests__/memory-upsert-concurrency.test.ts +0 -1
  133. package/src/__tests__/messaging-send-tool.test.ts +35 -0
  134. package/src/__tests__/messaging-skill-split.test.ts +138 -0
  135. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
  136. package/src/__tests__/migration-export-http.test.ts +2 -3
  137. package/src/__tests__/migration-import-commit-http.test.ts +1 -2
  138. package/src/__tests__/migration-import-preflight-http.test.ts +1 -2
  139. package/src/__tests__/migration-validate-http.test.ts +1 -2
  140. package/src/__tests__/native-web-search.test.ts +475 -0
  141. package/src/__tests__/navigate-settings-tab.test.ts +84 -0
  142. package/src/__tests__/non-member-access-request.test.ts +0 -1
  143. package/src/__tests__/notification-broadcaster.test.ts +15 -15
  144. package/src/__tests__/notification-decision-strategy.test.ts +6 -6
  145. package/src/__tests__/notification-deep-link.test.ts +7 -7
  146. package/src/__tests__/notification-guardian-path.test.ts +2 -3
  147. package/src/__tests__/notification-telegram-adapter.test.ts +1 -1
  148. package/src/__tests__/notification-thread-candidates.test.ts +4 -4
  149. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  150. package/src/__tests__/onboarding-template-contract.test.ts +0 -10
  151. package/src/__tests__/playbook-execution.test.ts +0 -1
  152. package/src/__tests__/playbook-tools.test.ts +0 -1
  153. package/src/__tests__/profile-compiler.test.ts +0 -1
  154. package/src/__tests__/provider-fail-open-selection.test.ts +12 -2
  155. package/src/__tests__/provider-managed-proxy-integration.test.ts +25 -0
  156. package/src/__tests__/qdrant-collection-migration.test.ts +223 -0
  157. package/src/__tests__/recording-handler.test.ts +30 -94
  158. package/src/__tests__/registry.test.ts +28 -35
  159. package/src/__tests__/relay-server.test.ts +0 -1
  160. package/src/__tests__/ride-shotgun-handler.test.ts +4 -20
  161. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  162. package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
  163. package/src/__tests__/runtime-events-sse.test.ts +0 -1
  164. package/src/__tests__/sandbox-diagnostics.test.ts +0 -1
  165. package/src/__tests__/scaffold-managed-skill-tool.test.ts +30 -28
  166. package/src/__tests__/schedule-store.test.ts +441 -1
  167. package/src/__tests__/schedule-tools.test.ts +468 -7
  168. package/src/__tests__/scheduler-recurrence.test.ts +196 -23
  169. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  170. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  171. package/src/__tests__/secret-prompt-log-hygiene.test.ts +6 -3
  172. package/src/__tests__/secret-response-routing.test.ts +4 -1
  173. package/src/__tests__/send-endpoint-busy.test.ts +14 -5
  174. package/src/__tests__/send-notification-tool.test.ts +0 -7
  175. package/src/__tests__/sequence-store.test.ts +0 -1
  176. package/src/__tests__/server-history-render.test.ts +1 -2
  177. package/src/__tests__/session-abort-tool-results.test.ts +0 -1
  178. package/src/__tests__/session-agent-loop.test.ts +46 -6
  179. package/src/__tests__/session-confirmation-signals.test.ts +7 -46
  180. package/src/__tests__/session-conflict-gate.test.ts +2 -6
  181. package/src/__tests__/session-error.test.ts +5 -14
  182. package/src/__tests__/session-init.benchmark.test.ts +3 -5
  183. package/src/__tests__/session-load-history-repair.test.ts +0 -1
  184. package/src/__tests__/session-media-retry.test.ts +12 -74
  185. package/src/__tests__/session-pre-run-repair.test.ts +0 -1
  186. package/src/__tests__/session-profile-injection.test.ts +2 -6
  187. package/src/__tests__/session-provider-retry-repair.test.ts +2 -6
  188. package/src/__tests__/session-queue.test.ts +94 -139
  189. package/src/__tests__/session-skill-tools.test.ts +115 -115
  190. package/src/__tests__/session-slash-known.test.ts +0 -1
  191. package/src/__tests__/session-slash-queue.test.ts +0 -1
  192. package/src/__tests__/session-slash-unknown.test.ts +0 -1
  193. package/src/__tests__/session-surfaces-task-progress.test.ts +34 -0
  194. package/src/__tests__/session-usage.test.ts +0 -1
  195. package/src/__tests__/session-workspace-cache-state.test.ts +2 -6
  196. package/src/__tests__/session-workspace-injection.test.ts +2 -6
  197. package/src/__tests__/session-workspace-tool-tracking.test.ts +2 -6
  198. package/src/__tests__/skill-feature-flags-integration.test.ts +180 -184
  199. package/src/__tests__/skill-feature-flags.test.ts +125 -18
  200. package/src/__tests__/skill-load-feature-flag.test.ts +1 -2
  201. package/src/__tests__/skill-load-tool.test.ts +194 -2
  202. package/src/__tests__/skill-projection-feature-flag.test.ts +27 -16
  203. package/src/__tests__/skill-projection.benchmark.test.ts +15 -14
  204. package/src/__tests__/skills.test.ts +14 -53
  205. package/src/__tests__/slack-channel-config.test.ts +0 -1
  206. package/src/__tests__/slack-inbound-verification.test.ts +0 -1
  207. package/src/__tests__/slack-skill.test.ts +1 -1
  208. package/src/__tests__/starter-task-flow.test.ts +9 -19
  209. package/src/__tests__/subagent-tools.test.ts +2 -2
  210. package/src/__tests__/system-prompt.test.ts +7 -7
  211. package/src/__tests__/task-compiler.test.ts +0 -1
  212. package/src/__tests__/task-management-tools.test.ts +0 -1
  213. package/src/__tests__/task-memory-cleanup.test.ts +0 -1
  214. package/src/__tests__/task-runner.test.ts +0 -1
  215. package/src/__tests__/task-scheduler.test.ts +0 -1
  216. package/src/__tests__/terminal-tools.test.ts +0 -1
  217. package/src/__tests__/test-support/computer-use-skill-harness.ts +2 -4
  218. package/src/__tests__/thread-seed-composer.test.ts +5 -5
  219. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  220. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  221. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  222. package/src/__tests__/tool-executor.test.ts +8 -86
  223. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  224. package/src/__tests__/tool-notification-listener.test.ts +1 -1
  225. package/src/__tests__/tool-preview-lifecycle.test.ts +416 -0
  226. package/src/__tests__/trust-store.test.ts +84 -8
  227. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  228. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  229. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -1
  230. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  231. package/src/__tests__/trusted-contact-verification.test.ts +0 -1
  232. package/src/__tests__/twilio-provider.test.ts +0 -1
  233. package/src/__tests__/twilio-routes.test.ts +0 -1
  234. package/src/__tests__/{request-file-tool.test.ts → ui-file-upload-surface.test.ts} +11 -72
  235. package/src/__tests__/update-bulletin.test.ts +0 -1
  236. package/src/__tests__/usage-cache-backfill-migration.test.ts +0 -1
  237. package/src/__tests__/usage-routes.test.ts +0 -1
  238. package/src/__tests__/verification-control-plane-policy.test.ts +4 -4
  239. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  240. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  241. package/src/__tests__/voice-session-bridge.test.ts +9 -1
  242. package/src/__tests__/web-fetch.test.ts +57 -0
  243. package/src/__tests__/workspace-git-service.test.ts +5 -14
  244. package/src/__tests__/workspace-policy.test.ts +0 -1
  245. package/src/agent/loop.ts +22 -34
  246. package/src/bundler/bundle-signer.ts +4 -4
  247. package/src/calls/call-controller.ts +1 -1
  248. package/src/calls/relay-server.ts +1 -1
  249. package/src/calls/twilio-rest.ts +1 -1
  250. package/src/calls/voice-session-bridge.ts +3 -1
  251. package/src/cli/__tests__/notifications.test.ts +3 -4
  252. package/src/cli/commands/map.ts +2 -6
  253. package/src/cli/commands/mcp.ts +73 -15
  254. package/src/cli/commands/notifications.ts +4 -4
  255. package/src/cli/commands/sessions.ts +9 -1
  256. package/src/cli/commands/skills.ts +6 -10
  257. package/src/cli/http-client.ts +2 -3
  258. package/src/cli/main-screen.tsx +10 -10
  259. package/src/cli/program.ts +0 -4
  260. package/src/cli/reference.ts +0 -2
  261. package/src/cli.ts +15 -9
  262. package/src/config/__tests__/bundled-tool-registry-guard.test.ts +120 -0
  263. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +11 -0
  264. package/src/config/bundled-skills/app-builder/SKILL.md +6 -7
  265. package/src/config/bundled-skills/app-builder/TOOLS.json +0 -4
  266. package/src/config/bundled-skills/browser/SKILL.md +6 -1
  267. package/src/config/bundled-skills/chatgpt-import/SKILL.md +5 -1
  268. package/src/config/bundled-skills/claude-code/SKILL.md +5 -1
  269. package/src/config/bundled-skills/computer-use/SKILL.md +6 -1
  270. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -69
  271. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +10 -1
  272. package/src/config/bundled-skills/contacts/SKILL.md +10 -1
  273. package/src/config/bundled-skills/contacts/TOOLS.json +35 -0
  274. package/src/config/bundled-skills/{messaging → contacts}/tools/google-contacts.ts +9 -2
  275. package/src/config/bundled-skills/document/SKILL.md +4 -1
  276. package/src/config/bundled-skills/doordash/SKILL.md +8 -2
  277. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +1 -82
  278. package/src/config/bundled-skills/doordash/doordash-cli.ts +17 -28
  279. package/src/config/bundled-skills/doordash/lib/session.ts +21 -17
  280. package/src/config/bundled-skills/doordash/lib/shared/platform.ts +4 -1
  281. package/src/config/bundled-skills/followups/SKILL.md +4 -1
  282. package/src/config/bundled-skills/gmail/SKILL.md +180 -0
  283. package/src/config/bundled-skills/gmail/TOOLS.json +506 -0
  284. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +149 -0
  285. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +110 -0
  286. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-draft.ts +1 -1
  287. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-filters.ts +1 -1
  288. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-follow-up.ts +1 -1
  289. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-forward.ts +1 -1
  290. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +50 -0
  291. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-outreach-scan.ts +8 -90
  292. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-send-draft.ts +1 -1
  293. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-sender-digest.ts +2 -2
  294. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-trash.ts +1 -1
  295. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-unsubscribe.ts +1 -1
  296. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-vacation.ts +1 -1
  297. package/src/config/bundled-skills/gmail/tools/shared.ts +47 -0
  298. package/src/config/bundled-skills/google-calendar/SKILL.md +5 -1
  299. package/src/config/bundled-skills/image-studio/SKILL.md +5 -1
  300. package/src/config/bundled-skills/knowledge-graph/SKILL.md +4 -1
  301. package/src/config/bundled-skills/media-processing/SKILL.md +7 -13
  302. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -22
  303. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +12 -1
  304. package/src/config/bundled-skills/messaging/SKILL.md +23 -139
  305. package/src/config/bundled-skills/messaging/TOOLS.json +33 -1215
  306. package/src/config/bundled-skills/messaging/tools/gmail-mime-helpers.ts +42 -0
  307. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +165 -2
  308. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +1 -13
  309. package/src/config/bundled-skills/messaging/tools/shared.ts +81 -34
  310. package/src/config/bundled-skills/notifications/SKILL.md +5 -1
  311. package/src/config/bundled-skills/orchestration/SKILL.md +30 -0
  312. package/src/config/bundled-skills/orchestration/TOOLS.json +35 -0
  313. package/src/config/bundled-skills/{reminder/tools/reminder-cancel.ts → orchestration/tools/swarm-delegate.ts} +3 -3
  314. package/src/config/bundled-skills/phone-calls/SKILL.md +9 -1
  315. package/src/config/bundled-skills/playbooks/SKILL.md +4 -1
  316. package/src/config/bundled-skills/schedule/SKILL.md +70 -9
  317. package/src/config/bundled-skills/schedule/TOOLS.json +38 -6
  318. package/src/config/bundled-skills/screen-watch/SKILL.md +28 -0
  319. package/src/config/bundled-skills/screen-watch/TOOLS.json +35 -0
  320. package/src/config/bundled-skills/{reminder/tools/reminder-create.ts → screen-watch/tools/start-screen-watch.ts} +3 -3
  321. package/src/config/bundled-skills/sequences/SKILL.md +47 -0
  322. package/src/config/bundled-skills/sequences/TOOLS.json +340 -0
  323. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +128 -0
  324. package/src/config/bundled-skills/sequences/tools/shared.ts +9 -0
  325. package/src/config/bundled-skills/settings/SKILL.md +12 -0
  326. package/src/config/bundled-skills/settings/TOOLS.json +112 -0
  327. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +43 -0
  328. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +52 -0
  329. package/src/config/bundled-skills/{computer-use/tools/computer-use-right-click.ts → settings/tools/set-avatar.ts} +2 -6
  330. package/src/{tools/system/voice-config.ts → config/bundled-skills/settings/tools/voice-config-update.ts} +59 -96
  331. package/src/config/bundled-skills/skill-management/SKILL.md +18 -0
  332. package/src/config/bundled-skills/skill-management/TOOLS.json +90 -0
  333. package/src/config/bundled-skills/{computer-use/tools/computer-use-double-click.ts → skill-management/tools/delete-managed.ts} +2 -6
  334. package/src/config/bundled-skills/skill-management/tools/scaffold-managed.ts +12 -0
  335. package/src/config/bundled-skills/slack/SKILL.md +5 -1
  336. package/src/config/bundled-skills/subagent/SKILL.md +4 -1
  337. package/src/config/bundled-skills/tasks/SKILL.md +5 -2
  338. package/src/config/bundled-skills/transcribe/SKILL.md +4 -1
  339. package/src/config/bundled-skills/watcher/SKILL.md +4 -1
  340. package/src/config/bundled-tool-registry.ts +118 -107
  341. package/src/config/env.ts +5 -2
  342. package/src/config/feature-flag-registry.json +33 -9
  343. package/src/config/loader.ts +10 -2
  344. package/src/config/schema.ts +19 -16
  345. package/src/config/schemas/inference.ts +12 -22
  346. package/src/config/schemas/memory-storage.ts +19 -1
  347. package/src/config/schemas/platform.ts +0 -16
  348. package/src/config/skill-state.ts +11 -8
  349. package/src/config/skills.ts +83 -32
  350. package/src/context/token-estimator.ts +11 -0
  351. package/src/context/window-manager.ts +180 -151
  352. package/src/daemon/computer-use-session.ts +11 -43
  353. package/src/daemon/daemon-control.ts +4 -1
  354. package/src/daemon/handlers/config-channels.ts +5 -9
  355. package/src/daemon/handlers/config-ingress.ts +0 -4
  356. package/src/daemon/handlers/config-model.ts +7 -13
  357. package/src/daemon/handlers/config-telegram.ts +4 -8
  358. package/src/daemon/handlers/config-voice.ts +2 -5
  359. package/src/daemon/handlers/dictation.ts +2 -12
  360. package/src/daemon/handlers/identity.ts +0 -105
  361. package/src/daemon/handlers/recording.ts +3 -23
  362. package/src/daemon/handlers/session-history.ts +42 -10
  363. package/src/daemon/handlers/sessions.ts +53 -72
  364. package/src/daemon/handlers/shared.ts +7 -28
  365. package/src/daemon/handlers/skills.ts +31 -27
  366. package/src/daemon/host-bash-proxy.ts +148 -0
  367. package/src/daemon/host-file-proxy.ts +135 -0
  368. package/src/daemon/lifecycle.ts +53 -41
  369. package/src/daemon/mcp-reload-service.ts +123 -0
  370. package/src/daemon/message-protocol.ts +6 -0
  371. package/src/daemon/message-types/apps.ts +0 -25
  372. package/src/daemon/message-types/browser.ts +1 -1
  373. package/src/daemon/message-types/computer-use.ts +1 -4
  374. package/src/daemon/message-types/guardian-actions.ts +1 -1
  375. package/src/daemon/message-types/host-bash.ts +18 -0
  376. package/src/daemon/message-types/host-file.ts +44 -0
  377. package/src/daemon/message-types/integrations.ts +1 -73
  378. package/src/daemon/message-types/messages.ts +15 -0
  379. package/src/daemon/message-types/schedules.ts +11 -27
  380. package/src/daemon/message-types/sessions.ts +8 -2
  381. package/src/daemon/message-types/settings.ts +1 -1
  382. package/src/daemon/message-types/shared.ts +1 -1
  383. package/src/daemon/message-types/surfaces.ts +2 -0
  384. package/src/daemon/ride-shotgun-handler.ts +35 -43
  385. package/src/daemon/seed-files.ts +3 -27
  386. package/src/daemon/server.ts +45 -28
  387. package/src/daemon/session-agent-loop-handlers.ts +72 -9
  388. package/src/daemon/session-agent-loop.ts +97 -66
  389. package/src/daemon/session-attachments.ts +1 -1
  390. package/src/daemon/session-error.ts +17 -16
  391. package/src/daemon/session-lifecycle.ts +20 -1
  392. package/src/daemon/session-media-retry.ts +1 -15
  393. package/src/daemon/session-messaging.ts +14 -6
  394. package/src/daemon/session-process.ts +36 -7
  395. package/src/daemon/session-queue-manager.ts +62 -103
  396. package/src/daemon/session-runtime-assembly.ts +27 -7
  397. package/src/daemon/session-skill-tools.ts +12 -11
  398. package/src/daemon/session-slash.ts +7 -0
  399. package/src/daemon/session-surfaces.ts +192 -118
  400. package/src/daemon/session-tool-setup.ts +146 -6
  401. package/src/daemon/session.ts +75 -37
  402. package/src/errors.ts +0 -2
  403. package/src/export/formatter.ts +6 -0
  404. package/src/mcp/mcp-oauth-provider.ts +1 -3
  405. package/src/media/avatar-router.ts +20 -28
  406. package/src/media/avatar-types.ts +7 -14
  407. package/src/media/managed-avatar-client.ts +70 -34
  408. package/src/memory/app-store.ts +0 -18
  409. package/src/memory/conversation-title-service.ts +1 -2
  410. package/src/memory/db-init.ts +16 -0
  411. package/src/memory/embedding-backend.ts +129 -27
  412. package/src/memory/embedding-gemini.test.ts +256 -0
  413. package/src/memory/embedding-gemini.ts +47 -13
  414. package/src/memory/embedding-local.ts +14 -2
  415. package/src/memory/embedding-ollama.ts +15 -2
  416. package/src/memory/embedding-openai.ts +15 -2
  417. package/src/memory/embedding-types.test.ts +116 -0
  418. package/src/memory/embedding-types.ts +61 -0
  419. package/src/memory/fingerprint.ts +1 -1
  420. package/src/memory/indexer.ts +25 -1
  421. package/src/memory/job-handlers/embedding.test.ts +258 -0
  422. package/src/memory/job-handlers/embedding.ts +81 -1
  423. package/src/memory/job-handlers/index-maintenance.ts +35 -1
  424. package/src/memory/job-handlers/media-processing.ts +11 -1
  425. package/src/memory/job-utils.ts +21 -6
  426. package/src/memory/jobs-store.ts +5 -1
  427. package/src/memory/jobs-worker.ts +8 -0
  428. package/src/memory/message-content.ts +66 -0
  429. package/src/memory/migrations/100-core-tables.ts +1 -31
  430. package/src/memory/migrations/104-core-indexes.ts +0 -11
  431. package/src/memory/migrations/145-drop-accounts-table.ts +19 -0
  432. package/src/memory/migrations/146-schedule-oneshot-routing.ts +94 -0
  433. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +129 -0
  434. package/src/memory/migrations/148-drop-reminders-table.ts +18 -0
  435. package/src/memory/migrations/index.ts +4 -0
  436. package/src/memory/migrations/registry.ts +19 -0
  437. package/src/memory/qdrant-client.ts +158 -43
  438. package/src/memory/retriever.test.ts +0 -1
  439. package/src/memory/retriever.ts +12 -2
  440. package/src/memory/schema/infrastructure.ts +5 -37
  441. package/src/memory/search/formatting.ts +34 -9
  442. package/src/memory/search/semantic.ts +57 -2
  443. package/src/memory/search/types.ts +2 -1
  444. package/src/notifications/AGENTS.md +2 -2
  445. package/src/notifications/README.md +59 -58
  446. package/src/notifications/adapters/macos.ts +1 -1
  447. package/src/notifications/broadcaster.ts +5 -5
  448. package/src/notifications/copy-composer.ts +1 -1
  449. package/src/notifications/decision-engine.ts +2 -2
  450. package/src/notifications/destination-resolver.ts +2 -2
  451. package/src/notifications/emit-signal.ts +8 -8
  452. package/src/notifications/signal.ts +1 -1
  453. package/src/notifications/thread-seed-composer.ts +1 -1
  454. package/src/oauth/connect-orchestrator.ts +1 -1
  455. package/src/oauth/token-persistence.ts +1 -1
  456. package/src/permissions/checker.ts +12 -1
  457. package/src/permissions/defaults.ts +13 -17
  458. package/src/permissions/trust-store.ts +37 -0
  459. package/src/permissions/workspace-policy.ts +0 -1
  460. package/src/prompts/__tests__/build-cli-reference-section.test.ts +11 -0
  461. package/src/prompts/computer-use-prompt.ts +1 -1
  462. package/src/prompts/system-prompt.ts +33 -35
  463. package/src/prompts/templates/BOOTSTRAP.md +0 -3
  464. package/src/prompts/templates/SOUL.md +1 -2
  465. package/src/prompts/templates/UPDATES.md +16 -7
  466. package/src/providers/anthropic/client.ts +87 -33
  467. package/src/providers/gemini/client.ts +6 -0
  468. package/src/providers/managed-proxy/constants.ts +5 -0
  469. package/src/providers/openai/client.ts +15 -0
  470. package/src/providers/registry.ts +4 -6
  471. package/src/providers/types.ts +24 -2
  472. package/src/runtime/AGENTS.md +18 -0
  473. package/src/runtime/assistant-event-hub.ts +2 -3
  474. package/src/runtime/assistant-event.ts +4 -4
  475. package/src/runtime/auth/__tests__/context.test.ts +5 -5
  476. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  477. package/src/runtime/auth/__tests__/guard-tests.test.ts +3 -2
  478. package/src/runtime/auth/__tests__/{ipc-auth-context.test.ts → local-auth-context.test.ts} +21 -21
  479. package/src/runtime/auth/__tests__/route-policy.test.ts +2 -2
  480. package/src/runtime/auth/__tests__/scopes.test.ts +9 -8
  481. package/src/runtime/auth/__tests__/subject.test.ts +8 -8
  482. package/src/runtime/auth/__tests__/token-service.test.ts +0 -1
  483. package/src/runtime/auth/route-policy.ts +8 -8
  484. package/src/runtime/auth/scopes.ts +2 -1
  485. package/src/runtime/auth/subject.ts +4 -4
  486. package/src/runtime/auth/token-service.ts +1 -24
  487. package/src/runtime/auth/types.ts +3 -3
  488. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  489. package/src/runtime/guardian-action-grant-minter.ts +1 -1
  490. package/src/runtime/guardian-action-service.ts +3 -3
  491. package/src/runtime/http-server.ts +15 -2
  492. package/src/runtime/http-types.ts +10 -0
  493. package/src/runtime/invite-service.ts +3 -3
  494. package/src/runtime/local-actor-identity.ts +17 -22
  495. package/src/runtime/middleware/error-handler.ts +14 -1
  496. package/src/runtime/pending-interactions.ts +21 -9
  497. package/src/runtime/routes/app-management-routes.ts +63 -67
  498. package/src/runtime/routes/approval-routes.ts +1 -3
  499. package/src/runtime/routes/brain-graph/brain-graph.html +1845 -0
  500. package/src/runtime/routes/brain-graph-routes.ts +4 -42
  501. package/src/runtime/routes/btw-routes.ts +155 -0
  502. package/src/runtime/routes/computer-use-routes.ts +77 -31
  503. package/src/runtime/routes/conversation-routes.ts +234 -47
  504. package/src/runtime/routes/diagnostics-routes.ts +154 -43
  505. package/src/runtime/routes/documents-routes.ts +2 -2
  506. package/src/runtime/routes/global-search-routes.ts +1 -1
  507. package/src/runtime/routes/host-bash-routes.ts +83 -0
  508. package/src/runtime/routes/host-file-routes.ts +79 -0
  509. package/src/runtime/routes/integrations/slack/share.ts +1 -1
  510. package/src/runtime/routes/log-export-routes.ts +120 -0
  511. package/src/runtime/routes/mcp-routes.ts +20 -0
  512. package/src/runtime/routes/migration-routes.ts +3 -3
  513. package/src/runtime/routes/pairing-routes.ts +1 -1
  514. package/src/runtime/routes/recording-routes.ts +6 -4
  515. package/src/runtime/routes/schedule-routes.ts +31 -5
  516. package/src/runtime/routes/session-management-routes.ts +2 -6
  517. package/src/runtime/routes/session-query-routes.ts +18 -15
  518. package/src/runtime/routes/settings-routes.ts +7 -351
  519. package/src/runtime/routes/skills-routes.ts +7 -6
  520. package/src/runtime/routes/subagents-routes.ts +4 -10
  521. package/src/runtime/routes/surface-action-routes.ts +3 -14
  522. package/src/runtime/routes/surface-content-routes.ts +22 -5
  523. package/src/runtime/routes/work-items-routes.ts +21 -25
  524. package/src/runtime/routes/workspace-routes.test.ts +3 -3
  525. package/src/runtime/routes/workspace-utils.ts +1 -1
  526. package/src/runtime/telegram-streaming-delivery.ts +3 -0
  527. package/src/runtime/verification-outbound-actions.ts +2 -2
  528. package/src/schedule/integration-status.ts +0 -6
  529. package/src/schedule/schedule-store.ts +234 -43
  530. package/src/schedule/scheduler.ts +73 -74
  531. package/src/security/oauth2.ts +1 -1
  532. package/src/sequence/store.ts +12 -2
  533. package/src/skills/frontmatter.ts +19 -77
  534. package/src/skills/managed-store.ts +11 -2
  535. package/src/subagent/manager.ts +5 -3
  536. package/src/tasks/ephemeral-permissions.ts +3 -5
  537. package/src/tools/AGENTS.md +37 -0
  538. package/src/tools/apps/executors.ts +0 -6
  539. package/src/tools/browser/browser-manager.ts +17 -11
  540. package/src/tools/browser/jit-auth.ts +4 -1
  541. package/src/tools/claude-code/claude-code.ts +1 -1
  542. package/src/tools/computer-use/definitions.ts +48 -60
  543. package/src/tools/document/document-tool.ts +6 -6
  544. package/src/tools/document/editor-template.ts +10 -8
  545. package/src/tools/filesystem/edit.ts +2 -1
  546. package/src/tools/filesystem/read.ts +20 -2
  547. package/src/tools/filesystem/write.ts +2 -1
  548. package/src/tools/host-filesystem/edit.ts +17 -1
  549. package/src/tools/host-filesystem/read.ts +16 -1
  550. package/src/tools/host-filesystem/write.ts +15 -1
  551. package/src/tools/host-terminal/host-shell.ts +24 -0
  552. package/src/tools/memory/definitions.ts +45 -81
  553. package/src/tools/memory/handlers.test.ts +0 -1
  554. package/src/tools/memory/handlers.ts +1 -1
  555. package/src/tools/memory/register.ts +26 -60
  556. package/src/tools/network/script-proxy/session-manager.ts +6 -8
  557. package/src/tools/network/web-fetch.ts +7 -1
  558. package/src/tools/network/web-search.ts +2 -1
  559. package/src/tools/registry.ts +23 -0
  560. package/src/tools/schedule/create.ts +113 -5
  561. package/src/tools/schedule/list.ts +57 -15
  562. package/src/tools/schedule/update.ts +73 -3
  563. package/src/tools/shared/filesystem/image-read.ts +192 -0
  564. package/src/tools/side-effects.ts +1 -7
  565. package/src/tools/skills/delete-managed.ts +27 -64
  566. package/src/tools/skills/execute.ts +54 -0
  567. package/src/tools/skills/load.ts +127 -5
  568. package/src/tools/skills/scaffold-managed.ts +93 -172
  569. package/src/tools/subagent/message.ts +0 -7
  570. package/src/tools/subagent/spawn.ts +1 -1
  571. package/src/tools/swarm/delegate.ts +0 -3
  572. package/src/tools/system/avatar-generator.ts +13 -19
  573. package/src/tools/system/request-permission.ts +2 -1
  574. package/src/tools/terminal/safe-env.ts +1 -0
  575. package/src/tools/tool-manifest.ts +41 -47
  576. package/src/tools/types.ts +6 -2
  577. package/src/tools/ui-surface/definitions.ts +0 -55
  578. package/src/util/errors.ts +12 -10
  579. package/src/workspace/git-service.ts +0 -2
  580. package/src/__tests__/account-registry.test.ts +0 -258
  581. package/src/__tests__/email-classifier.test.ts +0 -25
  582. package/src/__tests__/gmail-integration.test.ts +0 -97
  583. package/src/__tests__/handle-user-message-secret-resume.test.ts +0 -172
  584. package/src/__tests__/home-base-bootstrap.test.ts +0 -84
  585. package/src/__tests__/managed-twitter-guardrails.test.ts +0 -353
  586. package/src/__tests__/prebuilt-home-base-seed.test.ts +0 -79
  587. package/src/__tests__/recording-intent-fallback.test.ts +0 -199
  588. package/src/__tests__/recording-intent.test.ts +0 -985
  589. package/src/__tests__/recording-state-machine.test.ts +0 -1574
  590. package/src/__tests__/reminder-store.test.ts +0 -350
  591. package/src/__tests__/reminder.test.ts +0 -337
  592. package/src/__tests__/scan-result-store.test.ts +0 -121
  593. package/src/__tests__/twitter-platform-proxy-client.test.ts +0 -450
  594. package/src/__tests__/view-image-tool.test.ts +0 -241
  595. package/src/cli/commands/amazon/cart.ts +0 -513
  596. package/src/cli/commands/amazon/checkout.ts +0 -394
  597. package/src/cli/commands/amazon/client.ts +0 -513
  598. package/src/cli/commands/amazon/index.ts +0 -920
  599. package/src/cli/commands/amazon/product-details.ts +0 -145
  600. package/src/cli/commands/amazon/request-extractor.ts +0 -187
  601. package/src/cli/commands/amazon/search.ts +0 -76
  602. package/src/cli/commands/amazon/session.ts +0 -116
  603. package/src/cli/commands/twitter/__tests__/cli-error-shaping.test.ts +0 -265
  604. package/src/cli/commands/twitter/__tests__/cli-read-routing.test.ts +0 -483
  605. package/src/cli/commands/twitter/__tests__/cli-routing.test.ts +0 -412
  606. package/src/cli/commands/twitter/__tests__/oauth-client.test.ts +0 -197
  607. package/src/cli/commands/twitter/client.ts +0 -989
  608. package/src/cli/commands/twitter/index.ts +0 -1160
  609. package/src/cli/commands/twitter/oauth-client.ts +0 -94
  610. package/src/cli/commands/twitter/router.ts +0 -396
  611. package/src/cli/commands/twitter/session.ts +0 -121
  612. package/src/config/bundled-skills/agentmail/SKILL.md +0 -132
  613. package/src/config/bundled-skills/agentmail/icon.svg +0 -21
  614. package/src/config/bundled-skills/amazon/SKILL.md +0 -137
  615. package/src/config/bundled-skills/amazon/icon.svg +0 -13
  616. package/src/config/bundled-skills/api-mapping/SKILL.md +0 -78
  617. package/src/config/bundled-skills/api-mapping/icon.svg +0 -18
  618. package/src/config/bundled-skills/cli-discover/SKILL.md +0 -68
  619. package/src/config/bundled-skills/deploy-fullstack-vercel/SKILL.md +0 -179
  620. package/src/config/bundled-skills/document-writer/SKILL.md +0 -195
  621. package/src/config/bundled-skills/elevenlabs-voice/SKILL.md +0 -140
  622. package/src/config/bundled-skills/email-setup/SKILL.md +0 -68
  623. package/src/config/bundled-skills/frontend-design/SKILL.md +0 -44
  624. package/src/config/bundled-skills/frontend-design/icon.svg +0 -16
  625. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +0 -452
  626. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +0 -203
  627. package/src/config/bundled-skills/influencer/SKILL.md +0 -144
  628. package/src/config/bundled-skills/influencer/scripts/client.ts +0 -1269
  629. package/src/config/bundled-skills/influencer/scripts/influencer.ts +0 -267
  630. package/src/config/bundled-skills/macos-automation/SKILL.md +0 -65
  631. package/src/config/bundled-skills/macos-automation/icon.svg +0 -12
  632. package/src/config/bundled-skills/mcp-setup/SKILL.md +0 -75
  633. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +0 -184
  634. package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +0 -80
  635. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +0 -29
  636. package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +0 -56
  637. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +0 -34
  638. package/src/config/bundled-skills/messaging/tools/gmail-download-attachment.ts +0 -47
  639. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +0 -31
  640. package/src/config/bundled-skills/messaging/tools/gmail-list-attachments.ts +0 -67
  641. package/src/config/bundled-skills/messaging/tools/gmail-send-with-attachments.ts +0 -97
  642. package/src/config/bundled-skills/messaging/tools/gmail-summarize-thread.ts +0 -87
  643. package/src/config/bundled-skills/messaging/tools/gmail-triage.ts +0 -135
  644. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +0 -24
  645. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +0 -201
  646. package/src/config/bundled-skills/messaging/tools/send-notification.ts +0 -1
  647. package/src/config/bundled-skills/messaging/tools/sequence-cancel.ts +0 -27
  648. package/src/config/bundled-skills/messaging/tools/sequence-pause.ts +0 -48
  649. package/src/config/bundled-skills/messaging/tools/sequence-resume.ts +0 -27
  650. package/src/config/bundled-skills/messaging/tools/sequence-update.ts +0 -56
  651. package/src/config/bundled-skills/notion/SKILL.md +0 -240
  652. package/src/config/bundled-skills/notion-oauth-setup/SKILL.md +0 -126
  653. package/src/config/bundled-skills/oauth-setup/SKILL.md +0 -143
  654. package/src/config/bundled-skills/public-ingress/SKILL.md +0 -258
  655. package/src/config/bundled-skills/reminder/SKILL.md +0 -79
  656. package/src/config/bundled-skills/reminder/TOOLS.json +0 -89
  657. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +0 -12
  658. package/src/config/bundled-skills/restaurant-reservation/SKILL.md +0 -141
  659. package/src/config/bundled-skills/screen-recording/SKILL.md +0 -148
  660. package/src/config/bundled-skills/self-upgrade/SKILL.md +0 -69
  661. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -78
  662. package/src/config/bundled-skills/slack-app-setup/SKILL.md +0 -178
  663. package/src/config/bundled-skills/slack-digest-setup/SKILL.md +0 -163
  664. package/src/config/bundled-skills/slack-oauth-setup/SKILL.md +0 -157
  665. package/src/config/bundled-skills/start-the-day/SKILL.md +0 -70
  666. package/src/config/bundled-skills/start-the-day/icon.svg +0 -13
  667. package/src/config/bundled-skills/telegram-setup/SKILL.md +0 -105
  668. package/src/config/bundled-skills/time-based-actions/SKILL.md +0 -142
  669. package/src/config/bundled-skills/twilio-setup/SKILL.md +0 -232
  670. package/src/config/bundled-skills/twitter/SKILL.md +0 -319
  671. package/src/config/bundled-skills/twitter/icon.svg +0 -14
  672. package/src/config/bundled-skills/typescript-eval/SKILL.md +0 -60
  673. package/src/config/bundled-skills/vercel-token-setup/SKILL.md +0 -214
  674. package/src/config/bundled-skills/voice-setup/SKILL.md +0 -131
  675. package/src/config/bundled-skills/voice-setup/icon.svg +0 -20
  676. package/src/daemon/handlers/pairing.ts +0 -119
  677. package/src/daemon/handlers/session-user-message.ts +0 -961
  678. package/src/daemon/recording-executor.ts +0 -180
  679. package/src/daemon/recording-intent-fallback.ts +0 -162
  680. package/src/daemon/recording-intent.ts +0 -493
  681. package/src/home-base/app-link-store.ts +0 -78
  682. package/src/home-base/bootstrap.ts +0 -74
  683. package/src/home-base/prebuilt/brain-graph.html +0 -1483
  684. package/src/home-base/prebuilt/index.html +0 -702
  685. package/src/home-base/prebuilt/seed-metadata.json +0 -21
  686. package/src/home-base/prebuilt/seed.ts +0 -122
  687. package/src/home-base/prebuilt-home-base-updater.ts +0 -36
  688. package/src/memory/account-store.ts +0 -117
  689. package/src/messaging/activity-analyzer.ts +0 -76
  690. package/src/messaging/email-classifier.ts +0 -208
  691. package/src/messaging/index.ts +0 -2
  692. package/src/messaging/outreach-classifier.ts +0 -185
  693. package/src/messaging/thread-summarizer.ts +0 -346
  694. package/src/messaging/types.ts +0 -17
  695. package/src/tools/browser/x-auto-navigate.ts +0 -254
  696. package/src/tools/credentials/account-registry.ts +0 -144
  697. package/src/tools/filesystem/view-image.ts +0 -244
  698. package/src/tools/reminder/reminder-store.ts +0 -194
  699. package/src/tools/reminder/reminder.ts +0 -158
  700. package/src/tools/system/navigate-settings.ts +0 -74
  701. package/src/tools/system/open-system-settings.ts +0 -85
  702. package/src/tools/system/version.ts +0 -54
  703. package/src/twitter/platform-proxy-client.ts +0 -405
  704. package/src/util/cookie-session.ts +0 -98
  705. /package/src/config/bundled-skills/{messaging → gmail}/tools/scan-result-store.ts +0 -0
  706. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-analytics.ts +0 -0
  707. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-create.ts +0 -0
  708. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-delete.ts +0 -0
  709. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-enroll.ts +0 -0
  710. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-enrollment-list.ts +0 -0
  711. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-get.ts +0 -0
  712. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-import.ts +0 -0
  713. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-list.ts +0 -0
@@ -1,1269 +0,0 @@
1
- /**
2
- * Influencer Research Client — Standalone Version
3
- *
4
- * This is a self-contained version of the influencer research client that
5
- * communicates with the Chrome extension relay via the `assistant browser
6
- * chrome relay` CLI subprocess instead of importing internal assistant modules.
7
- *
8
- * ARCHITECTURE
9
- * ============
10
- * All scraping runs inside Chrome browser tabs via the extension relay. The
11
- * relay's evaluate command uses CDP Runtime.evaluate (via chrome.debugger API)
12
- * as a fallback, which bypasses strict CSP on sites like Instagram.
13
- *
14
- * The user must be logged into Instagram, TikTok, and/or X in their Chrome
15
- * browser for this to work.
16
- *
17
- * INSTAGRAM DISCOVERY FLOW
18
- * ========================
19
- * Instagram's search at /explore/search/keyword/?q=... returns a grid of POSTS
20
- * (not profiles). To discover influencers:
21
- * 1. Search by keyword → get grid of post links (/p/ and /reel/)
22
- * 2. Visit each post → extract the author username from page text
23
- * 3. Deduplicate usernames
24
- * 4. Visit each unique profile → scrape stats from meta[name="description"]
25
- * which reliably contains "49K Followers, 463 Following, 551 Posts - ..."
26
- * 5. Filter by criteria and rank
27
- *
28
- * TIKTOK DISCOVERY FLOW
29
- * =====================
30
- * TikTok has a dedicated user search at /search/user?q=... which returns
31
- * profile cards directly with follower counts and bios.
32
- *
33
- * X/TWITTER DISCOVERY FLOW
34
- * ========================
35
- * X has a people search at /search?q=...&f=user which returns UserCell
36
- * components with profile data.
37
- *
38
- * EVALUATE SCRIPTS
39
- * ================
40
- * All scripts passed to evalInTab() are wrapped in (function(){ ... })() by
41
- * the relay's CDP Runtime.evaluate. Use `return` to return values. Results
42
- * should be JSON strings for complex data.
43
- *
44
- * LIMITATIONS
45
- * ===========
46
- * - Requires the user to be logged in on each platform in Chrome
47
- * - Rate limiting may apply; built-in delays of 1.5-3s between navigations
48
- * - Platform HTML structures change frequently; selectors may need updates
49
- * - The chrome.debugger API shows a yellow infobar on the tab being debugged
50
- */
51
-
52
- import { execFile, spawn } from "node:child_process";
53
- import { promisify } from "node:util";
54
-
55
- const execFileAsync = promisify(execFile);
56
-
57
- // ---------------------------------------------------------------------------
58
- // Types
59
- // ---------------------------------------------------------------------------
60
-
61
- interface RelayResponse {
62
- ok: boolean;
63
- tabId?: number;
64
- result?: unknown;
65
- error?: string;
66
- }
67
-
68
- export interface InfluencerSearchCriteria {
69
- /** Keywords, niche, or topic to search for */
70
- query: string;
71
- /** Platforms to search on */
72
- platforms?: ("instagram" | "tiktok" | "twitter")[];
73
- /** Minimum follower count */
74
- minFollowers?: number;
75
- /** Maximum follower count */
76
- maxFollowers?: number;
77
- /** Maximum number of results per platform */
78
- limit?: number;
79
- /** Language/locale filter */
80
- language?: string;
81
- /** Look for verified accounts only */
82
- verifiedOnly?: boolean;
83
- }
84
-
85
- export interface InfluencerProfile {
86
- /** Platform the profile was found on */
87
- platform: "instagram" | "tiktok" | "twitter";
88
- /** Username/handle */
89
- username: string;
90
- /** Display name */
91
- displayName: string;
92
- /** Profile URL */
93
- profileUrl: string;
94
- /** Bio/description */
95
- bio: string;
96
- /** Follower count (numeric) */
97
- followers: number | undefined;
98
- /** Follower count (display string, e.g. "1.2M") */
99
- followersDisplay: string;
100
- /** Following count */
101
- following: number | undefined;
102
- /** Post/video count */
103
- postCount: number | undefined;
104
- /** Whether the account is verified */
105
- isVerified: boolean;
106
- /** Profile picture URL */
107
- avatarUrl: string | undefined;
108
- /** Engagement rate estimate (if available) */
109
- engagementRate: number | undefined;
110
- /** Average likes per post (if available from recent posts) */
111
- avgLikes: number | undefined;
112
- /** Average comments per post (if available from recent posts) */
113
- avgComments: number | undefined;
114
- /** Content categories/themes detected from bio and recent posts */
115
- contentThemes: string[];
116
- /** Recent post captions/snippets for context */
117
- recentPosts: { text: string; likes?: number; comments?: number }[];
118
- /** Raw score for ranking */
119
- relevanceScore: number;
120
- }
121
-
122
- export interface InfluencerSearchResult {
123
- platform: string;
124
- profiles: InfluencerProfile[];
125
- count: number;
126
- query: string;
127
- error?: string;
128
- }
129
-
130
- // ---------------------------------------------------------------------------
131
- // Relay command routing via `assistant browser chrome relay` subprocess
132
- // ---------------------------------------------------------------------------
133
-
134
- async function sendRelayCommand(
135
- action: string,
136
- args: Record<string, string | number>,
137
- ): Promise<RelayResponse> {
138
- const cmdArgs = ["browser", "chrome", "relay", action];
139
- for (const [key, value] of Object.entries(args)) {
140
- cmdArgs.push(`--${key}`, String(value));
141
- }
142
- const { stdout } = await execFileAsync("assistant", cmdArgs);
143
- return JSON.parse(stdout);
144
- }
145
-
146
- // ---------------------------------------------------------------------------
147
- // Tab management & eval
148
- // ---------------------------------------------------------------------------
149
-
150
- async function findOrOpenTab(
151
- urlPattern: string,
152
- fallbackUrl: string,
153
- ): Promise<number> {
154
- const resp = await sendRelayCommand("find-tab", { url: urlPattern });
155
- if (resp.ok && resp.tabId !== undefined) return resp.tabId;
156
- const newTab = await sendRelayCommand("new-tab", { url: fallbackUrl });
157
- if (!newTab.ok || newTab.tabId === undefined) {
158
- throw new Error(`Could not open tab for ${fallbackUrl}`);
159
- }
160
- await sleep(2500);
161
- return newTab.tabId;
162
- }
163
-
164
- async function navigateTab(tabId: number, url: string): Promise<void> {
165
- const resp = await sendRelayCommand("navigate", { "tab-id": tabId, url });
166
- if (!resp.ok)
167
- throw new Error(`Failed to navigate: ${resp.error ?? "unknown error"}`);
168
- await sleep(3000);
169
- }
170
-
171
- /**
172
- * Evaluate a JS script in a tab. The script is wrapped in an IIFE by the relay
173
- * so use `return` to yield a value. For complex results, return a JSON string.
174
- *
175
- * Uses `spawn` to pipe code via stdin to avoid shell escaping issues with long
176
- * JS snippets.
177
- */
178
- async function evalInTab(tabId: number, script: string): Promise<unknown> {
179
- return new Promise((resolve, reject) => {
180
- const proc = spawn("assistant", [
181
- "browser",
182
- "chrome",
183
- "relay",
184
- "evaluate",
185
- "--tab-id",
186
- String(tabId),
187
- ]);
188
- let stdout = "";
189
- let stderr = "";
190
- proc.stdout.on("data", (d: Buffer) => {
191
- stdout += d;
192
- });
193
- proc.stderr.on("data", (d: Buffer) => {
194
- stderr += d;
195
- });
196
- proc.on("close", (code) => {
197
- if (code !== 0) {
198
- // The relay CLI writes structured errors to stdout, not stderr.
199
- // Try to parse stdout for a JSON error message before falling back.
200
- try {
201
- const parsed: RelayResponse = JSON.parse(stdout);
202
- if (parsed.error) return reject(new Error(parsed.error));
203
- } catch {
204
- // stdout wasn't valid JSON — fall through to stderr/exit code
205
- }
206
- return reject(new Error(stderr || `Exit code ${code}`));
207
- }
208
- try {
209
- const result: RelayResponse = JSON.parse(stdout);
210
- if (!result.ok) return reject(new Error(result.error ?? "Eval failed"));
211
- resolve(result.result);
212
- } catch {
213
- reject(new Error(`Invalid JSON: ${stdout}`));
214
- }
215
- });
216
- proc.stdin.write(script);
217
- proc.stdin.end();
218
- });
219
- }
220
-
221
- function sleep(ms: number): Promise<void> {
222
- return new Promise((resolve) => setTimeout(resolve, ms));
223
- }
224
-
225
- // ---------------------------------------------------------------------------
226
- // Follower count parser
227
- // ---------------------------------------------------------------------------
228
-
229
- function parseFollowerCount(text: string): number | undefined {
230
- if (!text) return undefined;
231
- const cleaned = text
232
- .toLowerCase()
233
- .replace(/,/g, "")
234
- .replace(/\s+/g, "")
235
- .trim();
236
- const match = cleaned.match(/([\d.]+)\s*([kmbt]?)/);
237
- if (!match) return undefined;
238
-
239
- const num = parseFloat(match[1]);
240
- const suffix = match[2];
241
- const multipliers: Record<string, number> = {
242
- "": 1,
243
- k: 1_000,
244
- m: 1_000_000,
245
- b: 1_000_000_000,
246
- t: 1_000_000_000_000,
247
- };
248
- return Math.round(num * (multipliers[suffix] || 1));
249
- }
250
-
251
- // ---------------------------------------------------------------------------
252
- // Instagram scraping
253
- // ---------------------------------------------------------------------------
254
-
255
- /**
256
- * Search Instagram for influencers by keyword.
257
- *
258
- * Strategy: search by keyword → extract post links → visit each post to find
259
- * the author → deduplicate → visit each unique profile for stats.
260
- */
261
- async function searchInstagram(
262
- criteria: InfluencerSearchCriteria,
263
- ): Promise<InfluencerProfile[]> {
264
- const limit = criteria.limit ?? 10;
265
- const tabId = await findOrOpenTab(
266
- "*://*.instagram.com/*",
267
- "https://www.instagram.com",
268
- );
269
-
270
- // Step 1: Navigate to keyword search (shows a grid of posts)
271
- const searchUrl = `https://www.instagram.com/explore/search/keyword/?q=${encodeURIComponent(
272
- criteria.query,
273
- )}`;
274
- await navigateTab(tabId, searchUrl);
275
- await sleep(2000);
276
-
277
- // Step 2: Extract post links from the search grid
278
- const postLinksRaw = await evalInTab(
279
- tabId,
280
- `
281
- var links = [];
282
- document.querySelectorAll('a[href]').forEach(function(a) {
283
- var h = a.getAttribute('href');
284
- if (h && (h.indexOf('/p/') > -1 || h.indexOf('/reel/') > -1)) links.push(h);
285
- });
286
- return JSON.stringify(links.slice(0, ${limit * 2}));
287
- `,
288
- );
289
-
290
- let postLinks: string[];
291
- try {
292
- postLinks = JSON.parse(String(postLinksRaw));
293
- } catch {
294
- postLinks = [];
295
- }
296
-
297
- if (postLinks.length === 0) {
298
- return [];
299
- }
300
-
301
- // Step 3: Visit each post to extract the author username
302
- const seenUsernames = new Set<string>();
303
- const authorUsernames: string[] = [];
304
-
305
- // Navigation skip list — known non-profile IG paths
306
- const skipUsernames = new Set([
307
- "reels",
308
- "explore",
309
- "stories",
310
- "direct",
311
- "accounts",
312
- "about",
313
- "p",
314
- "reel",
315
- "tv",
316
- "search",
317
- "nametag",
318
- "directory",
319
- "",
320
- ]);
321
-
322
- for (const postLink of postLinks) {
323
- if (authorUsernames.length >= limit) break;
324
-
325
- try {
326
- await navigateTab(tabId, `https://www.instagram.com${postLink}`);
327
- await sleep(1000);
328
-
329
- // Extract the author username from the post page.
330
- // The post page body text starts with navigation items, then shows:
331
- // "username\n...audio info...\nFollow\nusername\n..."
332
- // We look for the first profile link that isn't a nav item.
333
- const authorRaw = await evalInTab(
334
- tabId,
335
- `
336
- var bodyText = document.body.innerText;
337
- // The author name appears after navigation elements, usually right before "Follow"
338
- // Also try extracting from links
339
- var links = document.querySelectorAll('a[href]');
340
- var skip = ['', 'reels', 'explore', 'stories', 'direct', 'accounts', 'about',
341
- 'p', 'reel', 'tv', 'search', 'nametag', 'directory'];
342
- var navLabels = ['Instagram', 'Home', 'HomeHome', 'Reels', 'ReelsReels', 'Messages',
343
- 'MessagesMessages', 'Search', 'SearchSearch', 'Explore', 'ExploreExplore',
344
- 'Notifications', 'NotificationsNotifications', 'Create', 'New postCreate',
345
- 'Profile', 'More', 'SettingsMore', 'Also from Meta', 'Also from MetaAlso from Meta'];
346
- var author = null;
347
- for (var i = 0; i < links.length; i++) {
348
- var href = links[i].getAttribute('href') || '';
349
- var text = links[i].textContent.trim();
350
- var match = href.match(/^\\/([a-zA-Z0-9_.]+)\\/$/);
351
- if (!match) continue;
352
- var username = match[1];
353
- if (skip.indexOf(username) > -1) continue;
354
- if (navLabels.indexOf(text) > -1) continue;
355
- // Skip the logged-in user's profile link (usually "Profile" or their own name in nav)
356
- if (text === 'Profile' || text === '') continue;
357
- author = username;
358
- break;
359
- }
360
- // Fallback: parse from body text — look for the pattern after "Follow\\n"
361
- if (!author) {
362
- var followIdx = bodyText.indexOf('Follow\\n');
363
- if (followIdx > -1) {
364
- var afterFollow = bodyText.substring(followIdx + 7, followIdx + 50);
365
- var lineEnd = afterFollow.indexOf('\\n');
366
- if (lineEnd > -1) {
367
- author = afterFollow.substring(0, lineEnd).trim();
368
- }
369
- }
370
- }
371
- return author;
372
- `,
373
- );
374
-
375
- const authorUsername = String(authorRaw || "").trim();
376
- if (
377
- authorUsername &&
378
- !skipUsernames.has(authorUsername) &&
379
- !seenUsernames.has(authorUsername)
380
- ) {
381
- seenUsernames.add(authorUsername);
382
- authorUsernames.push(authorUsername);
383
- }
384
- } catch {
385
- // Skip posts that fail
386
- continue;
387
- }
388
- }
389
-
390
- if (authorUsernames.length === 0) {
391
- return [];
392
- }
393
-
394
- // Step 4: Visit each unique profile to scrape stats
395
- const profiles: InfluencerProfile[] = [];
396
-
397
- for (const username of authorUsernames) {
398
- try {
399
- const profile = await scrapeInstagramProfile(tabId, username, criteria);
400
- if (profile && matchesCriteria(profile, criteria)) {
401
- profiles.push(profile);
402
- }
403
- await sleep(1500);
404
- } catch {
405
- continue;
406
- }
407
- }
408
-
409
- return profiles;
410
- }
411
-
412
- /**
413
- * Scrape a single Instagram profile page for stats.
414
- *
415
- * The most reliable data source is the meta[name="description"] tag which
416
- * contains: "49K Followers, 463 Following, 551 Posts - Display Name (@username)
417
- * on Instagram: "bio text""
418
- *
419
- * Falls back to parsing from body text.
420
- */
421
- async function scrapeInstagramProfile(
422
- tabId: number,
423
- username: string,
424
- criteria: InfluencerSearchCriteria,
425
- ): Promise<InfluencerProfile | null> {
426
- await navigateTab(tabId, `https://www.instagram.com/${username}/`);
427
- await sleep(2000);
428
-
429
- const raw = await evalInTab(
430
- tabId,
431
- `
432
- var r = { username: '${username}' };
433
-
434
- // Primary source: meta description tag
435
- // Format: "49K Followers, 463 Following, 551 Posts - Display Name (@user) on Instagram: \\"bio\\""
436
- var meta = document.querySelector('meta[name="description"]');
437
- r.meta = meta ? meta.getAttribute('content') : '';
438
-
439
- // Parse meta for structured data
440
- if (r.meta) {
441
- var fMatch = r.meta.match(/([\\d,.]+[KkMmBb]?)\\s*Follower/i);
442
- var fgMatch = r.meta.match(/([\\d,.]+[KkMmBb]?)\\s*Following/i);
443
- var pMatch = r.meta.match(/([\\d,.]+[KkMmBb]?)\\s*Post/i);
444
- r.followers = fMatch ? fMatch[1] : '';
445
- r.following = fgMatch ? fgMatch[1] : '';
446
- r.posts = pMatch ? pMatch[1] : '';
447
-
448
- // Display name: between "Posts - " and " (@"
449
- var nameMatch = r.meta.match(/Posts\\s*-\\s*(.+?)\\s*\\(@/);
450
- r.displayName = nameMatch ? nameMatch[1].trim() : '';
451
-
452
- // Bio: after 'on Instagram: "' until end quote
453
- var bioMatch = r.meta.match(/on Instagram:\\s*"(.+?)"/);
454
- r.bio = bioMatch ? bioMatch[1] : '';
455
- }
456
-
457
- // Fallback: parse from body text
458
- var bodyText = document.body.innerText;
459
- if (!r.followers) {
460
- var bfMatch = bodyText.match(/([\\d,.]+[KkMmBb]?)\\s*followers/i);
461
- r.followers = bfMatch ? bfMatch[1] : '';
462
- }
463
- if (!r.following) {
464
- var bgMatch = bodyText.match(/([\\d,.]+[KkMmBb]?)\\s*following/i);
465
- r.following = bgMatch ? bgMatch[1] : '';
466
- }
467
- if (!r.posts) {
468
- var bpMatch = bodyText.match(/([\\d,.]+[KkMmBb]?)\\s*posts/i);
469
- r.posts = bpMatch ? bpMatch[1] : '';
470
- }
471
-
472
- // Verified status
473
- r.isVerified = bodyText.indexOf('Verified') > -1;
474
-
475
- // Bio fallback: grab the text between "following" and "Follow" button
476
- if (!r.bio) {
477
- var followingIdx = bodyText.indexOf(' following');
478
- if (followingIdx > -1) {
479
- var afterFollowing = bodyText.substring(followingIdx + 10, followingIdx + 400);
480
- // Cut at common boundaries
481
- var cutPoints = ['Follow', 'Message', 'Meta', 'About'];
482
- var minCut = afterFollowing.length;
483
- for (var c = 0; c < cutPoints.length; c++) {
484
- var idx = afterFollowing.indexOf(cutPoints[c]);
485
- if (idx > -1 && idx < minCut) minCut = idx;
486
- }
487
- r.bio = afterFollowing.substring(0, minCut).trim();
488
- }
489
- }
490
-
491
- // Avatar
492
- var avatarEl = document.querySelector('header img') ||
493
- document.querySelector('img[alt*="profile"]');
494
- r.avatarUrl = avatarEl ? avatarEl.getAttribute('src') : null;
495
-
496
- return JSON.stringify(r);
497
- `,
498
- );
499
-
500
- let data: Record<string, unknown>;
501
- try {
502
- data = JSON.parse(String(raw));
503
- } catch {
504
- return null;
505
- }
506
-
507
- const followersNum = parseFollowerCount(String(data.followers || ""));
508
- const followingNum = parseFollowerCount(String(data.following || ""));
509
- const postCount = parseFollowerCount(String(data.posts || ""));
510
-
511
- return {
512
- platform: "instagram",
513
- username,
514
- displayName: String(data.displayName || username),
515
- profileUrl: `https://www.instagram.com/${username}/`,
516
- bio: String(data.bio || ""),
517
- followers: followersNum,
518
- followersDisplay: String(data.followers || "unknown"),
519
- following: followingNum,
520
- postCount,
521
- isVerified: Boolean(data.isVerified),
522
- avatarUrl: data.avatarUrl ? String(data.avatarUrl) : undefined,
523
- engagementRate: undefined,
524
- avgLikes: undefined,
525
- avgComments: undefined,
526
- contentThemes: extractThemes(
527
- String(data.bio || "") + " " + String(data.meta || ""),
528
- criteria.query,
529
- ),
530
- recentPosts: [],
531
- relevanceScore: 0,
532
- };
533
- }
534
-
535
- // ---------------------------------------------------------------------------
536
- // TikTok scraping
537
- // ---------------------------------------------------------------------------
538
-
539
- /**
540
- * Search TikTok for influencers by keyword.
541
- *
542
- * TikTok's user search at /search/user?q=... renders a list where each card
543
- * produces a predictable text pattern in innerText:
544
- *
545
- * DisplayName
546
- * username
547
- * 77.9K (follower count)
548
- * Followers
549
- * ·
550
- * 1.5M (like count)
551
- * Likes
552
- * Follow
553
- *
554
- * DOM class-based selectors are unreliable on TikTok (obfuscated class names),
555
- * so we parse this text pattern directly.
556
- */
557
- async function searchTikTok(
558
- criteria: InfluencerSearchCriteria,
559
- ): Promise<InfluencerProfile[]> {
560
- const limit = criteria.limit ?? 10;
561
- const tabId = await findOrOpenTab(
562
- "*://*.tiktok.com/*",
563
- "https://www.tiktok.com",
564
- );
565
-
566
- const searchUrl = `https://www.tiktok.com/search/user?q=${encodeURIComponent(
567
- criteria.query,
568
- )}`;
569
- await navigateTab(tabId, searchUrl);
570
- await sleep(3000);
571
-
572
- // Scroll to load more results
573
- await evalInTab(
574
- tabId,
575
- `window.scrollTo(0, document.body.scrollHeight); return 'scrolled'`,
576
- );
577
- await sleep(2000);
578
-
579
- // Parse the text pattern: DisplayName, username, count, "Followers", "·", count, "Likes"
580
- const raw = await evalInTab(
581
- tabId,
582
- `
583
- var text = document.body.innerText;
584
- var lines = text.split('\\n').map(function(l) { return l.trim(); }).filter(function(l) { return l.length > 0; });
585
- var users = [];
586
- for (var i = 0; i < lines.length - 6; i++) {
587
- if (lines[i+2] &&
588
- lines[i+2].match(/^[\\d,.]+[KkMmBb]?$/) &&
589
- lines[i+3] === 'Followers' &&
590
- lines[i+4] === '·' &&
591
- lines[i+6] === 'Likes') {
592
- var username = lines[i+1];
593
- if (!username.match(/^[a-zA-Z0-9_.]+$/)) continue;
594
- users.push({
595
- displayName: lines[i],
596
- username: username,
597
- followers: lines[i+2],
598
- likes: lines[i+5],
599
- });
600
- i += 7;
601
- }
602
- }
603
- return JSON.stringify(users.slice(0, ${limit * 2}));
604
- `,
605
- );
606
-
607
- let searchResults: Array<{
608
- username: string;
609
- displayName: string;
610
- followers: string;
611
- likes: string;
612
- }>;
613
- try {
614
- searchResults = JSON.parse(String(raw));
615
- } catch {
616
- return [];
617
- }
618
-
619
- // Convert to profiles — we only have basic data from search, no bios yet
620
- const profiles: InfluencerProfile[] = searchResults.map((p) => ({
621
- platform: "tiktok" as const,
622
- username: p.username,
623
- displayName: p.displayName || p.username,
624
- profileUrl: `https://www.tiktok.com/@${p.username}`,
625
- bio: "",
626
- followers: parseFollowerCount(p.followers),
627
- followersDisplay: p.followers || "unknown",
628
- following: undefined,
629
- postCount: undefined,
630
- isVerified: false,
631
- avatarUrl: undefined,
632
- engagementRate: undefined,
633
- avgLikes: undefined,
634
- avgComments: undefined,
635
- contentThemes: extractThemes(p.displayName, criteria.query),
636
- recentPosts: [],
637
- relevanceScore: 0,
638
- }));
639
-
640
- // Filter by criteria first to avoid unnecessary profile visits
641
- const filtered = profiles.filter((p) => matchesCriteria(p, criteria));
642
-
643
- // Enrich with bios by visiting each profile
644
- const enriched: InfluencerProfile[] = [];
645
- for (const profile of filtered.slice(0, limit)) {
646
- try {
647
- const detailed = await scrapeTikTokProfile(
648
- tabId,
649
- profile.username,
650
- criteria,
651
- );
652
- if (detailed) {
653
- enriched.push(detailed);
654
- } else {
655
- enriched.push(profile);
656
- }
657
- await sleep(1500);
658
- } catch {
659
- enriched.push(profile);
660
- }
661
- }
662
-
663
- return enriched;
664
- }
665
-
666
- /**
667
- * Scrape a single TikTok profile page for detailed stats.
668
- *
669
- * TikTok profile pages show stats and bio in the body text. We use a
670
- * combination of data-e2e selectors (when they work) and body text regex
671
- * as a fallback. The bio is also extracted from the region between
672
- * "Following" and "Videos" in the body text.
673
- */
674
- async function scrapeTikTokProfile(
675
- tabId: number,
676
- username: string,
677
- criteria: InfluencerSearchCriteria,
678
- ): Promise<InfluencerProfile | null> {
679
- await navigateTab(tabId, `https://www.tiktok.com/@${username}`);
680
- await sleep(2500);
681
-
682
- const raw = await evalInTab(
683
- tabId,
684
- `
685
- var r = { username: '${username}' };
686
- var bodyText = document.body.innerText;
687
-
688
- // Stats from body text (most reliable)
689
- var fMatch = bodyText.match(/([\\d,.]+[KkMmBb]?)\\s*[Ff]ollower/);
690
- var fgMatch = bodyText.match(/([\\d,.]+[KkMmBb]?)\\s*[Ff]ollowing/);
691
- var lMatch = bodyText.match(/([\\d,.]+[KkMmBb]?)\\s*[Ll]ike/);
692
- r.followers = fMatch ? fMatch[1] : '';
693
- r.following = fgMatch ? fgMatch[1] : '';
694
- r.likes = lMatch ? lMatch[1] : '';
695
-
696
- // Bio: try data-e2e selector first, fall back to text parsing
697
- var bioEl = document.querySelector('[data-e2e="user-bio"]') ||
698
- document.querySelector('h2[data-e2e="user-subtitle"]');
699
- r.bio = bioEl ? bioEl.textContent.trim() : '';
700
-
701
- if (!r.bio) {
702
- // Fallback: extract bio from between "Following" and "Videos" in body text
703
- var followingIdx = bodyText.indexOf('Following');
704
- if (followingIdx > -1) {
705
- var chunk = bodyText.substring(followingIdx + 10, followingIdx + 500);
706
- var videosIdx = chunk.indexOf('Videos');
707
- if (videosIdx > -1) chunk = chunk.substring(0, videosIdx);
708
- // Also cut at "Liked" or "Reposts"
709
- var likedIdx = chunk.indexOf('Liked');
710
- if (likedIdx > -1 && likedIdx < chunk.length) chunk = chunk.substring(0, likedIdx);
711
- r.bio = chunk.trim();
712
- }
713
- }
714
-
715
- // Display name: try data-e2e, fall back to page title
716
- var nameEl = document.querySelector('[data-e2e="user-title"]') ||
717
- document.querySelector('h1[data-e2e="user-title"]');
718
- r.displayName = nameEl ? nameEl.textContent.trim() : '';
719
- if (!r.displayName) {
720
- // TikTok titles are often "displayname (@username) | TikTok"
721
- var titleMatch = document.title.match(/^(.+?)\\s*\\(@/);
722
- r.displayName = titleMatch ? titleMatch[1].trim() : '${username}';
723
- }
724
-
725
- // Verified
726
- r.isVerified = bodyText.indexOf('Verified') > -1 ||
727
- !!document.querySelector('svg[class*="verify"]') ||
728
- !!document.querySelector('[class*="verified"]');
729
-
730
- // Avatar
731
- var img = document.querySelector('img[class*="avatar"]') ||
732
- document.querySelector('img[src*="tiktokcdn"]');
733
- r.avatarUrl = img ? img.getAttribute('src') : null;
734
-
735
- return JSON.stringify(r);
736
- `,
737
- );
738
-
739
- let data: Record<string, unknown>;
740
- try {
741
- data = JSON.parse(String(raw));
742
- } catch {
743
- return null;
744
- }
745
-
746
- const bio = String(data.bio || "");
747
-
748
- return {
749
- platform: "tiktok",
750
- username,
751
- displayName: String(data.displayName || username),
752
- profileUrl: `https://www.tiktok.com/@${username}`,
753
- bio,
754
- followers: parseFollowerCount(String(data.followers || "")),
755
- followersDisplay: String(data.followers || "unknown"),
756
- following: parseFollowerCount(String(data.following || "")),
757
- postCount: undefined,
758
- isVerified: Boolean(data.isVerified),
759
- avatarUrl: data.avatarUrl ? String(data.avatarUrl) : undefined,
760
- engagementRate: undefined,
761
- avgLikes: undefined,
762
- avgComments: undefined,
763
- contentThemes: extractThemes(bio, criteria.query),
764
- recentPosts: [],
765
- relevanceScore: 0,
766
- };
767
- }
768
-
769
- // ---------------------------------------------------------------------------
770
- // X / Twitter scraping
771
- // ---------------------------------------------------------------------------
772
-
773
- /**
774
- * Search X/Twitter for influencers by keyword.
775
- *
776
- * X has a people search at /search?q=...&f=user. Results are rendered as
777
- * [data-testid="UserCell"] components. Each cell's innerText follows this
778
- * pattern:
779
- *
780
- * [Followed by X and Y others] (optional social proof line)
781
- * Display Name
782
- * @username
783
- * Follow
784
- * Bio text...
785
- *
786
- * We parse the @username from the text (the DOM selector approach picks up
787
- * "Followed by..." text instead of handles). After extracting from search,
788
- * we visit each profile to get follower counts since the search page doesn't
789
- * include them.
790
- *
791
- * NOTE: Keep search queries SHORT (2-4 words). X returns "No results" for
792
- * long multi-word people searches.
793
- */
794
- async function searchTwitter(
795
- criteria: InfluencerSearchCriteria,
796
- ): Promise<InfluencerProfile[]> {
797
- const limit = criteria.limit ?? 10;
798
- const tabId = await findOrOpenTab("*://*.x.com/*", "https://x.com");
799
-
800
- // Use a short query — X people search fails with long queries
801
- const queryWords = criteria.query.split(/\s+/).slice(0, 4).join(" ");
802
- const searchUrl = `https://x.com/search?q=${encodeURIComponent(
803
- queryWords,
804
- )}&f=user`;
805
- await navigateTab(tabId, searchUrl);
806
- await sleep(4000);
807
-
808
- // Scroll to load more results
809
- await evalInTab(tabId, `window.scrollTo(0, 800); return 'ok'`);
810
- await sleep(2000);
811
- await evalInTab(
812
- tabId,
813
- `window.scrollTo(0, document.body.scrollHeight); return 'ok'`,
814
- );
815
- await sleep(2000);
816
-
817
- // Extract profiles from UserCell components using text pattern parsing
818
- const raw = await evalInTab(
819
- tabId,
820
- `
821
- var cells = document.querySelectorAll('[data-testid="UserCell"]');
822
- var results = [];
823
- var seen = {};
824
- for (var j = 0; j < cells.length; j++) {
825
- var text = cells[j].innerText;
826
- var lines = text.split('\\n').map(function(l) { return l.trim(); }).filter(function(l) { return l.length > 0; });
827
-
828
- var username = '';
829
- var displayName = '';
830
- var bio = '';
831
- for (var k = 0; k < lines.length; k++) {
832
- var m = lines[k].match(/^@([a-zA-Z0-9_]+)$/);
833
- if (m) {
834
- username = m[1];
835
- // Display name is the line before @username (unless it's "Followed by...")
836
- if (k > 0 && !lines[k-1].startsWith('Followed')) {
837
- displayName = lines[k-1];
838
- } else if (k > 1) {
839
- displayName = lines[k-2] || '';
840
- }
841
- // Bio is everything after "Follow" button text
842
- var afterFollow = false;
843
- for (var n = k + 1; n < lines.length; n++) {
844
- if (lines[n] === 'Follow') { afterFollow = true; continue; }
845
- if (afterFollow) {
846
- bio = lines.slice(n).join(' ').substring(0, 250);
847
- break;
848
- }
849
- }
850
- break;
851
- }
852
- }
853
-
854
- if (!username || seen[username]) continue;
855
- seen[username] = true;
856
- if (!displayName || displayName.startsWith('Followed')) displayName = username;
857
-
858
- var verified = !!cells[j].querySelector('svg[data-testid="icon-verified"]');
859
- var img = cells[j].querySelector('img[src*="profile_images"]');
860
-
861
- results.push({
862
- username: username,
863
- displayName: displayName,
864
- bio: bio,
865
- isVerified: verified,
866
- avatarUrl: img ? img.getAttribute('src') : null,
867
- });
868
- }
869
- return JSON.stringify(results.slice(0, ${limit * 3}));
870
- `,
871
- );
872
-
873
- let searchResults: Array<{
874
- username: string;
875
- displayName: string;
876
- bio: string;
877
- isVerified: boolean;
878
- avatarUrl: string | null;
879
- }>;
880
- try {
881
- searchResults = JSON.parse(String(raw));
882
- } catch {
883
- return [];
884
- }
885
-
886
- if (searchResults.length === 0) return [];
887
-
888
- // Visit each profile to get follower counts (search results don't include them)
889
- const profiles: InfluencerProfile[] = [];
890
- for (const sr of searchResults.slice(0, limit)) {
891
- try {
892
- const profile = await scrapeTwitterProfile(tabId, sr.username, criteria);
893
- if (profile && matchesCriteria(profile, criteria)) {
894
- profiles.push(profile);
895
- }
896
- await sleep(1500);
897
- } catch {
898
- // Still include with search data if profile visit fails
899
- profiles.push({
900
- platform: "twitter",
901
- username: sr.username,
902
- displayName: sr.displayName,
903
- profileUrl: `https://x.com/${sr.username}`,
904
- bio: sr.bio,
905
- followers: undefined,
906
- followersDisplay: "unknown",
907
- following: undefined,
908
- postCount: undefined,
909
- isVerified: sr.isVerified,
910
- avatarUrl: sr.avatarUrl ?? undefined,
911
- engagementRate: undefined,
912
- avgLikes: undefined,
913
- avgComments: undefined,
914
- contentThemes: extractThemes(sr.bio, criteria.query),
915
- recentPosts: [],
916
- relevanceScore: 0,
917
- });
918
- }
919
- }
920
-
921
- return profiles;
922
- }
923
-
924
- /**
925
- * Scrape a single X/Twitter profile page for detailed stats.
926
- *
927
- * Uses a combination of data-testid selectors (reliable on X) and body text
928
- * regex for follower/following counts. The data-testid="UserName",
929
- * data-testid="UserDescription" selectors work well on X profile pages.
930
- * Follower counts are extracted from body text as the DOM structure for
931
- * stat links varies.
932
- */
933
- async function scrapeTwitterProfile(
934
- tabId: number,
935
- username: string,
936
- _criteria: InfluencerSearchCriteria,
937
- ): Promise<InfluencerProfile | null> {
938
- await navigateTab(tabId, `https://x.com/${username}`);
939
- await sleep(2500);
940
-
941
- const raw = await evalInTab(
942
- tabId,
943
- `
944
- var r = { username: '${username}' };
945
-
946
- // Display name from UserName testid
947
- var nameEl = document.querySelector('[data-testid="UserName"]');
948
- if (nameEl) {
949
- var spans = nameEl.querySelectorAll('span');
950
- if (spans.length > 0) r.displayName = spans[0].textContent.trim();
951
- }
952
-
953
- // Bio from UserDescription testid
954
- var bioEl = document.querySelector('[data-testid="UserDescription"]');
955
- r.bio = bioEl ? bioEl.textContent.trim() : '';
956
-
957
- // Follower/following counts from body text (most reliable)
958
- var bodyText = document.body.innerText;
959
- var fMatch = bodyText.match(/([\\.\\d,]+[KkMm]?)\\s*Follower/);
960
- var fgMatch = bodyText.match(/([\\.\\d,]+[KkMm]?)\\s*Following/);
961
- r.followers = fMatch ? fMatch[1] : '';
962
- r.following = fgMatch ? fgMatch[1] : '';
963
-
964
- // Verified
965
- r.isVerified = !!document.querySelector('svg[data-testid="icon-verified"]') ||
966
- !!document.querySelector('[aria-label*="Verified"]');
967
-
968
- // Avatar
969
- var img = document.querySelector('img[src*="profile_images"]');
970
- r.avatarUrl = img ? img.getAttribute('src') : null;
971
-
972
- return JSON.stringify(r);
973
- `,
974
- );
975
-
976
- let data: Record<string, unknown>;
977
- try {
978
- data = JSON.parse(String(raw));
979
- } catch {
980
- return null;
981
- }
982
-
983
- return {
984
- platform: "twitter",
985
- username,
986
- displayName: String(data.displayName || username),
987
- profileUrl: `https://x.com/${username}`,
988
- bio: String(data.bio || ""),
989
- followers: parseFollowerCount(String(data.followers || "")),
990
- followersDisplay: String(data.followers || "unknown"),
991
- following: parseFollowerCount(String(data.following || "")),
992
- postCount: undefined,
993
- isVerified: Boolean(data.isVerified),
994
- avatarUrl: data.avatarUrl ? String(data.avatarUrl) : undefined,
995
- engagementRate: undefined,
996
- avgLikes: undefined,
997
- avgComments: undefined,
998
- contentThemes: extractThemes(String(data.bio || ""), ""),
999
- recentPosts: [],
1000
- relevanceScore: 0,
1001
- };
1002
- }
1003
-
1004
- // ---------------------------------------------------------------------------
1005
- // Scoring & filtering
1006
- // ---------------------------------------------------------------------------
1007
-
1008
- function matchesCriteria(
1009
- profile: InfluencerProfile,
1010
- criteria: InfluencerSearchCriteria,
1011
- ): boolean {
1012
- if (criteria.minFollowers && profile.followers !== undefined) {
1013
- if (profile.followers < criteria.minFollowers) return false;
1014
- }
1015
- if (criteria.maxFollowers && profile.followers !== undefined) {
1016
- if (profile.followers > criteria.maxFollowers) return false;
1017
- }
1018
- if (criteria.verifiedOnly && !profile.isVerified) {
1019
- return false;
1020
- }
1021
- return true;
1022
- }
1023
-
1024
- function scoreProfile(
1025
- profile: InfluencerProfile,
1026
- criteria: InfluencerSearchCriteria,
1027
- ): number {
1028
- let score = 0;
1029
-
1030
- // Follower count scoring
1031
- if (profile.followers !== undefined) {
1032
- if (profile.followers >= 1_000) score += 10;
1033
- if (profile.followers >= 10_000) score += 20;
1034
- if (profile.followers >= 100_000) score += 30;
1035
- if (profile.followers >= 1_000_000) score += 20;
1036
-
1037
- // Bonus for being within requested range
1038
- if (criteria.minFollowers && criteria.maxFollowers) {
1039
- const mid = (criteria.minFollowers + criteria.maxFollowers) / 2;
1040
- const distance = Math.abs(profile.followers - mid) / mid;
1041
- score += Math.max(0, 20 - distance * 20);
1042
- }
1043
- }
1044
-
1045
- // Verified boost
1046
- if (profile.isVerified) score += 15;
1047
-
1048
- // Bio relevance
1049
- const queryTerms = criteria.query.toLowerCase().split(/\s+/);
1050
- const bioLower = profile.bio.toLowerCase();
1051
- for (const term of queryTerms) {
1052
- if (bioLower.includes(term)) score += 10;
1053
- }
1054
-
1055
- // Content theme matching
1056
- if (profile.contentThemes.length > 0)
1057
- score += 5 * profile.contentThemes.length;
1058
-
1059
- // Completeness bonuses
1060
- if (profile.avatarUrl) score += 5;
1061
- if (profile.bio.length > 20) score += 5;
1062
-
1063
- return score;
1064
- }
1065
-
1066
- function extractThemes(bio: string, query: string): string[] {
1067
- const themes: string[] = [];
1068
- const text = (bio + " " + query).toLowerCase();
1069
-
1070
- const themeKeywords: Record<string, string[]> = {
1071
- fashion: [
1072
- "fashion",
1073
- "style",
1074
- "outfit",
1075
- "ootd",
1076
- "clothing",
1077
- "wear",
1078
- "designer",
1079
- ],
1080
- beauty: ["beauty", "makeup", "skincare", "cosmetic", "hair", "glow"],
1081
- fitness: [
1082
- "fitness",
1083
- "gym",
1084
- "workout",
1085
- "health",
1086
- "training",
1087
- "athlete",
1088
- "sports",
1089
- ],
1090
- food: ["food", "recipe", "cooking", "chef", "foodie", "restaurant", "eat"],
1091
- travel: [
1092
- "travel",
1093
- "wanderlust",
1094
- "adventure",
1095
- "explore",
1096
- "tourism",
1097
- "destination",
1098
- ],
1099
- tech: [
1100
- "tech",
1101
- "technology",
1102
- "gadget",
1103
- "software",
1104
- "coding",
1105
- "developer",
1106
- "ai",
1107
- "artificial intelligence",
1108
- ],
1109
- gaming: ["gaming", "gamer", "esports", "twitch", "stream", "game"],
1110
- music: ["music", "musician", "singer", "artist", "producer", "dj"],
1111
- lifestyle: ["lifestyle", "daily", "vlog", "life", "mom", "dad", "family"],
1112
- business: [
1113
- "business",
1114
- "entrepreneur",
1115
- "startup",
1116
- "marketing",
1117
- "ceo",
1118
- "founder",
1119
- ],
1120
- photography: ["photo", "photography", "photographer", "visual", "creative"],
1121
- comedy: ["comedy", "funny", "humor", "meme", "comedian", "laugh"],
1122
- education: [
1123
- "education",
1124
- "learn",
1125
- "teach",
1126
- "tutor",
1127
- "tips",
1128
- "howto",
1129
- "teaching",
1130
- ],
1131
- wellness: [
1132
- "wellness",
1133
- "mindfulness",
1134
- "meditation",
1135
- "yoga",
1136
- "mental health",
1137
- ],
1138
- career: [
1139
- "career",
1140
- "job",
1141
- "hiring",
1142
- "resume",
1143
- "interview",
1144
- "salary",
1145
- "remote work",
1146
- ],
1147
- };
1148
-
1149
- for (const [theme, keywords] of Object.entries(themeKeywords)) {
1150
- if (keywords.some((kw) => text.includes(kw))) {
1151
- themes.push(theme);
1152
- }
1153
- }
1154
-
1155
- return themes;
1156
- }
1157
-
1158
- // ---------------------------------------------------------------------------
1159
- // Main search orchestrator
1160
- // ---------------------------------------------------------------------------
1161
-
1162
- /**
1163
- * Search for influencers across specified platforms.
1164
- */
1165
- export async function searchInfluencers(
1166
- criteria: InfluencerSearchCriteria,
1167
- ): Promise<InfluencerSearchResult[]> {
1168
- const platforms = criteria.platforms ?? ["instagram", "tiktok", "twitter"];
1169
- const results: InfluencerSearchResult[] = [];
1170
-
1171
- for (const platform of platforms) {
1172
- try {
1173
- let profiles: InfluencerProfile[];
1174
-
1175
- switch (platform) {
1176
- case "instagram":
1177
- profiles = await searchInstagram(criteria);
1178
- break;
1179
- case "tiktok":
1180
- profiles = await searchTikTok(criteria);
1181
- break;
1182
- case "twitter":
1183
- profiles = await searchTwitter(criteria);
1184
- break;
1185
- default:
1186
- continue;
1187
- }
1188
-
1189
- // Score and sort
1190
- profiles = profiles.map((p) => ({
1191
- ...p,
1192
- relevanceScore: scoreProfile(p, criteria),
1193
- }));
1194
- profiles.sort((a, b) => b.relevanceScore - a.relevanceScore);
1195
-
1196
- results.push({
1197
- platform,
1198
- profiles,
1199
- count: profiles.length,
1200
- query: criteria.query,
1201
- });
1202
- } catch (err) {
1203
- results.push({
1204
- platform,
1205
- profiles: [],
1206
- count: 0,
1207
- query: criteria.query,
1208
- error: err instanceof Error ? err.message : String(err),
1209
- });
1210
- }
1211
- }
1212
-
1213
- return results;
1214
- }
1215
-
1216
- /**
1217
- * Get detailed profile data for a specific influencer.
1218
- */
1219
- export async function getInfluencerProfile(
1220
- platform: "instagram" | "tiktok" | "twitter",
1221
- username: string,
1222
- ): Promise<InfluencerProfile | null> {
1223
- const criteria: InfluencerSearchCriteria = { query: "" };
1224
-
1225
- switch (platform) {
1226
- case "instagram": {
1227
- const tabId = await findOrOpenTab(
1228
- "*://*.instagram.com/*",
1229
- "https://www.instagram.com",
1230
- );
1231
- return scrapeInstagramProfile(tabId, username, criteria);
1232
- }
1233
- case "twitter": {
1234
- const tabId = await findOrOpenTab("*://*.x.com/*", "https://x.com");
1235
- return scrapeTwitterProfile(tabId, username, criteria);
1236
- }
1237
- case "tiktok": {
1238
- const tabId = await findOrOpenTab(
1239
- "*://*.tiktok.com/*",
1240
- "https://www.tiktok.com",
1241
- );
1242
- return scrapeTikTokProfile(tabId, username, criteria);
1243
- }
1244
- default:
1245
- return null;
1246
- }
1247
- }
1248
-
1249
- /**
1250
- * Compare multiple influencers side by side.
1251
- */
1252
- export async function compareInfluencers(
1253
- influencers: {
1254
- platform: "instagram" | "tiktok" | "twitter";
1255
- username: string;
1256
- }[],
1257
- ): Promise<InfluencerProfile[]> {
1258
- const profiles: InfluencerProfile[] = [];
1259
-
1260
- for (const inf of influencers) {
1261
- const profile = await getInfluencerProfile(inf.platform, inf.username);
1262
- if (profile) {
1263
- profiles.push(profile);
1264
- }
1265
- await sleep(2000);
1266
- }
1267
-
1268
- return profiles;
1269
- }