@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.
- package/README.md +20 -27
- package/dist/bundled-skills/lobu/SKILL.md +11 -11
- package/dist/commands/_lib/apply/apply-cmd.d.ts +2 -0
- package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
- package/dist/commands/_lib/apply/apply-cmd.js +26 -0
- package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
- package/dist/commands/_lib/apply/client.d.ts +1 -1
- package/dist/commands/_lib/apply/client.d.ts.map +1 -1
- package/dist/commands/_lib/apply/desired-state.js +4 -4
- package/dist/commands/_lib/apply/desired-state.js.map +1 -1
- package/dist/commands/agent.d.ts +7 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +65 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/chat.d.ts +12 -9
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +117 -56
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/dev.d.ts +15 -7
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +79 -44
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +136 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/eval.d.ts +8 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +56 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/init.d.ts +20 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +332 -183
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +11 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +28 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +14 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
- package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/mcp.d.ts +2 -2
- package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
- package/dist/commands/memory/_lib/mcp.js +24 -12
- package/dist/commands/memory/_lib/mcp.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
- package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
- package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/schema.d.ts +1 -1
- package/dist/commands/memory/_lib/schema.js +1 -1
- package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.js +5 -6
- package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
- package/dist/commands/memory/run.d.ts.map +1 -1
- package/dist/commands/memory/run.js +2 -2
- package/dist/commands/memory/run.js.map +1 -1
- package/dist/commands/platforms/platform-prompts.d.ts +0 -1
- package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
- package/dist/commands/platforms/platform-prompts.js +54 -8
- package/dist/commands/platforms/platform-prompts.js.map +1 -1
- package/dist/commands/telemetry.d.ts +10 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +68 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/connectors/README.md +534 -0
- package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
- package/dist/connectors/browser-scraper-utils.ts +214 -0
- package/dist/connectors/capterra.ts +273 -0
- package/dist/connectors/g2.ts +286 -0
- package/dist/connectors/github.ts +1553 -0
- package/dist/connectors/glassdoor.ts +291 -0
- package/dist/connectors/gmaps.ts +197 -0
- package/dist/connectors/google_calendar.ts +631 -0
- package/dist/connectors/google_gmail.ts +751 -0
- package/dist/connectors/google_photos.ts +776 -0
- package/dist/connectors/google_play.ts +342 -0
- package/dist/connectors/hackernews.ts +471 -0
- package/dist/connectors/index.ts +23 -0
- package/dist/connectors/ios_appstore.ts +226 -0
- package/dist/connectors/linkedin.ts +471 -0
- package/dist/connectors/microsoft_outlook.ts +410 -0
- package/dist/connectors/producthunt.ts +471 -0
- package/dist/connectors/reddit.ts +600 -0
- package/dist/connectors/rss.ts +448 -0
- package/dist/connectors/spotify.ts +590 -0
- package/dist/connectors/trustpilot.ts +199 -0
- package/dist/connectors/website.ts +629 -0
- package/dist/connectors/whatsapp.ts +1073 -0
- package/dist/connectors/x.ts +526 -0
- package/dist/connectors/youtube.ts +666 -0
- package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
- package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
- package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
- package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
- package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
- package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
- package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
- package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
- package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
- package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
- package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
- package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
- package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
- package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
- package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
- package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
- package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
- package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
- package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
- package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
- package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
- package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
- package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
- package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
- package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
- package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
- package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
- package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
- package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
- package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
- package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
- package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
- package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
- package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
- package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
- package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
- package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
- package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
- package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
- package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
- package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
- package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
- package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
- package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
- package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
- package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -23
- package/dist/index.js.map +1 -1
- package/dist/internal/api-client.d.ts +4 -8
- package/dist/internal/api-client.d.ts.map +1 -1
- package/dist/internal/api-client.js +1 -1
- package/dist/internal/api-client.js.map +1 -1
- package/dist/internal/context.js +2 -2
- package/dist/internal/context.js.map +1 -1
- package/dist/internal/credentials.d.ts.map +1 -1
- package/dist/internal/credentials.js +6 -1
- package/dist/internal/credentials.js.map +1 -1
- package/dist/internal/index.d.ts +2 -3
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +2 -2
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/oauth.d.ts +6 -5
- package/dist/internal/oauth.d.ts.map +1 -1
- package/dist/internal/oauth.js +2 -2
- package/dist/internal/project-link.d.ts +10 -0
- package/dist/internal/project-link.d.ts.map +1 -0
- package/dist/internal/project-link.js +48 -0
- package/dist/internal/project-link.js.map +1 -0
- package/dist/providers.json +2 -2
- package/dist/server.bundle.mjs +3090 -4321
- package/dist/start-local.bundle.mjs +71481 -0
- package/dist/templates/README.md.tmpl +10 -11
- package/package.json +14 -12
- package/dist/__tests__/chat.integration.test.d.ts +0 -2
- package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
- package/dist/__tests__/chat.integration.test.js +0 -337
- package/dist/__tests__/chat.integration.test.js.map +0 -1
- package/dist/__tests__/dev.test.d.ts +0 -2
- package/dist/__tests__/dev.test.d.ts.map +0 -1
- package/dist/__tests__/dev.test.js +0 -25
- package/dist/__tests__/dev.test.js.map +0 -1
- package/dist/__tests__/init-memory.test.d.ts +0 -2
- package/dist/__tests__/init-memory.test.d.ts.map +0 -1
- package/dist/__tests__/init-memory.test.js +0 -45
- package/dist/__tests__/init-memory.test.js.map +0 -1
- package/dist/__tests__/token.test.d.ts +0 -2
- package/dist/__tests__/token.test.d.ts.map +0 -1
- package/dist/__tests__/token.test.js +0 -52
- package/dist/__tests__/token.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
- package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
- package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
- package/dist/commands/apply.d.ts +0 -3
- package/dist/commands/apply.d.ts.map +0 -1
- package/dist/commands/apply.js +0 -5
- package/dist/commands/apply.js.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
- package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
- package/dist/internal/__tests__/api-client.test.d.ts +0 -2
- package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
- package/dist/internal/__tests__/api-client.test.js +0 -95
- package/dist/internal/__tests__/api-client.test.js.map +0 -1
- package/dist/internal/__tests__/context.test.d.ts +0 -2
- package/dist/internal/__tests__/context.test.d.ts.map +0 -1
- package/dist/internal/__tests__/context.test.js +0 -77
- 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 | - |
|