@vellumai/assistant 0.4.35 → 0.4.37
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/AGENTS.md +1 -1
- package/ARCHITECTURE.md +44 -49
- package/README.md +32 -20
- package/docs/architecture/keychain-broker.md +186 -0
- package/docs/architecture/security.md +110 -116
- package/docs/runbook-trusted-contacts.md +2 -2
- package/docs/skills.md +25 -25
- package/package.json +5 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +11 -2
- package/src/__tests__/actor-token-service.test.ts +1 -0
- package/src/__tests__/amazon-cdp-integration.test.ts +74 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +38 -9
- package/src/__tests__/assistant-id-boundary-guard.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/bundle-scanner.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +102 -102
- package/src/__tests__/channel-invite-transport.test.ts +155 -256
- package/src/__tests__/channel-readiness-routes.test.ts +336 -0
- package/src/__tests__/checker.test.ts +6 -6
- package/src/__tests__/chrome-cdp.test.ts +350 -0
- package/src/__tests__/computer-use-session-lifecycle.test.ts +3 -3
- package/src/__tests__/computer-use-session-working-dir.test.ts +86 -52
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +1 -1
- package/src/__tests__/config-loader-migration.test.ts +85 -0
- package/src/__tests__/conversation-pairing.test.ts +370 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +1 -10
- package/src/__tests__/credential-broker-server-use.test.ts +1 -10
- package/src/__tests__/credential-security-e2e.test.ts +7 -1
- package/src/__tests__/credential-security-invariants.test.ts +14 -20
- package/src/__tests__/credential-vault-unit.test.ts +1 -11
- package/src/__tests__/credential-vault.test.ts +5 -19
- package/src/__tests__/credentials-cli.test.ts +814 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +23 -4
- package/src/__tests__/email-invite-adapter.test.ts +78 -0
- package/src/__tests__/email-service-config-fallback.test.ts +102 -0
- package/src/__tests__/encrypted-store.test.ts +6 -6
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +5 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +70 -12
- package/src/__tests__/guardian-outbound-http.test.ts +53 -47
- package/src/__tests__/handle-user-message-secret-resume.test.ts +23 -0
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +32 -23
- package/src/__tests__/handlers-telegram-config.test.ts +8 -2
- package/src/__tests__/handlers-twitter-config.test.ts +2 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +108 -7
- package/src/__tests__/ingress-reconcile.test.ts +6 -0
- package/src/__tests__/intent-routing.test.ts +23 -4
- package/src/__tests__/invite-routes-http.test.ts +12 -0
- package/src/__tests__/ipc-snapshot.test.ts +8 -2
- package/src/__tests__/keychain-broker-client.test.ts +543 -0
- package/src/__tests__/llm-usage-store.test.ts +344 -0
- package/src/__tests__/mcp-client-auth.test.ts +2 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
- package/src/__tests__/migration-transport.test.ts +49 -0
- package/src/__tests__/notification-broadcaster.test.ts +205 -5
- package/src/__tests__/notification-deep-link.test.ts +365 -1
- package/src/__tests__/oauth-connect-handler.test.ts +2 -2
- package/src/__tests__/onboarding-starter-tasks.test.ts +17 -4
- package/src/__tests__/proxy-approval-callback.test.ts +1 -1
- package/src/__tests__/recording-handler.test.ts +1 -1
- package/src/__tests__/recording-intent-handler.test.ts +6 -1
- package/src/__tests__/recording-state-machine.test.ts +1 -1
- package/src/__tests__/relay-server.test.ts +9 -1
- package/src/__tests__/ride-shotgun-handler.test.ts +499 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +160 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +299 -2
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +8 -2
- package/src/__tests__/secure-keys.test.ts +175 -216
- package/src/__tests__/session-confirmation-signals.test.ts +1 -1
- package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/session-queue.test.ts +2 -1
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +2 -2
- package/src/__tests__/skill-feature-flags-integration.test.ts +29 -4
- package/src/__tests__/skill-feature-flags.test.ts +12 -9
- package/src/__tests__/skill-load-feature-flag.test.ts +26 -5
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skills.test.ts +34 -4
- package/src/__tests__/slack-channel-config.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +26 -4
- package/src/__tests__/telegram-bot-username-resolution.test.ts +212 -0
- package/src/__tests__/telegram-invite-adapter.test.ts +164 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-permission-simulate-handler.test.ts +8 -2
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +9 -1
- package/src/__tests__/twitter-auth-handler.test.ts +2 -2
- package/src/__tests__/twitter-oauth-client.test.ts +1 -1
- package/src/__tests__/usage-routes.test.ts +339 -0
- package/src/__tests__/whatsapp-invite-adapter.test.ts +94 -0
- package/src/agent/loop.ts +3 -0
- package/src/amazon/checkout.ts +0 -1
- package/src/approvals/guardian-request-resolvers.ts +9 -1
- package/src/bundler/app-bundler.ts +28 -12
- package/src/bundler/bundle-scanner.ts +1 -1
- package/src/bundler/bundle-signer.ts +3 -3
- package/src/bundler/manifest.ts +1 -1
- package/src/bundler/signature-verifier.ts +3 -3
- package/src/channels/config.ts +1 -1
- package/src/cli/AGENTS.md +63 -0
- package/src/cli/__tests__/notifications.test.ts +470 -0
- package/src/cli/amazon.ts +344 -167
- package/src/cli/audit.ts +85 -0
- package/src/cli/autonomy.ts +369 -0
- package/src/cli/channels.ts +51 -0
- package/src/cli/completions.ts +208 -0
- package/src/cli/config.ts +220 -0
- package/src/cli/contacts.ts +471 -0
- package/src/cli/credentials.ts +564 -0
- package/src/cli/default-action.ts +14 -0
- package/src/cli/dev.ts +131 -0
- package/src/cli/doctor.ts +398 -0
- package/src/cli/email.ts +494 -0
- package/src/cli/influencer.ts +72 -0
- package/src/cli/integrations.ts +248 -57
- package/src/cli/keys.ts +114 -0
- package/src/cli/map.ts +46 -54
- package/src/cli/mcp.ts +111 -3
- package/src/cli/{config-commands.ts → memory.ts} +134 -245
- package/src/cli/notifications.ts +407 -0
- package/src/cli/program.ts +65 -0
- package/src/cli/reference.ts +48 -0
- package/src/cli/sequence.ts +154 -0
- package/src/cli/sessions.ts +262 -0
- package/src/cli/trust.ts +175 -0
- package/src/cli/twitter.ts +323 -106
- package/src/config/__tests__/build-cli-reference-section.test.ts +49 -0
- package/src/config/bundled-skills/amazon/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/TOOLS.json +26 -0
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +13 -0
- package/src/config/bundled-skills/contacts/SKILL.md +178 -10
- package/src/config/bundled-skills/doordash/doordash-cli.ts +23 -168
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +135 -34
- package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
- package/src/config/bundled-skills/twilio-setup/SKILL.md +70 -17
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/core-schema.ts +7 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +26 -0
- package/src/config/schema.ts +4 -0
- package/src/config/skill-state.ts +0 -13
- package/src/config/system-prompt.ts +27 -0
- package/src/contacts/contact-store.ts +25 -0
- package/src/daemon/computer-use-session.ts +1 -1
- package/src/daemon/handlers/apps.ts +1 -0
- package/src/daemon/handlers/config-channels.ts +3 -3
- package/src/daemon/handlers/config-dispatch.ts +29 -0
- package/src/daemon/handlers/config-inbox.ts +4 -3
- package/src/daemon/handlers/config.ts +3 -43
- package/src/daemon/handlers/contacts.ts +34 -0
- package/src/daemon/handlers/index.ts +17 -3
- package/src/daemon/handlers/session-user-message.ts +7 -0
- package/src/daemon/handlers/sessions.ts +21 -2
- package/src/daemon/handlers/shared.ts +17 -0
- package/src/daemon/ipc-contract/apps.ts +2 -0
- package/src/daemon/ipc-contract/computer-use.ts +9 -0
- package/src/daemon/ipc-contract/contacts.ts +3 -3
- package/src/daemon/ipc-contract/inbox.ts +2 -0
- package/src/daemon/ipc-contract/messages.ts +4 -0
- package/src/daemon/ipc-contract/sessions.ts +8 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/ride-shotgun-handler.ts +139 -25
- package/src/daemon/session-agent-loop-handlers.ts +100 -0
- package/src/daemon/session-agent-loop.ts +72 -0
- package/src/daemon/session-tool-setup.ts +7 -0
- package/src/daemon/session.ts +23 -1
- package/src/daemon/tool-side-effects.ts +39 -1
- package/src/email/service.ts +59 -2
- package/src/index.ts +2 -60
- package/src/mcp/mcp-oauth-provider.ts +90 -8
- package/src/media/app-icon-generator.ts +86 -0
- package/src/memory/db-init.ts +11 -0
- package/src/memory/llm-usage-store.ts +186 -0
- package/src/memory/migrations/137-usage-dashboard-indexes.ts +26 -0
- package/src/memory/migrations/139-drop-usage-composite-indexes.ts +30 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/shared-app-links-store.ts +1 -1
- package/src/messaging/registry.ts +27 -0
- package/src/notifications/README.md +79 -70
- package/src/notifications/broadcaster.ts +2 -1
- package/src/notifications/conversation-pairing.ts +147 -13
- package/src/notifications/copy-composer.ts +7 -3
- package/src/notifications/destination-resolver.ts +14 -1
- package/src/notifications/emit-signal.ts +3 -2
- package/src/notifications/signal.ts +105 -1
- package/src/notifications/types.ts +16 -0
- package/src/permissions/checker.ts +29 -3
- package/src/permissions/prompter.ts +11 -3
- package/src/runtime/access-request-helper.ts +2 -1
- package/src/runtime/auth/route-policy.ts +7 -1
- package/src/runtime/channel-invite-transport.ts +40 -63
- package/src/runtime/channel-invite-transports/email.ts +13 -39
- package/src/runtime/channel-invite-transports/slack.ts +5 -34
- package/src/runtime/channel-invite-transports/sms.ts +8 -29
- package/src/runtime/channel-invite-transports/telegram.ts +69 -28
- package/src/runtime/channel-invite-transports/voice.ts +0 -7
- package/src/runtime/channel-invite-transports/whatsapp.ts +43 -0
- package/src/runtime/channel-readiness-service.ts +202 -45
- package/src/runtime/confirmation-request-guardian-bridge.ts +2 -1
- package/src/runtime/guardian-outbound-actions.ts +8 -5
- package/src/runtime/http-server.ts +2 -0
- package/src/runtime/invite-instruction-generator.ts +178 -0
- package/src/runtime/invite-service.ts +22 -25
- package/src/runtime/migrations/migration-transport.ts +13 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +8 -7
- package/src/runtime/routes/channel-readiness-routes.ts +30 -11
- package/src/runtime/routes/contact-routes.ts +54 -26
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -1
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +2 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +2 -1
- package/src/runtime/routes/integration-routes.ts +1 -1
- package/src/runtime/routes/invite-routes.ts +1 -1
- package/src/runtime/routes/secret-routes.ts +31 -7
- package/src/runtime/routes/twilio-routes.ts +32 -1
- package/src/runtime/routes/usage-routes.ts +114 -0
- package/src/runtime/tool-grant-request-helper.ts +2 -1
- package/src/security/encrypted-store.ts +9 -5
- package/src/security/keychain-broker-client.ts +393 -0
- package/src/security/secure-keys.ts +106 -321
- package/src/tools/apps/executors.ts +73 -0
- package/src/tools/browser/auto-navigate.ts +15 -6
- package/src/tools/browser/chrome-cdp.ts +211 -0
- package/src/tools/browser/network-recorder.test.ts +83 -0
- package/src/tools/browser/network-recorder.ts +8 -7
- package/src/tools/browser/x-auto-navigate.ts +12 -6
- package/src/tools/credentials/policy-types.ts +24 -0
- package/src/tools/credentials/vault.ts +22 -27
- package/src/tools/network/script-proxy/session-manager.ts +47 -3
- package/src/tools/permission-checker.ts +1 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +1 -2
- package/src/tools/watch/watch-state.ts +2 -0
- package/src/__tests__/key-migration.test.ts +0 -240
- package/src/__tests__/keychain.test.ts +0 -286
- package/src/cli/core-commands.ts +0 -899
- package/src/security/keychain-to-encrypted-migration.ts +0 -66
- package/src/security/keychain.ts +0 -490
package/src/cli/twitter.ts
CHANGED
|
@@ -118,12 +118,57 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
118
118
|
)
|
|
119
119
|
.option("--json", "Machine-readable JSON output");
|
|
120
120
|
|
|
121
|
+
tw.addHelpText(
|
|
122
|
+
"after",
|
|
123
|
+
`
|
|
124
|
+
Twitter (X) uses a dual-path architecture for interacting with the platform:
|
|
125
|
+
|
|
126
|
+
1. OAuth (official API) — uses an authenticated Twitter OAuth application for
|
|
127
|
+
posting and replying. Requires a connected OAuth credential.
|
|
128
|
+
2. Browser session (Ride Shotgun) — uses cookies captured from a real Chrome
|
|
129
|
+
session to call Twitter's internal GraphQL API. Supports all read operations
|
|
130
|
+
and posting as a fallback.
|
|
131
|
+
|
|
132
|
+
The strategy system controls which path is used for operations that support both:
|
|
133
|
+
oauth — always use the OAuth API; fail if unavailable
|
|
134
|
+
browser — always use the browser session; fail if unavailable
|
|
135
|
+
auto — try OAuth first, fall back to browser session (default)
|
|
136
|
+
|
|
137
|
+
Session management:
|
|
138
|
+
- "login" imports cookies from a Ride Shotgun recording file
|
|
139
|
+
- "refresh" launches Chrome with CDP, navigates to x.com/login, and runs a
|
|
140
|
+
Ride Shotgun learn session to capture fresh cookies automatically
|
|
141
|
+
- "status" shows whether browser session and OAuth are active
|
|
142
|
+
- "logout" clears the saved browser session cookies
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
$ vellum x status
|
|
146
|
+
$ vellum x post "Hello world"
|
|
147
|
+
$ vellum x timeline elonmusk --count 10
|
|
148
|
+
$ vellum x search "from:vaborsh AI agents" --product Latest
|
|
149
|
+
$ vellum x strategy set oauth`,
|
|
150
|
+
);
|
|
151
|
+
|
|
121
152
|
// =========================================================================
|
|
122
153
|
// login — import session from a recording
|
|
123
154
|
// =========================================================================
|
|
124
155
|
tw.command("login")
|
|
125
156
|
.description("Import a Twitter session from a Ride Shotgun recording")
|
|
126
157
|
.requiredOption("--recording <path>", "Path to the recording JSON file")
|
|
158
|
+
.addHelpText(
|
|
159
|
+
"after",
|
|
160
|
+
`
|
|
161
|
+
Imports cookies from a Ride Shotgun recording file to establish a browser
|
|
162
|
+
session. The recording file is a JSON file produced by a Ride Shotgun learn
|
|
163
|
+
session that contains captured cookies for x.com.
|
|
164
|
+
|
|
165
|
+
After import, all browser-path commands (timeline, search, bookmarks, etc.)
|
|
166
|
+
will use these cookies for authentication.
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
$ vellum x login --recording /tmp/ride-shotgun/recording-abc123.json
|
|
170
|
+
$ vellum x login --recording ~/recordings/twitter-session.json`,
|
|
171
|
+
)
|
|
127
172
|
.action(async (opts: { recording: string }, cmd: Command) => {
|
|
128
173
|
await run(cmd, async () => {
|
|
129
174
|
const session = importFromRecording(opts.recording);
|
|
@@ -140,6 +185,16 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
140
185
|
// =========================================================================
|
|
141
186
|
tw.command("logout")
|
|
142
187
|
.description("Clear the saved Twitter session")
|
|
188
|
+
.addHelpText(
|
|
189
|
+
"after",
|
|
190
|
+
`
|
|
191
|
+
Deletes all saved browser session cookies. After logout, browser-path commands
|
|
192
|
+
will fail until a new session is imported via "login" or captured via "refresh".
|
|
193
|
+
OAuth credentials are not affected.
|
|
194
|
+
|
|
195
|
+
Examples:
|
|
196
|
+
$ vellum x logout`,
|
|
197
|
+
)
|
|
143
198
|
.action((_opts: unknown, cmd: Command) => {
|
|
144
199
|
clearSession();
|
|
145
200
|
output({ ok: true, message: "Session cleared" }, getJson(cmd));
|
|
@@ -155,6 +210,24 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
155
210
|
"NOTE: Chrome will restart with debugging enabled; your tabs will be restored.",
|
|
156
211
|
)
|
|
157
212
|
.option("--duration <seconds>", "Recording duration in seconds", "180")
|
|
213
|
+
.addHelpText(
|
|
214
|
+
"after",
|
|
215
|
+
`
|
|
216
|
+
Restarts Chrome with CDP (Chrome DevTools Protocol) enabled, navigates to
|
|
217
|
+
x.com/login, and runs a Ride Shotgun learn session to capture fresh cookies.
|
|
218
|
+
Sign in when Chrome opens — the session will be recorded automatically.
|
|
219
|
+
|
|
220
|
+
The --duration flag sets how long (in seconds) the recording runs before
|
|
221
|
+
stopping. Default is 180 seconds (3 minutes). After the recording completes,
|
|
222
|
+
cookies are imported automatically and Chrome is minimized.
|
|
223
|
+
|
|
224
|
+
Requires the assistant to be running (Ride Shotgun runs via the assistant).
|
|
225
|
+
|
|
226
|
+
Examples:
|
|
227
|
+
$ vellum x refresh
|
|
228
|
+
$ vellum x refresh --duration 120
|
|
229
|
+
$ vellum x refresh --duration 300`,
|
|
230
|
+
)
|
|
158
231
|
.action(async (opts: { duration: string }, cmd: Command) => {
|
|
159
232
|
const json = getJson(cmd);
|
|
160
233
|
const duration = parseInt(opts.duration, 10);
|
|
@@ -166,7 +239,7 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
166
239
|
|
|
167
240
|
// Hide Chrome after capturing session
|
|
168
241
|
try {
|
|
169
|
-
await minimizeChromeWindow();
|
|
242
|
+
await minimizeChromeWindow(); // uses default CDP port
|
|
170
243
|
} catch {
|
|
171
244
|
/* best-effort */
|
|
172
245
|
}
|
|
@@ -201,6 +274,22 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
201
274
|
// =========================================================================
|
|
202
275
|
tw.command("status")
|
|
203
276
|
.description("Check Twitter session, OAuth, and strategy status")
|
|
277
|
+
.addHelpText(
|
|
278
|
+
"after",
|
|
279
|
+
`
|
|
280
|
+
Shows the current state of both authentication paths:
|
|
281
|
+
|
|
282
|
+
Browser session — whether cookies are loaded, cookie count, import timestamp,
|
|
283
|
+
and the recording ID they came from.
|
|
284
|
+
OAuth — whether an OAuth credential is connected, the linked account, the
|
|
285
|
+
current strategy setting, and whether a strategy has been explicitly configured.
|
|
286
|
+
|
|
287
|
+
If the assistant is not running, OAuth fields will be reported as undefined.
|
|
288
|
+
|
|
289
|
+
Examples:
|
|
290
|
+
$ vellum x status
|
|
291
|
+
$ vellum x status --json`,
|
|
292
|
+
)
|
|
204
293
|
.action(async (_opts: unknown, cmd: Command) => {
|
|
205
294
|
const session = loadSession();
|
|
206
295
|
const browserInfo: Record<string, unknown> = session
|
|
@@ -258,6 +347,26 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
258
347
|
.description(
|
|
259
348
|
"Get or set the Twitter operation strategy (oauth, browser, auto)",
|
|
260
349
|
)
|
|
350
|
+
.addHelpText(
|
|
351
|
+
"after",
|
|
352
|
+
`
|
|
353
|
+
The strategy controls which authentication path is used for operations that
|
|
354
|
+
support both OAuth and browser session:
|
|
355
|
+
|
|
356
|
+
oauth — always use the official Twitter OAuth API. Fails if no OAuth
|
|
357
|
+
credential is connected. Best for reliable posting.
|
|
358
|
+
browser — always use the browser session (captured cookies). Fails if no
|
|
359
|
+
session is loaded. Required for read-only endpoints not available
|
|
360
|
+
via OAuth (bookmarks, notifications, search).
|
|
361
|
+
auto — try OAuth first, fall back to browser session. This is the default.
|
|
362
|
+
|
|
363
|
+
Run without a subcommand to display the current strategy. Use "set" to change it.
|
|
364
|
+
|
|
365
|
+
Examples:
|
|
366
|
+
$ vellum x strategy
|
|
367
|
+
$ vellum x strategy set oauth
|
|
368
|
+
$ vellum x strategy set auto`,
|
|
369
|
+
)
|
|
261
370
|
.action(async (_opts: unknown, cmd: Command) => {
|
|
262
371
|
const json = getJson(cmd);
|
|
263
372
|
try {
|
|
@@ -279,6 +388,21 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
279
388
|
.command("set")
|
|
280
389
|
.description("Set the Twitter operation strategy")
|
|
281
390
|
.argument("<value>", "Strategy value: oauth, browser, or auto")
|
|
391
|
+
.addHelpText(
|
|
392
|
+
"after",
|
|
393
|
+
`
|
|
394
|
+
Arguments:
|
|
395
|
+
value Strategy to use: "oauth", "browser", or "auto"
|
|
396
|
+
|
|
397
|
+
Sets the preferred strategy for Twitter operations that support dual-path
|
|
398
|
+
routing. The setting is persisted by the assistant and applies to all subsequent
|
|
399
|
+
operations until changed.
|
|
400
|
+
|
|
401
|
+
Examples:
|
|
402
|
+
$ vellum x strategy set oauth
|
|
403
|
+
$ vellum x strategy set browser
|
|
404
|
+
$ vellum x strategy set auto`,
|
|
405
|
+
)
|
|
282
406
|
.action(async (value: string, _opts: unknown, cmd: Command) => {
|
|
283
407
|
const json = getJson(cmd);
|
|
284
408
|
try {
|
|
@@ -311,6 +435,20 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
311
435
|
tw.command("post")
|
|
312
436
|
.description("Post a tweet")
|
|
313
437
|
.argument("<text>", "Tweet text")
|
|
438
|
+
.addHelpText(
|
|
439
|
+
"after",
|
|
440
|
+
`
|
|
441
|
+
Arguments:
|
|
442
|
+
text The tweet text to post (max 280 characters)
|
|
443
|
+
|
|
444
|
+
Posts a new tweet using the routed dual-path system. The path used (oauth or
|
|
445
|
+
browser) depends on the current strategy setting. The response includes the
|
|
446
|
+
tweet ID, URL, and which path was used.
|
|
447
|
+
|
|
448
|
+
Examples:
|
|
449
|
+
$ vellum x post "Hello world"
|
|
450
|
+
$ vellum x post "Check out this thread on AI agents" --json`,
|
|
451
|
+
)
|
|
314
452
|
.action(async (text: string, _opts: unknown, cmd: Command) => {
|
|
315
453
|
await run(cmd, async () => {
|
|
316
454
|
const { result, pathUsed } = await routedPostTweet(text);
|
|
@@ -330,6 +468,21 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
330
468
|
.description("Reply to a tweet")
|
|
331
469
|
.argument("<tweetUrl>", "Tweet URL or tweet ID")
|
|
332
470
|
.argument("<text>", "Reply text")
|
|
471
|
+
.addHelpText(
|
|
472
|
+
"after",
|
|
473
|
+
`
|
|
474
|
+
Arguments:
|
|
475
|
+
tweetUrl Full tweet URL (e.g. https://x.com/user/status/123456) or a bare tweet ID
|
|
476
|
+
text The reply text to post (max 280 characters)
|
|
477
|
+
|
|
478
|
+
Posts a reply to the specified tweet. Accepts either a full tweet URL or a bare
|
|
479
|
+
numeric tweet ID. The tweet ID is extracted from the last numeric segment of the
|
|
480
|
+
URL. Uses the routed dual-path system based on the current strategy.
|
|
481
|
+
|
|
482
|
+
Examples:
|
|
483
|
+
$ vellum x reply https://x.com/elonmusk/status/1234567890 "Great point!"
|
|
484
|
+
$ vellum x reply 1234567890 "Interesting thread"`,
|
|
485
|
+
)
|
|
333
486
|
.action(
|
|
334
487
|
async (tweetUrl: string, text: string, _opts: unknown, cmd: Command) => {
|
|
335
488
|
await run(cmd, async () => {
|
|
@@ -359,6 +512,21 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
359
512
|
.description("Fetch a user's recent tweets")
|
|
360
513
|
.argument("<screenName>", "Twitter screen name (without @)")
|
|
361
514
|
.option("--count <n>", "Number of tweets to fetch", "20")
|
|
515
|
+
.addHelpText(
|
|
516
|
+
"after",
|
|
517
|
+
`
|
|
518
|
+
Arguments:
|
|
519
|
+
screenName Twitter screen name without the @ prefix (e.g. "elonmusk", not "@elonmusk")
|
|
520
|
+
|
|
521
|
+
Fetches a user's recent tweets via the browser session. Resolves the screen name
|
|
522
|
+
to a user ID first, then retrieves their tweet timeline. The --count flag controls
|
|
523
|
+
how many tweets to return (default: 20).
|
|
524
|
+
|
|
525
|
+
Examples:
|
|
526
|
+
$ vellum x timeline elonmusk
|
|
527
|
+
$ vellum x timeline vaborsh --count 50
|
|
528
|
+
$ vellum x timeline openai --count 10 --json`,
|
|
529
|
+
)
|
|
362
530
|
.action(
|
|
363
531
|
async (screenName: string, opts: { count: string }, cmd: Command) => {
|
|
364
532
|
await run(cmd, async () => {
|
|
@@ -378,6 +546,22 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
378
546
|
tw.command("tweet")
|
|
379
547
|
.description("Fetch a tweet and its reply thread")
|
|
380
548
|
.argument("<tweetIdOrUrl>", "Tweet ID or URL")
|
|
549
|
+
.addHelpText(
|
|
550
|
+
"after",
|
|
551
|
+
`
|
|
552
|
+
Arguments:
|
|
553
|
+
tweetIdOrUrl A bare tweet ID (e.g. 1234567890) or a full tweet URL
|
|
554
|
+
(e.g. https://x.com/user/status/1234567890)
|
|
555
|
+
|
|
556
|
+
Fetches a single tweet and its reply thread via the browser session. The tweet
|
|
557
|
+
ID is extracted from the last numeric segment of the input. Returns an array of
|
|
558
|
+
tweets representing the conversation thread.
|
|
559
|
+
|
|
560
|
+
Examples:
|
|
561
|
+
$ vellum x tweet 1234567890
|
|
562
|
+
$ vellum x tweet https://x.com/elonmusk/status/1234567890
|
|
563
|
+
$ vellum x tweet https://x.com/openai/status/9876543210 --json`,
|
|
564
|
+
)
|
|
381
565
|
.action(async (tweetIdOrUrl: string, _opts: unknown, cmd: Command) => {
|
|
382
566
|
await run(cmd, async () => {
|
|
383
567
|
const idMatch = tweetIdOrUrl.match(/(\d+)\s*$/);
|
|
@@ -395,6 +579,26 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
395
579
|
.description("Search tweets")
|
|
396
580
|
.argument("<query>", "Search query")
|
|
397
581
|
.option("--product <type>", "Top, Latest, People, or Media", "Top")
|
|
582
|
+
.addHelpText(
|
|
583
|
+
"after",
|
|
584
|
+
`
|
|
585
|
+
Arguments:
|
|
586
|
+
query Twitter search query string. Supports Twitter search operators
|
|
587
|
+
(e.g. "from:user", "to:user", "min_faves:100", quoted phrases)
|
|
588
|
+
|
|
589
|
+
The --product flag selects the search result type:
|
|
590
|
+
Top — most relevant tweets (default)
|
|
591
|
+
Latest — most recent tweets, reverse chronological
|
|
592
|
+
People — user accounts matching the query
|
|
593
|
+
Media — tweets containing images or video
|
|
594
|
+
|
|
595
|
+
Uses the browser session path. Requires an active browser session.
|
|
596
|
+
|
|
597
|
+
Examples:
|
|
598
|
+
$ vellum x search "AI agents"
|
|
599
|
+
$ vellum x search "from:elonmusk SpaceX" --product Latest
|
|
600
|
+
$ vellum x search "machine learning" --product Media --json`,
|
|
601
|
+
)
|
|
398
602
|
.action(async (query: string, opts: { product: string }, cmd: Command) => {
|
|
399
603
|
await run(cmd, async () => {
|
|
400
604
|
const tweets = await searchTweets(
|
|
@@ -411,6 +615,20 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
411
615
|
tw.command("bookmarks")
|
|
412
616
|
.description("Fetch your bookmarks")
|
|
413
617
|
.option("--count <n>", "Number of bookmarks", "20")
|
|
618
|
+
.addHelpText(
|
|
619
|
+
"after",
|
|
620
|
+
`
|
|
621
|
+
Fetches the authenticated user's bookmarked tweets via the browser session.
|
|
622
|
+
The --count flag controls how many bookmarks to return (default: 20).
|
|
623
|
+
|
|
624
|
+
Requires an active browser session. Bookmarks are private and only available
|
|
625
|
+
for the logged-in account.
|
|
626
|
+
|
|
627
|
+
Examples:
|
|
628
|
+
$ vellum x bookmarks
|
|
629
|
+
$ vellum x bookmarks --count 50
|
|
630
|
+
$ vellum x bookmarks --json`,
|
|
631
|
+
)
|
|
414
632
|
.action(async (opts: { count: string }, cmd: Command) => {
|
|
415
633
|
await run(cmd, async () => {
|
|
416
634
|
const tweets = await getBookmarks(parseInt(opts.count, 10));
|
|
@@ -424,6 +642,19 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
424
642
|
tw.command("home")
|
|
425
643
|
.description("Fetch your home timeline")
|
|
426
644
|
.option("--count <n>", "Number of tweets", "20")
|
|
645
|
+
.addHelpText(
|
|
646
|
+
"after",
|
|
647
|
+
`
|
|
648
|
+
Fetches the authenticated user's home timeline (the "For You" feed) via the
|
|
649
|
+
browser session. The --count flag controls how many tweets to return (default: 20).
|
|
650
|
+
|
|
651
|
+
Requires an active browser session.
|
|
652
|
+
|
|
653
|
+
Examples:
|
|
654
|
+
$ vellum x home
|
|
655
|
+
$ vellum x home --count 50
|
|
656
|
+
$ vellum x home --json`,
|
|
657
|
+
)
|
|
427
658
|
.action(async (opts: { count: string }, cmd: Command) => {
|
|
428
659
|
await run(cmd, async () => {
|
|
429
660
|
const tweets = await getHomeTimeline(parseInt(opts.count, 10));
|
|
@@ -437,6 +668,20 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
437
668
|
tw.command("notifications")
|
|
438
669
|
.description("Fetch your notifications")
|
|
439
670
|
.option("--count <n>", "Number of notifications", "20")
|
|
671
|
+
.addHelpText(
|
|
672
|
+
"after",
|
|
673
|
+
`
|
|
674
|
+
Fetches the authenticated user's Twitter notifications (mentions, likes,
|
|
675
|
+
retweets, follows, etc.) via the browser session. The --count flag controls
|
|
676
|
+
how many notifications to return (default: 20).
|
|
677
|
+
|
|
678
|
+
Requires an active browser session.
|
|
679
|
+
|
|
680
|
+
Examples:
|
|
681
|
+
$ vellum x notifications
|
|
682
|
+
$ vellum x notifications --count 50
|
|
683
|
+
$ vellum x notifications --json`,
|
|
684
|
+
)
|
|
440
685
|
.action(async (opts: { count: string }, cmd: Command) => {
|
|
441
686
|
await run(cmd, async () => {
|
|
442
687
|
const notifications = await getNotifications(parseInt(opts.count, 10));
|
|
@@ -451,6 +696,21 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
451
696
|
.description("Fetch a user's liked tweets")
|
|
452
697
|
.argument("<screenName>", "Twitter screen name (without @)")
|
|
453
698
|
.option("--count <n>", "Number of likes", "20")
|
|
699
|
+
.addHelpText(
|
|
700
|
+
"after",
|
|
701
|
+
`
|
|
702
|
+
Arguments:
|
|
703
|
+
screenName Twitter screen name without the @ prefix (e.g. "elonmusk", not "@elonmusk")
|
|
704
|
+
|
|
705
|
+
Fetches tweets liked by the specified user via the browser session. Resolves the
|
|
706
|
+
screen name to a user ID first. The --count flag controls how many liked tweets
|
|
707
|
+
to return (default: 20).
|
|
708
|
+
|
|
709
|
+
Examples:
|
|
710
|
+
$ vellum x likes elonmusk
|
|
711
|
+
$ vellum x likes vaborsh --count 50
|
|
712
|
+
$ vellum x likes openai --json`,
|
|
713
|
+
)
|
|
454
714
|
.action(
|
|
455
715
|
async (screenName: string, opts: { count: string }, cmd: Command) => {
|
|
456
716
|
await run(cmd, async () => {
|
|
@@ -467,6 +727,19 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
467
727
|
tw.command("followers")
|
|
468
728
|
.description("Fetch a user's followers")
|
|
469
729
|
.argument("<screenName>", "Twitter screen name (without @)")
|
|
730
|
+
.addHelpText(
|
|
731
|
+
"after",
|
|
732
|
+
`
|
|
733
|
+
Arguments:
|
|
734
|
+
screenName Twitter screen name without the @ prefix (e.g. "elonmusk", not "@elonmusk")
|
|
735
|
+
|
|
736
|
+
Fetches the list of accounts following the specified user via the browser session.
|
|
737
|
+
Resolves the screen name to a user ID first.
|
|
738
|
+
|
|
739
|
+
Examples:
|
|
740
|
+
$ vellum x followers elonmusk
|
|
741
|
+
$ vellum x followers vaborsh --json`,
|
|
742
|
+
)
|
|
470
743
|
.action(async (screenName: string, _opts: unknown, cmd: Command) => {
|
|
471
744
|
await run(cmd, async () => {
|
|
472
745
|
const cleanName = screenName.replace(/^@/, "");
|
|
@@ -483,6 +756,21 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
483
756
|
.description("Fetch who a user follows")
|
|
484
757
|
.argument("<screenName>", "Twitter screen name (without @)")
|
|
485
758
|
.option("--count <n>", "Number of following", "20")
|
|
759
|
+
.addHelpText(
|
|
760
|
+
"after",
|
|
761
|
+
`
|
|
762
|
+
Arguments:
|
|
763
|
+
screenName Twitter screen name without the @ prefix (e.g. "elonmusk", not "@elonmusk")
|
|
764
|
+
|
|
765
|
+
Fetches the list of accounts the specified user follows via the browser session.
|
|
766
|
+
Resolves the screen name to a user ID first. The --count flag controls how many
|
|
767
|
+
results to return (default: 20).
|
|
768
|
+
|
|
769
|
+
Examples:
|
|
770
|
+
$ vellum x following elonmusk
|
|
771
|
+
$ vellum x following vaborsh --count 100
|
|
772
|
+
$ vellum x following openai --json`,
|
|
773
|
+
)
|
|
486
774
|
.action(
|
|
487
775
|
async (screenName: string, opts: { count: string }, cmd: Command) => {
|
|
488
776
|
await run(cmd, async () => {
|
|
@@ -503,6 +791,21 @@ export function registerTwitterCommand(program: Command): void {
|
|
|
503
791
|
.description("Fetch a user's media tweets")
|
|
504
792
|
.argument("<screenName>", "Twitter screen name (without @)")
|
|
505
793
|
.option("--count <n>", "Number of media tweets", "20")
|
|
794
|
+
.addHelpText(
|
|
795
|
+
"after",
|
|
796
|
+
`
|
|
797
|
+
Arguments:
|
|
798
|
+
screenName Twitter screen name without the @ prefix (e.g. "elonmusk", not "@elonmusk")
|
|
799
|
+
|
|
800
|
+
Fetches tweets containing images or video from the specified user via the browser
|
|
801
|
+
session. Resolves the screen name to a user ID first. The --count flag controls
|
|
802
|
+
how many media tweets to return (default: 20).
|
|
803
|
+
|
|
804
|
+
Examples:
|
|
805
|
+
$ vellum x media elonmusk
|
|
806
|
+
$ vellum x media nasa --count 50
|
|
807
|
+
$ vellum x media openai --json`,
|
|
808
|
+
)
|
|
506
809
|
.action(
|
|
507
810
|
async (screenName: string, opts: { count: string }, cmd: Command) => {
|
|
508
811
|
await run(cmd, async () => {
|
|
@@ -613,108 +916,13 @@ function sendDaemonMessage(
|
|
|
613
916
|
}
|
|
614
917
|
|
|
615
918
|
// ---------------------------------------------------------------------------
|
|
616
|
-
// Chrome CDP
|
|
919
|
+
// Chrome CDP helpers (shared)
|
|
617
920
|
// ---------------------------------------------------------------------------
|
|
618
921
|
|
|
619
|
-
import {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const CDP_BASE = "http://localhost:9222";
|
|
624
|
-
const CHROME_DATA_DIR = pathJoin(
|
|
625
|
-
homedir(),
|
|
626
|
-
"Library/Application Support/Google/Chrome-CDP",
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
async function isCdpReady(): Promise<boolean> {
|
|
630
|
-
try {
|
|
631
|
-
const res = await fetch(`${CDP_BASE}/json/version`);
|
|
632
|
-
return res.ok;
|
|
633
|
-
} catch {
|
|
634
|
-
return false;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
async function ensureChromeWithCDP(): Promise<void> {
|
|
639
|
-
// Already running with CDP?
|
|
640
|
-
if (await isCdpReady()) return;
|
|
641
|
-
|
|
642
|
-
// Launch a separate Chrome instance with CDP flags alongside any existing Chrome.
|
|
643
|
-
// Using a dedicated --user-data-dir allows coexistence without killing the user's browser.
|
|
644
|
-
const chromeApp =
|
|
645
|
-
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
646
|
-
spawnChild(
|
|
647
|
-
chromeApp,
|
|
648
|
-
[
|
|
649
|
-
`--remote-debugging-port=9222`,
|
|
650
|
-
`--force-renderer-accessibility`,
|
|
651
|
-
`--user-data-dir=${CHROME_DATA_DIR}`,
|
|
652
|
-
"https://x.com/login",
|
|
653
|
-
],
|
|
654
|
-
{
|
|
655
|
-
detached: true,
|
|
656
|
-
stdio: "ignore",
|
|
657
|
-
},
|
|
658
|
-
).unref();
|
|
659
|
-
|
|
660
|
-
// Wait for CDP to be ready
|
|
661
|
-
for (let i = 0; i < 30; i++) {
|
|
662
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
663
|
-
if (await isCdpReady()) return;
|
|
664
|
-
}
|
|
665
|
-
throw new Error("Chrome started but CDP endpoint not responding after 15s");
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
async function minimizeChromeWindow(): Promise<void> {
|
|
669
|
-
const res = await fetch(`${CDP_BASE}/json/list`);
|
|
670
|
-
const targets = (await res.json()) as Array<{
|
|
671
|
-
type: string;
|
|
672
|
-
webSocketDebuggerUrl: string;
|
|
673
|
-
}>;
|
|
674
|
-
const pageTarget = targets.find((t) => t.type === "page");
|
|
675
|
-
if (!pageTarget) return;
|
|
676
|
-
|
|
677
|
-
const ws = new WebSocket(pageTarget.webSocketDebuggerUrl);
|
|
678
|
-
|
|
679
|
-
await new Promise<void>((resolve, reject) => {
|
|
680
|
-
const timeout = setTimeout(() => {
|
|
681
|
-
ws.close();
|
|
682
|
-
reject(new Error("CDP minimize timed out"));
|
|
683
|
-
}, 5000);
|
|
684
|
-
|
|
685
|
-
ws.addEventListener("open", () => {
|
|
686
|
-
ws.send(JSON.stringify({ id: 1, method: "Browser.getWindowForTarget" }));
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
ws.addEventListener("message", (event) => {
|
|
690
|
-
const msg = JSON.parse(String(event.data)) as {
|
|
691
|
-
id: number;
|
|
692
|
-
result?: { windowId: number };
|
|
693
|
-
};
|
|
694
|
-
if (msg.id === 1 && msg.result) {
|
|
695
|
-
ws.send(
|
|
696
|
-
JSON.stringify({
|
|
697
|
-
id: 2,
|
|
698
|
-
method: "Browser.setWindowBounds",
|
|
699
|
-
params: {
|
|
700
|
-
windowId: msg.result.windowId,
|
|
701
|
-
bounds: { windowState: "minimized" },
|
|
702
|
-
},
|
|
703
|
-
}),
|
|
704
|
-
);
|
|
705
|
-
} else if (msg.id === 2) {
|
|
706
|
-
clearTimeout(timeout);
|
|
707
|
-
ws.close();
|
|
708
|
-
resolve();
|
|
709
|
-
}
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
ws.addEventListener("error", (err) => {
|
|
713
|
-
clearTimeout(timeout);
|
|
714
|
-
reject(err);
|
|
715
|
-
});
|
|
716
|
-
});
|
|
717
|
-
}
|
|
922
|
+
import {
|
|
923
|
+
ensureChromeWithCdp,
|
|
924
|
+
minimizeChromeWindow,
|
|
925
|
+
} from "../tools/browser/chrome-cdp.js";
|
|
718
926
|
|
|
719
927
|
// ---------------------------------------------------------------------------
|
|
720
928
|
// Ride Shotgun learn session helper
|
|
@@ -725,9 +933,9 @@ interface LearnResult {
|
|
|
725
933
|
recordingPath?: string;
|
|
726
934
|
}
|
|
727
935
|
|
|
728
|
-
async function navigateToX(): Promise<void> {
|
|
936
|
+
async function navigateToX(cdpBase: string): Promise<void> {
|
|
729
937
|
try {
|
|
730
|
-
const res = await fetch(`${
|
|
938
|
+
const res = await fetch(`${cdpBase}/json/list`);
|
|
731
939
|
if (!res.ok) return;
|
|
732
940
|
const targets = (await res.json()) as Array<{
|
|
733
941
|
id: string;
|
|
@@ -737,7 +945,7 @@ async function navigateToX(): Promise<void> {
|
|
|
737
945
|
const tab = targets.find((t) => t.type === "page");
|
|
738
946
|
if (!tab) return;
|
|
739
947
|
await fetch(
|
|
740
|
-
`${
|
|
948
|
+
`${cdpBase}/json/navigate?url=${encodeURIComponent(
|
|
741
949
|
"https://x.com/login",
|
|
742
950
|
)}&id=${tab.id}`,
|
|
743
951
|
{ method: "PUT" },
|
|
@@ -750,8 +958,10 @@ async function navigateToX(): Promise<void> {
|
|
|
750
958
|
async function startLearnSession(
|
|
751
959
|
durationSeconds: number,
|
|
752
960
|
): Promise<LearnResult> {
|
|
753
|
-
await
|
|
754
|
-
|
|
961
|
+
const cdpSession = await ensureChromeWithCdp({
|
|
962
|
+
startUrl: "https://x.com/login",
|
|
963
|
+
});
|
|
964
|
+
await navigateToX(cdpSession.baseUrl);
|
|
755
965
|
|
|
756
966
|
return new Promise((resolve, reject) => {
|
|
757
967
|
const socketPath = getSocketPath();
|
|
@@ -813,6 +1023,13 @@ async function startLearnSession(
|
|
|
813
1023
|
continue;
|
|
814
1024
|
}
|
|
815
1025
|
|
|
1026
|
+
if (m.type === "ride_shotgun_error") {
|
|
1027
|
+
clearTimeout(timeoutHandle);
|
|
1028
|
+
socket.destroy();
|
|
1029
|
+
reject(new Error((m as { message: string }).message));
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
816
1033
|
if (m.type === "ride_shotgun_result") {
|
|
817
1034
|
clearTimeout(timeoutHandle);
|
|
818
1035
|
socket.destroy();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for buildCliReferenceSection — verifies the CLI reference section
|
|
3
|
+
* included in the system prompt has the expected structure and caching behaviour.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
_resetCliHelpCache,
|
|
10
|
+
buildCliReferenceSection,
|
|
11
|
+
} from "../system-prompt.js";
|
|
12
|
+
|
|
13
|
+
describe("buildCliReferenceSection", () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
_resetCliHelpCache();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("includes the Assistant CLI heading", () => {
|
|
19
|
+
const result = buildCliReferenceSection();
|
|
20
|
+
expect(result).toContain("## Assistant CLI");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("includes CLI help text with command listings", () => {
|
|
24
|
+
const result = buildCliReferenceSection();
|
|
25
|
+
// The reference is a side-effect-free snapshot of the top-level CLI help.
|
|
26
|
+
expect(result).toContain("Usage:");
|
|
27
|
+
expect(result).toContain("Commands:");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("mentions bash as the way to invoke the CLI", () => {
|
|
31
|
+
const result = buildCliReferenceSection();
|
|
32
|
+
expect(result).toContain("available via `bash`");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("result is cached — calling twice returns the same string", () => {
|
|
36
|
+
const first = buildCliReferenceSection();
|
|
37
|
+
const second = buildCliReferenceSection();
|
|
38
|
+
expect(first).toBe(second);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("cache is reset by _resetCliHelpCache", () => {
|
|
42
|
+
const first = buildCliReferenceSection();
|
|
43
|
+
_resetCliHelpCache();
|
|
44
|
+
const second = buildCliReferenceSection();
|
|
45
|
+
// Content should be identical even after reset (same CLI program),
|
|
46
|
+
// but they should be independently computed strings.
|
|
47
|
+
expect(first).toEqual(second);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: "Amazon"
|
|
3
3
|
description: "Shop on Amazon and Amazon Fresh using the built-in CLI integration"
|
|
4
4
|
user-invocable: true
|
|
5
|
-
metadata: {"vellum": {"emoji": "\uD83D\uDCE6"}}
|
|
5
|
+
metadata: { "vellum": { "emoji": "\uD83D\uDCE6" } }
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You can shop on Amazon (and Amazon Fresh for groceries) for the user using the `vellum amazon` CLI.
|
|
@@ -92,7 +92,7 @@ vellum amazon fresh select-slot --slot-id <id> --json
|
|
|
92
92
|
|
|
93
93
|
vellum amazon payment-methods --json
|
|
94
94
|
vellum amazon checkout --json
|
|
95
|
-
vellum amazon order place [--payment-method-id <id>]
|
|
95
|
+
vellum amazon order place [--payment-method-id <id>] --json
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
## Example Interactions
|
|
@@ -326,6 +326,32 @@
|
|
|
326
326
|
},
|
|
327
327
|
"executor": "tools/app-file-write.ts",
|
|
328
328
|
"execution_target": "host"
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"name": "app_generate_icon",
|
|
332
|
+
"description": "Generate or regenerate an AI-designed icon for an app. Uses Gemini to create a professional app icon. Call this when the user wants a new or different icon for their app.",
|
|
333
|
+
"category": "apps",
|
|
334
|
+
"risk": "low",
|
|
335
|
+
"input_schema": {
|
|
336
|
+
"type": "object",
|
|
337
|
+
"properties": {
|
|
338
|
+
"app_id": {
|
|
339
|
+
"type": "string",
|
|
340
|
+
"description": "The ID of the app to generate an icon for"
|
|
341
|
+
},
|
|
342
|
+
"description": {
|
|
343
|
+
"type": "string",
|
|
344
|
+
"description": "Optional description to guide icon generation (e.g. 'a blue calendar with a checkmark'). If omitted, the app name and description are used."
|
|
345
|
+
},
|
|
346
|
+
"reason": {
|
|
347
|
+
"type": "string",
|
|
348
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"required": ["app_id"]
|
|
352
|
+
},
|
|
353
|
+
"executor": "tools/app-generate-icon.ts",
|
|
354
|
+
"execution_target": "host"
|
|
329
355
|
}
|
|
330
356
|
]
|
|
331
357
|
}
|