@crouton-kit/crouter 0.3.28 → 0.3.29
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/dist/build-root.js +4 -2
- package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +20 -18
- package/dist/builtin-memory/design.md +1 -1
- package/dist/builtin-memory/development.md +1 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/planning.md +1 -1
- package/dist/builtin-memory/spec.md +6 -4
- package/dist/builtin-personas/orchestration-kernel.md +4 -5
- package/dist/builtin-personas/runtime-base.md +5 -4
- package/dist/builtin-personas/spec/PERSONA.md +8 -4
- package/dist/builtin-personas/spec/orchestrator.md +5 -3
- package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
- package/dist/builtin-views/canvas/core.mjs +586 -0
- package/dist/builtin-views/canvas/text.mjs +58 -0
- package/dist/builtin-views/canvas/tui.mjs +165 -0
- package/dist/builtin-views/canvas/web.jsx +120 -0
- package/dist/builtin-views/git-pr/core.mjs +673 -0
- package/dist/builtin-views/git-pr/text.mjs +84 -0
- package/dist/builtin-views/git-pr/tui.mjs +302 -0
- package/dist/builtin-views/git-pr/web.jsx +216 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
- package/dist/builtin-views/inbox/core.mjs +1273 -0
- package/dist/builtin-views/inbox/text.mjs +73 -0
- package/dist/builtin-views/inbox/tui.mjs +312 -0
- package/dist/builtin-views/inbox/web.jsx +188 -0
- package/dist/builtin-views/linkedin/core.mjs +906 -0
- package/dist/builtin-views/linkedin/text.mjs +69 -0
- package/dist/builtin-views/linkedin/tui.mjs +427 -0
- package/dist/builtin-views/linkedin/web.jsx +206 -0
- package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
- package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
- package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
- package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
- package/dist/cli.js +3 -3
- package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
- package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
- package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
- package/dist/clients/attach/__tests__/git-info.test.js +31 -0
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +282 -46
- package/dist/clients/attach/auth-pickers.d.ts +9 -0
- package/dist/clients/attach/auth-pickers.js +194 -0
- package/dist/clients/attach/canvas-panels.d.ts +4 -2
- package/dist/clients/attach/canvas-panels.js +45 -17
- package/dist/clients/attach/chat-view.d.ts +27 -2
- package/dist/clients/attach/chat-view.js +98 -9
- package/dist/clients/attach/clipboard-image.d.ts +47 -8
- package/dist/clients/attach/clipboard-image.js +150 -26
- package/dist/clients/attach/clipboard-text.d.ts +3 -0
- package/dist/clients/attach/clipboard-text.js +39 -0
- package/dist/clients/attach/config-load.d.ts +54 -7
- package/dist/clients/attach/config-load.js +99 -3
- package/dist/clients/attach/context-message.d.ts +12 -0
- package/dist/clients/attach/context-message.js +72 -0
- package/dist/clients/attach/git-info.d.ts +20 -0
- package/dist/clients/attach/git-info.js +44 -0
- package/dist/clients/attach/graph-overlay.d.ts +13 -4
- package/dist/clients/attach/graph-overlay.js +59 -19
- package/dist/clients/attach/input-controller.d.ts +75 -13
- package/dist/clients/attach/input-controller.js +305 -67
- package/dist/clients/attach/pickers.d.ts +58 -0
- package/dist/clients/attach/pickers.js +171 -0
- package/dist/clients/attach/slash-commands.d.ts +22 -0
- package/dist/clients/attach/slash-commands.js +91 -25
- package/dist/clients/attach/titled-editor.d.ts +33 -0
- package/dist/clients/attach/titled-editor.js +84 -0
- package/dist/clients/attach/view-socket.d.ts +18 -1
- package/dist/clients/attach/view-socket.js +70 -1
- package/dist/clients/web/dev-server.d.ts +7 -0
- package/dist/clients/web/dev-server.js +59 -0
- package/dist/clients/web/events.d.ts +14 -0
- package/dist/clients/web/events.js +151 -0
- package/dist/clients/web/server.d.ts +18 -0
- package/dist/clients/web/server.js +450 -0
- package/dist/clients/web/web-cmd.d.ts +2 -0
- package/dist/clients/web/web-cmd.js +120 -0
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/chord.js +1 -1
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
- package/dist/commands/memory/lint.js +14 -2
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +7 -1
- package/dist/commands/memory/shared.js +18 -3
- package/dist/commands/memory/write.js +8 -4
- package/dist/commands/memory.js +4 -4
- package/dist/commands/node.d.ts +1 -1
- package/dist/commands/node.js +64 -65
- package/dist/commands/pkg.js +1 -2
- package/dist/commands/revive.js +11 -3
- package/dist/commands/sys/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +3 -2
- package/dist/commands/view-cycle.js +2 -2
- package/dist/commands/view-list.js +8 -8
- package/dist/commands/view-new.js +21 -17
- package/dist/commands/view-pick.js +1 -1
- package/dist/commands/view-run.js +35 -14
- package/dist/commands/view.js +8 -6
- package/dist/commands/workspace.d.ts +2 -0
- package/dist/commands/workspace.js +161 -0
- package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
- package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
- package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
- package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
- package/dist/core/__tests__/child-death-wake.test.js +56 -111
- package/dist/core/__tests__/close.test.js +14 -20
- package/dist/core/__tests__/context-intro.test.js +19 -19
- package/dist/core/__tests__/daemon-boot.test.js +30 -23
- package/dist/core/__tests__/draw-style.test.js +1 -1
- package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +17 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +22 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +25 -13
- package/dist/core/__tests__/fork.test.js +22 -5
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
- package/dist/core/__tests__/full/cascade-close.test.js +22 -8
- package/dist/core/__tests__/full/detach-focus.test.js +66 -169
- package/dist/core/__tests__/helpers/harness.d.ts +6 -9
- package/dist/core/__tests__/helpers/harness.js +19 -36
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
- package/dist/core/__tests__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
- package/dist/core/__tests__/memory-resolver.test.js +1 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -2
- package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
- package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch-root.test.js +114 -0
- package/dist/core/__tests__/reset.test.js +19 -79
- package/dist/core/__tests__/steer-note.test.js +9 -7
- package/dist/core/__tests__/tmux-surface.test.js +13 -7
- package/dist/core/bootstrap.d.ts +0 -3
- package/dist/core/bootstrap.js +1 -143
- package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
- package/dist/core/canvas/browse/app.js +131 -8
- package/dist/core/canvas/browse/model.d.ts +23 -5
- package/dist/core/canvas/browse/model.js +68 -13
- package/dist/core/canvas/browse/render.d.ts +7 -1
- package/dist/core/canvas/browse/render.js +178 -47
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/canvas/focuses.d.ts +4 -4
- package/dist/core/canvas/focuses.js +7 -7
- package/dist/core/canvas/nav-model.js +8 -7
- package/dist/core/canvas/render.d.ts +68 -9
- package/dist/core/canvas/render.js +195 -61
- package/dist/core/canvas/types.d.ts +16 -23
- package/dist/core/config.js +1 -4
- package/dist/core/memory-resolver.js +43 -5
- package/dist/core/runtime/bearings.d.ts +1 -1
- package/dist/core/runtime/bearings.js +8 -8
- package/dist/core/runtime/branded-host.d.ts +16 -0
- package/dist/core/runtime/branded-host.js +127 -0
- package/dist/core/runtime/broker-protocol.d.ts +212 -6
- package/dist/core/runtime/broker.d.ts +4 -1
- package/dist/core/runtime/broker.js +494 -58
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +6 -14
- package/dist/core/runtime/host.d.ts +14 -34
- package/dist/core/runtime/host.js +14 -50
- package/dist/core/runtime/launch.d.ts +12 -9
- package/dist/core/runtime/launch.js +27 -15
- package/dist/core/runtime/lifecycle.d.ts +1 -1
- package/dist/core/runtime/lifecycle.js +15 -19
- package/dist/core/runtime/nodes.d.ts +0 -55
- package/dist/core/runtime/nodes.js +6 -74
- package/dist/core/runtime/placement.d.ts +91 -335
- package/dist/core/runtime/placement.js +262 -840
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +24 -6
- package/dist/core/runtime/recap.d.ts +8 -0
- package/dist/core/runtime/recap.js +107 -0
- package/dist/core/runtime/recycle.js +25 -61
- package/dist/core/runtime/reset.d.ts +43 -37
- package/dist/core/runtime/reset.js +131 -218
- package/dist/core/runtime/revive.d.ts +9 -29
- package/dist/core/runtime/revive.js +40 -139
- package/dist/core/runtime/spawn.d.ts +15 -16
- package/dist/core/runtime/spawn.js +165 -194
- package/dist/core/runtime/surface-bg.d.ts +11 -0
- package/dist/core/runtime/surface-bg.js +68 -0
- package/dist/core/runtime/tmux.d.ts +13 -50
- package/dist/core/runtime/tmux.js +33 -87
- package/dist/core/scope.d.ts +1 -2
- package/dist/core/scope.js +1 -5
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
- package/dist/core/skill-sync/builtins.d.ts +42 -0
- package/dist/core/skill-sync/builtins.js +112 -0
- package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
- package/dist/core/skill-sync/claude-plugins.js +71 -0
- package/dist/core/skill-sync/engine.d.ts +42 -0
- package/dist/core/skill-sync/engine.js +633 -0
- package/dist/core/skill-sync/export.d.ts +23 -0
- package/dist/core/skill-sync/export.js +86 -0
- package/dist/core/skill-sync/manifest.d.ts +64 -0
- package/dist/core/skill-sync/manifest.js +181 -0
- package/dist/core/skill-sync/profile.d.ts +76 -0
- package/dist/core/skill-sync/profile.js +173 -0
- package/dist/core/skill-sync/snapshot.d.ts +57 -0
- package/dist/core/skill-sync/snapshot.js +120 -0
- package/dist/core/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.js +3 -3
- package/dist/core/substrate/on-read.js +68 -6
- package/dist/core/substrate/render.d.ts +8 -11
- package/dist/core/substrate/render.js +29 -43
- package/dist/core/substrate/schema.d.ts +10 -3
- package/dist/core/substrate/schema.js +6 -3
- package/dist/core/tui/host.d.ts +12 -4
- package/dist/core/tui/host.js +280 -149
- package/dist/core/view/bridge.d.ts +10 -0
- package/dist/core/view/bridge.js +31 -0
- package/dist/core/view/chrome.d.ts +9 -0
- package/dist/core/view/chrome.js +22 -0
- package/dist/core/view/contract.d.ts +171 -0
- package/dist/core/view/contract.js +23 -0
- package/dist/core/view/loader.d.ts +31 -0
- package/dist/core/view/loader.js +188 -0
- package/dist/core/view/transport-local.d.ts +7 -0
- package/dist/core/view/transport-local.js +70 -0
- package/dist/core/view/transport.d.ts +4 -0
- package/dist/core/view/transport.js +15 -0
- package/dist/daemon/crtrd.d.ts +38 -27
- package/dist/daemon/crtrd.js +303 -376
- package/dist/daemon/manage.js +6 -1
- package/dist/index.js +1 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
- package/dist/pi-extensions/canvas-context-intro.js +4 -4
- package/dist/pi-extensions/canvas-doc-substrate.js +16 -16
- package/dist/pi-extensions/canvas-nav.js +5 -0
- package/dist/pi-extensions/canvas-recap.d.ts +37 -0
- package/dist/pi-extensions/canvas-recap.js +236 -0
- package/dist/pi-extensions/canvas-resume.js +2 -1
- package/dist/pi-extensions/canvas-stophook.d.ts +14 -12
- package/dist/pi-extensions/canvas-stophook.js +87 -101
- package/dist/pi-extensions/canvas-view.js +2 -1
- package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
- package/dist/pi-extensions/widget-order-bus.js +34 -0
- package/dist/prompts/view.d.ts +2 -2
- package/dist/prompts/view.js +145 -73
- package/dist/types.d.ts +1 -6
- package/dist/types.js +1 -3
- package/dist/web/ViewChrome.d.ts +7 -0
- package/dist/web/ViewChrome.js +28 -0
- package/dist/web/ViewPane.d.ts +39 -0
- package/dist/web/ViewPane.js +48 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +16 -0
- package/dist/web/runtime.d.ts +39 -0
- package/dist/web/runtime.js +133 -0
- package/dist/web/states.d.ts +24 -0
- package/dist/web/states.js +24 -0
- package/dist/web/transport-http.d.ts +5 -0
- package/dist/web/transport-http.js +28 -0
- package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
- package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
- package/dist/web-client/index.html +13 -0
- package/package.json +20 -6
- package/dist/builtin-views/canvas/client.mjs +0 -303
- package/dist/builtin-views/canvas/view.mjs +0 -576
- package/dist/builtin-views/git-pr/client.mjs +0 -440
- package/dist/builtin-views/git-pr/view.mjs +0 -675
- package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
- package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
- package/dist/builtin-views/inbox/view.mjs +0 -889
- package/dist/builtin-views/linkedin/client.mjs +0 -610
- package/dist/builtin-views/linkedin/view.mjs +0 -1171
- package/dist/commands/pkg/bridge.d.ts +0 -1
- package/dist/commands/pkg/bridge.js +0 -137
- package/dist/commands/skill/author.d.ts +0 -3
- package/dist/commands/skill/author.js +0 -140
- package/dist/commands/skill/shared.d.ts +0 -3
- package/dist/commands/skill/shared.js +0 -19
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +0 -21
- package/dist/commands/tmux-spread.d.ts +0 -2
- package/dist/commands/tmux-spread.js +0 -144
- package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
- package/dist/core/__tests__/full/placement-focus.test.js +0 -309
- package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
- package/dist/core/__tests__/full/placement-revive.test.js +0 -238
- package/dist/core/__tests__/full/placement-teardown.test.js +0 -297
- package/dist/core/__tests__/home-session.test.js +0 -186
- package/dist/core/__tests__/relaunch.test.js +0 -335
- package/dist/core/bridge-map.d.ts +0 -19
- package/dist/core/bridge-map.js +0 -73
- package/dist/core/tui/contract.d.ts +0 -83
- package/dist/core/tui/contract.js +0 -8
- package/dist/core/tui/loader.d.ts +0 -16
- package/dist/core/tui/loader.js +0 -94
- package/dist/prompts/skill.d.ts +0 -2
- package/dist/prompts/skill.js +0 -650
- /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
- /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.test.d.ts} +0 -0
|
@@ -1,610 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
/**
|
|
3
|
-
* LinkedIn data layer for the crtr `linkedin` view (Phase A).
|
|
4
|
-
*
|
|
5
|
-
* Self-contained ESM, Node-builtins-only. Imports NOTHING from crtr so it can be
|
|
6
|
-
* shipped verbatim (`cp -R src/builtin-views dist/builtin-views`) and dynamically
|
|
7
|
-
* `import()`ed by the view at runtime where there is no TS toolchain.
|
|
8
|
-
*
|
|
9
|
-
* It wraps the `capture` CLI (`capture exec "<js>" --target <tabId> [--port N]`),
|
|
10
|
-
* which esbuild-bundles the forked LinkedIn vault libs and `Runtime.evaluate`s
|
|
11
|
-
* them inside the logged-in LinkedIn browser page over CDP. Each call is a fresh
|
|
12
|
-
* `capture` process (~1–3s); the view calls `getContext()` ONCE and threads
|
|
13
|
-
* `csrf`/`memberId` explicitly on every later call to skip re-auth round-trips.
|
|
14
|
-
*
|
|
15
|
-
* NOTHING here throws. Every exported function returns a `Result<T>` discriminated
|
|
16
|
-
* union; failures surface as a typed `ClientError` so the view can render guidance
|
|
17
|
-
* instead of crashing.
|
|
18
|
-
*
|
|
19
|
-
* @module linkedin/client
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { execFile } from 'node:child_process';
|
|
23
|
-
|
|
24
|
-
// ── Types ───────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Auth context read once from the page session via `getContext()`.
|
|
28
|
-
* @typedef {Object} LiContext
|
|
29
|
-
* @property {string} csrf CSRF token (form `"ajax:<digits>"`), from the JSESSIONID cookie.
|
|
30
|
-
* @property {string} memberId Current user member ID (`ACo…`).
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* One inbox conversation, flattened from the vault `listConversations` shape.
|
|
35
|
-
* @typedef {Object} Conversation
|
|
36
|
-
* @property {string} urn Conversation URN (`urn:li:msg_conversation:(…)`).
|
|
37
|
-
* @property {string} name Display name: group title, else first participant's name.
|
|
38
|
-
* @property {string} lastMessage Text of the most recent message.
|
|
39
|
-
* @property {boolean} unread True when `unreadCount > 0`.
|
|
40
|
-
* @property {number} ts Last-activity time as epoch ms (0 if unknown).
|
|
41
|
-
* @property {string} recipientId `participants[0].memberId` (the 1:1 counterpart; '' for empty/group).
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* One message in a thread, flattened from the vault `viewConversation` shape.
|
|
46
|
-
* @typedef {Object} Message
|
|
47
|
-
* @property {string} urn Message URN (`urn:li:msg_message:(…)`).
|
|
48
|
-
* @property {string} sender Sender full name.
|
|
49
|
-
* @property {string} text Message body text.
|
|
50
|
-
* @property {number} ts Delivery time as epoch ms (0 if unknown).
|
|
51
|
-
* @property {boolean} fromMe True when the sender's member ID equals the caller's `myMemberId`.
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Typed failure. `kind` drives the guidance banner the view shows; `error` only
|
|
56
|
-
* for the `error` catch-all carries a raw message.
|
|
57
|
-
* @typedef {{kind:'no-cdp'}
|
|
58
|
-
* | {kind:'no-tab'}
|
|
59
|
-
* | {kind:'not-logged-in'}
|
|
60
|
-
* | {kind:'not-messaging'}
|
|
61
|
-
* | {kind:'rate-limited'}
|
|
62
|
-
* | {kind:'not-connection'}
|
|
63
|
-
* | {kind:'capture-not-dev'}
|
|
64
|
-
* | {kind:'error', message:string}} ClientError
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Never-throw return contract. `ok:true` carries data; `ok:false` carries a typed error.
|
|
69
|
-
* @template T
|
|
70
|
-
* @typedef {{ok:true, data:T} | {ok:false, error:ClientError}} Result
|
|
71
|
-
*/
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Common options every function accepts. `target` is the CDP tab id from
|
|
75
|
-
* {@link discoverTab}; `port` (string|number) skips capture's port auto-detection.
|
|
76
|
-
* @typedef {Object} BaseOpts
|
|
77
|
-
* @property {string} [target] CDP tab id (`--target`). Omit ⇒ capture falls back to $CDP_TARGET/session.
|
|
78
|
-
* @property {string|number} [port] CDP port (`--port`); optional speed-up.
|
|
79
|
-
*/
|
|
80
|
-
|
|
81
|
-
// ── Config: locating the `capture` binary ────────────────────────────────────
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Dev-checkout fallback. The LinkedIn vault libs are dev-checkout-only (the
|
|
85
|
-
* published `capture` package throws `DEV_ONLY_MSG`), so this path is the most
|
|
86
|
-
* likely place a working binary lives if `capture` is not on PATH.
|
|
87
|
-
*/
|
|
88
|
-
const CAPTURE_DEV_FALLBACK = '/Users/silasrhyneer/Code/cli/capture/bin/capture';
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Binary candidates, in order. Prefer an explicit override, then PATH `capture`,
|
|
92
|
-
* then the dev checkout. We try the next only when a candidate fails to spawn
|
|
93
|
-
* (ENOENT) — a candidate that spawns and exits nonzero is authoritative.
|
|
94
|
-
* @returns {string[]}
|
|
95
|
-
*/
|
|
96
|
-
function captureCandidates() {
|
|
97
|
-
const out = [];
|
|
98
|
-
if (process.env.CAPTURE_BIN) out.push(process.env.CAPTURE_BIN);
|
|
99
|
-
out.push('capture');
|
|
100
|
-
out.push(CAPTURE_DEV_FALLBACK);
|
|
101
|
-
return out;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ── Result helpers ───────────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
/** @template T @param {T} data @returns {Result<T>} */
|
|
107
|
-
function ok(data) {
|
|
108
|
-
return { ok: true, data };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** @param {ClientError} error @returns {{ok:false, error:ClientError}} */
|
|
112
|
-
function fail(error) {
|
|
113
|
-
return { ok: false, error };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ── capture stderr → ClientError mapping ─────────────────────────────────────
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Map a failed `capture` invocation's stderr to a typed {@link ClientError}.
|
|
120
|
-
*
|
|
121
|
-
* Mapping table (kept in lockstep with the crtr-views spec "Data model"):
|
|
122
|
-
* No browser with CDP found ............................. no-cdp
|
|
123
|
-
* No tab found ......................................... no-tab
|
|
124
|
-
* Unauthenticated ..................................... not-logged-in
|
|
125
|
-
* Messaging queryId not found | Navigate to /messaging/ not-messaging
|
|
126
|
-
* RateLimited | 429 ................................... rate-limited
|
|
127
|
-
* must be a 1st-degree connection .................... not-connection
|
|
128
|
-
* DEV_ONLY_MSG ....................................... capture-not-dev
|
|
129
|
-
* (anything else) .................................... error{message}
|
|
130
|
-
*
|
|
131
|
-
* @param {string} stderr
|
|
132
|
-
* @returns {ClientError}
|
|
133
|
-
*/
|
|
134
|
-
function classifyError(stderr) {
|
|
135
|
-
const s = String(stderr || '');
|
|
136
|
-
if (/No browser with CDP found/i.test(s)) return { kind: 'no-cdp' };
|
|
137
|
-
// A dead/unreachable debugger port surfaces as a raw connection failure: capture
|
|
138
|
-
// probes http://localhost:<port>/json/version, so an unlistened port yields
|
|
139
|
-
// `fetch failed` (Node fetch wrapping ECONNREFUSED). Classify it as no-cdp so the
|
|
140
|
-
// view shows the §5 "launch a debuggable browser" panel, not the generic error.
|
|
141
|
-
if (/fetch failed/i.test(s) || /failed to fetch/i.test(s) || /ECONNREFUSED/i.test(s)) {
|
|
142
|
-
return { kind: 'no-cdp' };
|
|
143
|
-
}
|
|
144
|
-
if (/No tab found/i.test(s)) return { kind: 'no-tab' };
|
|
145
|
-
if (/Unauthenticated/i.test(s)) return { kind: 'not-logged-in' };
|
|
146
|
-
if (/Messaging queryId not found/i.test(s) || /Navigate to \/messaging\//i.test(s)) {
|
|
147
|
-
return { kind: 'not-messaging' };
|
|
148
|
-
}
|
|
149
|
-
if (/RateLimited/i.test(s) || /\b429\b/.test(s)) return { kind: 'rate-limited' };
|
|
150
|
-
if (/must be a 1st-degree connection/i.test(s)) return { kind: 'not-connection' };
|
|
151
|
-
if (/DEV_ONLY_MSG/i.test(s)) return { kind: 'capture-not-dev' };
|
|
152
|
-
return { kind: 'error', message: extractErrorMessage(s) };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Pull a human message out of capture stderr: prefer the last `ERROR:` line,
|
|
157
|
-
* else the last non-empty line, else a generic fallback.
|
|
158
|
-
* @param {string} stderr
|
|
159
|
-
* @returns {string}
|
|
160
|
-
*/
|
|
161
|
-
function extractErrorMessage(stderr) {
|
|
162
|
-
const lines = String(stderr || '')
|
|
163
|
-
.split(/\r?\n/)
|
|
164
|
-
.map((l) => l.trim())
|
|
165
|
-
.filter(Boolean);
|
|
166
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
167
|
-
if (/^ERROR:/i.test(lines[i])) return lines[i].replace(/^ERROR:\s*/i, '').trim();
|
|
168
|
-
}
|
|
169
|
-
if (lines.length) return lines[lines.length - 1];
|
|
170
|
-
return 'capture exec failed';
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ── capture command construction ─────────────────────────────────────────────
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Serialize a JS arg object as a literal safe to splice into the exec code
|
|
177
|
-
* string. JSON is a near-subset of JS object-literal syntax; we additionally
|
|
178
|
-
* escape U+2028/U+2029 (valid in JSON strings, historically illegal in JS) so a
|
|
179
|
-
* pasted reply can't break the splice.
|
|
180
|
-
* @param {Record<string, unknown>} obj
|
|
181
|
-
* @returns {string}
|
|
182
|
-
*/
|
|
183
|
-
function jsLiteral(obj) {
|
|
184
|
-
return JSON.stringify(obj).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Build the argv (for `execFile`) and the exec code string for a vault-lib call.
|
|
189
|
-
* The code string is a SINGLE argv element — passed as an array element, never a
|
|
190
|
-
* shell string — so there is no quoting to escape at the process boundary.
|
|
191
|
-
* `libArgs === null` emits a no-arg call (used by getContext).
|
|
192
|
-
*
|
|
193
|
-
* @param {string} fnName
|
|
194
|
-
* @param {Record<string, unknown> | null} libArgs
|
|
195
|
-
* @param {BaseOpts} [opts]
|
|
196
|
-
* @returns {{argv:string[], code:string}}
|
|
197
|
-
*/
|
|
198
|
-
function buildExec(fnName, libArgs, opts) {
|
|
199
|
-
const call = libArgs === null ? `${fnName}()` : `${fnName}(${jsLiteral(libArgs)})`;
|
|
200
|
-
const code = `import {${fnName}} from 'libs/linkedin'; return await ${call}`;
|
|
201
|
-
/** @type {string[]} */
|
|
202
|
-
const argv = ['exec', code];
|
|
203
|
-
if (opts && opts.target) argv.push('--target', String(opts.target));
|
|
204
|
-
if (opts && opts.port != null && opts.port !== '') argv.push('--port', String(opts.port));
|
|
205
|
-
return { argv, code };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Build the `capture list` argv (tab discovery; no CDP, no target).
|
|
210
|
-
* @param {BaseOpts} [opts]
|
|
211
|
-
* @returns {string[]}
|
|
212
|
-
*/
|
|
213
|
-
function buildListArgv(opts) {
|
|
214
|
-
const argv = ['list'];
|
|
215
|
-
if (opts && opts.port != null && opts.port !== '') argv.push('--port', String(opts.port));
|
|
216
|
-
return argv;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** The LinkedIn inbox URL the recovery flow opens / navigates to. */
|
|
220
|
-
const MESSAGING_URL = 'https://www.linkedin.com/messaging/';
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Build the `capture open <url>` argv (open/reuse a tab; no --target).
|
|
224
|
-
* @param {BaseOpts} [opts]
|
|
225
|
-
* @returns {string[]}
|
|
226
|
-
*/
|
|
227
|
-
function buildOpenArgv(opts) {
|
|
228
|
-
const argv = ['open', MESSAGING_URL];
|
|
229
|
-
if (opts && opts.port != null && opts.port !== '') argv.push('--port', String(opts.port));
|
|
230
|
-
return argv;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Build the `capture navigate <url> --target <id>` argv (drive an existing tab).
|
|
235
|
-
* @param {BaseOpts} [opts]
|
|
236
|
-
* @returns {string[]}
|
|
237
|
-
*/
|
|
238
|
-
function buildNavigateArgv(opts) {
|
|
239
|
-
const argv = ['navigate', MESSAGING_URL];
|
|
240
|
-
if (opts && opts.target) argv.push('--target', String(opts.target));
|
|
241
|
-
if (opts && opts.port != null && opts.port !== '') argv.push('--port', String(opts.port));
|
|
242
|
-
return argv;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Render an argv as the human-readable shell command (for logs/eyeballing). The
|
|
247
|
-
* code element (which contains spaces) gets double-quoted; nothing is executed.
|
|
248
|
-
* @param {string[]} argv
|
|
249
|
-
* @returns {string}
|
|
250
|
-
*/
|
|
251
|
-
function toDisplay(argv) {
|
|
252
|
-
return (
|
|
253
|
-
'capture ' +
|
|
254
|
-
argv
|
|
255
|
-
.map((a) => (/[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a))
|
|
256
|
-
.join(' ')
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// ── Process runner (never throws) ────────────────────────────────────────────
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* @typedef {Object} RunResult
|
|
264
|
-
* @property {boolean} spawned False ⇒ no candidate binary could be spawned.
|
|
265
|
-
* @property {number} exitCode Process exit code (0 on success; -1 if not spawned).
|
|
266
|
-
* @property {string} stdout
|
|
267
|
-
* @property {string} stderr
|
|
268
|
-
* @property {string} [bin] Which candidate actually ran.
|
|
269
|
-
*/
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Run one candidate binary. Resolves (never rejects). `spawnError` (ENOENT) is
|
|
273
|
-
* signalled by `spawned:false` so the caller can try the next candidate.
|
|
274
|
-
* @param {string} bin
|
|
275
|
-
* @param {string[]} argv
|
|
276
|
-
* @returns {Promise<RunResult>}
|
|
277
|
-
*/
|
|
278
|
-
function runOnce(bin, argv) {
|
|
279
|
-
return new Promise((resolve) => {
|
|
280
|
-
execFile(
|
|
281
|
-
bin,
|
|
282
|
-
argv,
|
|
283
|
-
{ maxBuffer: 64 * 1024 * 1024, encoding: 'utf8' },
|
|
284
|
-
(err, stdout, stderr) => {
|
|
285
|
-
const out = typeof stdout === 'string' ? stdout : '';
|
|
286
|
-
const errOut = typeof stderr === 'string' ? stderr : '';
|
|
287
|
-
if (err && /** @type {any} */ (err).code === 'ENOENT') {
|
|
288
|
-
resolve({ spawned: false, exitCode: -1, stdout: out, stderr: errOut, bin });
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
const code = err
|
|
292
|
-
? typeof /** @type {any} */ (err).code === 'number'
|
|
293
|
-
? /** @type {any} */ (err).code
|
|
294
|
-
: 1
|
|
295
|
-
: 0;
|
|
296
|
-
resolve({ spawned: true, exitCode: code, stdout: out, stderr: errOut, bin });
|
|
297
|
-
}
|
|
298
|
-
);
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Run `capture` with the given argv, walking the candidate list until one spawns.
|
|
304
|
-
* @param {string[]} argv
|
|
305
|
-
* @returns {Promise<RunResult>}
|
|
306
|
-
*/
|
|
307
|
-
async function runCapture(argv) {
|
|
308
|
-
let last = /** @type {RunResult} */ ({ spawned: false, exitCode: -1, stdout: '', stderr: '' });
|
|
309
|
-
for (const bin of captureCandidates()) {
|
|
310
|
-
last = await runOnce(bin, argv);
|
|
311
|
-
if (last.spawned) return last;
|
|
312
|
-
}
|
|
313
|
-
return last; // never spawned
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/** @returns {ClientError} */
|
|
317
|
-
function captureMissingError() {
|
|
318
|
-
return {
|
|
319
|
-
kind: 'error',
|
|
320
|
-
message:
|
|
321
|
-
`capture binary not found — tried CAPTURE_BIN, PATH 'capture', and dev fallback ` +
|
|
322
|
-
`${CAPTURE_DEV_FALLBACK}. Install capture or set CAPTURE_BIN.`,
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Run a vault-lib call and return the parsed JSON return value as a Result. The
|
|
328
|
-
* generic path; per-function wrappers map the raw value into the view's types.
|
|
329
|
-
* @param {string} fnName
|
|
330
|
-
* @param {Record<string, unknown> | null} libArgs
|
|
331
|
-
* @param {BaseOpts} opts
|
|
332
|
-
* @returns {Promise<Result<any>>}
|
|
333
|
-
*/
|
|
334
|
-
async function execLib(fnName, libArgs, opts) {
|
|
335
|
-
const { argv } = buildExec(fnName, libArgs, opts);
|
|
336
|
-
const r = await runCapture(argv);
|
|
337
|
-
if (!r.spawned) return fail(captureMissingError());
|
|
338
|
-
if (r.exitCode !== 0) return fail(classifyError(r.stderr));
|
|
339
|
-
const out = String(r.stdout || '').trim();
|
|
340
|
-
if (out === '') return ok(null);
|
|
341
|
-
try {
|
|
342
|
-
return ok(JSON.parse(out));
|
|
343
|
-
} catch {
|
|
344
|
-
return fail({ kind: 'error', message: `could not parse capture output as JSON: ${truncate(out, 300)}` });
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/** @param {string} s @param {number} n @returns {string} */
|
|
349
|
-
function truncate(s, n) {
|
|
350
|
-
const str = String(s);
|
|
351
|
-
return str.length > n ? str.slice(0, n) + '…' : str;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// ── Field mappers ────────────────────────────────────────────────────────────
|
|
355
|
-
|
|
356
|
-
/** @param {unknown} iso @returns {number} */
|
|
357
|
-
function parseTs(iso) {
|
|
358
|
-
if (typeof iso !== 'string' || iso === '') return 0;
|
|
359
|
-
const t = Date.parse(iso);
|
|
360
|
-
return Number.isNaN(t) ? 0 : t;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* @param {any} c raw vault conversation
|
|
365
|
-
* @returns {Conversation}
|
|
366
|
-
*/
|
|
367
|
-
function toConversation(c) {
|
|
368
|
-
const o = c || {};
|
|
369
|
-
const participants = Array.isArray(o.participants) ? o.participants : [];
|
|
370
|
-
const p0 = participants[0] || {};
|
|
371
|
-
const names = participants.map((/** @type {any} */ p) => p && p.name).filter(Boolean);
|
|
372
|
-
const name = o.title || p0.name || (names.length ? names.join(', ') : 'Unknown');
|
|
373
|
-
return {
|
|
374
|
-
urn: typeof o.conversationUrn === 'string' ? o.conversationUrn : '',
|
|
375
|
-
name,
|
|
376
|
-
lastMessage: typeof o.lastMessage === 'string' ? o.lastMessage : '',
|
|
377
|
-
unread: typeof o.unreadCount === 'number' ? o.unreadCount > 0 : false,
|
|
378
|
-
ts: parseTs(o.lastActivityAt),
|
|
379
|
-
recipientId: typeof p0.memberId === 'string' ? p0.memberId : '',
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* @param {any} m raw vault message
|
|
385
|
-
* @param {string} myMemberId
|
|
386
|
-
* @returns {Message}
|
|
387
|
-
*/
|
|
388
|
-
function toMessage(m, myMemberId) {
|
|
389
|
-
const o = m || {};
|
|
390
|
-
return {
|
|
391
|
-
urn: typeof o.messageUrn === 'string' ? o.messageUrn : '',
|
|
392
|
-
sender: typeof o.fromName === 'string' ? o.fromName : '',
|
|
393
|
-
text: typeof o.text === 'string' ? o.text : '',
|
|
394
|
-
ts: parseTs(o.sentAt),
|
|
395
|
-
fromMe: !!myMemberId && o.fromMemberId === myMemberId,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/** @param {string} url @returns {boolean} */
|
|
400
|
-
function isLinkedInUrl(url) {
|
|
401
|
-
try {
|
|
402
|
-
const h = new URL(url).hostname.toLowerCase();
|
|
403
|
-
return h === 'linkedin.com' || h.endsWith('.linkedin.com');
|
|
404
|
-
} catch {
|
|
405
|
-
return false;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// ── Public API ───────────────────────────────────────────────────────────────
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Find a usable CDP tab id. Runs `capture list`, parses the JSON, and returns the
|
|
413
|
-
* first LinkedIn `/messaging/` tab (preferred), else any LinkedIn tab. No
|
|
414
|
-
* LinkedIn tab ⇒ `no-tab`.
|
|
415
|
-
* @param {BaseOpts} [opts]
|
|
416
|
-
* @returns {Promise<Result<string>>}
|
|
417
|
-
*/
|
|
418
|
-
export async function discoverTab(opts = {}) {
|
|
419
|
-
const r = await runCapture(buildListArgv(opts));
|
|
420
|
-
if (!r.spawned) return fail(captureMissingError());
|
|
421
|
-
if (r.exitCode !== 0) return fail(classifyError(r.stderr));
|
|
422
|
-
let tabs;
|
|
423
|
-
try {
|
|
424
|
-
tabs = JSON.parse(String(r.stdout || '').trim() || '[]');
|
|
425
|
-
} catch {
|
|
426
|
-
return fail({ kind: 'error', message: `could not parse capture list output: ${truncate(r.stdout, 300)}` });
|
|
427
|
-
}
|
|
428
|
-
if (!Array.isArray(tabs)) return fail({ kind: 'no-tab' });
|
|
429
|
-
const linkedin = tabs.filter((t) => t && typeof t.url === 'string' && isLinkedInUrl(t.url));
|
|
430
|
-
const messaging = linkedin.find((t) => /\/messaging\//i.test(t.url));
|
|
431
|
-
const chosen = messaging || linkedin[0];
|
|
432
|
-
if (chosen && chosen.id) return ok(String(chosen.id));
|
|
433
|
-
return fail({ kind: 'no-tab' });
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Open (or reuse) a LinkedIn `/messaging/` tab and return its CDP tab id. Shells
|
|
438
|
-
* `capture open <url> [--port N]`, which finds an existing matching tab or opens
|
|
439
|
-
* one and BLOCKS on `Page.loadEventFired` (~10s timeout) — so it does the first
|
|
440
|
-
* page settle for us. Stdout is clean JSON `{id,title,url,port}` (the next-step
|
|
441
|
-
* hints go to stderr); we parse `.id`. Part of the not-ready recovery flow: the
|
|
442
|
-
* view drives the browser to the inbox instead of dead-ending on `no-tab`.
|
|
443
|
-
* @param {BaseOpts} [opts]
|
|
444
|
-
* @returns {Promise<Result<string>>}
|
|
445
|
-
*/
|
|
446
|
-
export async function openMessagingTab(opts = {}) {
|
|
447
|
-
const r = await runCapture(buildOpenArgv(opts));
|
|
448
|
-
if (!r.spawned) return fail(captureMissingError());
|
|
449
|
-
if (r.exitCode !== 0) return fail(classifyError(r.stderr));
|
|
450
|
-
let body;
|
|
451
|
-
try {
|
|
452
|
-
body = JSON.parse(String(r.stdout || '').trim() || '{}');
|
|
453
|
-
} catch {
|
|
454
|
-
return fail({ kind: 'error', message: `could not parse capture open output: ${truncate(r.stdout, 300)}` });
|
|
455
|
-
}
|
|
456
|
-
if (body && body.id) return ok(String(body.id));
|
|
457
|
-
return fail({ kind: 'error', message: 'capture open returned no tab id' });
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Drive an EXISTING LinkedIn tab to the `/messaging/` inbox. Shells
|
|
462
|
-
* `capture navigate <url> --target <tabId> [--port N]`. Used for the
|
|
463
|
-
* `not-messaging` recovery branch (a LinkedIn tab parked on the wrong page).
|
|
464
|
-
* Exit 0 ⇒ ok; we don't need the HAR body it returns on stdout.
|
|
465
|
-
* @param {BaseOpts} [opts]
|
|
466
|
-
* @returns {Promise<Result<void>>}
|
|
467
|
-
*/
|
|
468
|
-
export async function navigateToMessaging(opts = {}) {
|
|
469
|
-
const r = await runCapture(buildNavigateArgv(opts));
|
|
470
|
-
if (!r.spawned) return fail(captureMissingError());
|
|
471
|
-
if (r.exitCode !== 0) return fail(classifyError(r.stderr));
|
|
472
|
-
return ok(undefined);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Read the page auth context. Call ONCE; cache `csrf`+`memberId` and thread them
|
|
477
|
-
* into later calls.
|
|
478
|
-
* @param {BaseOpts} [opts]
|
|
479
|
-
* @returns {Promise<Result<LiContext>>}
|
|
480
|
-
*/
|
|
481
|
-
export async function getContext(opts = {}) {
|
|
482
|
-
const r = await execLib('getContext', null, opts);
|
|
483
|
-
if (!r.ok) return r;
|
|
484
|
-
const d = r.data || {};
|
|
485
|
-
if (!d.csrf || !d.memberId) {
|
|
486
|
-
return fail({ kind: 'error', message: 'getContext() returned no csrf/memberId' });
|
|
487
|
-
}
|
|
488
|
-
return ok({ csrf: String(d.csrf), memberId: String(d.memberId) });
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* List recent inbox conversations (newest-first from LinkedIn). Sorting
|
|
493
|
-
* unread-first is the VIEW's job; here we only map fields.
|
|
494
|
-
* @param {BaseOpts & {csrf?:string, memberId?:string, count?:number}} opts
|
|
495
|
-
* @returns {Promise<Result<Conversation[]>>}
|
|
496
|
-
*/
|
|
497
|
-
export async function listConversations(opts) {
|
|
498
|
-
const count = opts && typeof opts.count === 'number' ? opts.count : 20;
|
|
499
|
-
/** @type {Record<string, unknown>} */
|
|
500
|
-
const libArgs = { count };
|
|
501
|
-
if (opts && opts.csrf) libArgs.csrf = opts.csrf;
|
|
502
|
-
if (opts && opts.memberId) libArgs.memberId = opts.memberId;
|
|
503
|
-
const r = await execLib('listConversations', libArgs, opts);
|
|
504
|
-
if (!r.ok) return r;
|
|
505
|
-
const arr = r.data && Array.isArray(r.data.conversations) ? r.data.conversations : [];
|
|
506
|
-
return ok(arr.map(toConversation));
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Load a conversation's messages (oldest-first, up to ~40). `fromMe` is set by
|
|
511
|
-
* comparing each sender's member ID to `opts.myMemberId`.
|
|
512
|
-
* @param {BaseOpts & {csrf:string, conversationUrn:string, myMemberId?:string}} opts
|
|
513
|
-
* @returns {Promise<Result<Message[]>>}
|
|
514
|
-
*/
|
|
515
|
-
export async function viewConversation(opts) {
|
|
516
|
-
const libArgs = { csrf: opts.csrf, conversationUrn: opts.conversationUrn };
|
|
517
|
-
const r = await execLib('viewConversation', libArgs, opts);
|
|
518
|
-
if (!r.ok) return r;
|
|
519
|
-
const arr = r.data && Array.isArray(r.data.messages) ? r.data.messages : [];
|
|
520
|
-
const myId = (opts && opts.myMemberId) || '';
|
|
521
|
-
return ok(arr.map((/** @type {any} */ m) => toMessage(m, myId)));
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Send a message to a 1st-degree connection (optionally replying within
|
|
526
|
-
* `conversationUrn`). The 422 "must be a 1st-degree connection" maps to
|
|
527
|
-
* `not-connection` whether it arrives as exit-1 stderr OR as a `{success:false}`
|
|
528
|
-
* body.
|
|
529
|
-
* @param {BaseOpts & {csrf:string, myMemberId:string, recipient:string, text:string, conversationUrn?:string}} opts
|
|
530
|
-
* @returns {Promise<Result<void>>}
|
|
531
|
-
*/
|
|
532
|
-
export async function sendMessage(opts) {
|
|
533
|
-
/** @type {Record<string, unknown>} */
|
|
534
|
-
const libArgs = {
|
|
535
|
-
csrf: opts.csrf,
|
|
536
|
-
myMemberId: opts.myMemberId,
|
|
537
|
-
recipient: opts.recipient,
|
|
538
|
-
text: opts.text,
|
|
539
|
-
};
|
|
540
|
-
if (opts.conversationUrn) libArgs.conversationUrn = opts.conversationUrn;
|
|
541
|
-
const r = await execLib('sendMessage', libArgs, opts);
|
|
542
|
-
if (!r.ok) return r; // stderr-mapped errors (incl. 422 → not-connection)
|
|
543
|
-
const body = r.data || {};
|
|
544
|
-
if (body.success === false) {
|
|
545
|
-
const msg = typeof body.error === 'string' ? body.error : 'send failed';
|
|
546
|
-
if (/1st-degree connection/i.test(msg)) return fail({ kind: 'not-connection' });
|
|
547
|
-
return fail({ kind: 'error', message: msg });
|
|
548
|
-
}
|
|
549
|
-
return ok(undefined);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Mark a conversation as read (acknowledges its latest message).
|
|
554
|
-
* @param {BaseOpts & {csrf:string, conversationUrn:string}} opts
|
|
555
|
-
* @returns {Promise<Result<void>>}
|
|
556
|
-
*/
|
|
557
|
-
export async function markConversationAsRead(opts) {
|
|
558
|
-
const r = await execLib('markConversationAsRead', { csrf: opts.csrf, conversationUrn: opts.conversationUrn }, opts);
|
|
559
|
-
if (!r.ok) return r;
|
|
560
|
-
return ok(undefined);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* Add an emoji reaction to a message.
|
|
565
|
-
* @param {BaseOpts & {csrf:string, messageUrn:string, emoji:string}} opts
|
|
566
|
-
* @returns {Promise<Result<void>>}
|
|
567
|
-
*/
|
|
568
|
-
export async function reactToMessage(opts) {
|
|
569
|
-
const r = await execLib('reactToMessage', { csrf: opts.csrf, messageUrn: opts.messageUrn, emoji: opts.emoji }, opts);
|
|
570
|
-
if (!r.ok) return r;
|
|
571
|
-
const body = r.data || {};
|
|
572
|
-
if (body.success === false) return fail({ kind: 'error', message: 'LinkedIn rejected the reaction' });
|
|
573
|
-
return ok(undefined);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Introspection helper (not used at runtime): render the exact `capture` command
|
|
578
|
-
* each function constructs, with placeholder args, so the command shape can be
|
|
579
|
-
* eyeballed without a live browser.
|
|
580
|
-
* @param {BaseOpts} [opts]
|
|
581
|
-
* @returns {Record<string, string>}
|
|
582
|
-
*/
|
|
583
|
-
export function describeCommands(opts = {}) {
|
|
584
|
-
const o = { target: opts.target || '<tabId>', port: opts.port };
|
|
585
|
-
return {
|
|
586
|
-
discoverTab: toDisplay(buildListArgv(o)),
|
|
587
|
-
openMessagingTab: toDisplay(buildOpenArgv(o)),
|
|
588
|
-
navigateToMessaging: toDisplay(buildNavigateArgv(o)),
|
|
589
|
-
getContext: toDisplay(buildExec('getContext', null, o).argv),
|
|
590
|
-
listConversations: toDisplay(
|
|
591
|
-
buildExec('listConversations', { count: 20, csrf: '<csrf>', memberId: '<memberId>' }, o).argv
|
|
592
|
-
),
|
|
593
|
-
viewConversation: toDisplay(
|
|
594
|
-
buildExec('viewConversation', { csrf: '<csrf>', conversationUrn: '<conversationUrn>' }, o).argv
|
|
595
|
-
),
|
|
596
|
-
sendMessage: toDisplay(
|
|
597
|
-
buildExec(
|
|
598
|
-
'sendMessage',
|
|
599
|
-
{ csrf: '<csrf>', myMemberId: '<memberId>', recipient: '<recipientId>', text: 'hello' },
|
|
600
|
-
o
|
|
601
|
-
).argv
|
|
602
|
-
),
|
|
603
|
-
markConversationAsRead: toDisplay(
|
|
604
|
-
buildExec('markConversationAsRead', { csrf: '<csrf>', conversationUrn: '<conversationUrn>' }, o).argv
|
|
605
|
-
),
|
|
606
|
-
reactToMessage: toDisplay(
|
|
607
|
-
buildExec('reactToMessage', { csrf: '<csrf>', messageUrn: '<messageUrn>', emoji: '👍' }, o).argv
|
|
608
|
-
),
|
|
609
|
-
};
|
|
610
|
-
}
|