@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.
Files changed (239) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +44 -49
  3. package/README.md +32 -20
  4. package/docs/architecture/keychain-broker.md +186 -0
  5. package/docs/architecture/security.md +110 -116
  6. package/docs/runbook-trusted-contacts.md +2 -2
  7. package/docs/skills.md +25 -25
  8. package/package.json +5 -2
  9. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +11 -2
  10. package/src/__tests__/actor-token-service.test.ts +1 -0
  11. package/src/__tests__/amazon-cdp-integration.test.ts +74 -0
  12. package/src/__tests__/assistant-feature-flags-integration.test.ts +38 -9
  13. package/src/__tests__/assistant-id-boundary-guard.test.ts +29 -0
  14. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  15. package/src/__tests__/bundle-scanner.test.ts +1 -1
  16. package/src/__tests__/channel-guardian.test.ts +102 -102
  17. package/src/__tests__/channel-invite-transport.test.ts +155 -256
  18. package/src/__tests__/channel-readiness-routes.test.ts +336 -0
  19. package/src/__tests__/checker.test.ts +6 -6
  20. package/src/__tests__/chrome-cdp.test.ts +350 -0
  21. package/src/__tests__/computer-use-session-lifecycle.test.ts +3 -3
  22. package/src/__tests__/computer-use-session-working-dir.test.ts +86 -52
  23. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +1 -1
  24. package/src/__tests__/config-loader-migration.test.ts +85 -0
  25. package/src/__tests__/conversation-pairing.test.ts +370 -5
  26. package/src/__tests__/credential-broker-browser-fill.test.ts +1 -10
  27. package/src/__tests__/credential-broker-server-use.test.ts +1 -10
  28. package/src/__tests__/credential-security-e2e.test.ts +7 -1
  29. package/src/__tests__/credential-security-invariants.test.ts +14 -20
  30. package/src/__tests__/credential-vault-unit.test.ts +1 -11
  31. package/src/__tests__/credential-vault.test.ts +5 -19
  32. package/src/__tests__/credentials-cli.test.ts +814 -0
  33. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +23 -4
  34. package/src/__tests__/email-invite-adapter.test.ts +78 -0
  35. package/src/__tests__/email-service-config-fallback.test.ts +102 -0
  36. package/src/__tests__/encrypted-store.test.ts +6 -6
  37. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  38. package/src/__tests__/gateway-only-enforcement.test.ts +5 -1
  39. package/src/__tests__/guardian-actions-endpoint.test.ts +70 -12
  40. package/src/__tests__/guardian-outbound-http.test.ts +53 -47
  41. package/src/__tests__/handle-user-message-secret-resume.test.ts +23 -0
  42. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +32 -23
  43. package/src/__tests__/handlers-telegram-config.test.ts +8 -2
  44. package/src/__tests__/handlers-twitter-config.test.ts +2 -2
  45. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +108 -7
  46. package/src/__tests__/ingress-reconcile.test.ts +6 -0
  47. package/src/__tests__/intent-routing.test.ts +23 -4
  48. package/src/__tests__/invite-routes-http.test.ts +12 -0
  49. package/src/__tests__/ipc-snapshot.test.ts +8 -2
  50. package/src/__tests__/keychain-broker-client.test.ts +543 -0
  51. package/src/__tests__/llm-usage-store.test.ts +344 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +2 -2
  53. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  54. package/src/__tests__/migration-transport.test.ts +49 -0
  55. package/src/__tests__/notification-broadcaster.test.ts +205 -5
  56. package/src/__tests__/notification-deep-link.test.ts +365 -1
  57. package/src/__tests__/oauth-connect-handler.test.ts +2 -2
  58. package/src/__tests__/onboarding-starter-tasks.test.ts +17 -4
  59. package/src/__tests__/proxy-approval-callback.test.ts +1 -1
  60. package/src/__tests__/recording-handler.test.ts +1 -1
  61. package/src/__tests__/recording-intent-handler.test.ts +6 -1
  62. package/src/__tests__/recording-state-machine.test.ts +1 -1
  63. package/src/__tests__/relay-server.test.ts +9 -1
  64. package/src/__tests__/ride-shotgun-handler.test.ts +499 -0
  65. package/src/__tests__/runtime-attachment-metadata.test.ts +160 -1
  66. package/src/__tests__/script-proxy-injection-runtime.test.ts +299 -2
  67. package/src/__tests__/script-proxy-profile-template-fallback.test.ts +1 -1
  68. package/src/__tests__/secret-onetime-send.test.ts +8 -2
  69. package/src/__tests__/secure-keys.test.ts +175 -216
  70. package/src/__tests__/session-confirmation-signals.test.ts +1 -1
  71. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -1
  72. package/src/__tests__/session-queue.test.ts +2 -1
  73. package/src/__tests__/session-tool-setup-app-refresh.test.ts +2 -2
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +29 -4
  75. package/src/__tests__/skill-feature-flags.test.ts +12 -9
  76. package/src/__tests__/skill-load-feature-flag.test.ts +26 -5
  77. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  78. package/src/__tests__/skills.test.ts +34 -4
  79. package/src/__tests__/slack-channel-config.test.ts +2 -2
  80. package/src/__tests__/system-prompt.test.ts +26 -4
  81. package/src/__tests__/telegram-bot-username-resolution.test.ts +212 -0
  82. package/src/__tests__/telegram-invite-adapter.test.ts +164 -0
  83. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  84. package/src/__tests__/tool-permission-simulate-handler.test.ts +8 -2
  85. package/src/__tests__/trusted-contact-approval-notifier.test.ts +9 -1
  86. package/src/__tests__/twitter-auth-handler.test.ts +2 -2
  87. package/src/__tests__/twitter-oauth-client.test.ts +1 -1
  88. package/src/__tests__/usage-routes.test.ts +339 -0
  89. package/src/__tests__/whatsapp-invite-adapter.test.ts +94 -0
  90. package/src/agent/loop.ts +3 -0
  91. package/src/amazon/checkout.ts +0 -1
  92. package/src/approvals/guardian-request-resolvers.ts +9 -1
  93. package/src/bundler/app-bundler.ts +28 -12
  94. package/src/bundler/bundle-scanner.ts +1 -1
  95. package/src/bundler/bundle-signer.ts +3 -3
  96. package/src/bundler/manifest.ts +1 -1
  97. package/src/bundler/signature-verifier.ts +3 -3
  98. package/src/channels/config.ts +1 -1
  99. package/src/cli/AGENTS.md +63 -0
  100. package/src/cli/__tests__/notifications.test.ts +470 -0
  101. package/src/cli/amazon.ts +344 -167
  102. package/src/cli/audit.ts +85 -0
  103. package/src/cli/autonomy.ts +369 -0
  104. package/src/cli/channels.ts +51 -0
  105. package/src/cli/completions.ts +208 -0
  106. package/src/cli/config.ts +220 -0
  107. package/src/cli/contacts.ts +471 -0
  108. package/src/cli/credentials.ts +564 -0
  109. package/src/cli/default-action.ts +14 -0
  110. package/src/cli/dev.ts +131 -0
  111. package/src/cli/doctor.ts +398 -0
  112. package/src/cli/email.ts +494 -0
  113. package/src/cli/influencer.ts +72 -0
  114. package/src/cli/integrations.ts +248 -57
  115. package/src/cli/keys.ts +114 -0
  116. package/src/cli/map.ts +46 -54
  117. package/src/cli/mcp.ts +111 -3
  118. package/src/cli/{config-commands.ts → memory.ts} +134 -245
  119. package/src/cli/notifications.ts +407 -0
  120. package/src/cli/program.ts +65 -0
  121. package/src/cli/reference.ts +48 -0
  122. package/src/cli/sequence.ts +154 -0
  123. package/src/cli/sessions.ts +262 -0
  124. package/src/cli/trust.ts +175 -0
  125. package/src/cli/twitter.ts +323 -106
  126. package/src/config/__tests__/build-cli-reference-section.test.ts +49 -0
  127. package/src/config/bundled-skills/amazon/SKILL.md +2 -2
  128. package/src/config/bundled-skills/app-builder/TOOLS.json +26 -0
  129. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +13 -0
  130. package/src/config/bundled-skills/contacts/SKILL.md +178 -10
  131. package/src/config/bundled-skills/doordash/doordash-cli.ts +23 -168
  132. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +135 -34
  133. package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
  134. package/src/config/bundled-skills/twilio-setup/SKILL.md +70 -17
  135. package/src/config/bundled-tool-registry.ts +2 -0
  136. package/src/config/core-schema.ts +7 -0
  137. package/src/config/feature-flag-registry.json +16 -0
  138. package/src/config/loader.ts +26 -0
  139. package/src/config/schema.ts +4 -0
  140. package/src/config/skill-state.ts +0 -13
  141. package/src/config/system-prompt.ts +27 -0
  142. package/src/contacts/contact-store.ts +25 -0
  143. package/src/daemon/computer-use-session.ts +1 -1
  144. package/src/daemon/handlers/apps.ts +1 -0
  145. package/src/daemon/handlers/config-channels.ts +3 -3
  146. package/src/daemon/handlers/config-dispatch.ts +29 -0
  147. package/src/daemon/handlers/config-inbox.ts +4 -3
  148. package/src/daemon/handlers/config.ts +3 -43
  149. package/src/daemon/handlers/contacts.ts +34 -0
  150. package/src/daemon/handlers/index.ts +17 -3
  151. package/src/daemon/handlers/session-user-message.ts +7 -0
  152. package/src/daemon/handlers/sessions.ts +21 -2
  153. package/src/daemon/handlers/shared.ts +17 -0
  154. package/src/daemon/ipc-contract/apps.ts +2 -0
  155. package/src/daemon/ipc-contract/computer-use.ts +9 -0
  156. package/src/daemon/ipc-contract/contacts.ts +3 -3
  157. package/src/daemon/ipc-contract/inbox.ts +2 -0
  158. package/src/daemon/ipc-contract/messages.ts +4 -0
  159. package/src/daemon/ipc-contract/sessions.ts +8 -0
  160. package/src/daemon/ipc-contract-inventory.json +1 -0
  161. package/src/daemon/lifecycle.ts +0 -5
  162. package/src/daemon/ride-shotgun-handler.ts +139 -25
  163. package/src/daemon/session-agent-loop-handlers.ts +100 -0
  164. package/src/daemon/session-agent-loop.ts +72 -0
  165. package/src/daemon/session-tool-setup.ts +7 -0
  166. package/src/daemon/session.ts +23 -1
  167. package/src/daemon/tool-side-effects.ts +39 -1
  168. package/src/email/service.ts +59 -2
  169. package/src/index.ts +2 -60
  170. package/src/mcp/mcp-oauth-provider.ts +90 -8
  171. package/src/media/app-icon-generator.ts +86 -0
  172. package/src/memory/db-init.ts +11 -0
  173. package/src/memory/llm-usage-store.ts +186 -0
  174. package/src/memory/migrations/137-usage-dashboard-indexes.ts +26 -0
  175. package/src/memory/migrations/139-drop-usage-composite-indexes.ts +30 -0
  176. package/src/memory/migrations/index.ts +2 -0
  177. package/src/memory/schema-migration.ts +1 -0
  178. package/src/memory/shared-app-links-store.ts +1 -1
  179. package/src/messaging/registry.ts +27 -0
  180. package/src/notifications/README.md +79 -70
  181. package/src/notifications/broadcaster.ts +2 -1
  182. package/src/notifications/conversation-pairing.ts +147 -13
  183. package/src/notifications/copy-composer.ts +7 -3
  184. package/src/notifications/destination-resolver.ts +14 -1
  185. package/src/notifications/emit-signal.ts +3 -2
  186. package/src/notifications/signal.ts +105 -1
  187. package/src/notifications/types.ts +16 -0
  188. package/src/permissions/checker.ts +29 -3
  189. package/src/permissions/prompter.ts +11 -3
  190. package/src/runtime/access-request-helper.ts +2 -1
  191. package/src/runtime/auth/route-policy.ts +7 -1
  192. package/src/runtime/channel-invite-transport.ts +40 -63
  193. package/src/runtime/channel-invite-transports/email.ts +13 -39
  194. package/src/runtime/channel-invite-transports/slack.ts +5 -34
  195. package/src/runtime/channel-invite-transports/sms.ts +8 -29
  196. package/src/runtime/channel-invite-transports/telegram.ts +69 -28
  197. package/src/runtime/channel-invite-transports/voice.ts +0 -7
  198. package/src/runtime/channel-invite-transports/whatsapp.ts +43 -0
  199. package/src/runtime/channel-readiness-service.ts +202 -45
  200. package/src/runtime/confirmation-request-guardian-bridge.ts +2 -1
  201. package/src/runtime/guardian-outbound-actions.ts +8 -5
  202. package/src/runtime/http-server.ts +2 -0
  203. package/src/runtime/invite-instruction-generator.ts +178 -0
  204. package/src/runtime/invite-service.ts +22 -25
  205. package/src/runtime/migrations/migration-transport.ts +13 -0
  206. package/src/runtime/routes/app-routes.ts +1 -1
  207. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +8 -7
  208. package/src/runtime/routes/channel-readiness-routes.ts +30 -11
  209. package/src/runtime/routes/contact-routes.ts +54 -26
  210. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -1
  211. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +2 -1
  212. package/src/runtime/routes/inbound-stages/verification-intercept.ts +2 -1
  213. package/src/runtime/routes/integration-routes.ts +1 -1
  214. package/src/runtime/routes/invite-routes.ts +1 -1
  215. package/src/runtime/routes/secret-routes.ts +31 -7
  216. package/src/runtime/routes/twilio-routes.ts +32 -1
  217. package/src/runtime/routes/usage-routes.ts +114 -0
  218. package/src/runtime/tool-grant-request-helper.ts +2 -1
  219. package/src/security/encrypted-store.ts +9 -5
  220. package/src/security/keychain-broker-client.ts +393 -0
  221. package/src/security/secure-keys.ts +106 -321
  222. package/src/tools/apps/executors.ts +73 -0
  223. package/src/tools/browser/auto-navigate.ts +15 -6
  224. package/src/tools/browser/chrome-cdp.ts +211 -0
  225. package/src/tools/browser/network-recorder.test.ts +83 -0
  226. package/src/tools/browser/network-recorder.ts +8 -7
  227. package/src/tools/browser/x-auto-navigate.ts +12 -6
  228. package/src/tools/credentials/policy-types.ts +24 -0
  229. package/src/tools/credentials/vault.ts +22 -27
  230. package/src/tools/network/script-proxy/session-manager.ts +47 -3
  231. package/src/tools/permission-checker.ts +1 -0
  232. package/src/tools/types.ts +2 -0
  233. package/src/tools/ui-surface/definitions.ts +1 -2
  234. package/src/tools/watch/watch-state.ts +2 -0
  235. package/src/__tests__/key-migration.test.ts +0 -240
  236. package/src/__tests__/keychain.test.ts +0 -286
  237. package/src/cli/core-commands.ts +0 -899
  238. package/src/security/keychain-to-encrypted-migration.ts +0 -66
  239. package/src/security/keychain.ts +0 -490
@@ -0,0 +1,407 @@
1
+ import type { Command } from "commander";
2
+
3
+ import { getDeliverableChannels } from "../channels/config.js";
4
+ import { initializeDb } from "../memory/db.js";
5
+ import { emitNotificationSignal } from "../notifications/emit-signal.js";
6
+ import { listEvents } from "../notifications/events-store.js";
7
+ import {
8
+ isNotificationSourceChannel,
9
+ isNotificationSourceEventName,
10
+ NOTIFICATION_SOURCE_CHANNELS,
11
+ NOTIFICATION_SOURCE_EVENT_NAMES,
12
+ } from "../notifications/signal.js";
13
+ import type { NotificationChannel } from "../notifications/types.js";
14
+ import { getCliLogger } from "../util/logger.js";
15
+ import { shouldOutputJson, writeOutput } from "./integrations.js";
16
+
17
+ const log = getCliLogger("cli");
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Help text builders
21
+ // ---------------------------------------------------------------------------
22
+
23
+ function buildSourceChannelsHelpBlock(): string {
24
+ const lines = NOTIFICATION_SOURCE_CHANNELS.map(
25
+ (c) => ` ${c.id.padEnd(20)} ${c.description}`,
26
+ );
27
+ return `\nSource channels:\n${lines.join("\n")}`;
28
+ }
29
+
30
+ function buildSourceEventNamesHelpBlock(): string {
31
+ const lines = NOTIFICATION_SOURCE_EVENT_NAMES.map(
32
+ (e) => ` ${e.id.padEnd(50)} ${e.description}`,
33
+ );
34
+ return `\nSource event names:\n${lines.join("\n")}`;
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Command registration
39
+ // ---------------------------------------------------------------------------
40
+
41
+ export function registerNotificationsCommand(program: Command): void {
42
+ const notifications = program
43
+ .command("notifications")
44
+ .description(
45
+ "Send and inspect notifications through the unified notification router",
46
+ )
47
+ .option("--json", "Machine-readable compact JSON output");
48
+
49
+ notifications.addHelpText(
50
+ "after",
51
+ `
52
+ Notifications flow through a unified pipeline: a signal is emitted with a
53
+ source channel, event name, and attention hints. The decision engine evaluates
54
+ whether and where to deliver the notification based on connected channels,
55
+ urgency, and user preferences.
56
+ ${buildSourceChannelsHelpBlock()}
57
+ ${buildSourceEventNamesHelpBlock()}
58
+
59
+ Examples:
60
+ $ vellum notifications send --source-channel assistant_tool --source-event-name user.send_notification --message "Build finished"
61
+ $ vellum notifications send --source-channel scheduler --source-event-name reminder.fired --message "Stand-up in 5 minutes" --urgency high
62
+ $ vellum notifications send --source-channel watcher --source-event-name watcher.notification --message "File changed" --no-requires-action --is-async-background
63
+ $ vellum notifications send --source-channel assistant_tool --source-event-name user.send_notification --message "Deploy complete" --preferred-channels vellum,telegram --json`,
64
+ );
65
+
66
+ // -------------------------------------------------------------------------
67
+ // send
68
+ // -------------------------------------------------------------------------
69
+
70
+ notifications
71
+ .command("send")
72
+ .description("Send a notification through the unified notification router")
73
+ .requiredOption(
74
+ "--source-channel <channel>",
75
+ "Source channel producing this notification",
76
+ )
77
+ .requiredOption(
78
+ "--source-event-name <name>",
79
+ "Event name for audit, routing, and dedupe grouping",
80
+ )
81
+ .requiredOption(
82
+ "--message <message>",
83
+ "Notification message the user should receive",
84
+ )
85
+ .option("--title <title>", "Optional notification title")
86
+ .option(
87
+ "--urgency <urgency>",
88
+ "Urgency hint: low, medium, high (default: medium)",
89
+ )
90
+ .option(
91
+ "--requires-action",
92
+ "Whether the notification expects user action (default: true)",
93
+ )
94
+ .option(
95
+ "--no-requires-action",
96
+ "Mark that the notification does not expect user action",
97
+ )
98
+ .option(
99
+ "--is-async-background",
100
+ "Whether the event is asynchronous/background work (default: false)",
101
+ )
102
+ .option(
103
+ "--no-is-async-background",
104
+ "Mark that the event is not asynchronous/background work",
105
+ )
106
+ .option(
107
+ "--visible-in-source-now",
108
+ "Set true when user is already viewing the source context (default: false)",
109
+ )
110
+ .option(
111
+ "--no-visible-in-source-now",
112
+ "Mark that the user is not viewing the source context",
113
+ )
114
+ .option(
115
+ "--deadline-at <epoch>",
116
+ "Optional deadline timestamp in epoch milliseconds",
117
+ )
118
+ .option(
119
+ "--preferred-channels <channels>",
120
+ "Comma-separated channel hints (e.g. vellum,telegram,slack)",
121
+ )
122
+ .option(
123
+ "--session-id <id>",
124
+ "Source session or conversation ID (default: cli-<timestamp>)",
125
+ )
126
+ .option(
127
+ "--dedupe-key <key>",
128
+ "Optional dedupe key to suppress duplicate notifications",
129
+ )
130
+ .addHelpText(
131
+ "after",
132
+ `
133
+ Arguments:
134
+ --source-channel One of the registered source channels (see "vellum notifications --help")
135
+ --source-event-name One of the registered event names (see "vellum notifications --help")
136
+ --message The notification body text (required, must be non-empty)
137
+
138
+ Behavioral notes:
139
+ - The signal is emitted through the full notification pipeline: event store,
140
+ decision engine, deterministic checks, and channel dispatch.
141
+ - --requires-action defaults to true; use --no-requires-action to disable.
142
+ - --urgency defaults to medium if not specified.
143
+ - --preferred-channels are hints only; the decision engine may override them.
144
+ - --dedupe-key suppresses duplicate signals with the same key.
145
+
146
+ Examples:
147
+ $ vellum notifications send --source-channel assistant_tool --source-event-name user.send_notification --message "Task complete"
148
+ $ vellum notifications send --source-channel scheduler --source-event-name reminder.fired --message "Meeting in 5 min" --urgency high --title "Reminder"
149
+ $ vellum notifications send --source-channel watcher --source-event-name watcher.notification --message "Detected change" --no-requires-action --is-async-background --json`,
150
+ )
151
+ .action(
152
+ async (
153
+ opts: {
154
+ sourceChannel: string;
155
+ sourceEventName: string;
156
+ message: string;
157
+ title?: string;
158
+ urgency?: string;
159
+ requiresAction: boolean;
160
+ isAsyncBackground: boolean;
161
+ visibleInSourceNow: boolean;
162
+ deadlineAt?: string;
163
+ preferredChannels?: string;
164
+ sessionId?: string;
165
+ dedupeKey?: string;
166
+ },
167
+ cmd: Command,
168
+ ) => {
169
+ try {
170
+ // Validate --source-channel
171
+ if (!isNotificationSourceChannel(opts.sourceChannel)) {
172
+ const validChannels = NOTIFICATION_SOURCE_CHANNELS.map(
173
+ (c) => c.id,
174
+ ).join(", ");
175
+ writeOutput(cmd, {
176
+ ok: false,
177
+ error: `Invalid source channel "${opts.sourceChannel}". Valid values: ${validChannels}`,
178
+ });
179
+ process.exitCode = 1;
180
+ return;
181
+ }
182
+
183
+ // Validate --source-event-name
184
+ if (!isNotificationSourceEventName(opts.sourceEventName)) {
185
+ const validEvents = NOTIFICATION_SOURCE_EVENT_NAMES.map(
186
+ (e) => e.id,
187
+ ).join(", ");
188
+ writeOutput(cmd, {
189
+ ok: false,
190
+ error: `Invalid source event name "${opts.sourceEventName}". Valid values: ${validEvents}`,
191
+ });
192
+ process.exitCode = 1;
193
+ return;
194
+ }
195
+
196
+ // Validate --message
197
+ const message = opts.message.trim();
198
+ if (message.length === 0) {
199
+ writeOutput(cmd, {
200
+ ok: false,
201
+ error: "Message must be a non-empty string",
202
+ });
203
+ process.exitCode = 1;
204
+ return;
205
+ }
206
+
207
+ // Validate --urgency
208
+ const urgency = opts.urgency ?? "medium";
209
+ if (urgency !== "low" && urgency !== "medium" && urgency !== "high") {
210
+ writeOutput(cmd, {
211
+ ok: false,
212
+ error: `Invalid urgency "${opts.urgency}". Must be one of: low, medium, high`,
213
+ });
214
+ process.exitCode = 1;
215
+ return;
216
+ }
217
+
218
+ // Validate --deadline-at
219
+ let deadlineAt: number | undefined;
220
+ if (opts.deadlineAt != null) {
221
+ const parsed = Number(opts.deadlineAt);
222
+ if (!Number.isFinite(parsed)) {
223
+ writeOutput(cmd, {
224
+ ok: false,
225
+ error: `Invalid deadline-at "${opts.deadlineAt}". Must be a finite number (epoch milliseconds)`,
226
+ });
227
+ process.exitCode = 1;
228
+ return;
229
+ }
230
+ deadlineAt = parsed;
231
+ }
232
+
233
+ // Validate --preferred-channels
234
+ let preferredChannels: NotificationChannel[] | undefined;
235
+ if (opts.preferredChannels) {
236
+ const deliverable = getDeliverableChannels();
237
+ const requested = opts.preferredChannels
238
+ .split(",")
239
+ .map((ch) => ch.trim())
240
+ .filter((ch) => ch.length > 0);
241
+
242
+ for (const ch of requested) {
243
+ if (!deliverable.includes(ch as NotificationChannel)) {
244
+ writeOutput(cmd, {
245
+ ok: false,
246
+ error: `Invalid preferred channel "${ch}". Valid deliverable channels: ${deliverable.join(", ")}`,
247
+ });
248
+ process.exitCode = 1;
249
+ return;
250
+ }
251
+ }
252
+ preferredChannels = requested as NotificationChannel[];
253
+ }
254
+
255
+ initializeDb();
256
+
257
+ const sourceSessionId = opts.sessionId ?? `cli-${Date.now()}`;
258
+
259
+ const result = await emitNotificationSignal({
260
+ sourceEventName: opts.sourceEventName,
261
+ sourceChannel: opts.sourceChannel,
262
+ sourceSessionId,
263
+ attentionHints: {
264
+ requiresAction: opts.requiresAction ?? true,
265
+ urgency,
266
+ deadlineAt,
267
+ isAsyncBackground: opts.isAsyncBackground ?? false,
268
+ visibleInSourceNow: opts.visibleInSourceNow ?? false,
269
+ },
270
+ contextPayload: {
271
+ requestedMessage: message,
272
+ requestedBySource: opts.sourceChannel,
273
+ ...(opts.title ? { requestedTitle: opts.title } : {}),
274
+ ...(preferredChannels?.length ? { preferredChannels } : {}),
275
+ },
276
+ ...(opts.dedupeKey ? { dedupeKey: opts.dedupeKey } : {}),
277
+ throwOnError: true,
278
+ });
279
+
280
+ writeOutput(cmd, {
281
+ ok: true,
282
+ signalId: result.signalId,
283
+ dispatched: result.dispatched,
284
+ reason: result.reason,
285
+ });
286
+
287
+ if (!shouldOutputJson(cmd)) {
288
+ log.info(
289
+ `Signal ${result.signalId} emitted (dispatched: ${result.dispatched})`,
290
+ );
291
+ if (result.reason) {
292
+ log.info(` Reason: ${result.reason}`);
293
+ }
294
+ }
295
+ } catch (err) {
296
+ const message = err instanceof Error ? err.message : String(err);
297
+ writeOutput(cmd, { ok: false, error: message });
298
+ process.exitCode = 1;
299
+ }
300
+ },
301
+ );
302
+
303
+ // -------------------------------------------------------------------------
304
+ // list
305
+ // -------------------------------------------------------------------------
306
+
307
+ notifications
308
+ .command("list")
309
+ .description("List recent notification events from the local event store")
310
+ .option("--limit <n>", "Maximum number of events to return (default: 20)")
311
+ .option("--source-event-name <name>", "Filter by source event name")
312
+ .addHelpText(
313
+ "after",
314
+ `
315
+ Reads from the local notification events store, ordered by creation time
316
+ (newest first). Each event represents a signal that was emitted through the
317
+ notification pipeline.
318
+ ${buildSourceEventNamesHelpBlock()}
319
+
320
+ Examples:
321
+ $ vellum notifications list
322
+ $ vellum notifications list --limit 5
323
+ $ vellum notifications list --source-event-name reminder.fired
324
+ $ vellum notifications list --source-event-name reminder.fired --limit 10 --json`,
325
+ )
326
+ .action(
327
+ (
328
+ opts: {
329
+ limit?: string;
330
+ sourceEventName?: string;
331
+ },
332
+ cmd: Command,
333
+ ) => {
334
+ try {
335
+ // Validate --source-event-name (accept any non-empty string; custom
336
+ // event names are valid since skills can emit arbitrary names)
337
+ if (
338
+ opts.sourceEventName != null &&
339
+ opts.sourceEventName.trim().length === 0
340
+ ) {
341
+ writeOutput(cmd, {
342
+ ok: false,
343
+ error: "Source event name must be a non-empty string",
344
+ });
345
+ process.exitCode = 1;
346
+ return;
347
+ }
348
+
349
+ // Parse and validate --limit
350
+ let limit = 20;
351
+ if (opts.limit != null) {
352
+ const parsed = Number(opts.limit);
353
+ if (
354
+ !Number.isFinite(parsed) ||
355
+ !Number.isInteger(parsed) ||
356
+ parsed < 1
357
+ ) {
358
+ writeOutput(cmd, {
359
+ ok: false,
360
+ error: `Invalid limit "${opts.limit}". Must be a positive integer`,
361
+ });
362
+ process.exitCode = 1;
363
+ return;
364
+ }
365
+ limit = parsed;
366
+ }
367
+
368
+ initializeDb();
369
+
370
+ const rows = listEvents({
371
+ limit,
372
+ sourceEventName: opts.sourceEventName,
373
+ });
374
+
375
+ const events = rows.map((row) => ({
376
+ id: row.id,
377
+ sourceEventName: row.sourceEventName,
378
+ sourceChannel: row.sourceChannel,
379
+ sourceSessionId: row.sourceSessionId,
380
+ urgency: (JSON.parse(row.attentionHintsJson) as { urgency: string })
381
+ .urgency,
382
+ dedupeKey: row.dedupeKey,
383
+ createdAt: new Date(row.createdAt).toISOString(),
384
+ }));
385
+
386
+ writeOutput(cmd, { ok: true, events });
387
+
388
+ if (!shouldOutputJson(cmd)) {
389
+ if (events.length === 0) {
390
+ log.info("No notification events found");
391
+ } else {
392
+ log.info(`${events.length} event(s):\n`);
393
+ for (const event of events) {
394
+ log.info(
395
+ ` ${event.createdAt} ${event.sourceEventName} ${event.urgency} ${event.sourceChannel}`,
396
+ );
397
+ }
398
+ }
399
+ }
400
+ } catch (err) {
401
+ const message = err instanceof Error ? err.message : String(err);
402
+ writeOutput(cmd, { ok: false, error: message });
403
+ process.exitCode = 1;
404
+ }
405
+ },
406
+ );
407
+ }
@@ -0,0 +1,65 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ import { Command } from "commander";
4
+
5
+ import { registerHooksCommand } from "../hooks/cli.js";
6
+ import { registerAmazonCommand } from "./amazon.js";
7
+ import { registerAuditCommand } from "./audit.js";
8
+ import { registerAutonomyCommand } from "./autonomy.js";
9
+ import { registerChannelsCommand } from "./channels.js";
10
+ import { registerCompletionsCommand } from "./completions.js";
11
+ import { registerConfigCommand } from "./config.js";
12
+ import { registerContactsCommand } from "./contacts.js";
13
+ import { registerCredentialsCommand } from "./credentials.js";
14
+ import { registerDefaultAction } from "./default-action.js";
15
+ import { registerDevCommand } from "./dev.js";
16
+ import { registerDoctorCommand } from "./doctor.js";
17
+ import { registerEmailCommand } from "./email.js";
18
+ import { registerInfluencerCommand } from "./influencer.js";
19
+ import { registerIntegrationsCommand } from "./integrations.js";
20
+ import { registerKeysCommand } from "./keys.js";
21
+ import { registerMapCommand } from "./map.js";
22
+ import { registerMcpCommand } from "./mcp.js";
23
+ import { registerMemoryCommand } from "./memory.js";
24
+ import { registerNotificationsCommand } from "./notifications.js";
25
+ import { registerSequenceCommand } from "./sequence.js";
26
+ import { registerSessionsCommand } from "./sessions.js";
27
+ import { registerTrustCommand } from "./trust.js";
28
+ import { registerTwitterCommand } from "./twitter.js";
29
+
30
+ const require = createRequire(import.meta.url);
31
+ const { version } = require("../../package.json") as { version: string };
32
+
33
+ export function buildCliProgram(): Command {
34
+ const program = new Command();
35
+
36
+ program.name("assistant").description("Local AI assistant").version(version);
37
+
38
+ registerDefaultAction(program);
39
+ registerDevCommand(program);
40
+ registerSessionsCommand(program);
41
+ registerConfigCommand(program);
42
+ registerKeysCommand(program);
43
+ registerCredentialsCommand(program);
44
+ registerTrustCommand(program);
45
+ registerMemoryCommand(program);
46
+ registerAuditCommand(program);
47
+ registerDoctorCommand(program);
48
+ registerHooksCommand(program);
49
+ registerMcpCommand(program);
50
+ registerEmailCommand(program);
51
+ registerIntegrationsCommand(program);
52
+ registerContactsCommand(program);
53
+ registerChannelsCommand(program);
54
+ registerAmazonCommand(program);
55
+ registerAutonomyCommand(program);
56
+ registerCompletionsCommand(program);
57
+ registerNotificationsCommand(program);
58
+
59
+ registerTwitterCommand(program);
60
+ registerMapCommand(program);
61
+ registerInfluencerCommand(program);
62
+ registerSequenceCommand(program);
63
+
64
+ return program;
65
+ }
@@ -0,0 +1,48 @@
1
+ // Keep this snapshot in sync with buildCliProgram().helpInformation() for the
2
+ // top-level assistant CLI. It deliberately avoids importing the CLI command
3
+ // graph so prompt assembly stays side-effect-free.
4
+ export const CLI_HELP_REFERENCE = `Usage: assistant [options] [command]
5
+
6
+ Local AI assistant
7
+
8
+ Options:
9
+ -V, --version output the version number
10
+ -h, --help display help for command
11
+
12
+ Commands:
13
+ dev [options] Run the assistant in dev mode
14
+ sessions Manage sessions
15
+ config Manage configuration
16
+ keys Manage API keys in secure storage
17
+ credentials [options] Manage credentials in the encrypted vault (API keys,
18
+ tokens, passwords)
19
+ trust Manage trust rules
20
+ memory Manage long-term memory indexing/retrieval
21
+ audit [options] Show recent tool invocations
22
+ doctor Run diagnostic checks
23
+ hooks Manage hooks
24
+ mcp Manage MCP (Model Context Protocol) servers
25
+ email [options] Email operations (provider-agnostic)
26
+ integrations [options] Read integration and ingress status through the
27
+ gateway API
28
+ contacts [options] Manage and query the contact graph
29
+ channels [options] Query channel status
30
+ amazon [options] Shop on Amazon and Amazon Fresh. Requires a session
31
+ imported from a Ride Shotgun recording.
32
+ autonomy [options] View and configure autonomy tiers
33
+ completions <shell> Generate shell completion script (e.g. vellum
34
+ completions bash >> ~/.bashrc)
35
+ notifications [options] Send and inspect notifications through the unified
36
+ notification router
37
+ x|twitter [options] Post on X and manage connections. Supports OAuth
38
+ (official API) and browser session paths.
39
+ map [options] <domain> Auto-navigate a domain and produce a deduplicated API
40
+ map. Launches Chrome with CDP, starts a Ride Shotgun
41
+ learn session, then analyzes captured network
42
+ traffic.
43
+ influencer [options] Research influencers on Instagram, TikTok, and
44
+ X/Twitter. Uses the Chrome extension relay to browse
45
+ each platform. Requires the user to be logged in on
46
+ each platform in Chrome.
47
+ sequence [options] Manage email sequences
48
+ `;