vellum 0.2.1 → 0.2.2
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 +15 -2
- package/bun.lock +5 -2
- package/package.json +4 -2
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +133 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +4 -3
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +130 -4
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-store.test.ts +216 -1
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +299 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +62 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +5 -3
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
- package/src/__tests__/run-orchestrator.test.ts +4 -4
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
- package/src/__tests__/runtime-runs-http.test.ts +4 -4
- package/src/__tests__/runtime-runs.test.ts +4 -4
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +71 -48
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +218 -3
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +186 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +12 -8
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +43 -5
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +43 -17
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-store.ts +192 -5
- package/src/calls/relay-server.ts +41 -4
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-provider.ts +10 -6
- package/src/calls/twilio-routes.ts +90 -76
- package/src/calls/types.ts +1 -1
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +251 -1
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +246 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +82 -23
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +33 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +161 -1
- package/src/config/system-prompt.ts +61 -16
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +163 -5
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -277
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +384 -68
- package/src/daemon/ipc-contract-inventory.json +28 -4
- package/src/daemon/ipc-contract.ts +133 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +21 -0
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +143 -26
- package/src/daemon/session-agent-loop.ts +887 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +216 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1404
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1153
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +96 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +23 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/http-server.ts +108 -20
- package/src/runtime/routes/attachment-routes.ts +2 -3
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +5 -10
- package/src/runtime/routes/conversation-routes.ts +5 -5
- package/src/runtime/routes/run-routes.ts +2 -2
- package/src/runtime/run-orchestrator.ts +9 -3
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +220 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +17 -67
- package/src/tools/calls/call-start.ts +24 -85
- package/src/tools/calls/call-status.ts +35 -51
- package/src/tools/claude-code/claude-code.ts +77 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +20 -4
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -8
- package/src/tools/tasks/task-delete.ts +60 -88
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +72 -108
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +183 -215
- package/src/tools/tasks/work-item-list.ts +33 -63
- package/src/tools/tasks/work-item-remove.ts +45 -97
- package/src/tools/tasks/work-item-update.ts +91 -163
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +0 -62
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +27 -2
- package/src/workspace/commit-message-enrichment-service.ts +31 -7
- package/src/workspace/git-service.ts +87 -22
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +62 -3
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command group: `vellum twitter`
|
|
3
|
+
*
|
|
4
|
+
* Post tweets and manage Twitter sessions via the command line.
|
|
5
|
+
* All commands output JSON to stdout. Use --json for machine-readable output.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as net from 'node:net';
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
import {
|
|
11
|
+
loadSession,
|
|
12
|
+
importFromRecording,
|
|
13
|
+
clearSession,
|
|
14
|
+
} from '../twitter/session.js';
|
|
15
|
+
import {
|
|
16
|
+
postTweet,
|
|
17
|
+
getUserByScreenName,
|
|
18
|
+
getUserTweets,
|
|
19
|
+
getTweetDetail,
|
|
20
|
+
searchTweets,
|
|
21
|
+
getBookmarks,
|
|
22
|
+
getHomeTimeline,
|
|
23
|
+
getNotifications,
|
|
24
|
+
getLikes,
|
|
25
|
+
getFollowers,
|
|
26
|
+
getFollowing,
|
|
27
|
+
getUserMedia,
|
|
28
|
+
SessionExpiredError,
|
|
29
|
+
} from '../twitter/client.js';
|
|
30
|
+
import { getSocketPath, readSessionToken } from '../util/platform.js';
|
|
31
|
+
import {
|
|
32
|
+
serialize,
|
|
33
|
+
createMessageParser,
|
|
34
|
+
} from '../daemon/ipc-protocol.js';
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Helpers
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
function output(data: unknown, json: boolean): void {
|
|
41
|
+
process.stdout.write(
|
|
42
|
+
json ? JSON.stringify(data) + '\n' : JSON.stringify(data, null, 2) + '\n',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function outputError(message: string, code = 1): void {
|
|
47
|
+
output({ ok: false, error: message }, true);
|
|
48
|
+
process.exitCode = code;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getJson(cmd: Command): boolean {
|
|
52
|
+
let c: Command | null = cmd;
|
|
53
|
+
while (c) {
|
|
54
|
+
if ((c.opts() as { json?: boolean }).json) return true;
|
|
55
|
+
c = c.parent;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const SESSION_EXPIRED_MSG =
|
|
61
|
+
'Your Twitter session has expired. Please sign in to Twitter in Chrome — ' +
|
|
62
|
+
'run `vellum twitter refresh` to capture your session automatically.';
|
|
63
|
+
|
|
64
|
+
async function run(cmd: Command, fn: () => Promise<unknown>): Promise<void> {
|
|
65
|
+
try {
|
|
66
|
+
const result = await fn();
|
|
67
|
+
output(
|
|
68
|
+
{ ok: true, ...(result as Record<string, unknown>) },
|
|
69
|
+
getJson(cmd),
|
|
70
|
+
);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err instanceof SessionExpiredError) {
|
|
73
|
+
output(
|
|
74
|
+
{ ok: false, error: 'session_expired', message: SESSION_EXPIRED_MSG },
|
|
75
|
+
getJson(cmd),
|
|
76
|
+
);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Command registration
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
export function registerTwitterCommand(program: Command): void {
|
|
89
|
+
const tw = program
|
|
90
|
+
.command('x')
|
|
91
|
+
.alias('twitter')
|
|
92
|
+
.description(
|
|
93
|
+
'Post on X and manage sessions. Requires a session imported from a Ride Shotgun recording.',
|
|
94
|
+
)
|
|
95
|
+
.option('--json', 'Machine-readable JSON output');
|
|
96
|
+
|
|
97
|
+
// =========================================================================
|
|
98
|
+
// login — import session from a recording
|
|
99
|
+
// =========================================================================
|
|
100
|
+
tw.command('login')
|
|
101
|
+
.description('Import a Twitter session from a Ride Shotgun recording')
|
|
102
|
+
.requiredOption(
|
|
103
|
+
'--recording <path>',
|
|
104
|
+
'Path to the recording JSON file',
|
|
105
|
+
)
|
|
106
|
+
.action(async (opts: { recording: string }, cmd: Command) => {
|
|
107
|
+
await run(cmd, async () => {
|
|
108
|
+
const session = importFromRecording(opts.recording);
|
|
109
|
+
return {
|
|
110
|
+
message: 'Session imported successfully',
|
|
111
|
+
cookieCount: session.cookies.length,
|
|
112
|
+
recordingId: session.recordingId,
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// =========================================================================
|
|
118
|
+
// logout — clear saved session
|
|
119
|
+
// =========================================================================
|
|
120
|
+
tw.command('logout')
|
|
121
|
+
.description('Clear the saved Twitter session')
|
|
122
|
+
.action((_opts: unknown, cmd: Command) => {
|
|
123
|
+
clearSession();
|
|
124
|
+
output({ ok: true, message: 'Session cleared' }, getJson(cmd));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// =========================================================================
|
|
128
|
+
// refresh — start Ride Shotgun learn to capture fresh cookies
|
|
129
|
+
// =========================================================================
|
|
130
|
+
tw.command('refresh')
|
|
131
|
+
.description(
|
|
132
|
+
'Start a Ride Shotgun learn session to capture fresh Twitter cookies. ' +
|
|
133
|
+
'Opens x.com in Chrome — sign in when prompted. ' +
|
|
134
|
+
'NOTE: Chrome will restart with debugging enabled; your tabs will be restored.',
|
|
135
|
+
)
|
|
136
|
+
.option('--duration <seconds>', 'Recording duration in seconds', '180')
|
|
137
|
+
.action(async (opts: { duration: string }, cmd: Command) => {
|
|
138
|
+
const json = getJson(cmd);
|
|
139
|
+
const duration = parseInt(opts.duration, 10);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const result = await startLearnSession(duration);
|
|
143
|
+
if (result.recordingPath) {
|
|
144
|
+
const session = importFromRecording(result.recordingPath);
|
|
145
|
+
|
|
146
|
+
// Hide Chrome after capturing session
|
|
147
|
+
try { await minimizeChromeWindow(); } catch { /* best-effort */ }
|
|
148
|
+
|
|
149
|
+
output(
|
|
150
|
+
{
|
|
151
|
+
ok: true,
|
|
152
|
+
message: 'Session refreshed successfully',
|
|
153
|
+
cookieCount: session.cookies.length,
|
|
154
|
+
recordingId: result.recordingId,
|
|
155
|
+
},
|
|
156
|
+
json,
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
output(
|
|
160
|
+
{
|
|
161
|
+
ok: false,
|
|
162
|
+
error: 'Recording completed but no recording path returned',
|
|
163
|
+
recordingId: result.recordingId,
|
|
164
|
+
},
|
|
165
|
+
json,
|
|
166
|
+
);
|
|
167
|
+
process.exitCode = 1;
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// =========================================================================
|
|
175
|
+
// status — check session status
|
|
176
|
+
// =========================================================================
|
|
177
|
+
tw.command('status')
|
|
178
|
+
.description('Check if a Twitter session is active')
|
|
179
|
+
.action((_opts: unknown, cmd: Command) => {
|
|
180
|
+
const session = loadSession();
|
|
181
|
+
if (session) {
|
|
182
|
+
output(
|
|
183
|
+
{
|
|
184
|
+
ok: true,
|
|
185
|
+
loggedIn: true,
|
|
186
|
+
cookieCount: session.cookies.length,
|
|
187
|
+
importedAt: session.importedAt,
|
|
188
|
+
recordingId: session.recordingId,
|
|
189
|
+
},
|
|
190
|
+
getJson(cmd),
|
|
191
|
+
);
|
|
192
|
+
} else {
|
|
193
|
+
output({ ok: true, loggedIn: false }, getJson(cmd));
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// =========================================================================
|
|
198
|
+
// post — post a tweet
|
|
199
|
+
// =========================================================================
|
|
200
|
+
tw.command('post')
|
|
201
|
+
.description('Post a tweet')
|
|
202
|
+
.argument('<text>', 'Tweet text')
|
|
203
|
+
.action(async (text: string, _opts: unknown, cmd: Command) => {
|
|
204
|
+
await run(cmd, async () => {
|
|
205
|
+
const result = await postTweet(text);
|
|
206
|
+
return {
|
|
207
|
+
tweetId: result.tweetId,
|
|
208
|
+
text: result.text,
|
|
209
|
+
url: result.url,
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// =========================================================================
|
|
215
|
+
// reply — reply to a tweet
|
|
216
|
+
// =========================================================================
|
|
217
|
+
tw.command('reply')
|
|
218
|
+
.description('Reply to a tweet')
|
|
219
|
+
.argument('<tweetUrl>', 'Tweet URL or tweet ID')
|
|
220
|
+
.argument('<text>', 'Reply text')
|
|
221
|
+
.action(async (tweetUrl: string, text: string, _opts: unknown, cmd: Command) => {
|
|
222
|
+
await run(cmd, async () => {
|
|
223
|
+
// Extract tweet ID: either a bare numeric ID or the last numeric segment of a URL
|
|
224
|
+
const idMatch = tweetUrl.match(/(\d+)\s*$/);
|
|
225
|
+
if (!idMatch) {
|
|
226
|
+
throw new Error(`Could not extract tweet ID from: ${tweetUrl}`);
|
|
227
|
+
}
|
|
228
|
+
const inReplyToTweetId = idMatch[1];
|
|
229
|
+
const result = await postTweet(text, { inReplyToTweetId });
|
|
230
|
+
return {
|
|
231
|
+
tweetId: result.tweetId,
|
|
232
|
+
text: result.text,
|
|
233
|
+
url: result.url,
|
|
234
|
+
inReplyToTweetId,
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
// =========================================================================
|
|
239
|
+
// timeline — fetch a user's recent tweets
|
|
240
|
+
// =========================================================================
|
|
241
|
+
tw.command('timeline')
|
|
242
|
+
.description("Fetch a user's recent tweets")
|
|
243
|
+
.argument('<screenName>', 'Twitter screen name (without @)')
|
|
244
|
+
.option('--count <n>', 'Number of tweets to fetch', '20')
|
|
245
|
+
.action(async (screenName: string, opts: { count: string }, cmd: Command) => {
|
|
246
|
+
await run(cmd, async () => {
|
|
247
|
+
const user = await getUserByScreenName(screenName.replace(/^@/, ''));
|
|
248
|
+
const tweets = await getUserTweets(user.userId, parseInt(opts.count, 10));
|
|
249
|
+
return { user, tweets };
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// =========================================================================
|
|
254
|
+
// tweet — fetch a single tweet and its replies
|
|
255
|
+
// =========================================================================
|
|
256
|
+
tw.command('tweet')
|
|
257
|
+
.description('Fetch a tweet and its reply thread')
|
|
258
|
+
.argument('<tweetIdOrUrl>', 'Tweet ID or URL')
|
|
259
|
+
.action(async (tweetIdOrUrl: string, _opts: unknown, cmd: Command) => {
|
|
260
|
+
await run(cmd, async () => {
|
|
261
|
+
const idMatch = tweetIdOrUrl.match(/(\d+)\s*$/);
|
|
262
|
+
if (!idMatch) throw new Error(`Could not extract tweet ID from: ${tweetIdOrUrl}`);
|
|
263
|
+
const tweets = await getTweetDetail(idMatch[1]);
|
|
264
|
+
return { tweets };
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// =========================================================================
|
|
269
|
+
// search — search tweets
|
|
270
|
+
// =========================================================================
|
|
271
|
+
tw.command('search')
|
|
272
|
+
.description('Search tweets')
|
|
273
|
+
.argument('<query>', 'Search query')
|
|
274
|
+
.option('--product <type>', 'Top, Latest, People, or Media', 'Top')
|
|
275
|
+
.action(async (query: string, opts: { product: string }, cmd: Command) => {
|
|
276
|
+
await run(cmd, async () => {
|
|
277
|
+
const tweets = await searchTweets(
|
|
278
|
+
query,
|
|
279
|
+
opts.product as 'Top' | 'Latest' | 'People' | 'Media',
|
|
280
|
+
);
|
|
281
|
+
return { query, tweets };
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// =========================================================================
|
|
286
|
+
// bookmarks — fetch bookmarks
|
|
287
|
+
// =========================================================================
|
|
288
|
+
tw.command('bookmarks')
|
|
289
|
+
.description('Fetch your bookmarks')
|
|
290
|
+
.option('--count <n>', 'Number of bookmarks', '20')
|
|
291
|
+
.action(async (opts: { count: string }, cmd: Command) => {
|
|
292
|
+
await run(cmd, async () => {
|
|
293
|
+
const tweets = await getBookmarks(parseInt(opts.count, 10));
|
|
294
|
+
return { tweets };
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// =========================================================================
|
|
299
|
+
// home — fetch home timeline
|
|
300
|
+
// =========================================================================
|
|
301
|
+
tw.command('home')
|
|
302
|
+
.description('Fetch your home timeline')
|
|
303
|
+
.option('--count <n>', 'Number of tweets', '20')
|
|
304
|
+
.action(async (opts: { count: string }, cmd: Command) => {
|
|
305
|
+
await run(cmd, async () => {
|
|
306
|
+
const tweets = await getHomeTimeline(parseInt(opts.count, 10));
|
|
307
|
+
return { tweets };
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// =========================================================================
|
|
312
|
+
// notifications — fetch notifications
|
|
313
|
+
// =========================================================================
|
|
314
|
+
tw.command('notifications')
|
|
315
|
+
.description('Fetch your notifications')
|
|
316
|
+
.option('--count <n>', 'Number of notifications', '20')
|
|
317
|
+
.action(async (opts: { count: string }, cmd: Command) => {
|
|
318
|
+
await run(cmd, async () => {
|
|
319
|
+
const notifications = await getNotifications(parseInt(opts.count, 10));
|
|
320
|
+
return { notifications };
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// =========================================================================
|
|
325
|
+
// likes — fetch a user's liked tweets
|
|
326
|
+
// =========================================================================
|
|
327
|
+
tw.command('likes')
|
|
328
|
+
.description("Fetch a user's liked tweets")
|
|
329
|
+
.argument('<screenName>', 'Twitter screen name (without @)')
|
|
330
|
+
.option('--count <n>', 'Number of likes', '20')
|
|
331
|
+
.action(async (screenName: string, opts: { count: string }, cmd: Command) => {
|
|
332
|
+
await run(cmd, async () => {
|
|
333
|
+
const user = await getUserByScreenName(screenName.replace(/^@/, ''));
|
|
334
|
+
const tweets = await getLikes(user.userId, parseInt(opts.count, 10));
|
|
335
|
+
return { user, tweets };
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// =========================================================================
|
|
340
|
+
// followers — fetch a user's followers
|
|
341
|
+
// =========================================================================
|
|
342
|
+
tw.command('followers')
|
|
343
|
+
.description("Fetch a user's followers")
|
|
344
|
+
.argument('<screenName>', 'Twitter screen name (without @)')
|
|
345
|
+
.action(async (screenName: string, _opts: unknown, cmd: Command) => {
|
|
346
|
+
await run(cmd, async () => {
|
|
347
|
+
const cleanName = screenName.replace(/^@/, '');
|
|
348
|
+
const user = await getUserByScreenName(cleanName);
|
|
349
|
+
const followers = await getFollowers(user.userId, cleanName);
|
|
350
|
+
return { user, followers };
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// =========================================================================
|
|
355
|
+
// following — fetch who a user follows
|
|
356
|
+
// =========================================================================
|
|
357
|
+
tw.command('following')
|
|
358
|
+
.description("Fetch who a user follows")
|
|
359
|
+
.argument('<screenName>', 'Twitter screen name (without @)')
|
|
360
|
+
.option('--count <n>', 'Number of following', '20')
|
|
361
|
+
.action(async (screenName: string, opts: { count: string }, cmd: Command) => {
|
|
362
|
+
await run(cmd, async () => {
|
|
363
|
+
const user = await getUserByScreenName(screenName.replace(/^@/, ''));
|
|
364
|
+
const following = await getFollowing(user.userId, parseInt(opts.count, 10));
|
|
365
|
+
return { user, following };
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// =========================================================================
|
|
370
|
+
// media — fetch a user's media tweets
|
|
371
|
+
// =========================================================================
|
|
372
|
+
tw.command('media')
|
|
373
|
+
.description("Fetch a user's media tweets")
|
|
374
|
+
.argument('<screenName>', 'Twitter screen name (without @)')
|
|
375
|
+
.option('--count <n>', 'Number of media tweets', '20')
|
|
376
|
+
.action(async (screenName: string, opts: { count: string }, cmd: Command) => {
|
|
377
|
+
await run(cmd, async () => {
|
|
378
|
+
const user = await getUserByScreenName(screenName.replace(/^@/, ''));
|
|
379
|
+
const tweets = await getUserMedia(user.userId, parseInt(opts.count, 10));
|
|
380
|
+
return { user, tweets };
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ---------------------------------------------------------------------------
|
|
386
|
+
// Chrome CDP restart helper
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
import { spawn as spawnChild } from 'node:child_process';
|
|
390
|
+
import { homedir } from 'node:os';
|
|
391
|
+
import { join as pathJoin } from 'node:path';
|
|
392
|
+
|
|
393
|
+
const CDP_BASE = 'http://localhost:9222';
|
|
394
|
+
const CHROME_DATA_DIR = pathJoin(
|
|
395
|
+
homedir(),
|
|
396
|
+
'Library/Application Support/Google/Chrome-CDP',
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
async function isCdpReady(): Promise<boolean> {
|
|
400
|
+
try {
|
|
401
|
+
const res = await fetch(`${CDP_BASE}/json/version`);
|
|
402
|
+
return res.ok;
|
|
403
|
+
} catch {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function ensureChromeWithCDP(): Promise<void> {
|
|
409
|
+
// Already running with CDP?
|
|
410
|
+
if (await isCdpReady()) return;
|
|
411
|
+
|
|
412
|
+
// Launch a separate Chrome instance with CDP flags alongside any existing Chrome.
|
|
413
|
+
// Using a dedicated --user-data-dir allows coexistence without killing the user's browser.
|
|
414
|
+
const chromeApp =
|
|
415
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
416
|
+
spawnChild(chromeApp, [
|
|
417
|
+
`--remote-debugging-port=9222`,
|
|
418
|
+
`--force-renderer-accessibility`,
|
|
419
|
+
`--user-data-dir=${CHROME_DATA_DIR}`,
|
|
420
|
+
'https://x.com/login',
|
|
421
|
+
], {
|
|
422
|
+
detached: true,
|
|
423
|
+
stdio: 'ignore',
|
|
424
|
+
}).unref();
|
|
425
|
+
|
|
426
|
+
// Wait for CDP to be ready
|
|
427
|
+
for (let i = 0; i < 30; i++) {
|
|
428
|
+
await new Promise(r => setTimeout(r, 500));
|
|
429
|
+
if (await isCdpReady()) return;
|
|
430
|
+
}
|
|
431
|
+
throw new Error('Chrome started but CDP endpoint not responding after 15s');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function minimizeChromeWindow(): Promise<void> {
|
|
435
|
+
const res = await fetch(`${CDP_BASE}/json/list`);
|
|
436
|
+
const targets = (await res.json()) as Array<{ type: string; webSocketDebuggerUrl: string }>;
|
|
437
|
+
const pageTarget = targets.find(t => t.type === 'page');
|
|
438
|
+
if (!pageTarget) return;
|
|
439
|
+
|
|
440
|
+
const ws = new WebSocket(pageTarget.webSocketDebuggerUrl);
|
|
441
|
+
|
|
442
|
+
await new Promise<void>((resolve, reject) => {
|
|
443
|
+
const timeout = setTimeout(() => {
|
|
444
|
+
ws.close();
|
|
445
|
+
reject(new Error('CDP minimize timed out'));
|
|
446
|
+
}, 5000);
|
|
447
|
+
|
|
448
|
+
ws.addEventListener('open', () => {
|
|
449
|
+
ws.send(JSON.stringify({ id: 1, method: 'Browser.getWindowForTarget' }));
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
ws.addEventListener('message', (event) => {
|
|
453
|
+
const msg = JSON.parse(String(event.data)) as { id: number; result?: { windowId: number } };
|
|
454
|
+
if (msg.id === 1 && msg.result) {
|
|
455
|
+
ws.send(JSON.stringify({
|
|
456
|
+
id: 2,
|
|
457
|
+
method: 'Browser.setWindowBounds',
|
|
458
|
+
params: { windowId: msg.result.windowId, bounds: { windowState: 'minimized' } },
|
|
459
|
+
}));
|
|
460
|
+
} else if (msg.id === 2) {
|
|
461
|
+
clearTimeout(timeout);
|
|
462
|
+
ws.close();
|
|
463
|
+
resolve();
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
ws.addEventListener('error', (err) => {
|
|
468
|
+
clearTimeout(timeout);
|
|
469
|
+
reject(err);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
// Ride Shotgun learn session helper
|
|
476
|
+
// ---------------------------------------------------------------------------
|
|
477
|
+
|
|
478
|
+
interface LearnResult {
|
|
479
|
+
recordingId?: string;
|
|
480
|
+
recordingPath?: string;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function navigateToX(): Promise<void> {
|
|
484
|
+
try {
|
|
485
|
+
const res = await fetch(`${CDP_BASE}/json/list`);
|
|
486
|
+
if (!res.ok) return;
|
|
487
|
+
const targets = (await res.json()) as Array<{ id: string; type: string; url: string }>;
|
|
488
|
+
const tab = targets.find(t => t.type === 'page');
|
|
489
|
+
if (!tab) return;
|
|
490
|
+
await fetch(`${CDP_BASE}/json/navigate?url=${encodeURIComponent('https://x.com/login')}&id=${tab.id}`, { method: 'PUT' });
|
|
491
|
+
} catch {
|
|
492
|
+
// best-effort
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function startLearnSession(durationSeconds: number): Promise<LearnResult> {
|
|
497
|
+
await ensureChromeWithCDP();
|
|
498
|
+
await navigateToX();
|
|
499
|
+
|
|
500
|
+
return new Promise((resolve, reject) => {
|
|
501
|
+
const socketPath = getSocketPath();
|
|
502
|
+
const sessionToken = readSessionToken();
|
|
503
|
+
const socket = net.createConnection(socketPath);
|
|
504
|
+
const parser = createMessageParser();
|
|
505
|
+
|
|
506
|
+
socket.on('error', (err) => {
|
|
507
|
+
reject(new Error(`Cannot connect to daemon: ${err.message}. Is the daemon running?`));
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const timeoutHandle = setTimeout(() => {
|
|
511
|
+
socket.destroy();
|
|
512
|
+
reject(new Error(`Learn session timed out after ${durationSeconds + 30}s`));
|
|
513
|
+
}, (durationSeconds + 30) * 1000);
|
|
514
|
+
timeoutHandle.unref();
|
|
515
|
+
|
|
516
|
+
let authenticated = !sessionToken;
|
|
517
|
+
|
|
518
|
+
const sendStartCommand = () => {
|
|
519
|
+
socket.write(
|
|
520
|
+
serialize({
|
|
521
|
+
type: 'ride_shotgun_start',
|
|
522
|
+
durationSeconds,
|
|
523
|
+
intervalSeconds: 5,
|
|
524
|
+
mode: 'learn',
|
|
525
|
+
targetDomain: 'x.com',
|
|
526
|
+
} as unknown as import('../daemon/ipc-protocol.js').ClientMessage),
|
|
527
|
+
);
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
socket.on('data', (chunk) => {
|
|
531
|
+
const messages = parser.feed(chunk.toString('utf-8'));
|
|
532
|
+
for (const msg of messages) {
|
|
533
|
+
const m = msg as unknown as Record<string, unknown>;
|
|
534
|
+
|
|
535
|
+
if (!authenticated && m.type === 'auth_result') {
|
|
536
|
+
if ((m as { success: boolean }).success) {
|
|
537
|
+
authenticated = true;
|
|
538
|
+
sendStartCommand();
|
|
539
|
+
} else {
|
|
540
|
+
clearTimeout(timeoutHandle);
|
|
541
|
+
socket.destroy();
|
|
542
|
+
reject(new Error('Daemon authentication failed'));
|
|
543
|
+
}
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (m.type === 'auth_result') {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (m.type === 'ride_shotgun_result') {
|
|
552
|
+
clearTimeout(timeoutHandle);
|
|
553
|
+
socket.destroy();
|
|
554
|
+
resolve({
|
|
555
|
+
recordingId: m.recordingId as string | undefined,
|
|
556
|
+
recordingPath: m.recordingPath as string | undefined,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
socket.on('connect', () => {
|
|
563
|
+
if (sessionToken) {
|
|
564
|
+
socket.write(
|
|
565
|
+
serialize({
|
|
566
|
+
type: 'auth',
|
|
567
|
+
token: sessionToken,
|
|
568
|
+
} as unknown as import('../daemon/ipc-protocol.js').ClientMessage),
|
|
569
|
+
);
|
|
570
|
+
} else {
|
|
571
|
+
sendStartCommand();
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -13,12 +13,14 @@ import {
|
|
|
13
13
|
} from './daemon/ipc-protocol.js';
|
|
14
14
|
import { formatDiff, formatNewFileDiff } from './util/diff.js';
|
|
15
15
|
import { Spinner } from './util/spinner.js';
|
|
16
|
+
import { truncate } from './util/truncate.js';
|
|
16
17
|
import { copyToClipboard, extractLastCodeBlock, formatSessionForExport } from './util/clipboard.js';
|
|
17
18
|
import { timeAgo } from './util/time.js';
|
|
18
19
|
import { ensureDaemonRunning } from './daemon/lifecycle.js';
|
|
19
20
|
import { shouldAutoStartDaemon } from './daemon/connection-policy.js';
|
|
20
21
|
import { renderMainScreen, updateStatusText, updateDaemonText, type MainScreenLayout } from './cli/main-screen.jsx';
|
|
21
22
|
|
|
23
|
+
const SHORT_HASH_LENGTH = 8;
|
|
22
24
|
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
23
25
|
const HEARTBEAT_TIMEOUT_MS = 10_000;
|
|
24
26
|
const RECONNECT_BASE_DELAY_MS = 1_000;
|
|
@@ -51,7 +53,7 @@ export function formatPrincipalTag(req: Pick<ConfirmationRequest, 'principalKind
|
|
|
51
53
|
const name = req.principalId ?? req.principalKind;
|
|
52
54
|
// Show a shortened version hash when available (first 8 hex chars after any scheme prefix)
|
|
53
55
|
const versionSuffix = req.principalVersion
|
|
54
|
-
? `@${req.principalVersion.replace(/^[^:]+:/, '').slice(0,
|
|
56
|
+
? `@${req.principalVersion.replace(/^[^:]+:/, '').slice(0, SHORT_HASH_LENGTH)}`
|
|
55
57
|
: '';
|
|
56
58
|
const target = req.executionTarget ? ` \u2192 ${req.executionTarget}` : '';
|
|
57
59
|
return `[${req.principalKind}: ${name}${versionSuffix}${target}]`;
|
|
@@ -179,7 +181,7 @@ export async function startCli(): Promise<void> {
|
|
|
179
181
|
if (req.toolName === 'browser_press_key') {
|
|
180
182
|
return `press "${req.input.key ?? ''}"`;
|
|
181
183
|
}
|
|
182
|
-
return `${req.toolName}: ${JSON.stringify(req.input)
|
|
184
|
+
return `${req.toolName}: ${truncate(JSON.stringify(req.input), 80)}`;
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
function renderConfirmationPrompt(req: ConfirmationRequest): void {
|
|
@@ -331,7 +333,7 @@ export async function startCli(): Promise<void> {
|
|
|
331
333
|
for (let i = 0; i < sessions.length; i++) {
|
|
332
334
|
const s = sessions[i];
|
|
333
335
|
const ago = timeAgo(s.updatedAt);
|
|
334
|
-
const title = s.title
|
|
336
|
+
const title = truncate(s.title, 50);
|
|
335
337
|
const padding = ' '.repeat(Math.max(1, 55 - title.length));
|
|
336
338
|
process.stdout.write(` [${i + 1}] ${title}${padding}${ago}\n`);
|
|
337
339
|
}
|
|
@@ -499,7 +501,7 @@ export async function startCli(): Promise<void> {
|
|
|
499
501
|
}
|
|
500
502
|
process.stdout.write('\n');
|
|
501
503
|
} else {
|
|
502
|
-
process.stdout.write(`\n[Tool: ${msg.result
|
|
504
|
+
process.stdout.write(`\n[Tool: ${truncate(msg.result, 200)}]\n`);
|
|
503
505
|
}
|
|
504
506
|
toolStreaming = false;
|
|
505
507
|
if (msg.diff) {
|
|
@@ -585,7 +587,7 @@ export async function startCli(): Promise<void> {
|
|
|
585
587
|
} else {
|
|
586
588
|
for (const m of msg.messages) {
|
|
587
589
|
const label = m.role === 'user' ? 'you' : 'assistant';
|
|
588
|
-
const preview = m.text
|
|
590
|
+
const preview = truncate(m.text, 120);
|
|
589
591
|
process.stdout.write(` ${label}> ${preview.replace(/\n/g, ' ')}\n`);
|
|
590
592
|
}
|
|
591
593
|
}
|