@lobu/cli 6.0.1 → 7.0.0
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 +38 -0
- package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
- package/dist/commands/_lib/apply/apply-cmd.js +574 -40
- package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
- package/dist/commands/_lib/apply/client.d.ts +180 -1
- package/dist/commands/_lib/apply/client.d.ts.map +1 -1
- package/dist/commands/_lib/apply/client.js +308 -28
- package/dist/commands/_lib/apply/client.js.map +1 -1
- package/dist/commands/_lib/apply/desired-state.d.ts +134 -3
- package/dist/commands/_lib/apply/desired-state.d.ts.map +1 -1
- package/dist/commands/_lib/apply/desired-state.js +703 -89
- package/dist/commands/_lib/apply/desired-state.js.map +1 -1
- package/dist/commands/_lib/apply/diff.d.ts +61 -3
- package/dist/commands/_lib/apply/diff.d.ts.map +1 -1
- package/dist/commands/_lib/apply/diff.js +382 -92
- package/dist/commands/_lib/apply/diff.js.map +1 -1
- package/dist/commands/_lib/apply/prompt.d.ts +6 -0
- package/dist/commands/_lib/apply/prompt.d.ts.map +1 -1
- package/dist/commands/_lib/apply/prompt.js +16 -0
- package/dist/commands/_lib/apply/prompt.js.map +1 -1
- package/dist/commands/_lib/apply/render.d.ts +9 -0
- package/dist/commands/_lib/apply/render.d.ts.map +1 -1
- package/dist/commands/_lib/apply/render.js +80 -3
- package/dist/commands/_lib/apply/render.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 +125 -57
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/dev.d.ts +23 -7
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +197 -49
- 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 +72 -6
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/init.d.ts +22 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +355 -182
- 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 +29 -2
- package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
- package/dist/commands/memory/_lib/schema.js +121 -5
- package/dist/commands/memory/_lib/schema.js.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.js +46 -24
- 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/org.d.ts +4 -0
- package/dist/commands/org.d.ts.map +1 -1
- package/dist/commands/org.js +10 -0
- package/dist/commands/org.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/token.d.ts +9 -0
- package/dist/commands/token.d.ts.map +1 -1
- package/dist/commands/token.js +54 -0
- package/dist/commands/token.js.map +1 -1
- 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/apple_health.ts +138 -0
- package/dist/connectors/apple_screen_time.ts +82 -0
- package/dist/connectors/browser-scraper-utils.ts +246 -0
- package/dist/connectors/capterra.ts +277 -0
- package/dist/connectors/g2.ts +290 -0
- package/dist/connectors/github.ts +1530 -0
- package/dist/connectors/glassdoor.ts +295 -0
- package/dist/connectors/gmaps.ts +197 -0
- package/dist/connectors/google_calendar.ts +641 -0
- package/dist/connectors/google_gmail.ts +754 -0
- package/dist/connectors/google_photos.ts +776 -0
- package/dist/connectors/google_play.ts +349 -0
- package/dist/connectors/hackernews.ts +471 -0
- package/dist/connectors/index.ts +28 -0
- package/dist/connectors/ios_appstore.ts +226 -0
- package/dist/connectors/linkedin.ts +494 -0
- package/dist/connectors/local_directory.ts +91 -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/revolut.ts +572 -0
- package/dist/connectors/rss.ts +448 -0
- package/dist/connectors/spotify.ts +590 -0
- package/dist/connectors/trustpilot.ts +203 -0
- package/dist/connectors/website.ts +629 -0
- package/dist/connectors/whatsapp.ts +1081 -0
- package/dist/connectors/whatsapp_local.ts +125 -0
- package/dist/connectors/x.ts +536 -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/db/migrations/20260510220000_connector_required_capability.sql +47 -0
- package/dist/db/migrations/20260512000000_device_worker_connection_binding.sql +113 -0
- package/dist/db/migrations/20260512131703_connections_slug.sql +131 -0
- package/dist/db/migrations/20260513000000_chat_user_identities.sql +24 -0
- package/dist/db/migrations/20260513120000_auth_profiles_device_binding.sql +50 -0
- package/dist/db/migrations/20260513150000_auth_profiles_cdp_url.sql +43 -0
- package/dist/db/migrations/20260513200000_notifications_as_events.sql +86 -0
- package/dist/db/migrations/20260514000000_scheduled_jobs.sql +97 -0
- package/dist/db/migrations/20260514120000_auth_profiles_connector_key_nullable.sql +42 -0
- package/dist/eval/types.d.ts +2 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +210 -132
- 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/gateway-url.d.ts +14 -0
- package/dist/internal/gateway-url.d.ts.map +1 -1
- package/dist/internal/gateway-url.js +19 -0
- package/dist/internal/gateway-url.js.map +1 -1
- package/dist/internal/index.d.ts +3 -4
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +3 -3
- 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 +31654 -30866
- package/dist/start-local.bundle.mjs +74409 -0
- package/dist/templates/README.md.tmpl +10 -11
- package/dist/templates/TESTING.md.tmpl +9 -9
- package/package.json +15 -13
- 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,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkedIn Connector
|
|
3
|
+
*
|
|
4
|
+
* Scrapes LinkedIn company pages via browser network interception.
|
|
5
|
+
* Uses Playwright to navigate company pages and intercept Voyager API responses.
|
|
6
|
+
* Auth via persisted browser cookies captured into a reusable browser auth profile.
|
|
7
|
+
*
|
|
8
|
+
* Follows the same pattern as the X (Twitter) connector.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
type ActionContext,
|
|
13
|
+
type ActionResult,
|
|
14
|
+
browserNetworkSync,
|
|
15
|
+
type ConnectorDefinition,
|
|
16
|
+
ConnectorRuntime,
|
|
17
|
+
calculateEngagementScore,
|
|
18
|
+
type EventEnvelope,
|
|
19
|
+
type SyncContext,
|
|
20
|
+
type SyncResult,
|
|
21
|
+
} from '@lobu/connector-sdk';
|
|
22
|
+
import {
|
|
23
|
+
getBrowserCdpUrl,
|
|
24
|
+
getBrowserCookies,
|
|
25
|
+
getBrowserUserDataDir,
|
|
26
|
+
validateCookieNotExpired,
|
|
27
|
+
} from './browser-scraper-utils';
|
|
28
|
+
|
|
29
|
+
// ── Types ──────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
interface LinkedInCheckpoint {
|
|
32
|
+
last_post_id?: string;
|
|
33
|
+
last_job_id?: string;
|
|
34
|
+
last_timestamp?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface LinkedInPost {
|
|
38
|
+
id: string;
|
|
39
|
+
text: string;
|
|
40
|
+
author: string;
|
|
41
|
+
authorHeadline?: string;
|
|
42
|
+
likes: number;
|
|
43
|
+
comments: number;
|
|
44
|
+
shares: number;
|
|
45
|
+
publishedAt: Date;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface LinkedInJob {
|
|
49
|
+
id: string;
|
|
50
|
+
title: string;
|
|
51
|
+
location: string;
|
|
52
|
+
postedAt: Date;
|
|
53
|
+
url: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeCheckpointPostId(postId?: string): string | undefined {
|
|
58
|
+
if (!postId) return undefined;
|
|
59
|
+
return postId.startsWith('li_post_') ? postId.slice('li_post_'.length) : postId;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function filterPostsSinceCheckpoint(
|
|
63
|
+
posts: LinkedInPost[],
|
|
64
|
+
checkpoint: LinkedInCheckpoint
|
|
65
|
+
): LinkedInPost[] {
|
|
66
|
+
const seenIds = new Set<string>();
|
|
67
|
+
const checkpointPostId = normalizeCheckpointPostId(checkpoint.last_post_id);
|
|
68
|
+
const checkpointTimestamp = checkpoint.last_timestamp
|
|
69
|
+
? new Date(checkpoint.last_timestamp).getTime()
|
|
70
|
+
: null;
|
|
71
|
+
|
|
72
|
+
const filtered: LinkedInPost[] = [];
|
|
73
|
+
for (const post of posts) {
|
|
74
|
+
if (!post.id || !post.text || seenIds.has(post.id)) continue;
|
|
75
|
+
seenIds.add(post.id);
|
|
76
|
+
|
|
77
|
+
if (checkpointPostId && post.id === checkpointPostId) break;
|
|
78
|
+
if (checkpointTimestamp !== null && post.publishedAt.getTime() <= checkpointTimestamp) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
filtered.push(post);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return filtered;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Voyager API Response Parsers ──────────────────────────────
|
|
89
|
+
|
|
90
|
+
function parseCompanyUpdates(_url: string, json: unknown): LinkedInPost[] {
|
|
91
|
+
const posts: LinkedInPost[] = [];
|
|
92
|
+
const data = json as any;
|
|
93
|
+
|
|
94
|
+
// Build URN lookup from `included` array (LinkedIn GraphQL uses references)
|
|
95
|
+
const included: any[] = data?.included ?? [];
|
|
96
|
+
const byUrn: Record<string, any> = {};
|
|
97
|
+
for (const item of included) {
|
|
98
|
+
const urn = item.entityUrn || item.$id;
|
|
99
|
+
if (urn) byUrn[urn] = item;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Find feed elements - LinkedIn nests under data.data with a long key
|
|
103
|
+
const feedRoot = data?.data?.data ?? data?.data ?? data;
|
|
104
|
+
let elements: any[] = [];
|
|
105
|
+
for (const key of Object.keys(feedRoot)) {
|
|
106
|
+
const val = feedRoot[key];
|
|
107
|
+
if (val?.['*elements'] && Array.isArray(val['*elements'])) {
|
|
108
|
+
elements = val['*elements'];
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
if (val?.elements && Array.isArray(val.elements)) {
|
|
112
|
+
elements = val.elements;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const resolve = (ref: any) => (typeof ref === 'string' ? byUrn[ref] : ref) ?? {};
|
|
118
|
+
|
|
119
|
+
for (const ref of elements) {
|
|
120
|
+
const el = resolve(ref);
|
|
121
|
+
|
|
122
|
+
// Get commentary text (may be a reference)
|
|
123
|
+
const commentaryObj = resolve(el['*commentary'] ?? el.commentary);
|
|
124
|
+
const textObj = commentaryObj?.text ?? commentaryObj;
|
|
125
|
+
const text = textObj?.text ?? textObj?.attributedText?.text ?? '';
|
|
126
|
+
if (!text) continue;
|
|
127
|
+
|
|
128
|
+
// Get actor
|
|
129
|
+
const actorObj = resolve(el['*actor'] ?? el.actor);
|
|
130
|
+
const authorName = actorObj?.name?.text ?? actorObj?.name ?? 'Unknown';
|
|
131
|
+
const authorDesc = actorObj?.description?.text ?? actorObj?.description ?? undefined;
|
|
132
|
+
|
|
133
|
+
// Get social counts
|
|
134
|
+
const socialRef = el['*socialDetail'] ?? el.socialDetail;
|
|
135
|
+
const social = resolve(socialRef);
|
|
136
|
+
const counts =
|
|
137
|
+
social?.totalSocialActivityCounts ??
|
|
138
|
+
social?.socialActivityCountsInsight?.totalSocialActivityCounts ??
|
|
139
|
+
{};
|
|
140
|
+
|
|
141
|
+
// Get URN for ID
|
|
142
|
+
const urn = el.entityUrn ?? el['*backendUrn'] ?? '';
|
|
143
|
+
const urnParts = urn.split(':');
|
|
144
|
+
const id =
|
|
145
|
+
urnParts[urnParts.length - 1] || `li_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
146
|
+
|
|
147
|
+
// Get timestamp
|
|
148
|
+
const metadata = resolve(el['*metadata'] ?? el.metadata);
|
|
149
|
+
const publishedAt = metadata?.publishedAt ?? el.createdAt ?? Date.now();
|
|
150
|
+
|
|
151
|
+
posts.push({
|
|
152
|
+
id,
|
|
153
|
+
text,
|
|
154
|
+
author: authorName,
|
|
155
|
+
authorHeadline: typeof authorDesc === 'string' ? authorDesc : undefined,
|
|
156
|
+
likes: counts.numLikes ?? 0,
|
|
157
|
+
comments: counts.numComments ?? 0,
|
|
158
|
+
shares: counts.numShares ?? 0,
|
|
159
|
+
publishedAt: new Date(publishedAt),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return posts;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parseJobListings(_url: string, json: unknown): LinkedInJob[] {
|
|
167
|
+
const jobs: LinkedInJob[] = [];
|
|
168
|
+
const data = json as any;
|
|
169
|
+
|
|
170
|
+
const elements = data?.elements ?? data?.data?.elements ?? [];
|
|
171
|
+
|
|
172
|
+
for (const element of elements) {
|
|
173
|
+
const jobPosting = element?.jobPosting ?? element;
|
|
174
|
+
const title = jobPosting?.title ?? element?.title ?? '';
|
|
175
|
+
if (!title) continue;
|
|
176
|
+
|
|
177
|
+
const urnParts = (jobPosting?.entityUrn ?? element?.dashEntityUrn ?? '').split(':');
|
|
178
|
+
const id =
|
|
179
|
+
urnParts[urnParts.length - 1] ||
|
|
180
|
+
`job_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
181
|
+
|
|
182
|
+
jobs.push({
|
|
183
|
+
id,
|
|
184
|
+
title,
|
|
185
|
+
location: jobPosting?.formattedLocation ?? jobPosting?.location ?? '',
|
|
186
|
+
postedAt: new Date(jobPosting?.listedAt ?? element?.createdAt ?? Date.now()),
|
|
187
|
+
url: `https://www.linkedin.com/jobs/view/${id}`,
|
|
188
|
+
description: jobPosting?.description?.text ?? undefined,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return jobs;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Config Schemas ────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
const companyUpdatesConfigSchema = {
|
|
198
|
+
type: 'object',
|
|
199
|
+
required: ['company_url'],
|
|
200
|
+
properties: {
|
|
201
|
+
company_url: {
|
|
202
|
+
type: 'string',
|
|
203
|
+
description: 'LinkedIn company page URL (e.g., "https://www.linkedin.com/company/openai")',
|
|
204
|
+
},
|
|
205
|
+
max_scrolls: {
|
|
206
|
+
type: 'integer',
|
|
207
|
+
minimum: 1,
|
|
208
|
+
maximum: 20,
|
|
209
|
+
default: 5,
|
|
210
|
+
description: 'Maximum scroll iterations for pagination (default: 5)',
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const jobsConfigSchema = {
|
|
216
|
+
type: 'object',
|
|
217
|
+
required: ['company_url'],
|
|
218
|
+
properties: {
|
|
219
|
+
company_url: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
description: 'LinkedIn company page URL (e.g., "https://www.linkedin.com/company/openai")',
|
|
222
|
+
},
|
|
223
|
+
max_scrolls: {
|
|
224
|
+
type: 'integer',
|
|
225
|
+
minimum: 1,
|
|
226
|
+
maximum: 10,
|
|
227
|
+
default: 3,
|
|
228
|
+
description: 'Maximum scroll iterations for job listings (default: 3)',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// ── Connector ─────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
export default class LinkedInConnector extends ConnectorRuntime {
|
|
236
|
+
readonly definition: ConnectorDefinition = {
|
|
237
|
+
key: 'linkedin',
|
|
238
|
+
name: 'LinkedIn',
|
|
239
|
+
description: 'Scrapes LinkedIn company pages for posts, hiring signals, and team data.',
|
|
240
|
+
version: '1.1.0',
|
|
241
|
+
faviconDomain: 'linkedin.com',
|
|
242
|
+
authSchema: {
|
|
243
|
+
methods: [
|
|
244
|
+
{
|
|
245
|
+
type: 'browser',
|
|
246
|
+
capture: 'cli',
|
|
247
|
+
requiredDomains: ['linkedin.com', '.linkedin.com'],
|
|
248
|
+
description:
|
|
249
|
+
'Preferred auth mode for LinkedIn scraping. Captures your existing browser session cookies.',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
type: 'oauth',
|
|
253
|
+
provider: 'linkedin',
|
|
254
|
+
requiredScopes: ['openid', 'profile', 'email'],
|
|
255
|
+
loginScopes: ['openid', 'profile', 'email'],
|
|
256
|
+
authorizationUrl: 'https://www.linkedin.com/oauth/v2/authorization',
|
|
257
|
+
tokenUrl: 'https://www.linkedin.com/oauth/v2/accessToken',
|
|
258
|
+
userinfoUrl: 'https://api.linkedin.com/v2/userinfo',
|
|
259
|
+
tokenEndpointAuthMethod: 'client_secret_post',
|
|
260
|
+
clientIdKey: 'LINKEDIN_CLIENT_ID',
|
|
261
|
+
clientSecretKey: 'LINKEDIN_CLIENT_SECRET',
|
|
262
|
+
description:
|
|
263
|
+
'Optional LinkedIn OAuth app config for sign-in and future API-based access. Current company page and jobs feeds still scrape via browser session cookies.',
|
|
264
|
+
setupInstructions:
|
|
265
|
+
'Create a LinkedIn OAuth app, add {{redirect_uri}} as the callback URL, then paste the client ID and client secret below.',
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
feeds: {
|
|
270
|
+
company_updates: {
|
|
271
|
+
key: 'company_updates',
|
|
272
|
+
name: 'Company Updates',
|
|
273
|
+
description: 'Posts and updates from the company LinkedIn page.',
|
|
274
|
+
configSchema: companyUpdatesConfigSchema,
|
|
275
|
+
eventKinds: {
|
|
276
|
+
post: {
|
|
277
|
+
description: 'A company LinkedIn post',
|
|
278
|
+
metadataSchema: {
|
|
279
|
+
type: 'object',
|
|
280
|
+
properties: {
|
|
281
|
+
author_headline: { type: 'string' },
|
|
282
|
+
likes: { type: 'number' },
|
|
283
|
+
comments: { type: 'number' },
|
|
284
|
+
shares: { type: 'number' },
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
jobs: {
|
|
291
|
+
key: 'jobs',
|
|
292
|
+
name: 'Job Listings',
|
|
293
|
+
description: 'Open job positions (hiring velocity signal).',
|
|
294
|
+
configSchema: jobsConfigSchema,
|
|
295
|
+
eventKinds: {
|
|
296
|
+
job_posting: {
|
|
297
|
+
description: 'An open job listing',
|
|
298
|
+
metadataSchema: {
|
|
299
|
+
type: 'object',
|
|
300
|
+
properties: {
|
|
301
|
+
location: { type: 'string' },
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
optionsSchema: companyUpdatesConfigSchema,
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
async sync(ctx: SyncContext): Promise<SyncResult> {
|
|
312
|
+
const config = ctx.config as Record<string, unknown>;
|
|
313
|
+
const checkpoint = (ctx.checkpoint ?? {}) as LinkedInCheckpoint;
|
|
314
|
+
const feedKey = ctx.feedKey ?? 'company_updates';
|
|
315
|
+
|
|
316
|
+
const companyUrl = config.company_url as string;
|
|
317
|
+
if (!companyUrl) {
|
|
318
|
+
throw new Error('company_url is required');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Normalize URL - remove trailing slash
|
|
322
|
+
const baseUrl = companyUrl.replace(/\/$/, '');
|
|
323
|
+
|
|
324
|
+
const userDataDir = getBrowserUserDataDir(ctx.sessionState);
|
|
325
|
+
const cdpUrlFromSession = getBrowserCdpUrl(ctx.sessionState);
|
|
326
|
+
const cdpUrl = cdpUrlFromSession ?? 'auto';
|
|
327
|
+
// No need to require cookies when the device tells us to attach directly
|
|
328
|
+
// (managed --user-data-dir on disk, or an explicit CDP endpoint pointed
|
|
329
|
+
// at the user's running Chrome). The cookie cascade is only the fallback
|
|
330
|
+
// for the cloud/auto path.
|
|
331
|
+
const skipServerCookies = !!userDataDir || !!cdpUrlFromSession;
|
|
332
|
+
const cookies = skipServerCookies
|
|
333
|
+
? []
|
|
334
|
+
: getBrowserCookies(ctx.checkpoint as any, ctx.sessionState as any, 'linkedin');
|
|
335
|
+
if (!skipServerCookies) {
|
|
336
|
+
validateCookieNotExpired(cookies, 'li_at', 'linkedin');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const maxScrolls = (config.max_scrolls as number) ?? (feedKey === 'jobs' ? 3 : 5);
|
|
340
|
+
|
|
341
|
+
if (feedKey === 'jobs') {
|
|
342
|
+
return this.syncJobs(baseUrl, cookies, maxScrolls, checkpoint, userDataDir, cdpUrl);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return this.syncUpdates(baseUrl, cookies, maxScrolls, checkpoint, userDataDir, cdpUrl);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private async syncUpdates(
|
|
349
|
+
baseUrl: string,
|
|
350
|
+
cookies: any[],
|
|
351
|
+
maxScrolls: number,
|
|
352
|
+
checkpoint: LinkedInCheckpoint,
|
|
353
|
+
userDataDir: string | undefined,
|
|
354
|
+
cdpUrl: string | 'auto'
|
|
355
|
+
): Promise<SyncResult> {
|
|
356
|
+
const postsUrl = `${baseUrl}/posts/`;
|
|
357
|
+
|
|
358
|
+
const result = await browserNetworkSync<LinkedInPost>({
|
|
359
|
+
config: {
|
|
360
|
+
interceptPatterns: [
|
|
361
|
+
/voyager\/api\/graphql\?variables=.*ORGANIZATION_MEMBER_FEED/,
|
|
362
|
+
/voyager\/api\/graphql\?variables=.*organizationalPageUrn/,
|
|
363
|
+
],
|
|
364
|
+
authDomains: ['linkedin.com', '.linkedin.com', '.www.linkedin.com'],
|
|
365
|
+
stealth: true,
|
|
366
|
+
maxScrolls,
|
|
367
|
+
scrollDelayMs: 3000,
|
|
368
|
+
responseTimeoutMs: 8000,
|
|
369
|
+
navigationTimeoutMs: 20000,
|
|
370
|
+
},
|
|
371
|
+
url: postsUrl,
|
|
372
|
+
cdpUrl,
|
|
373
|
+
cookies,
|
|
374
|
+
userDataDir,
|
|
375
|
+
parseResponse: parseCompanyUpdates,
|
|
376
|
+
checkAuth: async (page) => {
|
|
377
|
+
const url = page.url();
|
|
378
|
+
return !url.includes('/login') && !url.includes('/authwall');
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const posts = filterPostsSinceCheckpoint(result.items, checkpoint);
|
|
383
|
+
|
|
384
|
+
const events: EventEnvelope[] = posts.map((post) => ({
|
|
385
|
+
origin_id: `li_post_${post.id}`,
|
|
386
|
+
payload_text: post.text,
|
|
387
|
+
author_name: post.author,
|
|
388
|
+
occurred_at: post.publishedAt,
|
|
389
|
+
origin_type: 'post',
|
|
390
|
+
source_url: `https://www.linkedin.com/feed/update/urn:li:activity:${post.id}`,
|
|
391
|
+
score: calculateEngagementScore('linkedin', {
|
|
392
|
+
upvotes: post.likes,
|
|
393
|
+
reply_count: post.comments,
|
|
394
|
+
}),
|
|
395
|
+
metadata: {
|
|
396
|
+
author_headline: post.authorHeadline,
|
|
397
|
+
likes: post.likes,
|
|
398
|
+
comments: post.comments,
|
|
399
|
+
shares: post.shares,
|
|
400
|
+
},
|
|
401
|
+
}));
|
|
402
|
+
|
|
403
|
+
events.sort((a, b) => new Date(b.occurred_at).getTime() - new Date(a.occurred_at).getTime());
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
events,
|
|
407
|
+
checkpoint: {
|
|
408
|
+
last_post_id: posts[0]?.id ?? checkpoint.last_post_id,
|
|
409
|
+
last_timestamp: events[0]?.occurred_at?.toISOString?.() ?? checkpoint.last_timestamp,
|
|
410
|
+
} as unknown as Record<string, unknown>,
|
|
411
|
+
auth_update: { cookies: result.cookies },
|
|
412
|
+
metadata: {
|
|
413
|
+
items_found: events.length,
|
|
414
|
+
items_skipped: result.items.length - posts.length,
|
|
415
|
+
api_calls: result.apiCallCount,
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private async syncJobs(
|
|
421
|
+
baseUrl: string,
|
|
422
|
+
cookies: any[],
|
|
423
|
+
maxScrolls: number,
|
|
424
|
+
checkpoint: LinkedInCheckpoint,
|
|
425
|
+
userDataDir: string | undefined,
|
|
426
|
+
cdpUrl: string | 'auto'
|
|
427
|
+
): Promise<SyncResult> {
|
|
428
|
+
const jobsUrl = `${baseUrl}/jobs/`;
|
|
429
|
+
|
|
430
|
+
const result = await browserNetworkSync<LinkedInJob>({
|
|
431
|
+
config: {
|
|
432
|
+
interceptPatterns: [
|
|
433
|
+
/voyager\/api\/graphql.*jobPosting/i,
|
|
434
|
+
/voyager\/api\/search\/dash\/.*jobs/i,
|
|
435
|
+
/voyager\/api\/organization\/.*jobs/i,
|
|
436
|
+
],
|
|
437
|
+
authDomains: ['linkedin.com', '.linkedin.com', '.www.linkedin.com'],
|
|
438
|
+
stealth: true,
|
|
439
|
+
maxScrolls,
|
|
440
|
+
scrollDelayMs: 3000,
|
|
441
|
+
responseTimeoutMs: 8000,
|
|
442
|
+
navigationTimeoutMs: 20000,
|
|
443
|
+
},
|
|
444
|
+
url: jobsUrl,
|
|
445
|
+
cdpUrl,
|
|
446
|
+
cookies,
|
|
447
|
+
userDataDir,
|
|
448
|
+
parseResponse: parseJobListings,
|
|
449
|
+
checkAuth: async (page) => {
|
|
450
|
+
const url = page.url();
|
|
451
|
+
return !url.includes('/login') && !url.includes('/authwall');
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Deduplicate
|
|
456
|
+
const seenIds = new Set<string>();
|
|
457
|
+
const jobs = result.items.filter((j) => {
|
|
458
|
+
if (!j.id || seenIds.has(j.id)) return false;
|
|
459
|
+
seenIds.add(j.id);
|
|
460
|
+
return true;
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
jobs.sort((a, b) => b.postedAt.getTime() - a.postedAt.getTime());
|
|
464
|
+
|
|
465
|
+
const events: EventEnvelope[] = jobs.map((job) => ({
|
|
466
|
+
origin_id: `li_job_${job.id}`,
|
|
467
|
+
payload_text: job.description ?? job.title,
|
|
468
|
+
title: job.title,
|
|
469
|
+
occurred_at: job.postedAt,
|
|
470
|
+
origin_type: 'job_posting',
|
|
471
|
+
source_url: job.url,
|
|
472
|
+
metadata: {
|
|
473
|
+
location: job.location,
|
|
474
|
+
},
|
|
475
|
+
}));
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
events,
|
|
479
|
+
checkpoint: {
|
|
480
|
+
last_job_id: jobs[0]?.id ?? checkpoint.last_job_id,
|
|
481
|
+
last_timestamp: jobs[0]?.postedAt?.toISOString?.() ?? checkpoint.last_timestamp,
|
|
482
|
+
} as unknown as Record<string, unknown>,
|
|
483
|
+
auth_update: { cookies: result.cookies },
|
|
484
|
+
metadata: {
|
|
485
|
+
items_found: events.length,
|
|
486
|
+
api_calls: result.apiCallCount,
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async execute(_ctx: ActionContext): Promise<ActionResult> {
|
|
492
|
+
return { success: false, error: 'Actions not supported' };
|
|
493
|
+
}
|
|
494
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Directory Connector (V1 runtime) — Lobu for Mac only.
|
|
3
|
+
*
|
|
4
|
+
* Syncs text files (txt/md/json/csv/html) from a local folder on the user's
|
|
5
|
+
* Mac via Lobu for Mac. The app advertises the `local_directory`
|
|
6
|
+
* capability on /api/workers/poll, reads the folder, and streams file events
|
|
7
|
+
* back through the standard worker protocol.
|
|
8
|
+
*
|
|
9
|
+
* The sync() / execute() stubs here throw immediately if a server-side worker
|
|
10
|
+
* somehow bypassed the capability gate — same pattern as apple_screen_time.ts.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
type ActionResult,
|
|
15
|
+
type ConnectorDefinition,
|
|
16
|
+
ConnectorRuntime,
|
|
17
|
+
type SyncContext,
|
|
18
|
+
type SyncResult,
|
|
19
|
+
} from '@lobu/connector-sdk';
|
|
20
|
+
|
|
21
|
+
const BRIDGE_ONLY_MESSAGE =
|
|
22
|
+
'local.directory runs only on a worker advertising capability "local_directory" (Lobu for Mac). ' +
|
|
23
|
+
'This run was claimed by a worker without that capability — check connector_definitions.required_capability and the poll-time capability filter.';
|
|
24
|
+
|
|
25
|
+
export default class LocalDirectoryConnector extends ConnectorRuntime {
|
|
26
|
+
readonly definition: ConnectorDefinition = {
|
|
27
|
+
key: 'local.directory',
|
|
28
|
+
name: 'Local Folder',
|
|
29
|
+
description:
|
|
30
|
+
'Sync text files (txt/md/json/csv/html) from a folder on your Mac via Lobu for Mac.',
|
|
31
|
+
version: '0.1.0',
|
|
32
|
+
faviconDomain: 'apple.com',
|
|
33
|
+
requiredCapability: 'local_directory',
|
|
34
|
+
runtime: { platforms: ['macos'] },
|
|
35
|
+
authSchema: { methods: [{ type: 'none' }] },
|
|
36
|
+
feeds: {
|
|
37
|
+
files: {
|
|
38
|
+
key: 'files',
|
|
39
|
+
name: 'Files',
|
|
40
|
+
description: 'Text files from one local folder on the user\'s Mac. One feed per folder — folder_id is an opaque stable id minted by the Mac app (the security-scoped bookmark is held device-side; the server never sees the absolute path).',
|
|
41
|
+
userManaged: true,
|
|
42
|
+
configSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
required: ['folder_id', 'display_name'],
|
|
45
|
+
properties: {
|
|
46
|
+
folder_id: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
minLength: 8,
|
|
49
|
+
maxLength: 64,
|
|
50
|
+
description: 'Opaque stable id (UUID) minted on the Mac. Maps to a security-scoped bookmark stored locally on the device.',
|
|
51
|
+
},
|
|
52
|
+
display_name: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
minLength: 1,
|
|
55
|
+
maxLength: 200,
|
|
56
|
+
description: 'Folder name shown in the UI (e.g., "Documents"). Not used to locate the folder — the device resolves folder_id to its bookmark.',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
eventKinds: {
|
|
61
|
+
file_document: {
|
|
62
|
+
description: 'A text file from a configured local folder.',
|
|
63
|
+
metadataSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
// No absolute filesystem path — the bridge sends the folder's
|
|
66
|
+
// display name and the file name, which is enough context
|
|
67
|
+
// without leaking the user's home directory / disk layout.
|
|
68
|
+
required: ['source', 'folder', 'name'],
|
|
69
|
+
properties: {
|
|
70
|
+
source: { type: 'string', const: 'local_directory' },
|
|
71
|
+
folder: { type: 'string', description: 'Display name of the local folder.' },
|
|
72
|
+
name: { type: 'string', description: 'File name.' },
|
|
73
|
+
ext: { type: 'string' },
|
|
74
|
+
size_bytes: { type: 'number' },
|
|
75
|
+
modified_at: { type: 'string' },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
async sync(_ctx: SyncContext): Promise<SyncResult> {
|
|
85
|
+
throw new Error(BRIDGE_ONLY_MESSAGE);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async execute(): Promise<ActionResult> {
|
|
89
|
+
throw new Error(BRIDGE_ONLY_MESSAGE);
|
|
90
|
+
}
|
|
91
|
+
}
|