@vellumai/assistant 0.4.44 → 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 (681) hide show
  1. package/.prettierignore +4 -0
  2. package/ARCHITECTURE.md +34 -31
  3. package/README.md +4 -4
  4. package/bun.lock +10 -35
  5. package/docs/architecture/integrations.md +102 -197
  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 -1
  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-skill-retrieval-guard.test.ts +23 -9
  40. package/src/__tests__/call-controller.test.ts +0 -1
  41. package/src/__tests__/call-conversation-messages.test.ts +0 -1
  42. package/src/__tests__/call-domain.test.ts +0 -1
  43. package/src/__tests__/call-pointer-messages.test.ts +0 -1
  44. package/src/__tests__/call-recovery.test.ts +0 -1
  45. package/src/__tests__/call-routes-http.test.ts +0 -1
  46. package/src/__tests__/call-store.test.ts +0 -1
  47. package/src/__tests__/canonical-guardian-store.test.ts +0 -1
  48. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  49. package/src/__tests__/channel-approvals.test.ts +1 -1
  50. package/src/__tests__/channel-delivery-store.test.ts +0 -1
  51. package/src/__tests__/channel-guardian.test.ts +5 -7
  52. package/src/__tests__/channel-retry-sweep.test.ts +0 -1
  53. package/src/__tests__/checker.test.ts +4 -11
  54. package/src/__tests__/compaction.benchmark.test.ts +16 -14
  55. package/src/__tests__/computer-use-session-lifecycle.test.ts +10 -11
  56. package/src/__tests__/computer-use-session-working-dir.test.ts +2 -6
  57. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +2 -5
  58. package/src/__tests__/computer-use-tools.test.ts +35 -31
  59. package/src/__tests__/config-schema.test.ts +11 -15
  60. package/src/__tests__/config-watcher.test.ts +0 -1
  61. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  62. package/src/__tests__/conflict-store.test.ts +0 -1
  63. package/src/__tests__/connection-policy.test.ts +4 -7
  64. package/src/__tests__/contacts-tools.test.ts +0 -1
  65. package/src/__tests__/context-memory-e2e.test.ts +2 -4
  66. package/src/__tests__/context-overflow-reducer.test.ts +2 -4
  67. package/src/__tests__/context-window-manager.test.ts +147 -60
  68. package/src/__tests__/contradiction-checker.test.ts +0 -1
  69. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  70. package/src/__tests__/conversation-attention-telegram.test.ts +1 -1
  71. package/src/__tests__/conversation-pairing.test.ts +2 -2
  72. package/src/__tests__/conversation-routes-guardian-reply.test.ts +25 -1
  73. package/src/__tests__/conversation-routes-slash-commands.test.ts +381 -0
  74. package/src/__tests__/conversation-store.test.ts +0 -1
  75. package/src/__tests__/conversation-unread-route.test.ts +1 -2
  76. package/src/__tests__/credential-security-invariants.test.ts +7 -8
  77. package/src/__tests__/cross-provider-web-search.test.ts +353 -0
  78. package/src/__tests__/daemon-assistant-events.test.ts +6 -7
  79. package/src/__tests__/db-schedule-syntax-migration.test.ts +15 -3
  80. package/src/__tests__/delete-managed-skill-tool.test.ts +5 -9
  81. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
  82. package/src/__tests__/diagnostics-export.test.ts +189 -0
  83. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  84. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
  85. package/src/__tests__/entity-extractor.test.ts +0 -1
  86. package/src/__tests__/entity-search.test.ts +0 -1
  87. package/src/__tests__/ephemeral-permissions.test.ts +2 -4
  88. package/src/__tests__/file-read-tool.test.ts +86 -0
  89. package/src/__tests__/followup-tools.test.ts +0 -1
  90. package/src/__tests__/frontmatter.test.ts +77 -34
  91. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  92. package/src/__tests__/gateway-only-guard.test.ts +1 -1
  93. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -1
  94. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -1
  95. package/src/__tests__/guardian-action-followup-store.test.ts +0 -1
  96. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  97. package/src/__tests__/guardian-action-late-reply.test.ts +0 -1
  98. package/src/__tests__/guardian-action-store.test.ts +0 -1
  99. package/src/__tests__/guardian-action-sweep.test.ts +0 -1
  100. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  101. package/src/__tests__/guardian-dispatch.test.ts +1 -2
  102. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  103. package/src/__tests__/guardian-outbound-http.test.ts +0 -1
  104. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -1
  105. package/src/__tests__/guardian-routing-invariants.test.ts +1 -1
  106. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  107. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  108. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +3 -5
  109. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +28 -426
  110. package/src/__tests__/host-bash-proxy.test.ts +335 -0
  111. package/src/__tests__/host-file-proxy.test.ts +374 -0
  112. package/src/__tests__/host-shell-tool.test.ts +147 -1
  113. package/src/__tests__/http-user-message-parity.test.ts +361 -0
  114. package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
  115. package/src/__tests__/integration-status.test.ts +3 -8
  116. package/src/__tests__/intent-routing.test.ts +7 -46
  117. package/src/__tests__/invite-redemption-service.test.ts +0 -1
  118. package/src/__tests__/invite-routes-http.test.ts +0 -1
  119. package/src/__tests__/llm-usage-store.test.ts +0 -1
  120. package/src/__tests__/managed-avatar-client.test.ts +101 -55
  121. package/src/__tests__/managed-skill-lifecycle.test.ts +9 -18
  122. package/src/__tests__/managed-store.test.ts +94 -21
  123. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -1
  124. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +2 -4
  125. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -1
  126. package/src/__tests__/memory-recall-quality.test.ts +0 -1
  127. package/src/__tests__/memory-regressions.experimental.test.ts +0 -1
  128. package/src/__tests__/memory-regressions.test.ts +0 -1
  129. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -1
  130. package/src/__tests__/memory-upsert-concurrency.test.ts +0 -1
  131. package/src/__tests__/messaging-send-tool.test.ts +35 -0
  132. package/src/__tests__/messaging-skill-split.test.ts +138 -0
  133. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
  134. package/src/__tests__/migration-export-http.test.ts +2 -3
  135. package/src/__tests__/migration-import-commit-http.test.ts +1 -2
  136. package/src/__tests__/migration-import-preflight-http.test.ts +1 -2
  137. package/src/__tests__/migration-validate-http.test.ts +1 -2
  138. package/src/__tests__/native-web-search.test.ts +475 -0
  139. package/src/__tests__/navigate-settings-tab.test.ts +84 -0
  140. package/src/__tests__/non-member-access-request.test.ts +0 -1
  141. package/src/__tests__/notification-broadcaster.test.ts +15 -15
  142. package/src/__tests__/notification-decision-strategy.test.ts +6 -6
  143. package/src/__tests__/notification-deep-link.test.ts +7 -7
  144. package/src/__tests__/notification-guardian-path.test.ts +2 -3
  145. package/src/__tests__/notification-telegram-adapter.test.ts +1 -1
  146. package/src/__tests__/notification-thread-candidates.test.ts +4 -4
  147. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  148. package/src/__tests__/playbook-execution.test.ts +0 -1
  149. package/src/__tests__/playbook-tools.test.ts +0 -1
  150. package/src/__tests__/profile-compiler.test.ts +0 -1
  151. package/src/__tests__/provider-managed-proxy-integration.test.ts +25 -0
  152. package/src/__tests__/qdrant-collection-migration.test.ts +223 -0
  153. package/src/__tests__/recording-handler.test.ts +30 -94
  154. package/src/__tests__/registry.test.ts +28 -35
  155. package/src/__tests__/relay-server.test.ts +0 -1
  156. package/src/__tests__/ride-shotgun-handler.test.ts +4 -20
  157. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  158. package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
  159. package/src/__tests__/runtime-events-sse.test.ts +0 -1
  160. package/src/__tests__/sandbox-diagnostics.test.ts +0 -1
  161. package/src/__tests__/scaffold-managed-skill-tool.test.ts +30 -28
  162. package/src/__tests__/schedule-store.test.ts +441 -1
  163. package/src/__tests__/schedule-tools.test.ts +468 -7
  164. package/src/__tests__/scheduler-recurrence.test.ts +196 -23
  165. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  166. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  167. package/src/__tests__/secret-prompt-log-hygiene.test.ts +6 -3
  168. package/src/__tests__/secret-response-routing.test.ts +4 -1
  169. package/src/__tests__/send-endpoint-busy.test.ts +14 -2
  170. package/src/__tests__/send-notification-tool.test.ts +0 -7
  171. package/src/__tests__/sequence-store.test.ts +0 -1
  172. package/src/__tests__/server-history-render.test.ts +1 -2
  173. package/src/__tests__/session-abort-tool-results.test.ts +0 -1
  174. package/src/__tests__/session-agent-loop.test.ts +46 -6
  175. package/src/__tests__/session-confirmation-signals.test.ts +0 -1
  176. package/src/__tests__/session-conflict-gate.test.ts +2 -6
  177. package/src/__tests__/session-error.test.ts +5 -14
  178. package/src/__tests__/session-init.benchmark.test.ts +3 -5
  179. package/src/__tests__/session-load-history-repair.test.ts +0 -1
  180. package/src/__tests__/session-media-retry.test.ts +12 -74
  181. package/src/__tests__/session-pre-run-repair.test.ts +0 -1
  182. package/src/__tests__/session-profile-injection.test.ts +2 -6
  183. package/src/__tests__/session-provider-retry-repair.test.ts +2 -6
  184. package/src/__tests__/session-queue.test.ts +94 -139
  185. package/src/__tests__/session-skill-tools.test.ts +115 -115
  186. package/src/__tests__/session-slash-known.test.ts +0 -1
  187. package/src/__tests__/session-slash-queue.test.ts +0 -1
  188. package/src/__tests__/session-slash-unknown.test.ts +0 -1
  189. package/src/__tests__/session-surfaces-task-progress.test.ts +34 -0
  190. package/src/__tests__/session-usage.test.ts +0 -1
  191. package/src/__tests__/session-workspace-cache-state.test.ts +2 -6
  192. package/src/__tests__/session-workspace-injection.test.ts +2 -6
  193. package/src/__tests__/session-workspace-tool-tracking.test.ts +2 -6
  194. package/src/__tests__/skill-feature-flags-integration.test.ts +180 -184
  195. package/src/__tests__/skill-feature-flags.test.ts +125 -18
  196. package/src/__tests__/skill-load-feature-flag.test.ts +1 -2
  197. package/src/__tests__/skill-load-tool.test.ts +194 -2
  198. package/src/__tests__/skill-projection-feature-flag.test.ts +27 -16
  199. package/src/__tests__/skill-projection.benchmark.test.ts +15 -14
  200. package/src/__tests__/skills.test.ts +14 -53
  201. package/src/__tests__/slack-channel-config.test.ts +0 -1
  202. package/src/__tests__/slack-inbound-verification.test.ts +0 -1
  203. package/src/__tests__/slack-skill.test.ts +1 -1
  204. package/src/__tests__/subagent-tools.test.ts +2 -2
  205. package/src/__tests__/system-prompt.test.ts +4 -3
  206. package/src/__tests__/task-compiler.test.ts +0 -1
  207. package/src/__tests__/task-management-tools.test.ts +0 -1
  208. package/src/__tests__/task-memory-cleanup.test.ts +0 -1
  209. package/src/__tests__/task-runner.test.ts +0 -1
  210. package/src/__tests__/task-scheduler.test.ts +0 -1
  211. package/src/__tests__/terminal-tools.test.ts +0 -1
  212. package/src/__tests__/test-support/computer-use-skill-harness.ts +2 -4
  213. package/src/__tests__/thread-seed-composer.test.ts +5 -5
  214. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  215. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  216. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  217. package/src/__tests__/tool-executor.test.ts +8 -86
  218. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  219. package/src/__tests__/tool-notification-listener.test.ts +1 -1
  220. package/src/__tests__/tool-preview-lifecycle.test.ts +416 -0
  221. package/src/__tests__/trust-store.test.ts +80 -4
  222. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  223. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  224. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -1
  225. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  226. package/src/__tests__/trusted-contact-verification.test.ts +0 -1
  227. package/src/__tests__/twilio-provider.test.ts +0 -1
  228. package/src/__tests__/twilio-routes.test.ts +0 -1
  229. package/src/__tests__/{request-file-tool.test.ts → ui-file-upload-surface.test.ts} +11 -72
  230. package/src/__tests__/update-bulletin.test.ts +0 -1
  231. package/src/__tests__/usage-cache-backfill-migration.test.ts +0 -1
  232. package/src/__tests__/usage-routes.test.ts +0 -1
  233. package/src/__tests__/verification-control-plane-policy.test.ts +4 -4
  234. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  235. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  236. package/src/__tests__/voice-session-bridge.test.ts +9 -1
  237. package/src/__tests__/web-fetch.test.ts +57 -0
  238. package/src/__tests__/workspace-git-service.test.ts +5 -14
  239. package/src/__tests__/workspace-policy.test.ts +0 -1
  240. package/src/agent/loop.ts +22 -34
  241. package/src/bundler/bundle-signer.ts +4 -4
  242. package/src/calls/call-controller.ts +1 -1
  243. package/src/calls/relay-server.ts +1 -1
  244. package/src/calls/twilio-rest.ts +1 -1
  245. package/src/calls/voice-session-bridge.ts +3 -1
  246. package/src/cli/__tests__/notifications.test.ts +3 -4
  247. package/src/cli/commands/map.ts +2 -6
  248. package/src/cli/commands/mcp.ts +73 -15
  249. package/src/cli/commands/notifications.ts +4 -4
  250. package/src/cli/commands/sessions.ts +9 -1
  251. package/src/cli/commands/skills.ts +6 -10
  252. package/src/cli/http-client.ts +2 -3
  253. package/src/cli/main-screen.tsx +10 -10
  254. package/src/cli/program.ts +0 -4
  255. package/src/cli/reference.ts +0 -2
  256. package/src/cli.ts +15 -9
  257. package/src/config/__tests__/bundled-tool-registry-guard.test.ts +120 -0
  258. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +11 -0
  259. package/src/config/bundled-skills/app-builder/SKILL.md +6 -1
  260. package/src/config/bundled-skills/browser/SKILL.md +6 -1
  261. package/src/config/bundled-skills/chatgpt-import/SKILL.md +5 -1
  262. package/src/config/bundled-skills/claude-code/SKILL.md +5 -1
  263. package/src/config/bundled-skills/computer-use/SKILL.md +6 -1
  264. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -69
  265. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +10 -1
  266. package/src/config/bundled-skills/contacts/SKILL.md +10 -1
  267. package/src/config/bundled-skills/contacts/TOOLS.json +35 -0
  268. package/src/config/bundled-skills/{messaging → contacts}/tools/google-contacts.ts +9 -2
  269. package/src/config/bundled-skills/document/SKILL.md +4 -1
  270. package/src/config/bundled-skills/doordash/SKILL.md +8 -1
  271. package/src/config/bundled-skills/doordash/lib/shared/platform.ts +4 -1
  272. package/src/config/bundled-skills/followups/SKILL.md +4 -1
  273. package/src/config/bundled-skills/gmail/SKILL.md +180 -0
  274. package/src/config/bundled-skills/gmail/TOOLS.json +506 -0
  275. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +149 -0
  276. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +110 -0
  277. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-draft.ts +1 -1
  278. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-filters.ts +1 -1
  279. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-follow-up.ts +1 -1
  280. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-forward.ts +1 -1
  281. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +50 -0
  282. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-outreach-scan.ts +8 -90
  283. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-send-draft.ts +1 -1
  284. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-sender-digest.ts +2 -2
  285. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-trash.ts +1 -1
  286. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-unsubscribe.ts +1 -1
  287. package/src/config/bundled-skills/{messaging → gmail}/tools/gmail-vacation.ts +1 -1
  288. package/src/config/bundled-skills/gmail/tools/shared.ts +47 -0
  289. package/src/config/bundled-skills/google-calendar/SKILL.md +5 -1
  290. package/src/config/bundled-skills/image-studio/SKILL.md +5 -1
  291. package/src/config/bundled-skills/knowledge-graph/SKILL.md +4 -1
  292. package/src/config/bundled-skills/media-processing/SKILL.md +7 -13
  293. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -22
  294. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +12 -1
  295. package/src/config/bundled-skills/messaging/SKILL.md +23 -139
  296. package/src/config/bundled-skills/messaging/TOOLS.json +33 -1215
  297. package/src/config/bundled-skills/messaging/tools/gmail-mime-helpers.ts +42 -0
  298. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +165 -2
  299. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +1 -13
  300. package/src/config/bundled-skills/messaging/tools/shared.ts +81 -34
  301. package/src/config/bundled-skills/notifications/SKILL.md +5 -1
  302. package/src/config/bundled-skills/orchestration/SKILL.md +30 -0
  303. package/src/config/bundled-skills/orchestration/TOOLS.json +35 -0
  304. package/src/config/bundled-skills/{reminder/tools/reminder-create.ts → orchestration/tools/swarm-delegate.ts} +3 -3
  305. package/src/config/bundled-skills/phone-calls/SKILL.md +9 -1
  306. package/src/config/bundled-skills/playbooks/SKILL.md +4 -1
  307. package/src/config/bundled-skills/schedule/SKILL.md +70 -9
  308. package/src/config/bundled-skills/schedule/TOOLS.json +38 -6
  309. package/src/config/bundled-skills/screen-watch/SKILL.md +28 -0
  310. package/src/config/bundled-skills/screen-watch/TOOLS.json +35 -0
  311. package/src/config/bundled-skills/{reminder/tools/reminder-cancel.ts → screen-watch/tools/start-screen-watch.ts} +3 -3
  312. package/src/config/bundled-skills/sequences/SKILL.md +47 -0
  313. package/src/config/bundled-skills/sequences/TOOLS.json +340 -0
  314. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +128 -0
  315. package/src/config/bundled-skills/sequences/tools/shared.ts +9 -0
  316. package/src/config/bundled-skills/settings/SKILL.md +12 -0
  317. package/src/config/bundled-skills/settings/TOOLS.json +112 -0
  318. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +43 -0
  319. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +52 -0
  320. package/src/config/bundled-skills/{computer-use/tools/computer-use-right-click.ts → settings/tools/set-avatar.ts} +2 -6
  321. package/src/{tools/system/voice-config.ts → config/bundled-skills/settings/tools/voice-config-update.ts} +59 -96
  322. package/src/config/bundled-skills/skill-management/SKILL.md +18 -0
  323. package/src/config/bundled-skills/skill-management/TOOLS.json +90 -0
  324. package/src/config/bundled-skills/{computer-use/tools/computer-use-double-click.ts → skill-management/tools/delete-managed.ts} +2 -6
  325. package/src/config/bundled-skills/skill-management/tools/scaffold-managed.ts +12 -0
  326. package/src/config/bundled-skills/slack/SKILL.md +5 -1
  327. package/src/config/bundled-skills/subagent/SKILL.md +4 -1
  328. package/src/config/bundled-skills/tasks/SKILL.md +5 -2
  329. package/src/config/bundled-skills/transcribe/SKILL.md +4 -1
  330. package/src/config/bundled-skills/watcher/SKILL.md +4 -1
  331. package/src/config/bundled-tool-registry.ts +118 -107
  332. package/src/config/env.ts +5 -2
  333. package/src/config/feature-flag-registry.json +25 -9
  334. package/src/config/loader.ts +10 -2
  335. package/src/config/schema.ts +19 -16
  336. package/src/config/schemas/inference.ts +12 -22
  337. package/src/config/schemas/memory-storage.ts +19 -1
  338. package/src/config/schemas/platform.ts +0 -16
  339. package/src/config/skill-state.ts +11 -8
  340. package/src/config/skills.ts +83 -32
  341. package/src/context/token-estimator.ts +11 -0
  342. package/src/context/window-manager.ts +180 -151
  343. package/src/daemon/computer-use-session.ts +11 -43
  344. package/src/daemon/daemon-control.ts +4 -1
  345. package/src/daemon/handlers/config-channels.ts +5 -9
  346. package/src/daemon/handlers/config-ingress.ts +0 -4
  347. package/src/daemon/handlers/config-model.ts +7 -13
  348. package/src/daemon/handlers/config-telegram.ts +4 -8
  349. package/src/daemon/handlers/config-voice.ts +2 -5
  350. package/src/daemon/handlers/dictation.ts +2 -12
  351. package/src/daemon/handlers/identity.ts +0 -105
  352. package/src/daemon/handlers/recording.ts +3 -23
  353. package/src/daemon/handlers/session-history.ts +1 -1
  354. package/src/daemon/handlers/sessions.ts +53 -72
  355. package/src/daemon/handlers/shared.ts +7 -28
  356. package/src/daemon/handlers/skills.ts +31 -27
  357. package/src/daemon/host-bash-proxy.ts +148 -0
  358. package/src/daemon/host-file-proxy.ts +135 -0
  359. package/src/daemon/lifecycle.ts +49 -24
  360. package/src/daemon/mcp-reload-service.ts +123 -0
  361. package/src/daemon/message-protocol.ts +6 -0
  362. package/src/daemon/message-types/browser.ts +1 -1
  363. package/src/daemon/message-types/computer-use.ts +1 -4
  364. package/src/daemon/message-types/guardian-actions.ts +1 -1
  365. package/src/daemon/message-types/host-bash.ts +18 -0
  366. package/src/daemon/message-types/host-file.ts +44 -0
  367. package/src/daemon/message-types/integrations.ts +1 -67
  368. package/src/daemon/message-types/messages.ts +15 -0
  369. package/src/daemon/message-types/schedules.ts +11 -27
  370. package/src/daemon/message-types/sessions.ts +2 -1
  371. package/src/daemon/message-types/settings.ts +1 -1
  372. package/src/daemon/message-types/shared.ts +1 -1
  373. package/src/daemon/ride-shotgun-handler.ts +2 -42
  374. package/src/daemon/server.ts +43 -10
  375. package/src/daemon/session-agent-loop-handlers.ts +48 -7
  376. package/src/daemon/session-agent-loop.ts +97 -66
  377. package/src/daemon/session-attachments.ts +1 -1
  378. package/src/daemon/session-error.ts +17 -16
  379. package/src/daemon/session-lifecycle.ts +20 -1
  380. package/src/daemon/session-media-retry.ts +1 -15
  381. package/src/daemon/session-messaging.ts +14 -6
  382. package/src/daemon/session-process.ts +36 -7
  383. package/src/daemon/session-queue-manager.ts +62 -103
  384. package/src/daemon/session-runtime-assembly.ts +27 -0
  385. package/src/daemon/session-skill-tools.ts +12 -11
  386. package/src/daemon/session-slash.ts +7 -0
  387. package/src/daemon/session-surfaces.ts +19 -97
  388. package/src/daemon/session-tool-setup.ts +146 -6
  389. package/src/daemon/session.ts +77 -13
  390. package/src/errors.ts +0 -2
  391. package/src/export/formatter.ts +6 -0
  392. package/src/mcp/mcp-oauth-provider.ts +1 -3
  393. package/src/media/avatar-router.ts +20 -28
  394. package/src/media/avatar-types.ts +7 -14
  395. package/src/media/managed-avatar-client.ts +70 -34
  396. package/src/memory/conversation-title-service.ts +1 -2
  397. package/src/memory/db-init.ts +16 -0
  398. package/src/memory/embedding-backend.ts +129 -27
  399. package/src/memory/embedding-gemini.test.ts +256 -0
  400. package/src/memory/embedding-gemini.ts +47 -13
  401. package/src/memory/embedding-local.ts +14 -2
  402. package/src/memory/embedding-ollama.ts +15 -2
  403. package/src/memory/embedding-openai.ts +15 -2
  404. package/src/memory/embedding-types.test.ts +116 -0
  405. package/src/memory/embedding-types.ts +61 -0
  406. package/src/memory/fingerprint.ts +1 -1
  407. package/src/memory/indexer.ts +25 -1
  408. package/src/memory/job-handlers/embedding.test.ts +258 -0
  409. package/src/memory/job-handlers/embedding.ts +81 -1
  410. package/src/memory/job-handlers/index-maintenance.ts +35 -1
  411. package/src/memory/job-handlers/media-processing.ts +11 -1
  412. package/src/memory/job-utils.ts +21 -6
  413. package/src/memory/jobs-store.ts +5 -1
  414. package/src/memory/jobs-worker.ts +8 -0
  415. package/src/memory/message-content.ts +66 -0
  416. package/src/memory/migrations/100-core-tables.ts +1 -31
  417. package/src/memory/migrations/104-core-indexes.ts +0 -11
  418. package/src/memory/migrations/145-drop-accounts-table.ts +19 -0
  419. package/src/memory/migrations/146-schedule-oneshot-routing.ts +94 -0
  420. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +129 -0
  421. package/src/memory/migrations/148-drop-reminders-table.ts +18 -0
  422. package/src/memory/migrations/index.ts +4 -0
  423. package/src/memory/migrations/registry.ts +19 -0
  424. package/src/memory/qdrant-client.ts +158 -43
  425. package/src/memory/retriever.test.ts +0 -1
  426. package/src/memory/retriever.ts +12 -2
  427. package/src/memory/schema/infrastructure.ts +5 -29
  428. package/src/memory/search/formatting.ts +34 -9
  429. package/src/memory/search/semantic.ts +57 -2
  430. package/src/memory/search/types.ts +2 -1
  431. package/src/notifications/AGENTS.md +2 -2
  432. package/src/notifications/README.md +59 -58
  433. package/src/notifications/adapters/macos.ts +1 -1
  434. package/src/notifications/broadcaster.ts +5 -5
  435. package/src/notifications/copy-composer.ts +1 -1
  436. package/src/notifications/decision-engine.ts +2 -2
  437. package/src/notifications/destination-resolver.ts +2 -2
  438. package/src/notifications/emit-signal.ts +8 -8
  439. package/src/notifications/signal.ts +1 -1
  440. package/src/notifications/thread-seed-composer.ts +1 -1
  441. package/src/oauth/connect-orchestrator.ts +1 -1
  442. package/src/oauth/token-persistence.ts +1 -1
  443. package/src/permissions/checker.ts +12 -1
  444. package/src/permissions/defaults.ts +10 -14
  445. package/src/permissions/trust-store.ts +37 -0
  446. package/src/permissions/workspace-policy.ts +0 -1
  447. package/src/prompts/__tests__/build-cli-reference-section.test.ts +11 -0
  448. package/src/prompts/computer-use-prompt.ts +1 -1
  449. package/src/prompts/system-prompt.ts +29 -30
  450. package/src/prompts/templates/SOUL.md +1 -2
  451. package/src/prompts/templates/UPDATES.md +16 -7
  452. package/src/providers/anthropic/client.ts +87 -33
  453. package/src/providers/gemini/client.ts +6 -0
  454. package/src/providers/managed-proxy/constants.ts +5 -0
  455. package/src/providers/openai/client.ts +15 -0
  456. package/src/providers/registry.ts +2 -2
  457. package/src/providers/types.ts +24 -2
  458. package/src/runtime/AGENTS.md +18 -0
  459. package/src/runtime/assistant-event-hub.ts +2 -3
  460. package/src/runtime/assistant-event.ts +4 -4
  461. package/src/runtime/auth/__tests__/context.test.ts +5 -5
  462. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  463. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -2
  464. package/src/runtime/auth/__tests__/{ipc-auth-context.test.ts → local-auth-context.test.ts} +21 -21
  465. package/src/runtime/auth/__tests__/route-policy.test.ts +2 -2
  466. package/src/runtime/auth/__tests__/scopes.test.ts +7 -7
  467. package/src/runtime/auth/__tests__/subject.test.ts +8 -8
  468. package/src/runtime/auth/__tests__/token-service.test.ts +0 -1
  469. package/src/runtime/auth/route-policy.ts +8 -4
  470. package/src/runtime/auth/scopes.ts +1 -1
  471. package/src/runtime/auth/subject.ts +4 -4
  472. package/src/runtime/auth/token-service.ts +0 -23
  473. package/src/runtime/auth/types.ts +3 -3
  474. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  475. package/src/runtime/guardian-action-grant-minter.ts +1 -1
  476. package/src/runtime/guardian-action-service.ts +3 -3
  477. package/src/runtime/http-server.ts +15 -2
  478. package/src/runtime/invite-service.ts +3 -3
  479. package/src/runtime/local-actor-identity.ts +17 -22
  480. package/src/runtime/pending-interactions.ts +21 -9
  481. package/src/runtime/routes/app-management-routes.ts +2 -3
  482. package/src/runtime/routes/approval-routes.ts +1 -3
  483. package/src/runtime/routes/btw-routes.ts +155 -0
  484. package/src/runtime/routes/computer-use-routes.ts +77 -31
  485. package/src/runtime/routes/conversation-routes.ts +230 -46
  486. package/src/runtime/routes/diagnostics-routes.ts +63 -29
  487. package/src/runtime/routes/documents-routes.ts +2 -2
  488. package/src/runtime/routes/global-search-routes.ts +1 -1
  489. package/src/runtime/routes/host-bash-routes.ts +83 -0
  490. package/src/runtime/routes/host-file-routes.ts +79 -0
  491. package/src/runtime/routes/integrations/slack/share.ts +1 -1
  492. package/src/runtime/routes/log-export-routes.ts +120 -0
  493. package/src/runtime/routes/mcp-routes.ts +20 -0
  494. package/src/runtime/routes/migration-routes.ts +3 -3
  495. package/src/runtime/routes/pairing-routes.ts +1 -1
  496. package/src/runtime/routes/recording-routes.ts +6 -4
  497. package/src/runtime/routes/schedule-routes.ts +31 -5
  498. package/src/runtime/routes/session-management-routes.ts +2 -6
  499. package/src/runtime/routes/session-query-routes.ts +18 -15
  500. package/src/runtime/routes/settings-routes.ts +7 -261
  501. package/src/runtime/routes/skills-routes.ts +7 -6
  502. package/src/runtime/routes/subagents-routes.ts +4 -10
  503. package/src/runtime/routes/surface-action-routes.ts +3 -14
  504. package/src/runtime/routes/surface-content-routes.ts +22 -5
  505. package/src/runtime/routes/work-items-routes.ts +21 -25
  506. package/src/runtime/routes/workspace-routes.test.ts +3 -3
  507. package/src/runtime/routes/workspace-utils.ts +1 -1
  508. package/src/runtime/telegram-streaming-delivery.ts +3 -0
  509. package/src/runtime/verification-outbound-actions.ts +2 -2
  510. package/src/schedule/integration-status.ts +0 -6
  511. package/src/schedule/schedule-store.ts +234 -43
  512. package/src/schedule/scheduler.ts +73 -74
  513. package/src/security/oauth2.ts +1 -1
  514. package/src/sequence/store.ts +12 -2
  515. package/src/skills/frontmatter.ts +19 -77
  516. package/src/skills/managed-store.ts +11 -2
  517. package/src/subagent/manager.ts +5 -3
  518. package/src/tasks/ephemeral-permissions.ts +3 -5
  519. package/src/tools/AGENTS.md +0 -1
  520. package/src/tools/browser/browser-manager.ts +17 -11
  521. package/src/tools/browser/jit-auth.ts +4 -1
  522. package/src/tools/claude-code/claude-code.ts +1 -1
  523. package/src/tools/computer-use/definitions.ts +48 -60
  524. package/src/tools/document/document-tool.ts +6 -6
  525. package/src/tools/filesystem/edit.ts +2 -1
  526. package/src/tools/filesystem/read.ts +20 -2
  527. package/src/tools/filesystem/write.ts +2 -1
  528. package/src/tools/host-filesystem/edit.ts +17 -1
  529. package/src/tools/host-filesystem/read.ts +16 -1
  530. package/src/tools/host-filesystem/write.ts +15 -1
  531. package/src/tools/host-terminal/host-shell.ts +24 -0
  532. package/src/tools/memory/definitions.ts +45 -81
  533. package/src/tools/memory/handlers.test.ts +0 -1
  534. package/src/tools/memory/handlers.ts +1 -1
  535. package/src/tools/memory/register.ts +26 -60
  536. package/src/tools/network/script-proxy/session-manager.ts +6 -8
  537. package/src/tools/network/web-fetch.ts +7 -1
  538. package/src/tools/network/web-search.ts +2 -1
  539. package/src/tools/registry.ts +23 -0
  540. package/src/tools/schedule/create.ts +113 -5
  541. package/src/tools/schedule/list.ts +57 -15
  542. package/src/tools/schedule/update.ts +73 -3
  543. package/src/tools/shared/filesystem/image-read.ts +192 -0
  544. package/src/tools/side-effects.ts +1 -7
  545. package/src/tools/skills/delete-managed.ts +27 -64
  546. package/src/tools/skills/execute.ts +54 -0
  547. package/src/tools/skills/load.ts +127 -5
  548. package/src/tools/skills/scaffold-managed.ts +93 -172
  549. package/src/tools/subagent/message.ts +0 -7
  550. package/src/tools/subagent/spawn.ts +1 -1
  551. package/src/tools/swarm/delegate.ts +0 -3
  552. package/src/tools/system/avatar-generator.ts +13 -19
  553. package/src/tools/system/request-permission.ts +2 -1
  554. package/src/tools/terminal/safe-env.ts +1 -0
  555. package/src/tools/tool-manifest.ts +41 -47
  556. package/src/tools/types.ts +6 -2
  557. package/src/tools/ui-surface/definitions.ts +0 -55
  558. package/src/util/errors.ts +0 -10
  559. package/src/workspace/git-service.ts +0 -2
  560. package/src/__tests__/account-registry.test.ts +0 -258
  561. package/src/__tests__/email-classifier.test.ts +0 -25
  562. package/src/__tests__/gmail-integration.test.ts +0 -97
  563. package/src/__tests__/handle-user-message-secret-resume.test.ts +0 -172
  564. package/src/__tests__/managed-twitter-guardrails.test.ts +0 -357
  565. package/src/__tests__/recording-intent-fallback.test.ts +0 -199
  566. package/src/__tests__/recording-intent.test.ts +0 -985
  567. package/src/__tests__/recording-state-machine.test.ts +0 -1574
  568. package/src/__tests__/reminder-store.test.ts +0 -350
  569. package/src/__tests__/reminder.test.ts +0 -337
  570. package/src/__tests__/scan-result-store.test.ts +0 -121
  571. package/src/__tests__/twitter-platform-proxy-client.test.ts +0 -475
  572. package/src/__tests__/view-image-tool.test.ts +0 -241
  573. package/src/cli/commands/amazon/cart.ts +0 -513
  574. package/src/cli/commands/amazon/checkout.ts +0 -394
  575. package/src/cli/commands/amazon/client.ts +0 -513
  576. package/src/cli/commands/amazon/index.ts +0 -885
  577. package/src/cli/commands/amazon/product-details.ts +0 -145
  578. package/src/cli/commands/amazon/request-extractor.ts +0 -187
  579. package/src/cli/commands/amazon/search.ts +0 -76
  580. package/src/cli/commands/amazon/session.ts +0 -108
  581. package/src/cli/commands/twitter/__tests__/cli-read-routing.test.ts +0 -345
  582. package/src/cli/commands/twitter/__tests__/cli-routing.test.ts +0 -252
  583. package/src/cli/commands/twitter/__tests__/oauth-client.test.ts +0 -151
  584. package/src/cli/commands/twitter/index.ts +0 -420
  585. package/src/cli/commands/twitter/oauth-client.ts +0 -60
  586. package/src/cli/commands/twitter/router.ts +0 -351
  587. package/src/cli/commands/twitter/types.ts +0 -30
  588. package/src/config/bundled-skills/agentmail/SKILL.md +0 -132
  589. package/src/config/bundled-skills/agentmail/icon.svg +0 -21
  590. package/src/config/bundled-skills/amazon/SKILL.md +0 -136
  591. package/src/config/bundled-skills/amazon/icon.svg +0 -13
  592. package/src/config/bundled-skills/api-mapping/SKILL.md +0 -78
  593. package/src/config/bundled-skills/api-mapping/icon.svg +0 -18
  594. package/src/config/bundled-skills/cli-discover/SKILL.md +0 -68
  595. package/src/config/bundled-skills/deploy-fullstack-vercel/SKILL.md +0 -179
  596. package/src/config/bundled-skills/document-writer/SKILL.md +0 -195
  597. package/src/config/bundled-skills/elevenlabs-voice/SKILL.md +0 -140
  598. package/src/config/bundled-skills/email-setup/SKILL.md +0 -68
  599. package/src/config/bundled-skills/frontend-design/SKILL.md +0 -44
  600. package/src/config/bundled-skills/frontend-design/icon.svg +0 -16
  601. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +0 -452
  602. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +0 -203
  603. package/src/config/bundled-skills/influencer/SKILL.md +0 -144
  604. package/src/config/bundled-skills/influencer/scripts/client.ts +0 -1269
  605. package/src/config/bundled-skills/influencer/scripts/influencer.ts +0 -267
  606. package/src/config/bundled-skills/macos-automation/SKILL.md +0 -65
  607. package/src/config/bundled-skills/macos-automation/icon.svg +0 -12
  608. package/src/config/bundled-skills/mcp-setup/SKILL.md +0 -75
  609. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +0 -184
  610. package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +0 -80
  611. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +0 -29
  612. package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +0 -56
  613. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +0 -34
  614. package/src/config/bundled-skills/messaging/tools/gmail-download-attachment.ts +0 -47
  615. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +0 -31
  616. package/src/config/bundled-skills/messaging/tools/gmail-list-attachments.ts +0 -67
  617. package/src/config/bundled-skills/messaging/tools/gmail-send-with-attachments.ts +0 -97
  618. package/src/config/bundled-skills/messaging/tools/gmail-summarize-thread.ts +0 -87
  619. package/src/config/bundled-skills/messaging/tools/gmail-triage.ts +0 -135
  620. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +0 -24
  621. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +0 -201
  622. package/src/config/bundled-skills/messaging/tools/send-notification.ts +0 -1
  623. package/src/config/bundled-skills/messaging/tools/sequence-cancel.ts +0 -27
  624. package/src/config/bundled-skills/messaging/tools/sequence-pause.ts +0 -48
  625. package/src/config/bundled-skills/messaging/tools/sequence-resume.ts +0 -27
  626. package/src/config/bundled-skills/messaging/tools/sequence-update.ts +0 -56
  627. package/src/config/bundled-skills/notion/SKILL.md +0 -240
  628. package/src/config/bundled-skills/notion-oauth-setup/SKILL.md +0 -126
  629. package/src/config/bundled-skills/oauth-setup/SKILL.md +0 -143
  630. package/src/config/bundled-skills/public-ingress/SKILL.md +0 -258
  631. package/src/config/bundled-skills/reminder/SKILL.md +0 -79
  632. package/src/config/bundled-skills/reminder/TOOLS.json +0 -89
  633. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +0 -12
  634. package/src/config/bundled-skills/restaurant-reservation/SKILL.md +0 -141
  635. package/src/config/bundled-skills/screen-recording/SKILL.md +0 -148
  636. package/src/config/bundled-skills/self-upgrade/SKILL.md +0 -69
  637. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -78
  638. package/src/config/bundled-skills/slack-app-setup/SKILL.md +0 -178
  639. package/src/config/bundled-skills/slack-digest-setup/SKILL.md +0 -163
  640. package/src/config/bundled-skills/slack-oauth-setup/SKILL.md +0 -157
  641. package/src/config/bundled-skills/start-the-day/SKILL.md +0 -70
  642. package/src/config/bundled-skills/start-the-day/icon.svg +0 -13
  643. package/src/config/bundled-skills/telegram-setup/SKILL.md +0 -105
  644. package/src/config/bundled-skills/time-based-actions/SKILL.md +0 -142
  645. package/src/config/bundled-skills/twilio-setup/SKILL.md +0 -232
  646. package/src/config/bundled-skills/twitter/SKILL.md +0 -206
  647. package/src/config/bundled-skills/twitter/icon.svg +0 -14
  648. package/src/config/bundled-skills/typescript-eval/SKILL.md +0 -60
  649. package/src/config/bundled-skills/vercel-token-setup/SKILL.md +0 -214
  650. package/src/config/bundled-skills/voice-setup/SKILL.md +0 -131
  651. package/src/config/bundled-skills/voice-setup/icon.svg +0 -20
  652. package/src/daemon/handlers/pairing.ts +0 -119
  653. package/src/daemon/handlers/session-user-message.ts +0 -961
  654. package/src/daemon/recording-executor.ts +0 -180
  655. package/src/daemon/recording-intent-fallback.ts +0 -162
  656. package/src/daemon/recording-intent.ts +0 -493
  657. package/src/memory/account-store.ts +0 -117
  658. package/src/messaging/activity-analyzer.ts +0 -76
  659. package/src/messaging/email-classifier.ts +0 -208
  660. package/src/messaging/index.ts +0 -2
  661. package/src/messaging/outreach-classifier.ts +0 -185
  662. package/src/messaging/thread-summarizer.ts +0 -346
  663. package/src/messaging/types.ts +0 -17
  664. package/src/tools/browser/x-auto-navigate.ts +0 -254
  665. package/src/tools/credentials/account-registry.ts +0 -144
  666. package/src/tools/filesystem/view-image.ts +0 -244
  667. package/src/tools/reminder/reminder-store.ts +0 -194
  668. package/src/tools/reminder/reminder.ts +0 -158
  669. package/src/tools/system/navigate-settings.ts +0 -74
  670. package/src/tools/system/open-system-settings.ts +0 -85
  671. package/src/tools/system/version.ts +0 -54
  672. package/src/twitter/platform-proxy-client.ts +0 -408
  673. /package/src/config/bundled-skills/{messaging → gmail}/tools/scan-result-store.ts +0 -0
  674. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-analytics.ts +0 -0
  675. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-create.ts +0 -0
  676. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-delete.ts +0 -0
  677. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-enroll.ts +0 -0
  678. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-enrollment-list.ts +0 -0
  679. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-get.ts +0 -0
  680. /package/src/config/bundled-skills/{messaging → sequences}/tools/sequence-import.ts +0 -0
  681. /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
- }