@vellumai/assistant 0.4.2 → 0.4.4

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 (221) hide show
  1. package/.env.example +3 -0
  2. package/ARCHITECTURE.md +124 -10
  3. package/README.md +43 -35
  4. package/docs/trusted-contact-access.md +20 -0
  5. package/package.json +1 -1
  6. package/scripts/ipc/generate-swift.ts +1 -0
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -120
  8. package/src/__tests__/access-request-decision.test.ts +0 -1
  9. package/src/__tests__/actor-token-service.test.ts +1099 -0
  10. package/src/__tests__/agent-loop.test.ts +51 -0
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -5
  13. package/src/__tests__/assistant-id-boundary-guard.test.ts +415 -0
  14. package/src/__tests__/call-controller.test.ts +49 -0
  15. package/src/__tests__/call-pointer-message-composer.test.ts +171 -0
  16. package/src/__tests__/call-pointer-messages.test.ts +93 -3
  17. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +42 -0
  18. package/src/__tests__/call-routes-http.test.ts +0 -25
  19. package/src/__tests__/callback-handoff-copy.test.ts +186 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +133 -12
  21. package/src/__tests__/channel-guardian.test.ts +0 -86
  22. package/src/__tests__/channel-readiness-service.test.ts +10 -16
  23. package/src/__tests__/checker.test.ts +33 -12
  24. package/src/__tests__/config-schema.test.ts +6 -0
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +410 -0
  26. package/src/__tests__/conversation-routes-guardian-reply.test.ts +256 -0
  27. package/src/__tests__/conversation-routes.test.ts +12 -3
  28. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  29. package/src/__tests__/daemon-server-session-init.test.ts +4 -0
  30. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
  31. package/src/__tests__/guardian-actions-endpoint.test.ts +39 -13
  32. package/src/__tests__/guardian-dispatch.test.ts +8 -0
  33. package/src/__tests__/guardian-outbound-http.test.ts +4 -5
  34. package/src/__tests__/guardian-question-mode.test.ts +200 -0
  35. package/src/__tests__/guardian-routing-invariants.test.ts +178 -0
  36. package/src/__tests__/guardian-routing-state.test.ts +525 -0
  37. package/src/__tests__/handle-user-message-secret-resume.test.ts +2 -0
  38. package/src/__tests__/handlers-telegram-config.test.ts +0 -83
  39. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +55 -0
  40. package/src/__tests__/headless-browser-navigate.test.ts +2 -0
  41. package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
  42. package/src/__tests__/ingress-routes-http.test.ts +55 -0
  43. package/src/__tests__/ipc-snapshot.test.ts +18 -51
  44. package/src/__tests__/non-member-access-request.test.ts +159 -9
  45. package/src/__tests__/notification-decision-fallback.test.ts +129 -4
  46. package/src/__tests__/notification-decision-strategy.test.ts +106 -2
  47. package/src/__tests__/notification-guardian-path.test.ts +3 -0
  48. package/src/__tests__/recording-intent-handler.test.ts +1 -0
  49. package/src/__tests__/relay-server.test.ts +1475 -33
  50. package/src/__tests__/send-endpoint-busy.test.ts +5 -0
  51. package/src/__tests__/session-agent-loop.test.ts +1 -0
  52. package/src/__tests__/session-confirmation-signals.test.ts +523 -0
  53. package/src/__tests__/session-init.benchmark.test.ts +0 -2
  54. package/src/__tests__/session-runtime-assembly.test.ts +4 -1
  55. package/src/__tests__/session-surfaces-task-progress.test.ts +44 -1
  56. package/src/__tests__/session-tool-setup-app-refresh.test.ts +81 -2
  57. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -1
  58. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -1
  59. package/src/__tests__/tool-executor.test.ts +21 -2
  60. package/src/__tests__/tool-grant-request-escalation.test.ts +333 -27
  61. package/src/__tests__/trusted-contact-approval-notifier.test.ts +678 -0
  62. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1064 -0
  63. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +11 -1
  64. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  65. package/src/__tests__/trusted-contact-verification.test.ts +0 -1
  66. package/src/__tests__/twilio-config.test.ts +2 -13
  67. package/src/__tests__/twilio-routes.test.ts +4 -3
  68. package/src/__tests__/update-bulletin.test.ts +0 -1
  69. package/src/agent/loop.ts +1 -1
  70. package/src/approvals/guardian-decision-primitive.ts +12 -3
  71. package/src/approvals/guardian-request-resolvers.ts +169 -11
  72. package/src/calls/call-constants.ts +29 -0
  73. package/src/calls/call-controller.ts +11 -3
  74. package/src/calls/call-domain.ts +33 -11
  75. package/src/calls/call-pointer-message-composer.ts +154 -0
  76. package/src/calls/call-pointer-messages.ts +106 -27
  77. package/src/calls/guardian-dispatch.ts +4 -2
  78. package/src/calls/relay-server.ts +921 -112
  79. package/src/calls/twilio-config.ts +4 -11
  80. package/src/calls/twilio-routes.ts +4 -6
  81. package/src/calls/types.ts +3 -1
  82. package/src/calls/voice-session-bridge.ts +4 -3
  83. package/src/cli/core-commands.ts +7 -4
  84. package/src/cli.ts +5 -4
  85. package/src/config/bundled-skills/agentmail/SKILL.md +4 -0
  86. package/src/config/bundled-skills/app-builder/SKILL.md +309 -10
  87. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -1
  88. package/src/config/bundled-skills/email-setup/SKILL.md +1 -1
  89. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +105 -81
  90. package/src/config/bundled-skills/messaging/SKILL.md +61 -12
  91. package/src/config/bundled-skills/messaging/TOOLS.json +58 -0
  92. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +6 -1
  93. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +35 -0
  94. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +52 -0
  95. package/src/config/bundled-skills/phone-calls/SKILL.md +30 -39
  96. package/src/config/bundled-skills/twitter/SKILL.md +3 -3
  97. package/src/config/bundled-skills/vercel-token-setup/SKILL.md +215 -0
  98. package/src/config/calls-schema.ts +36 -0
  99. package/src/config/env.ts +22 -0
  100. package/src/config/feature-flag-registry.json +8 -8
  101. package/src/config/schema.ts +2 -2
  102. package/src/config/skills.ts +11 -0
  103. package/src/config/system-prompt.ts +11 -1
  104. package/src/config/templates/SOUL.md +2 -0
  105. package/src/config/vellum-skills/sms-setup/SKILL.md +71 -82
  106. package/src/config/vellum-skills/trusted-contacts/SKILL.md +8 -1
  107. package/src/config/vellum-skills/twilio-setup/SKILL.md +88 -73
  108. package/src/daemon/call-pointer-generators.ts +59 -0
  109. package/src/daemon/computer-use-session.ts +2 -5
  110. package/src/daemon/handlers/apps.ts +76 -20
  111. package/src/daemon/handlers/config-channels.ts +9 -61
  112. package/src/daemon/handlers/config-inbox.ts +11 -3
  113. package/src/daemon/handlers/config-ingress.ts +28 -3
  114. package/src/daemon/handlers/config-telegram.ts +12 -0
  115. package/src/daemon/handlers/config.ts +2 -6
  116. package/src/daemon/handlers/index.ts +2 -1
  117. package/src/daemon/handlers/pairing.ts +2 -0
  118. package/src/daemon/handlers/publish.ts +11 -46
  119. package/src/daemon/handlers/sessions.ts +59 -5
  120. package/src/daemon/handlers/shared.ts +17 -2
  121. package/src/daemon/ipc-contract/apps.ts +1 -0
  122. package/src/daemon/ipc-contract/inbox.ts +4 -0
  123. package/src/daemon/ipc-contract/integrations.ts +1 -97
  124. package/src/daemon/ipc-contract/messages.ts +47 -1
  125. package/src/daemon/ipc-contract/notifications.ts +11 -0
  126. package/src/daemon/ipc-contract-inventory.json +2 -4
  127. package/src/daemon/lifecycle.ts +17 -0
  128. package/src/daemon/server.ts +16 -2
  129. package/src/daemon/session-agent-loop-handlers.ts +20 -0
  130. package/src/daemon/session-agent-loop.ts +24 -12
  131. package/src/daemon/session-lifecycle.ts +1 -1
  132. package/src/daemon/session-process.ts +11 -1
  133. package/src/daemon/session-runtime-assembly.ts +6 -1
  134. package/src/daemon/session-surfaces.ts +32 -3
  135. package/src/daemon/session.ts +88 -1
  136. package/src/daemon/tool-side-effects.ts +22 -0
  137. package/src/home-base/prebuilt/brain-graph.html +1483 -0
  138. package/src/home-base/prebuilt/index.html +40 -0
  139. package/src/inbound/platform-callback-registration.ts +157 -0
  140. package/src/memory/canonical-guardian-store.ts +1 -1
  141. package/src/memory/conversation-crud.ts +2 -1
  142. package/src/memory/conversation-title-service.ts +16 -2
  143. package/src/memory/db-init.ts +8 -0
  144. package/src/memory/delivery-crud.ts +2 -1
  145. package/src/memory/guardian-action-store.ts +2 -1
  146. package/src/memory/guardian-approvals.ts +3 -2
  147. package/src/memory/ingress-invite-store.ts +12 -2
  148. package/src/memory/ingress-member-store.ts +4 -3
  149. package/src/memory/migrations/038-actor-token-records.ts +39 -0
  150. package/src/memory/migrations/124-voice-invite-display-metadata.ts +14 -0
  151. package/src/memory/migrations/index.ts +2 -0
  152. package/src/memory/schema.ts +26 -5
  153. package/src/messaging/provider-types.ts +24 -0
  154. package/src/messaging/provider.ts +7 -0
  155. package/src/messaging/providers/gmail/adapter.ts +127 -0
  156. package/src/messaging/providers/sms/adapter.ts +40 -37
  157. package/src/notifications/adapters/macos.ts +45 -2
  158. package/src/notifications/broadcaster.ts +16 -0
  159. package/src/notifications/copy-composer.ts +50 -2
  160. package/src/notifications/decision-engine.ts +22 -9
  161. package/src/notifications/destination-resolver.ts +16 -2
  162. package/src/notifications/emit-signal.ts +18 -9
  163. package/src/notifications/guardian-question-mode.ts +419 -0
  164. package/src/notifications/signal.ts +14 -3
  165. package/src/permissions/checker.ts +13 -1
  166. package/src/permissions/prompter.ts +14 -0
  167. package/src/providers/anthropic/client.ts +20 -0
  168. package/src/providers/provider-send-message.ts +15 -3
  169. package/src/runtime/access-request-helper.ts +82 -4
  170. package/src/runtime/actor-token-service.ts +234 -0
  171. package/src/runtime/actor-token-store.ts +236 -0
  172. package/src/runtime/actor-trust-resolver.ts +2 -2
  173. package/src/runtime/assistant-scope.ts +10 -0
  174. package/src/runtime/channel-approvals.ts +5 -3
  175. package/src/runtime/channel-readiness-service.ts +23 -64
  176. package/src/runtime/channel-readiness-types.ts +3 -4
  177. package/src/runtime/channel-retry-sweep.ts +4 -1
  178. package/src/runtime/confirmation-request-guardian-bridge.ts +197 -0
  179. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  180. package/src/runtime/guardian-context-resolver.ts +82 -0
  181. package/src/runtime/guardian-outbound-actions.ts +5 -7
  182. package/src/runtime/guardian-reply-router.ts +67 -30
  183. package/src/runtime/guardian-vellum-migration.ts +57 -0
  184. package/src/runtime/http-server.ts +75 -31
  185. package/src/runtime/http-types.ts +13 -0
  186. package/src/runtime/ingress-service.ts +14 -0
  187. package/src/runtime/invite-redemption-service.ts +10 -1
  188. package/src/runtime/local-actor-identity.ts +76 -0
  189. package/src/runtime/middleware/actor-token.ts +271 -0
  190. package/src/runtime/middleware/twilio-validation.ts +2 -4
  191. package/src/runtime/routes/approval-routes.ts +82 -7
  192. package/src/runtime/routes/brain-graph-routes.ts +222 -0
  193. package/src/runtime/routes/call-routes.ts +2 -1
  194. package/src/runtime/routes/channel-readiness-routes.ts +71 -0
  195. package/src/runtime/routes/channel-route-shared.ts +3 -3
  196. package/src/runtime/routes/conversation-attention-routes.ts +2 -1
  197. package/src/runtime/routes/conversation-routes.ts +142 -53
  198. package/src/runtime/routes/events-routes.ts +22 -8
  199. package/src/runtime/routes/guardian-action-routes.ts +45 -3
  200. package/src/runtime/routes/guardian-approval-interception.ts +29 -0
  201. package/src/runtime/routes/guardian-bootstrap-routes.ts +145 -0
  202. package/src/runtime/routes/inbound-conversation.ts +4 -3
  203. package/src/runtime/routes/inbound-message-handler.ts +147 -5
  204. package/src/runtime/routes/ingress-routes.ts +2 -0
  205. package/src/runtime/routes/integration-routes.ts +7 -15
  206. package/src/runtime/routes/pairing-routes.ts +163 -0
  207. package/src/runtime/routes/twilio-routes.ts +934 -0
  208. package/src/runtime/tool-grant-request-helper.ts +3 -1
  209. package/src/security/oauth2.ts +27 -2
  210. package/src/security/token-manager.ts +46 -10
  211. package/src/tools/browser/browser-execution.ts +4 -3
  212. package/src/tools/browser/browser-handoff.ts +10 -18
  213. package/src/tools/browser/browser-manager.ts +80 -25
  214. package/src/tools/browser/browser-screencast.ts +35 -119
  215. package/src/tools/calls/call-start.ts +2 -1
  216. package/src/tools/permission-checker.ts +15 -4
  217. package/src/tools/terminal/parser.ts +12 -0
  218. package/src/tools/tool-approval-handler.ts +244 -19
  219. package/src/workspace/git-service.ts +19 -0
  220. package/src/__tests__/handlers-twilio-config.test.ts +0 -1928
  221. package/src/daemon/handlers/config-twilio.ts +0 -1082
@@ -15,18 +15,11 @@ export interface TwilioConfig {
15
15
  wssBaseUrl: string;
16
16
  }
17
17
 
18
- function resolveTwilioPhoneNumber(assistantId: string | undefined, config: ReturnType<typeof loadConfig>): string {
18
+ function resolveTwilioPhoneNumber(config: ReturnType<typeof loadConfig>, assistantId?: string): string {
19
19
  if (assistantId) {
20
- const assistantPhone = config.sms?.assistantPhoneNumbers?.[assistantId];
21
- if (assistantPhone) {
22
- return assistantPhone;
23
- }
20
+ const scoped = (config.sms?.assistantPhoneNumbers as Record<string, string> | undefined)?.[assistantId];
21
+ if (scoped) return scoped;
24
22
  }
25
-
26
- // Global fallback order:
27
- // 1. TWILIO_PHONE_NUMBER env var (explicit override)
28
- // 2. config file sms.phoneNumber (primary storage)
29
- // 3. credential:twilio:phone_number secure key (backward-compat fallback)
30
23
  return getTwilioPhoneNumberEnv() || config.sms?.phoneNumber || getSecureKey('credential:twilio:phone_number') || '';
31
24
  }
32
25
 
@@ -34,7 +27,7 @@ export function getTwilioConfig(assistantId?: string): TwilioConfig {
34
27
  const accountSid = getSecureKey('credential:twilio:account_sid');
35
28
  const authToken = getSecureKey('credential:twilio:auth_token');
36
29
  const config = loadConfig();
37
- const phoneNumber = resolveTwilioPhoneNumber(assistantId, config);
30
+ const phoneNumber = resolveTwilioPhoneNumber(config, assistantId);
38
31
  const webhookBaseUrl = getPublicBaseUrl(config);
39
32
 
40
33
  // Always use the centralized relay URL derived from the public ingress base URL.
@@ -147,10 +147,9 @@ function mapTwilioStatus(twilioStatus: string): CallStatus | null {
147
147
  * Supports two modes:
148
148
  * - **Outbound** (callSessionId present in query): uses the existing session
149
149
  * - **Inbound** (callSessionId absent): creates or reuses a session keyed
150
- * by the Twilio CallSid. The optional `forwardedAssistantId` is resolved
151
- * by the gateway from the "To" phone number.
150
+ * by the Twilio CallSid. Uses daemon internal scope for assistant identity.
152
151
  */
153
- export async function handleVoiceWebhook(req: Request, forwardedAssistantId?: string): Promise<Response> {
152
+ export async function handleVoiceWebhook(req: Request): Promise<Response> {
154
153
  const url = new URL(req.url);
155
154
  const callSessionId = url.searchParams.get('callSessionId');
156
155
 
@@ -167,13 +166,12 @@ export async function handleVoiceWebhook(req: Request, forwardedAssistantId?: st
167
166
  return new Response('Missing CallSid', { status: 400 });
168
167
  }
169
168
 
170
- log.info({ callSid, from: callerFrom, to: callerTo, assistantId: forwardedAssistantId }, 'Inbound voice webhook — creating/reusing session');
169
+ log.info({ callSid, from: callerFrom, to: callerTo }, 'Inbound voice webhook — creating/reusing session');
171
170
 
172
171
  const { session } = createInboundVoiceSession({
173
172
  callSid,
174
173
  fromNumber: callerFrom,
175
174
  toNumber: callerTo,
176
- assistantId: forwardedAssistantId,
177
175
  });
178
176
 
179
177
  return buildVoiceWebhookTwiml(session.id, session.assistantId ?? undefined, session.task, session.guardianVerificationSessionId);
@@ -254,7 +252,7 @@ function buildVoiceWebhookTwiml(
254
252
  });
255
253
  }
256
254
 
257
- const twilioConfig = getTwilioConfig(assistantId);
255
+ const twilioConfig = getTwilioConfig();
258
256
  let relayUrl: string;
259
257
  try {
260
258
  relayUrl = getTwilioRelayUrl(loadConfig());
@@ -1,5 +1,7 @@
1
1
  export type CallStatus = 'initiated' | 'ringing' | 'in_progress' | 'waiting_on_user' | 'completed' | 'failed' | 'cancelled';
2
- export type CallEventType = 'call_started' | 'call_connected' | 'caller_spoke' | 'assistant_spoke' | 'user_question_asked' | 'user_answered' | 'user_instruction_relayed' | 'call_ended' | 'call_failed' | 'callee_verification_started' | 'callee_verification_succeeded' | 'callee_verification_failed' | 'guardian_voice_verification_started' | 'guardian_voice_verification_succeeded' | 'guardian_voice_verification_failed' | 'outbound_guardian_voice_verification_started' | 'outbound_guardian_voice_verification_succeeded' | 'outbound_guardian_voice_verification_failed' | 'guardian_consultation_timed_out' | 'guardian_unavailable_skipped' | 'guardian_consult_deferred' | 'guardian_consult_coalesced' | 'inbound_acl_denied' | 'invite_redemption_started' | 'invite_redemption_succeeded' | 'invite_redemption_failed';
2
+ export type CallEventType = 'call_started' | 'call_connected' | 'caller_spoke' | 'assistant_spoke' | 'user_question_asked' | 'user_answered' | 'user_instruction_relayed' | 'call_ended' | 'call_failed' | 'callee_verification_started' | 'callee_verification_succeeded' | 'callee_verification_failed' | 'guardian_voice_verification_started' | 'guardian_voice_verification_succeeded' | 'guardian_voice_verification_failed' | 'outbound_guardian_voice_verification_started' | 'outbound_guardian_voice_verification_succeeded' | 'outbound_guardian_voice_verification_failed' | 'guardian_consultation_timed_out' | 'guardian_unavailable_skipped' | 'guardian_consult_deferred' | 'guardian_consult_coalesced' | 'inbound_acl_denied' | 'inbound_acl_name_capture_started' | 'inbound_acl_name_captured' | 'inbound_acl_name_capture_timeout' | 'inbound_acl_access_approved' | 'inbound_acl_access_denied' | 'inbound_acl_access_timeout' | 'invite_redemption_started' | 'invite_redemption_succeeded' | 'invite_redemption_failed' | 'voice_guardian_wait_heartbeat_sent' | 'voice_guardian_wait_prompt_classified' | 'voice_guardian_wait_callback_offer_sent' | 'voice_guardian_wait_callback_opt_in_set' | 'voice_guardian_wait_callback_opt_in_declined'
3
+ | 'callback_handoff_notified'
4
+ | 'callback_handoff_failed';
3
5
  export type PendingQuestionStatus = 'pending' | 'answered' | 'expired' | 'cancelled';
4
6
 
5
7
  /**
@@ -19,6 +19,7 @@ import type { GuardianRuntimeContext } from '../daemon/session-runtime-assembly.
19
19
  import { resolveChannelCapabilities } from '../daemon/session-runtime-assembly.js';
20
20
  import { buildAssistantEvent } from '../runtime/assistant-event.js';
21
21
  import { assistantEventHub } from '../runtime/assistant-event-hub.js';
22
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
22
23
  import { checkIngressForSecrets } from '../security/secret-ingress.js';
23
24
  import { computeToolApprovalDigest } from '../security/tool-approval-digest.js';
24
25
  import { IngressBlockedError } from '../util/errors.js';
@@ -306,7 +307,7 @@ export async function startVoiceTurn(opts: VoiceTurnOptions): Promise<VoiceTurnH
306
307
  ...session.memoryPolicy,
307
308
  strictSideEffects,
308
309
  };
309
- session.setAssistantId(opts.assistantId ?? 'self');
310
+ session.setAssistantId(opts.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID);
310
311
  session.callSessionId = opts.callSessionId;
311
312
  session.setGuardianContext(opts.guardianContext ?? null);
312
313
  session.setCommandIntent(null);
@@ -330,7 +331,7 @@ export async function startVoiceTurn(opts: VoiceTurnOptions): Promise<VoiceTurnH
330
331
  ? (msg as { sessionId: string }).sessionId
331
332
  : undefined;
332
333
  const resolvedSessionId = msgSessionId ?? opts.conversationId;
333
- const event = buildAssistantEvent('self', msg, resolvedSessionId);
334
+ const event = buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, msg, resolvedSessionId);
334
335
  hubChain = (async () => {
335
336
  await hubChain;
336
337
  try {
@@ -364,7 +365,7 @@ export async function startVoiceTurn(opts: VoiceTurnOptions): Promise<VoiceTurnH
364
365
  toolName: msg.toolName,
365
366
  inputDigest,
366
367
  consumingRequestId: msg.requestId,
367
- assistantId: opts.assistantId ?? 'self',
368
+ assistantId: opts.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
368
369
  executionChannel: 'voice',
369
370
  conversationId: opts.conversationId,
370
371
  callSessionId: opts.callSessionId,
@@ -107,8 +107,9 @@ export function registerDaemonCommand(program: Command): void {
107
107
  export function registerDevCommand(program: Command): void {
108
108
  program
109
109
  .command('dev')
110
- .description('Run the daemon in dev mode with auto-restart on file changes')
111
- .action(async () => {
110
+ .description('Run the daemon in dev mode')
111
+ .option('--watch', 'Auto-restart on source file changes (disruptive during Claude Code sessions)')
112
+ .action(async (opts: { watch?: boolean }) => {
112
113
  let status = await getDaemonStatus();
113
114
  if (status.running) {
114
115
  log.info('Stopping existing daemon...');
@@ -161,10 +162,12 @@ export function registerDevCommand(program: Command): void {
161
162
 
162
163
  const mainPath = `${import.meta.dirname}/../daemon/main.ts`;
163
164
 
164
- log.info('Starting daemon in dev mode (Ctrl+C to stop)');
165
+ const useWatch = opts.watch === true;
166
+ log.info(`Starting daemon in dev mode${useWatch ? ' with file watching' : ''} (Ctrl+C to stop)`);
165
167
 
166
168
  const repoRoot = join(import.meta.dirname, '..', '..', '..');
167
- const child = spawn('bun', ['--watch', 'run', mainPath], {
169
+ const args = useWatch ? ['--watch', 'run', mainPath] : ['run', mainPath];
170
+ const child = spawn('bun', args, {
168
171
  stdio: 'inherit',
169
172
  env: {
170
173
  ...process.env,
package/src/cli.ts CHANGED
@@ -233,7 +233,7 @@ export async function startCli(): Promise<void> {
233
233
  process.stdout.write(`\u2502\n`);
234
234
  process.stdout.write(`\u2502 [a] Allow once\n`);
235
235
  process.stdout.write(`\u2502 [d] Deny once\n`);
236
- if (req.allowlistOptions.length > 0) {
236
+ if (req.allowlistOptions.length > 0 && req.scopeOptions.length > 0) {
237
237
  process.stdout.write(`\u2502 [A] Allowlist...\n`);
238
238
  process.stdout.write(`\u2502 [H] Allowlist (high-risk)...\n`);
239
239
  process.stdout.write(`\u2502 [D] Denylist...\n`);
@@ -246,21 +246,22 @@ export async function startCli(): Promise<void> {
246
246
  const choice = trimmed.toLowerCase();
247
247
 
248
248
  // Uppercase 'A' → allowlist pattern selection (check before lowercase 'a')
249
- if (trimmed === 'A' || choice === 'allowlist') {
249
+ // Only process when scope options exist, matching the display guard above
250
+ if ((trimmed === 'A' || choice === 'allowlist') && req.allowlistOptions.length > 0 && req.scopeOptions.length > 0) {
250
251
  // pendingConfirmation stays true through sub-prompts
251
252
  renderPatternSelection(req, 'always_allow');
252
253
  return;
253
254
  }
254
255
 
255
256
  // Uppercase 'H' → high-risk allowlist pattern selection
256
- if (trimmed === 'H') {
257
+ if (trimmed === 'H' && req.allowlistOptions.length > 0 && req.scopeOptions.length > 0) {
257
258
  // pendingConfirmation stays true through sub-prompts
258
259
  renderPatternSelection(req, 'always_allow_high_risk');
259
260
  return;
260
261
  }
261
262
 
262
263
  // Uppercase 'D' → denylist pattern selection (check before lowercase 'd')
263
- if (trimmed === 'D' || choice === 'denylist') {
264
+ if ((trimmed === 'D' || choice === 'denylist') && req.allowlistOptions.length > 0 && req.scopeOptions.length > 0) {
264
265
  // pendingConfirmation stays true through sub-prompts
265
266
  renderPatternSelection(req, 'always_deny');
266
267
  return;
@@ -14,6 +14,10 @@ Example: `host_bash("vellum email status --json")`
14
14
 
15
15
  Never use browser/computer-use unless user explicitly approves fallback.
16
16
 
17
+ ## When to Use This Skill
18
+
19
+ This skill manages the **assistant's own** AgentMail address (`@agentmail.to`) — not the user's personal email. Only use this skill when the user explicitly asks the assistant to send email **from the assistant's own address**, manage the assistant's inbox, or perform operations on the assistant's AgentMail account. Generic email requests ("send an email", "check my email", "draft a reply") are about the **user's Gmail** and should be handled by the Messaging skill instead.
20
+
17
21
  ## Rules
18
22
 
19
23
  - Always run `vellum email` commands via `host_bash` and parse JSON output.
@@ -34,6 +34,7 @@ These are hard prohibitions. Violating any of these produces that unmistakable "
34
34
  - **NEVER** make all text the same size and weight — establish clear hierarchy with at least 3 distinct levels
35
35
  - **NEVER** use a pure white (`#fff`) or pure dark (`#000`/`#0a0a0a`) background — ALWAYS tint it to match the domain (cream `#FEFCF9` for lifestyle, sage `#F0F5F0` for nature, cool gray `#F5F7FA` for finance, warm blush `#FDF6F3` for wellness)
36
36
  - **NEVER** leave clickable elements without hover AND active states
37
+ - **NEVER** hand-code SVG/CSS charts (lines, bars, sparklines, gauges) — ALWAYS use `vellum.widgets.lineChart()`, `.barChart()`, `.sparkline()`, or `.progressRing()`. They handle bounds, clipping, scaling, and dark mode correctly. Hand-coded charts invariably overflow and bleed into adjacent elements.
37
38
  - **ALWAYS** use emoji as visual identifiers in stat cards, list items, and navigation — they replace icon libraries and add instant personality (🍎 for food, 🔥 for streaks, 💰 for money, 🌿 for plants)
38
39
  - **ALWAYS** apply the accent-word pattern in hero headings — color ONE key word or phrase in the accent color: "Your <span style='color: var(--accent)'>Week</span> in Motion"
39
40
  - **ALWAYS** include a contextual/personalized header — a greeting ("Good morning"), date ("Saturday, Feb 15"), or welcome ("Welcome back, Alex") — not just the app title
@@ -92,6 +93,23 @@ These are hard prohibitions. Violating any of these produces that unmistakable "
92
93
  Copy-paste-ready CSS techniques. All work in the sandboxed WebView with no external dependencies.
93
94
 
94
95
  ### Animated Gradient Background
96
+
97
+ Choose **one** of the following color variants (do not combine both):
98
+
99
+ **Variant A — Warm pastels (light, airy feel):**
100
+ ```css
101
+ body {
102
+ background: linear-gradient(-45deg, #fef3c7, #fce7f3, #e0e7ff, #d1fae5);
103
+ background-size: 400% 400%;
104
+ animation: gradientShift 15s ease infinite;
105
+ }
106
+ @keyframes gradientShift {
107
+ 0%, 100% { background-position: 0% 50%; }
108
+ 50% { background-position: 100% 50%; }
109
+ }
110
+ ```
111
+
112
+ **Variant B — Dark jewel tones (deep, rich feel):**
95
113
  ```css
96
114
  body {
97
115
  background: linear-gradient(-45deg, #0f172a, #1e1b4b, #172554, #0c4a6e);
@@ -108,9 +126,9 @@ body {
108
126
  ```css
109
127
  body {
110
128
  background:
111
- radial-gradient(ellipse at 20% 50%, color-mix(in srgb, var(--v-violet-500) 15%, transparent) 0%, transparent 50%),
112
- radial-gradient(ellipse at 80% 20%, color-mix(in srgb, var(--v-indigo-500) 12%, transparent) 0%, transparent 50%),
113
- radial-gradient(ellipse at 50% 80%, color-mix(in srgb, var(--v-emerald-500) 8%, transparent) 0%, transparent 50%),
129
+ radial-gradient(ellipse at 20% 50%, color-mix(in srgb, var(--v-rose-400) 15%, transparent) 0%, transparent 50%),
130
+ radial-gradient(ellipse at 80% 20%, color-mix(in srgb, var(--v-amber-400) 12%, transparent) 0%, transparent 50%),
131
+ radial-gradient(ellipse at 50% 80%, color-mix(in srgb, var(--v-teal-400) 10%, transparent) 0%, transparent 50%),
114
132
  var(--v-bg);
115
133
  }
116
134
  ```
@@ -161,7 +179,7 @@ body::before {
161
179
  ### Gradient Text
162
180
  ```css
163
181
  .gradient-text {
164
- background: linear-gradient(135deg, var(--v-violet-500), var(--v-indigo-400));
182
+ background: linear-gradient(135deg, var(--v-rose-500), var(--v-amber-400));
165
183
  -webkit-background-clip: text;
166
184
  -webkit-text-fill-color: transparent;
167
185
  background-clip: text;
@@ -348,7 +366,7 @@ document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
348
366
  .accent-word { color: var(--accent, var(--v-accent)); }
349
367
  /* Gradient variant — use .v-gradient-text from the design system, or customize: */
350
368
  .accent-gradient {
351
- background: linear-gradient(135deg, var(--accent, var(--v-violet-500)), var(--v-indigo-400));
369
+ background: linear-gradient(135deg, var(--accent, var(--v-rose-500)), var(--v-amber-400));
352
370
  -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
353
371
  }
354
372
  ```
@@ -673,6 +691,28 @@ Wrap in `.v-metric-grid` for responsive 2-4 column layout. Always use a semantic
673
691
 
674
692
  `.v-pullquote` — Blockquote with gradient accent border. `.v-comparison` — Before/after cards (3-column grid with `.before`/`.after` modifiers). `.v-page` — Centered container (max-width 600px). Use `.v-animate-in` on children for staggered fade-in. Use `.v-gradient-text` for accent-colored gradient text.
675
693
 
694
+ `.v-slideshow` — Presentation slide deck with transitions and navigation. Init with `vellum.widgets.slideshow()`:
695
+ ```html
696
+ <div class="v-slideshow" id="deck">
697
+ <div class="v-slide">
698
+ <div class="v-slide-header">
699
+ <span class="v-slide-label">Overview</span>
700
+ </div>
701
+ <h1 class="v-slide-title">The city that never <span class="accent-word">sleeps</span></h1>
702
+ <p class="v-slide-body">Body text here...</p>
703
+ <div class="v-slide-stats">
704
+ <div class="v-slide-stat">
705
+ <span class="v-slide-stat-value">8.3M</span>
706
+ <span class="v-slide-stat-label">Residents</span>
707
+ </div>
708
+ </div>
709
+ </div>
710
+ <div class="v-slide"><!-- Slide 2 --></div>
711
+ <div class="v-slide"><!-- Slide 3 --></div>
712
+ </div>
713
+ ```
714
+ Slide content helpers: `.v-slide-label` (section label with colored dot), `.v-slide-title` (responsive heading), `.v-slide-body` (body text, max-width 540px), `.v-slide-stats` (auto-fit grid), `.v-slide-stat` / `.v-slide-stat-value` / `.v-slide-stat-label` (big-number cards), `.v-slide-quote` / `.v-slide-quote-attribution` (blockquote), `.v-slide-list` (styled list), `.v-slide-columns` / `.v-slide-column` (2-column comparison grid).
715
+
676
716
  #### Widget JavaScript utilities
677
717
 
678
718
  Interactive utilities at `window.vellum.widgets.*`:
@@ -724,6 +764,13 @@ vellum.widgets.toast('Connection lost', 'error', 0); // Manual dismiss
724
764
  vellum.widgets.countdown('timer-el', '2025-12-31T00:00:00Z', {
725
765
  onComplete: () => console.log('Done!')
726
766
  });
767
+
768
+ // Slideshow — presentation deck with transitions and navigation
769
+ vellum.widgets.slideshow('deck', {
770
+ transition: 'fade', showDots: true, showArrows: true,
771
+ showCounter: true, keyboard: true, loop: true
772
+ });
773
+ // Returns: { goTo(index), next(), prev() }
727
774
  ```
728
775
 
729
776
  #### Composition recipes
@@ -992,12 +1039,147 @@ async function handleBulk(action) {
992
1039
  }
993
1040
  ```
994
1041
 
1042
+ **Presentation slideshow** — multi-slide deck with 8 layout variants (title, stats, bullets, quote, comparison, visual, timeline, closing). Use the slideshow widget for presentations, pitch decks, and multi-slide educational content. The model provides slide content; the widget handles navigation, transitions, and keyboard support. Never tell the user how to navigate slides — the UI is self-explanatory.
1043
+
1044
+ > **For comprehensive slide design guidelines, see the "Presentation Slide Design" section below.** The following HTML shows the structural template for all 8 layout types.
1045
+
1046
+ ```html
1047
+ <!DOCTYPE html>
1048
+ <html lang="en">
1049
+ <head>
1050
+ <meta charset="UTF-8">
1051
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1052
+ <style>
1053
+ :root { --v-accent: #8B5CF6; }
1054
+ body { margin: 0; padding: 0; background: linear-gradient(-45deg, #0f172a, #1e1b4b, #172554); min-height: 100vh; }
1055
+ .v-slideshow { border-radius: 0; min-height: 100vh; }
1056
+ .accent-word { color: var(--v-accent); }
1057
+ .trust-pill { display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; border-radius: 999px; font-size: 13px; font-weight: 500; background: color-mix(in srgb, var(--v-surface) 60%, transparent); border: 1px solid var(--v-surface-border); color: var(--v-text-secondary); margin-top: var(--v-spacing-lg); }
1058
+ .trust-pill.accent { border-color: color-mix(in srgb, var(--v-accent) 30%, transparent); color: var(--v-accent); }
1059
+ .v-slide-list li { font-size: 15px; line-height: 1.7; }
1060
+ .v-slide-columns h3 { margin: 0 0 var(--v-spacing-sm); color: var(--v-text); font-size: var(--v-font-size-lg); }
1061
+ .slide-visual { position: relative; overflow: hidden; }
1062
+ .slide-visual-bg { position: absolute; inset: 0; background: radial-gradient(ellipse at 30% 40%, color-mix(in srgb, var(--v-accent) 15%, transparent), transparent 70%), radial-gradient(ellipse at 70% 60%, color-mix(in srgb, var(--v-forest-500, #22c55e) 10%, transparent), transparent 60%); }
1063
+ .slide-visual-overlay { position: relative; z-index: 1; background: color-mix(in srgb, var(--v-bg) 40%, transparent); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border-radius: var(--v-radius-lg); padding: var(--v-spacing-xl); max-width: 500px; }
1064
+ </style>
1065
+ </head>
1066
+ <body>
1067
+ <div class="v-slideshow" id="deck">
1068
+
1069
+ <!-- 1. Title slide -->
1070
+ <div class="v-slide">
1071
+ <span class="v-slide-label">Introduction</span>
1072
+ <h1 class="v-slide-title">The city that never <span class="accent-word">sleeps</span></h1>
1073
+ <p class="v-slide-body">A brief subtitle or tagline here.</p>
1074
+ <span class="trust-pill accent">&#x1f5fd; 8.3 million residents</span>
1075
+ </div>
1076
+
1077
+ <!-- 2. Content + Stats slide -->
1078
+ <div class="v-slide">
1079
+ <span class="v-slide-label">By the Numbers</span>
1080
+ <h2 class="v-slide-title">Economy at a glance</h2>
1081
+ <p class="v-slide-body">New York generates more GDP than most countries...</p>
1082
+ <div class="v-slide-stats">
1083
+ <div class="v-slide-stat"><span class="v-slide-stat-value">$2.1T</span><span class="v-slide-stat-label">Metro GDP</span></div>
1084
+ <div class="v-slide-stat"><span class="v-slide-stat-value">4.7M</span><span class="v-slide-stat-label">Jobs</span></div>
1085
+ <div class="v-slide-stat"><span class="v-slide-stat-value">#1</span><span class="v-slide-stat-label">Financial Hub</span></div>
1086
+ </div>
1087
+ </div>
1088
+
1089
+ <!-- 3. Bullet points slide -->
1090
+ <div class="v-slide">
1091
+ <span class="v-slide-label">Culture</span>
1092
+ <h2 class="v-slide-title">What makes it <span class="accent-word">unique</span></h2>
1093
+ <ul class="v-slide-list">
1094
+ <li>&#x1f3ad; Broadway — 41 professional theaters in the Theater District</li>
1095
+ <li>&#x1f3db;&#xfe0f; 80+ world-class museums including the Met and MoMA</li>
1096
+ <li>&#x1f355; Over 27,000 restaurants spanning every cuisine on earth</li>
1097
+ <li>&#x1f333; 843 acres of Central Park in the heart of Manhattan</li>
1098
+ </ul>
1099
+ </div>
1100
+
1101
+ <!-- 4. Quote slide -->
1102
+ <div class="v-slide" style="justify-content: center; align-items: center; text-align: center;">
1103
+ <div class="v-slide-quote" style="border-left: none; padding-left: 0;">
1104
+ "There is no place like New York. It is the most exciting city in the world."
1105
+ </div>
1106
+ <div class="v-slide-quote-attribution">— John Updike</div>
1107
+ </div>
1108
+
1109
+ <!-- 5. Comparison / Two-column slide -->
1110
+ <div class="v-slide">
1111
+ <span class="v-slide-label">Comparison</span>
1112
+ <h2 class="v-slide-title">Manhattan vs Brooklyn</h2>
1113
+ <div class="v-slide-columns">
1114
+ <div class="v-slide-column">
1115
+ <h3>&#x1f3d9;&#xfe0f; Manhattan</h3>
1116
+ <p class="v-slide-body" style="margin-bottom: var(--v-spacing-sm);">Financial center, dense skyscrapers, high-energy nightlife, world-famous landmarks.</p>
1117
+ <span class="v-slide-stat-value">1.6M</span>
1118
+ <span class="v-slide-stat-label">Population</span>
1119
+ </div>
1120
+ <div class="v-slide-column">
1121
+ <h3>&#x1f309; Brooklyn</h3>
1122
+ <p class="v-slide-body" style="margin-bottom: var(--v-spacing-sm);">Creative hub, brownstone neighborhoods, artisan food scene, waterfront parks.</p>
1123
+ <span class="v-slide-stat-value">2.7M</span>
1124
+ <span class="v-slide-stat-label">Population</span>
1125
+ </div>
1126
+ </div>
1127
+ </div>
1128
+
1129
+ <!-- 6. Image/visual slide -->
1130
+ <div class="v-slide slide-visual">
1131
+ <div class="slide-visual-bg"></div>
1132
+ <div class="slide-visual-overlay">
1133
+ <span class="v-slide-label">Skyline</span>
1134
+ <h2 class="v-slide-title">An iconic <span class="accent-word">horizon</span></h2>
1135
+ <p class="v-slide-body">The Manhattan skyline is recognized worldwide...</p>
1136
+ </div>
1137
+ </div>
1138
+
1139
+ <!-- 7. Timeline slide -->
1140
+ <div class="v-slide">
1141
+ <span class="v-slide-label">History</span>
1142
+ <h2 class="v-slide-title">Key <span class="accent-word">milestones</span></h2>
1143
+ <div class="v-timeline" style="margin-top: var(--v-spacing-lg);">
1144
+ <div class="v-timeline-entry"><span class="v-timeline-time">1626</span><span class="v-timeline-title">Manhattan purchased</span></div>
1145
+ <div class="v-timeline-entry"><span class="v-timeline-time">1886</span><span class="v-timeline-title">Statue of Liberty dedicated</span></div>
1146
+ <div class="v-timeline-entry active"><span class="v-timeline-time">1931</span><span class="v-timeline-title">Empire State Building opens</span></div>
1147
+ </div>
1148
+ </div>
1149
+
1150
+ <!-- 8. Closing / CTA slide -->
1151
+ <div class="v-slide" style="text-align: center; align-items: center;">
1152
+ <h1 class="v-slide-title">The world's <span class="accent-word">capital</span></h1>
1153
+ <p class="v-slide-body" style="max-width: 400px;">New York isn't just a city — it's an idea that never stops evolving.</p>
1154
+ <div class="v-slide-stats" style="margin-top: var(--v-spacing-xxl);">
1155
+ <div class="v-slide-stat"><span class="v-slide-stat-value">800+</span><span class="v-slide-stat-label">Languages spoken</span></div>
1156
+ <div class="v-slide-stat"><span class="v-slide-stat-value">62M</span><span class="v-slide-stat-label">Annual visitors</span></div>
1157
+ </div>
1158
+ </div>
1159
+
1160
+ </div>
1161
+ <script>
1162
+ document.addEventListener('DOMContentLoaded', function() {
1163
+ vellum.widgets.slideshow('deck', {
1164
+ transition: 'fade',
1165
+ showDots: true,
1166
+ showArrows: true,
1167
+ showCounter: true,
1168
+ keyboard: true,
1169
+ loop: true
1170
+ });
1171
+ });
1172
+ </script>
1173
+ </body>
1174
+ </html>
1175
+ ```
1176
+
995
1177
  #### When to use widgets vs custom HTML
996
1178
 
997
- - **Use widgets** for standard patterns — tables, metrics, timelines, notifications
1179
+ - **Use widgets** for standard patterns — tables, metrics, timelines, notifications, presentations
998
1180
  - **Use custom HTML** for novel or creative UIs — games, art tools, unique dashboards
999
1181
  - **Mix freely** — widgets compose well together and with custom elements
1000
- - Always prioritize the ideal user experience over using the widget library
1182
+ - Always prioritize the ideal user experience over using the widget library — EXCEPT for charts: always use `vellum.widgets.*` chart functions (lineChart, barChart, sparkline, progressRing) instead of hand-coding SVG/CSS charts. They handle overflow clipping, bounds, scaling, and dark mode. Hand-coded charts break layouts.
1001
1183
 
1002
1184
  #### Advanced techniques
1003
1185
 
@@ -1170,7 +1352,25 @@ Both `ui_show` and `app_create` support a `preview` object for an inline chat pr
1170
1352
  }
1171
1353
  ```
1172
1354
 
1173
- **With `app_create`:**
1355
+ **With `app_create` (image URL icon):**
1356
+ ```json
1357
+ {
1358
+ "name": "Microsoft Overview",
1359
+ "schema_json": "{}",
1360
+ "html": "...",
1361
+ "preview": {
1362
+ "title": "Microsoft",
1363
+ "subtitle": "3 Slides",
1364
+ "icon": "https://www.microsoft.com/favicon.ico",
1365
+ "metrics": [
1366
+ { "label": "Founded", "value": "1975" },
1367
+ { "label": "Market Cap", "value": "$2.98T" }
1368
+ ]
1369
+ }
1370
+ }
1371
+ ```
1372
+
1373
+ **With `app_create` (emoji icon):**
1174
1374
  ```json
1175
1375
  {
1176
1376
  "name": "Expense Tracker",
@@ -1187,7 +1387,7 @@ Both `ui_show` and `app_create` support a `preview` object for an inline chat pr
1187
1387
  }
1188
1388
  ```
1189
1389
 
1190
- Preview fields: `title` (required), `subtitle`, `description`, `icon` (emoji), `metrics` (up to 3 key-value pills). When `app_create` is called with `auto_open: true` (the default), the preview is forwarded through `app_open` automatically.
1390
+ Preview fields: `title` (required), `subtitle`, `description`, `icon`, `metrics` (up to 3 key-value pills). The `icon` field accepts an emoji or an image URL. **Prefer an image URL whenever you have a relevant one** — logos, favicons, product images, headshots, flags, album art, or any image you encountered during research. The preview card renders image URLs as a thumbnail automatically. Fall back to emoji only when there is no natural image. When `app_create` is called with `auto_open: true` (the default), the preview is forwarded through `app_open` automatically.
1191
1391
 
1192
1392
  ### 6. Handle Iteration
1193
1393
 
@@ -1326,8 +1526,107 @@ Before delivering any app, mentally verify these 10 items — they cover the gap
1326
1526
  | `.v-pill-toggles` | Time range / filter toggle group | `.v-pill-toggle` (`.active`) — container with pill buttons |
1327
1527
  | `.v-chip-group` | Suggestion / filter chip row | `.v-chip` (`.active`) — wrapping row of clickable pills |
1328
1528
  | `.v-metric-card .v-metric-icon` | Emoji icon in metric cards | Place emoji `<span>` with `.v-metric-icon` inside `.v-metric-card` |
1529
+ | `.v-slideshow` | Presentation slide deck with transitions | `.v-slide` (`.active`), `.v-slide-label`, `.v-slide-title`, `.v-slide-body`, `.v-slide-stats`, `.v-slide-stat`, `.v-slide-quote` — init with `vellum.widgets.slideshow()` |
1530
+
1531
+ Every app should include: search/filter, toast notifications for all CRUD operations, `window.vellum.confirm()` for destructive actions, staggered page-load animation, card hover effects, and skeleton loading states. (These requirements do not apply to presentation slide decks — see "Presentation Slide Design" below.)
1532
+
1533
+ ## Presentation Slide Design
1534
+
1535
+ ### Slide Design Philosophy
1536
+
1537
+ - **One idea per slide** — each slide communicates a single concept, not a cluster of related points
1538
+ - **Glanceable, not readable** — a slide should be understood in 3 seconds; dense text belongs in documents, not presentations
1539
+ - **Visual storytelling arc** — open strong, build context, create emotional resonance, close with impact
1540
+ - **Cinematic quality bar** — every deck should feel at home in a startup pitch, TED talk, or Apple keynote
1541
+ - **The slide is the visual** — don't describe what the audience should imagine; show it through layout, color, and typography
1329
1542
 
1330
- Every app should include: search/filter, toast notifications for all CRUD operations, `window.vellum.confirm()` for destructive actions, staggered page-load animation, card hover effects, and skeleton loading states.
1543
+ ### What App Rules to Skip for Slides
1544
+
1545
+ The general app design checklist does NOT apply to slide decks. Specifically skip:
1546
+
1547
+ - Contextual header/greeting ("Good morning, Alex") — slides are not dashboards
1548
+ - Search/filter, pill toggles, suggestion chips — slides are not interactive apps
1549
+ - Toast notifications, confirm dialogs, form validation — no CRUD in slides
1550
+ - Data bridge API / `window.vellum.data` — slides are static content
1551
+ - Skeleton loading states — slides render instantly
1552
+ - Mandatory trust/status pill badge — use only when slide content calls for it (e.g., a "verified" badge on a stats slide)
1553
+ - Mandatory emoji stat cards — use when they strengthen the message, skip when they clutter
1554
+ - The app Pre-Ship Design Checklist — use the Slide Pre-Ship Checklist instead
1555
+
1556
+ ### Slide Typography
1557
+
1558
+ - **Title slides:** `clamp(2rem, 5vw, 3rem)`, weight 800 — much larger than app text
1559
+ - **Body text:** `clamp(1rem, 2.5vw, 1.25rem)` — larger than app body (14px); keep to 2–3 sentences max
1560
+ - **Stat values:** `clamp(1.75rem, 4vw, 2.5rem)` — big numbers are the most impactful element on any slide
1561
+ - **Accent-word technique is ESSENTIAL** — even more than apps, every heading should color one key word with the accent color
1562
+ - **Contrast is everything** — near-white on dark, near-black on light; no washed-out middle ground
1563
+ - **Never go below 15px** for any visible text — if it doesn't fit, cut words, don't shrink font
1564
+
1565
+ ### Slide Color & Visual Treatment
1566
+
1567
+ - **Bold, full-bleed backgrounds** — warm cream, blush pink, soft lavender, deep navy, rich purple, dark emerald; vary light and dark across the deck
1568
+ - **Animated gradient backgrounds** are ideal for title and closing slides — use `background-size: 400% 400%` with CSS animation
1569
+ - **Domain-matched palettes still apply**, just executed more dramatically — a finance deck is navy/gold, a health deck is teal/white
1570
+ - **One accent color used sparingly** — titles, stat borders, label dots, CTA buttons; never more than one accent
1571
+ - **Glassmorphism works well** for slide overlays on visual/immersive slides — `backdrop-filter: blur()` with semi-transparent bg
1572
+ - **Full-screen immersion:** `.v-slideshow` should use `border-radius: 0; min-height: 100vh` for edge-to-edge feel
1573
+ - **Vary background darkness across slides** — alternate between dark, medium, and light backgrounds to create visual rhythm
1574
+
1575
+ ### Slide Layout Rhythm
1576
+
1577
+ When to use each of the 8 layout variants:
1578
+
1579
+ | Type | When to Use |
1580
+ |---|---|
1581
+ | **Title** | Bold title with accent word, subtitle, optional badge — always first |
1582
+ | **Stats** | Early for credibility; 2–4 stat cards with big numbers |
1583
+ | **Bullets / Content** | Core message; 3–5 bullets max, or 2–3 sentence body |
1584
+ | **Quote** | Emotional punctuation; center-aligned, breaks visual pattern |
1585
+ | **Comparison** | Two-column before/after, entity comparison, or pros/cons |
1586
+ | **Timeline** | Chronological progression; milestones, history, roadmap, or process steps using `.v-timeline-entry` entries |
1587
+ | **Visual / Immersive** | Gradient background with glass overlay, minimal text |
1588
+ | **Closing / CTA** | Bold title, short takeaway, optional stat reinforcement |
1589
+
1590
+ **Layout rhythm rules:**
1591
+
1592
+ - **NEVER** two slides of the same type back-to-back
1593
+ - **5–8 slides:** title → stats → bullets → quote → comparison or timeline or visual → closing
1594
+ - **3–4 slides:** title → stats or bullets → closing
1595
+ - **10+ slides:** repeat content/stats but always separate with a quote, timeline, or visual slide
1596
+ - **Every deck needs at least 3 different layout types** — variety creates visual interest
1597
+
1598
+ ### Slide Anti-Slop Rules
1599
+
1600
+ - **NEVER** more than 6 bullet points per slide — if you have more, split into two slides
1601
+ - **NEVER** body text smaller than 15px — cut words instead of shrinking
1602
+ - **NEVER** the same background color on consecutive slides — vary dark/light/gradient
1603
+ - **NEVER** skip accent-word on title/heading slides — it's the #1 visual technique
1604
+ - **NEVER** use `.v-slide-label` on every single slide — aim for 40–60% of slides
1605
+ - **NEVER** center-align bullet slides — only center quotes and closing slides
1606
+ - **NEVER** use the same stat value format everywhere — mix `$2.4M`, `147%`, `3x`, `12k+` for variety
1607
+ - **NEVER** hand-code charts on slides — use `vellum.widgets.lineChart()` / `.barChart()` / `.sparkline()` / `.progressRing()` rendered into a `<div>` with a fixed height. Hand-coded chart SVGs bleed into adjacent slide elements.
1608
+
1609
+ ### Slide Pre-Ship Checklist
1610
+
1611
+ Before delivering any slide deck, verify:
1612
+
1613
+ 1. **Domain-matched palette** — colors match the topic (not default violet for everything)
1614
+ 2. **Bold background** — dark, gradient, or strongly tinted; not plain white
1615
+ 3. **Accent word in every title** — one key word colored with the accent
1616
+ 4. **One idea per slide** — each slide understood in 3 seconds
1617
+ 5. **Layout variety** — 3+ different layout types, no consecutive same-type
1618
+ 6. **Typography scale** — clear hierarchy; titles much larger than body text
1619
+ 7. **Sparse content** — max 6 bullets, max 3 sentences body text per slide
1620
+ 8. **Visual punctuation** — at least one quote, visual, or center-aligned slide
1621
+ 9. **Strong open and close** — impactful title slide, clear takeaway closing
1622
+ 10. **Immersive feel** — full-viewport slides, `min-height: 100vh; border-radius: 0`
1623
+
1624
+ ### What Great Slide Decks Look Like
1625
+
1626
+ - **Startup pitch deck** — dark navy animated gradient, accent-word title, trust pill on stats, big stat numbers (`$12M ARR`, `3x growth`), customer quote mid-deck, CTA closing with one bold ask
1627
+ - **Company overview** — corporate blue on charcoal, stats-heavy early slides, comparison slide (us vs. competitors), timeline slide, professional/minimal emoji usage
1628
+ - **Educational deck** — bright accent on light background, emoji in bullet points for visual anchoring, expert quote, glass overlay visual slide, "key takeaways" closing
1629
+ - **Creative agency deck** — bold saturated palette, animated gradient backgrounds, minimal text per slide, maximum visual drama, notable client quote, portfolio-style comparison
1331
1630
 
1332
1631
  ## Error Handling
1333
1632
 
@@ -50,7 +50,7 @@
50
50
  "title": { "type": "string", "description": "Preview card title" },
51
51
  "subtitle": { "type": "string", "description": "Optional subtitle" },
52
52
  "description": { "type": "string", "description": "Optional short description" },
53
- "icon": { "type": "string", "description": "Optional emoji icon" },
53
+ "icon": { "type": "string", "description": "Optional icon — image URL preferred when available (logo, favicon, photo, etc.), emoji as fallback" },
54
54
  "metrics": {
55
55
  "type": "array",
56
56
  "description": "Optional key-value metrics",
@@ -9,7 +9,7 @@ You are setting up your own personal email address. This is a one-time operation
9
9
 
10
10
  ## Prerequisites
11
11
 
12
- Only proceed if the user explicitly asks you to create or set up your email address. Do NOT proactively run this skill.
12
+ Only proceed if the user explicitly asks you to create or set up **your own** (the assistant's) email address — e.g., "set up your email", "create your email address", "I want you to have your own email". Generic email requests like "send an email", "check my email", or "set up email" are about the **user's Gmail** and should be handled by the Messaging skill, not this one. Do NOT proactively run this skill.
13
13
 
14
14
  ## Step 1: Check if Email Already Exists
15
15