@lobu/cli 6.0.1 → 6.1.1

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 (217) hide show
  1. package/README.md +20 -27
  2. package/dist/bundled-skills/lobu/SKILL.md +11 -11
  3. package/dist/commands/_lib/apply/apply-cmd.d.ts +2 -0
  4. package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
  5. package/dist/commands/_lib/apply/apply-cmd.js +26 -0
  6. package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
  7. package/dist/commands/_lib/apply/client.d.ts +1 -1
  8. package/dist/commands/_lib/apply/client.d.ts.map +1 -1
  9. package/dist/commands/_lib/apply/desired-state.js +4 -4
  10. package/dist/commands/_lib/apply/desired-state.js.map +1 -1
  11. package/dist/commands/agent.d.ts +7 -0
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +65 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/chat.d.ts +12 -9
  16. package/dist/commands/chat.d.ts.map +1 -1
  17. package/dist/commands/chat.js +117 -56
  18. package/dist/commands/chat.js.map +1 -1
  19. package/dist/commands/dev.d.ts +15 -7
  20. package/dist/commands/dev.d.ts.map +1 -1
  21. package/dist/commands/dev.js +79 -44
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/doctor.d.ts +1 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +136 -0
  26. package/dist/commands/doctor.js.map +1 -1
  27. package/dist/commands/eval.d.ts +8 -0
  28. package/dist/commands/eval.d.ts.map +1 -1
  29. package/dist/commands/eval.js +56 -1
  30. package/dist/commands/eval.js.map +1 -1
  31. package/dist/commands/init.d.ts +20 -5
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +332 -183
  34. package/dist/commands/init.js.map +1 -1
  35. package/dist/commands/link.d.ts +11 -0
  36. package/dist/commands/link.d.ts.map +1 -0
  37. package/dist/commands/link.js +28 -0
  38. package/dist/commands/link.js.map +1 -0
  39. package/dist/commands/login.d.ts.map +1 -1
  40. package/dist/commands/login.js +14 -2
  41. package/dist/commands/login.js.map +1 -1
  42. package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
  43. package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
  44. package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
  45. package/dist/commands/memory/_lib/mcp.d.ts +2 -2
  46. package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
  47. package/dist/commands/memory/_lib/mcp.js +24 -12
  48. package/dist/commands/memory/_lib/mcp.js.map +1 -1
  49. package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
  50. package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
  51. package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
  52. package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
  53. package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
  54. package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
  55. package/dist/commands/memory/_lib/schema.d.ts +1 -1
  56. package/dist/commands/memory/_lib/schema.js +1 -1
  57. package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
  58. package/dist/commands/memory/_lib/seed-cmd.js +5 -6
  59. package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
  60. package/dist/commands/memory/run.d.ts.map +1 -1
  61. package/dist/commands/memory/run.js +2 -2
  62. package/dist/commands/memory/run.js.map +1 -1
  63. package/dist/commands/platforms/platform-prompts.d.ts +0 -1
  64. package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
  65. package/dist/commands/platforms/platform-prompts.js +54 -8
  66. package/dist/commands/platforms/platform-prompts.js.map +1 -1
  67. package/dist/commands/telemetry.d.ts +10 -0
  68. package/dist/commands/telemetry.d.ts.map +1 -0
  69. package/dist/commands/telemetry.js +68 -0
  70. package/dist/commands/telemetry.js.map +1 -0
  71. package/dist/commands/whoami.d.ts.map +1 -1
  72. package/dist/commands/whoami.js +1 -1
  73. package/dist/commands/whoami.js.map +1 -1
  74. package/dist/connectors/README.md +534 -0
  75. package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
  76. package/dist/connectors/browser-scraper-utils.ts +214 -0
  77. package/dist/connectors/capterra.ts +273 -0
  78. package/dist/connectors/g2.ts +286 -0
  79. package/dist/connectors/github.ts +1553 -0
  80. package/dist/connectors/glassdoor.ts +291 -0
  81. package/dist/connectors/gmaps.ts +197 -0
  82. package/dist/connectors/google_calendar.ts +631 -0
  83. package/dist/connectors/google_gmail.ts +751 -0
  84. package/dist/connectors/google_photos.ts +776 -0
  85. package/dist/connectors/google_play.ts +342 -0
  86. package/dist/connectors/hackernews.ts +471 -0
  87. package/dist/connectors/index.ts +23 -0
  88. package/dist/connectors/ios_appstore.ts +226 -0
  89. package/dist/connectors/linkedin.ts +471 -0
  90. package/dist/connectors/microsoft_outlook.ts +410 -0
  91. package/dist/connectors/producthunt.ts +471 -0
  92. package/dist/connectors/reddit.ts +600 -0
  93. package/dist/connectors/rss.ts +448 -0
  94. package/dist/connectors/spotify.ts +590 -0
  95. package/dist/connectors/trustpilot.ts +199 -0
  96. package/dist/connectors/website.ts +629 -0
  97. package/dist/connectors/whatsapp.ts +1073 -0
  98. package/dist/connectors/x.ts +526 -0
  99. package/dist/connectors/youtube.ts +666 -0
  100. package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
  101. package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
  102. package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
  103. package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
  104. package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
  105. package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
  106. package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
  107. package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
  108. package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
  109. package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
  110. package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
  111. package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
  112. package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
  113. package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
  114. package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
  115. package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
  116. package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
  117. package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
  118. package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
  119. package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
  120. package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
  121. package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
  122. package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
  123. package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
  124. package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
  125. package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
  126. package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
  127. package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
  128. package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
  129. package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
  130. package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
  131. package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
  132. package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
  133. package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
  134. package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
  135. package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
  136. package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
  137. package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
  138. package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
  139. package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
  140. package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
  141. package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
  142. package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
  143. package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
  144. package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
  145. package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +147 -23
  148. package/dist/index.js.map +1 -1
  149. package/dist/internal/api-client.d.ts +4 -8
  150. package/dist/internal/api-client.d.ts.map +1 -1
  151. package/dist/internal/api-client.js +1 -1
  152. package/dist/internal/api-client.js.map +1 -1
  153. package/dist/internal/context.js +2 -2
  154. package/dist/internal/context.js.map +1 -1
  155. package/dist/internal/credentials.d.ts.map +1 -1
  156. package/dist/internal/credentials.js +6 -1
  157. package/dist/internal/credentials.js.map +1 -1
  158. package/dist/internal/index.d.ts +2 -3
  159. package/dist/internal/index.d.ts.map +1 -1
  160. package/dist/internal/index.js +2 -2
  161. package/dist/internal/index.js.map +1 -1
  162. package/dist/internal/oauth.d.ts +6 -5
  163. package/dist/internal/oauth.d.ts.map +1 -1
  164. package/dist/internal/oauth.js +2 -2
  165. package/dist/internal/project-link.d.ts +10 -0
  166. package/dist/internal/project-link.d.ts.map +1 -0
  167. package/dist/internal/project-link.js +48 -0
  168. package/dist/internal/project-link.js.map +1 -0
  169. package/dist/providers.json +2 -2
  170. package/dist/server.bundle.mjs +3090 -4321
  171. package/dist/start-local.bundle.mjs +71481 -0
  172. package/dist/templates/README.md.tmpl +10 -11
  173. package/package.json +14 -12
  174. package/dist/__tests__/chat.integration.test.d.ts +0 -2
  175. package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
  176. package/dist/__tests__/chat.integration.test.js +0 -337
  177. package/dist/__tests__/chat.integration.test.js.map +0 -1
  178. package/dist/__tests__/dev.test.d.ts +0 -2
  179. package/dist/__tests__/dev.test.d.ts.map +0 -1
  180. package/dist/__tests__/dev.test.js +0 -25
  181. package/dist/__tests__/dev.test.js.map +0 -1
  182. package/dist/__tests__/init-memory.test.d.ts +0 -2
  183. package/dist/__tests__/init-memory.test.d.ts.map +0 -1
  184. package/dist/__tests__/init-memory.test.js +0 -45
  185. package/dist/__tests__/init-memory.test.js.map +0 -1
  186. package/dist/__tests__/token.test.d.ts +0 -2
  187. package/dist/__tests__/token.test.d.ts.map +0 -1
  188. package/dist/__tests__/token.test.js +0 -52
  189. package/dist/__tests__/token.test.js.map +0 -1
  190. package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
  191. package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
  192. package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
  193. package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
  194. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
  195. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
  196. package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
  197. package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
  198. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
  199. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
  200. package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
  201. package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
  202. package/dist/commands/apply.d.ts +0 -3
  203. package/dist/commands/apply.d.ts.map +0 -1
  204. package/dist/commands/apply.js +0 -5
  205. package/dist/commands/apply.js.map +0 -1
  206. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
  207. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
  208. package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
  209. package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
  210. package/dist/internal/__tests__/api-client.test.d.ts +0 -2
  211. package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
  212. package/dist/internal/__tests__/api-client.test.js +0 -95
  213. package/dist/internal/__tests__/api-client.test.js.map +0 -1
  214. package/dist/internal/__tests__/context.test.d.ts +0 -2
  215. package/dist/internal/__tests__/context.test.d.ts.map +0 -1
  216. package/dist/internal/__tests__/context.test.js +0 -77
  217. package/dist/internal/__tests__/context.test.js.map +0 -1
@@ -0,0 +1,534 @@
1
+ # Connector SDK
2
+
3
+ Connectors are TypeScript modules that sync data from external services into Lobu and optionally execute write-back actions. Each connector is a single `.ts` file that exports a class extending `ConnectorRuntime` from `@lobu/connector-sdk`.
4
+
5
+ ## Quick Start
6
+
7
+ ```typescript
8
+ import {
9
+ type ConnectorDefinition,
10
+ ConnectorRuntime,
11
+ type SyncContext,
12
+ type SyncResult,
13
+ type ActionContext,
14
+ type ActionResult,
15
+ type EventEnvelope,
16
+ } from '@lobu/connector-sdk';
17
+
18
+ export default class MyConnector extends ConnectorRuntime {
19
+ readonly definition: ConnectorDefinition = {
20
+ key: 'my_connector',
21
+ name: 'My Connector',
22
+ description: 'Fetches data from My Service.',
23
+ version: '1.0.0',
24
+ faviconDomain: 'example.com',
25
+ authSchema: {
26
+ methods: [{ type: 'none' }],
27
+ },
28
+ feeds: {
29
+ items: {
30
+ key: 'items',
31
+ name: 'Items',
32
+ description: 'Sync items from the service.',
33
+ configSchema: {
34
+ type: 'object',
35
+ required: ['query'],
36
+ properties: {
37
+ query: { type: 'string', description: 'Search query' },
38
+ },
39
+ },
40
+ eventKinds: {
41
+ item: {
42
+ description: 'An item from the service',
43
+ metadataSchema: {
44
+ type: 'object',
45
+ properties: {
46
+ score: { type: 'number' },
47
+ },
48
+ },
49
+ },
50
+ },
51
+ },
52
+ },
53
+ };
54
+
55
+ async sync(ctx: SyncContext): Promise<SyncResult> {
56
+ const query = ctx.config.query as string;
57
+ // Fetch data, transform to events...
58
+ const events: EventEnvelope[] = [];
59
+
60
+ return {
61
+ events,
62
+ checkpoint: { last_sync_at: new Date().toISOString() },
63
+ metadata: { items_found: events.length },
64
+ };
65
+ }
66
+
67
+ async execute(_ctx: ActionContext): Promise<ActionResult> {
68
+ return { success: false, error: 'Actions not supported' };
69
+ }
70
+ }
71
+ ```
72
+
73
+ ## Connector Definition
74
+
75
+ The `definition` property declares everything about your connector: metadata, auth requirements, available feeds, actions, and configuration schemas.
76
+
77
+ ```typescript
78
+ interface ConnectorDefinition {
79
+ key: string; // Unique identifier (e.g. 'github', 'rss')
80
+ name: string; // Display name
81
+ description?: string; // What this connector does
82
+ version: string; // Semver
83
+ faviconDomain?: string; // Domain for favicon lookup (e.g. 'github.com')
84
+ authSchema?: ConnectorAuthSchema; // Authentication configuration
85
+ feeds?: Record<string, FeedDefinition>; // Data sources (keyed by feed_key)
86
+ actions?: Record<string, ActionDefinition>; // Write-back actions
87
+ optionsSchema?: Record<string, unknown>; // Global connector options (JSON Schema)
88
+ mcpConfig?: { upstreamUrl: string }; // Proxy an upstream MCP server
89
+ openapiConfig?: { // Generate actions from an OpenAPI spec
90
+ specUrl: string;
91
+ includeOperations?: string[];
92
+ excludeOperations?: string[];
93
+ includeTags?: string[];
94
+ serverUrl?: string;
95
+ };
96
+ }
97
+ ```
98
+
99
+ ### MCP Config
100
+
101
+ Set `mcpConfig` to proxy an upstream MCP server through Lobu. The connector acts as a bridge, exposing the MCP server's tools as connector actions. Useful for wrapping existing MCP servers with Lobu's auth, approval, and audit trail.
102
+
103
+ ### OpenAPI Config
104
+
105
+ Set `openapiConfig` to auto-generate connector actions from an OpenAPI specification. The platform fetches the spec, filters operations by `includeOperations`/`excludeOperations`/`includeTags`, and exposes them as actions. Useful for REST APIs that already have OpenAPI docs.
106
+
107
+ ## Authentication
108
+
109
+ The `authSchema.methods` array declares which auth methods your connector supports. Users configure credentials via auth profiles in the UI. A connector can support multiple methods (e.g. OAuth primary + env_keys fallback).
110
+
111
+ ### `none` - No authentication
112
+
113
+ ```typescript
114
+ authSchema: { methods: [{ type: 'none' }] }
115
+ ```
116
+
117
+ ### `env_keys` - API keys / tokens
118
+
119
+ ```typescript
120
+ authSchema: {
121
+ methods: [{
122
+ type: 'env_keys',
123
+ required: true,
124
+ scope: 'connection', // 'connection' (default) or 'organization'
125
+ description: 'API key for authentication.',
126
+ fields: [
127
+ {
128
+ key: 'API_KEY', // Key name, accessed via ctx.credentials
129
+ label: 'API Key', // UI label
130
+ description: 'Your service API key',
131
+ example: 'sk-...', // Placeholder hint
132
+ secret: true, // Mask in UI
133
+ required: true, // Whether this field is required
134
+ },
135
+ ],
136
+ }],
137
+ }
138
+ ```
139
+
140
+ When `scope` is `'organization'`, the auth profile is shared across all connections in the org. Default is `'connection'` (per-connection credentials).
141
+
142
+ ### `oauth` - OAuth providers
143
+
144
+ ```typescript
145
+ authSchema: {
146
+ methods: [{
147
+ type: 'oauth',
148
+ provider: 'github', // Built-in: github | google | reddit
149
+ requiredScopes: ['repo', 'read:user'],
150
+ required: false, // Whether OAuth is mandatory or optional
151
+ scope: 'connection', // 'connection' or 'organization'
152
+ description: 'Enables private repo access.',
153
+ setupInstructions: 'Create an OAuth App at ... Set callback URL to {{redirect_uri}}.',
154
+ // For custom OAuth providers (not built-in):
155
+ authorizationUrl: 'https://example.com/oauth/authorize',
156
+ tokenUrl: 'https://example.com/oauth/token',
157
+ clientIdKey: 'EXAMPLE_CLIENT_ID', // Env key for client ID
158
+ clientSecretKey: 'EXAMPLE_CLIENT_SECRET', // Env key for client secret
159
+ }],
160
+ }
161
+ ```
162
+
163
+ The OAuth token is available at `ctx.credentials?.accessToken`. The full credentials shape:
164
+
165
+ ```typescript
166
+ interface SyncCredentials {
167
+ provider: string; // e.g. 'github'
168
+ accessToken: string; // The OAuth access token
169
+ refreshToken?: string | null; // For token refresh
170
+ expiresAt?: string | null; // Token expiration (ISO string)
171
+ scope?: string | null; // Granted scopes
172
+ }
173
+ ```
174
+
175
+ ### `browser` - Browser session (cookies/CDP)
176
+
177
+ For connectors that scrape authenticated pages:
178
+
179
+ ```typescript
180
+ authSchema: {
181
+ methods: [{
182
+ type: 'browser',
183
+ capture: 'cli', // How auth is captured:
184
+ // 'cli' - Extract cookies from Chrome via `lobu memory browser-auth`
185
+ // 'cdp' - Connect to Chrome via DevTools Protocol (port 9222)
186
+ requiredDomains: [ // Cookie domains to extract (for 'cli' capture)
187
+ 'x.com',
188
+ '.x.com',
189
+ ],
190
+ defaultCdpUrl: 'auto', // CDP URL (for 'cdp' capture). 'auto' detects local Chrome.
191
+ description: 'Connect to Chrome for authenticated scraping.',
192
+ }],
193
+ }
194
+ ```
195
+
196
+ Use `'cdp'` for services like Google that block headless browsers — it connects to the user's real Chrome session. Use `'cli'` for sites where extracted cookies are sufficient.
197
+
198
+ ## Feeds
199
+
200
+ Feeds define the data sources your connector can sync. Each feed has:
201
+
202
+ ```typescript
203
+ interface FeedDefinition {
204
+ key: string; // Unique identifier within the connector
205
+ name: string; // Display name
206
+ description?: string; // What this feed syncs
207
+ displayNameTemplate?: string; // Template using config values: "{repo_owner}/{repo_name} issues"
208
+ configSchema?: object; // JSON Schema for feed-specific configuration
209
+ eventKinds?: Record<string, { // Event types this feed produces
210
+ description?: string;
211
+ metadataSchema?: object; // JSON Schema for event metadata
212
+ }>;
213
+ }
214
+ ```
215
+
216
+ The feed key is passed to `sync()` as `ctx.feedKey`, so a single connector can handle multiple feed types by switching on `ctx.feedKey`.
217
+
218
+ ## Syncing Data
219
+
220
+ The `sync()` method is called by the worker on a schedule. It receives a `SyncContext` and returns a `SyncResult`.
221
+
222
+ ### SyncContext
223
+
224
+ ```typescript
225
+ interface SyncContext {
226
+ feedKey: string; // Which feed to sync
227
+ config: Record<string, unknown>; // Feed + connector config merged
228
+ checkpoint: Record<string, unknown> | null; // Previous checkpoint (null on first sync)
229
+ credentials: SyncCredentials | null; // OAuth token, env keys, etc.
230
+ entityIds: number[]; // Linked entity IDs
231
+ sessionState?: Record<string, unknown>; // Browser session state (cookies, tokens)
232
+ emitEvents?: (events: EventEnvelope[]) => Promise<void>; // Stream events mid-sync
233
+ updateCheckpoint?: (cp: Record<string, unknown>) => Promise<void>; // Save progress mid-sync
234
+ }
235
+ ```
236
+
237
+ ### SyncResult
238
+
239
+ ```typescript
240
+ interface SyncResult {
241
+ events: EventEnvelope[]; // Events to ingest
242
+ checkpoint: Record<string, unknown> | null; // Updated checkpoint to persist
243
+ auth_update?: Record<string, unknown>; // Updated session state (browser cookies, etc.)
244
+ metadata?: {
245
+ items_found?: number;
246
+ items_skipped?: number;
247
+ [key: string]: unknown;
248
+ };
249
+ }
250
+ ```
251
+
252
+ ### EventEnvelope
253
+
254
+ Each piece of content is an `EventEnvelope`:
255
+
256
+ ```typescript
257
+ interface EventEnvelope {
258
+ origin_id: string; // Unique ID from the source platform
259
+ origin_type?: string; // Source-native type (must match a key in eventKinds)
260
+ payload_text: string; // Main text content
261
+ title?: string; // Title / subject
262
+ author_name?: string; // Author
263
+ source_url?: string; // Link to original
264
+ occurred_at: Date; // When the content was created
265
+ semantic_type?: string; // Semantic type (e.g. 'content', 'note', 'summary', 'fact')
266
+ score?: number; // Engagement score (0-100)
267
+ origin_parent_id?: string; // Parent reference for threaded content
268
+ metadata?: Record<string, unknown>; // Matches the eventKind's metadataSchema
269
+ embedding?: number[]; // Pre-computed embedding vector (optional)
270
+ }
271
+ ```
272
+
273
+ ### Checkpointing
274
+
275
+ Use checkpoints to implement incremental sync. Common patterns:
276
+
277
+ - **Timestamp-based**: Store `last_sync_at` and use it as a `since` filter on the next sync (see `github.ts`)
278
+ - **ID-based**: Store a list of seen IDs for deduplication, trimmed to a max size to prevent unbounded growth (see `rss.ts`)
279
+
280
+ For long-running syncs, use `ctx.emitEvents()` to stream event batches to the platform as they're collected, and `ctx.updateCheckpoint()` to persist progress. If the sync crashes mid-way, the next run resumes from the last saved checkpoint.
281
+
282
+ ## Actions
283
+
284
+ Actions let connectors write back to external services (e.g. create a GitHub issue). Define them in `definition.actions`:
285
+
286
+ ```typescript
287
+ interface ActionDefinition {
288
+ key: string; // Unique identifier
289
+ name: string; // Display name
290
+ description?: string; // What this action does
291
+ requiresApproval: boolean; // Whether user must approve before execution
292
+ inputSchema?: object; // JSON Schema for action input
293
+ outputSchema?: object; // JSON Schema for action output
294
+ annotations?: { // MCP tool annotations for client-side UX
295
+ destructiveHint?: boolean; // Action deletes or modifies data irreversibly
296
+ openWorldHint?: boolean; // Action interacts with external systems
297
+ idempotentHint?: boolean; // Safe to retry without side effects
298
+ };
299
+ }
300
+ ```
301
+
302
+ Example:
303
+
304
+ ```typescript
305
+ actions: {
306
+ create_issue: {
307
+ key: 'create_issue',
308
+ name: 'Create Issue',
309
+ description: 'Create a new issue in the repository.',
310
+ requiresApproval: true,
311
+ annotations: {
312
+ openWorldHint: true,
313
+ idempotentHint: false,
314
+ },
315
+ inputSchema: {
316
+ type: 'object',
317
+ required: ['title'],
318
+ properties: {
319
+ title: { type: 'string' },
320
+ body: { type: 'string' },
321
+ },
322
+ },
323
+ outputSchema: {
324
+ type: 'object',
325
+ properties: {
326
+ issue_number: { type: 'integer' },
327
+ url: { type: 'string' },
328
+ },
329
+ },
330
+ },
331
+ }
332
+ ```
333
+
334
+ Handle actions in `execute()`:
335
+
336
+ ```typescript
337
+ async execute(ctx: ActionContext): Promise<ActionResult> {
338
+ // ctx.actionKey - which action to run
339
+ // ctx.input - validated input matching inputSchema
340
+ // ctx.credentials - auth tokens (SyncCredentials | null)
341
+ // ctx.config - connector config
342
+
343
+ switch (ctx.actionKey) {
344
+ case 'create_issue':
345
+ const result = await createIssue(ctx.input.title, ctx.input.body);
346
+ return { success: true, output: { issue_number: result.number, url: result.url } };
347
+ default:
348
+ return { success: false, error: `Unknown action: ${ctx.actionKey}` };
349
+ }
350
+ }
351
+ ```
352
+
353
+ If your connector doesn't support actions:
354
+
355
+ ```typescript
356
+ async execute(_ctx: ActionContext): Promise<ActionResult> {
357
+ return { success: false, error: 'Actions not supported' };
358
+ }
359
+ ```
360
+
361
+ ## Options Schema
362
+
363
+ The `optionsSchema` defines global connector-level configuration (JSON Schema) that applies across all feeds. This is typically a superset of the common fields shared across feeds. It powers the connection setup UI — when a user creates a new connection, they fill out a form generated from this schema.
364
+
365
+ ## Engagement Scoring
366
+
367
+ The SDK exports `calculateEngagementScore()` for normalizing platform-specific engagement metrics to a 0-100 score:
368
+
369
+ ```typescript
370
+ import { calculateEngagementScore } from '@lobu/connector-sdk';
371
+
372
+ const score = calculateEngagementScore('reddit', {
373
+ score: 1500, // Reddit karma (upvotes - downvotes)
374
+ upvotes: 1600,
375
+ downvotes: 100,
376
+ reply_count: 42,
377
+ });
378
+ // => 15 (capped at 100)
379
+
380
+ const score2 = calculateEngagementScore('trustpilot', {
381
+ rating: 4, // Star rating (1-5)
382
+ helpful_count: 10, // Helpful votes
383
+ });
384
+ // => 45 (rating * 10 + helpful * 0.5)
385
+ ```
386
+
387
+ Signature:
388
+
389
+ ```typescript
390
+ function calculateEngagementScore(
391
+ connectorKey: string,
392
+ engagementData: {
393
+ score?: number;
394
+ upvotes?: number;
395
+ downvotes?: number;
396
+ rating?: number;
397
+ helpful_count?: number;
398
+ reply_count?: number;
399
+ }
400
+ ): number; // 0-100
401
+ ```
402
+
403
+ Platform-specific logic:
404
+ - **reddit**: `min(max(score, 0), 10000) / 100`
405
+ - **Rating-based** (reviews): `rating * 10 + helpful_count * 0.5`, capped at 100
406
+ - **Score-based** (default): `min(score, 100)`
407
+
408
+ ## Browser-Based Connectors
409
+
410
+ For scraping sites that need a real browser, import helpers from `./browser-scraper-utils.ts`. The typical pattern:
411
+
412
+ ```typescript
413
+ import {
414
+ openStealthBrowser,
415
+ handleCookieConsent,
416
+ validateUrlDomain,
417
+ withBrowserErrorCapture,
418
+ } from './browser-scraper-utils.ts';
419
+
420
+ async sync(ctx: SyncContext): Promise<SyncResult> {
421
+ validateUrlDomain(url, 'trustpilot.com');
422
+
423
+ // 1. Launch browser (tries CDP first if 'auto', falls back to Playwright)
424
+ const session = await openStealthBrowser({ cdpUrl: 'auto' });
425
+
426
+ // 2. Wrap scraping logic with error capture + guaranteed cleanup
427
+ return withBrowserErrorCapture(session, 'my-connector-sync', async (page) => {
428
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
429
+
430
+ // 3. Dismiss cookie banners
431
+ await handleCookieConsent(page, '[data-cookie-consent-accept]');
432
+
433
+ // 4. Scrape and return results
434
+ const events = await scrapeReviews(page);
435
+ return { events, checkpoint: { last_sync_at: new Date().toISOString() } };
436
+ });
437
+ }
438
+ ```
439
+
440
+ ### Utilities Reference
441
+
442
+ **`openStealthBrowser(opts?)`** - Acquire a browser session.
443
+ - `opts.cdpUrl` - `'auto'` to try CDP first (connects to user's Chrome), `null` for Playwright only
444
+ - `opts.cookies` - Playwright `Cookie[]` to inject
445
+ - `opts.authDomains` - Domains to set cookies on
446
+ - Returns `BrowserSession` with `.page`, `.browser`, `.backend` (`'cdp'` | `'playwright'`), `.ownsBrowser`
447
+
448
+ **`withBrowserErrorCapture(session, name, fn)`** - Run scraper with error artifact capture (screenshots + HTML snapshots on failure) and guaranteed browser cleanup. Always use this instead of manual try/finally.
449
+
450
+ **`handleCookieConsent(page, selector, timeout?)`** - Click a cookie consent button if it appears within `timeout` ms (default 2000). Silently continues if not found.
451
+
452
+ **`validateUrlDomain(url, domain)`** - Validate URL is HTTPS and on the expected domain. Throws on mismatch.
453
+
454
+ **`filterByCheckpoint(events, checkpoint)`** - Filter events newer than `checkpoint.last_timestamp`. Returns all events if no checkpoint.
455
+
456
+ **`getBrowserCookies(checkpoint, sessionState, connectorKey)`** - Extract browser cookies from checkpoint or session state. Throws with re-auth instructions if no cookies found.
457
+
458
+ **`validateCookieNotExpired(cookies, cookieName, connectorKey)`** - Check a specific cookie hasn't expired. Throws with re-auth instructions if expired.
459
+
460
+ ### Browser packages
461
+
462
+ Browser connectors use `patchright` (an npm alias for Playwright). The SDK also exports `acquireBrowser()` and `captureErrorArtifacts()` as lower-level primitives, but prefer the `browser-scraper-utils.ts` wrappers.
463
+
464
+ ## Worker Sandbox Environment
465
+
466
+ Connector code runs in a worker subprocess with a restricted environment. Key things to know:
467
+
468
+ - **Minimal env vars**: Only `PATH`, `HOME`, `TMPDIR`, `TZ`, `NODE_ENV`, and `NODE_PATH` are available. No access to the host's env vars.
469
+ - **Secrets via ctx**: API keys and tokens flow through `ctx.credentials` and `ctx.config`, not environment variables. The `env_keys` auth method stores secrets on auth profiles, and the platform injects them into `ctx.config` at sync time.
470
+ - **No filesystem persistence**: Don't write to disk expecting it to survive between syncs. Use `checkpoint` for state.
471
+
472
+ ## npm Dependencies
473
+
474
+ You can import npm packages inline using the `npm:` protocol:
475
+
476
+ ```typescript
477
+ import TurndownService from 'turndown';
478
+ ```
479
+
480
+ Always pin the version to avoid unexpected breakage.
481
+
482
+ ## Build & Installation
483
+
484
+ ### Generating the catalog
485
+
486
+ ```bash
487
+ npx tsx scripts/generate-connector-catalog.ts
488
+ ```
489
+
490
+ This compiles each `.ts` file in this directory via esbuild, extracts the `definition` metadata, and writes `connectors/catalog.json`. The catalog is a metadata-only index — it does not contain compiled code.
491
+
492
+ ### Auto-install per org
493
+
494
+ Connectors are **not** pre-installed globally. When an org first uses a connector, `ensureConnectorInstalled()` checks if the org already has it. If not, it:
495
+
496
+ 1. Reads the `.ts` source from `connectors/` on disk
497
+ 2. Compiles it temporarily via esbuild to extract metadata (key, name, feeds, etc.)
498
+ 3. Stores the metadata in `connector_definitions` scoped to that org
499
+ 4. Stores a `source_path` reference (e.g. `github.ts`) in `connector_versions` — **compiled code is NOT stored**
500
+
501
+ Connectors can also be installed manually via `client.connections.installConnector(...)` from inside an `execute` script (or the equivalent admin REST endpoint), passing a `source_url` or inline `source_code`. Manual installs store compiled code in the database as before.
502
+
503
+ ### How connector code runs
504
+
505
+ 1. The server checks `connector_versions.compiled_code` — if present, uses it directly
506
+ 2. If `compiled_code` is NULL (bundled connectors), it compiles from `connectors/{source_path}` on disk via esbuild
507
+ 3. The compiled code is sent to the worker, written to a temp file (`.connector-child-{pid}.mjs`), and loaded via dynamic `import()`
508
+ 4. Each sync/action runs in an **isolated child process** with a 10-minute timeout and 512MB memory limit
509
+ 5. The child process has a restricted environment — only `PATH`, `HOME`, `TMPDIR`, `TZ`, `NODE_ENV`, and `PLAYWRIGHT_BROWSERS_PATH` are available as env vars
510
+ 6. Secrets flow through `ctx.credentials` and `ctx.config`, not environment variables
511
+
512
+ This means edits to `.ts` files in `connectors/` take effect on the next sync without reinstalling.
513
+
514
+ ## Existing Connectors
515
+
516
+ | Connector | Auth | Feeds | Actions |
517
+ |-----------|------|-------|---------|
518
+ | `capterra` | none | reviews | - |
519
+ | `g2` | none | reviews | - |
520
+ | `github` | oauth/env_keys | issues, PRs, comments, discussions | create/close/reopen issues, PRs |
521
+ | `glassdoor` | none | reviews | - |
522
+ | `gmaps` | env_keys | reviews | - |
523
+ | `google_photos` | browser (CDP) | photos | - |
524
+ | `google_play` | none | reviews | - |
525
+ | `hackernews` | none | stories, comments | - |
526
+ | `ios_appstore` | none | reviews | - |
527
+ | `linkedin` | browser (CLI) | company updates, jobs | - |
528
+ | `producthunt` | env_keys | posts & comments | - |
529
+ | `reddit` | oauth/none | posts, comments | - |
530
+ | `rss` | none | articles | - |
531
+ | `trustpilot` | none | reviews | - |
532
+ | `website` | none | pages | - |
533
+ | `x` | browser (CLI) | tweets | - |
534
+ | `youtube` | oauth (Google) | videos & comments | - |