@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.
- package/.env.example +3 -0
- package/ARCHITECTURE.md +124 -10
- package/README.md +43 -35
- package/docs/trusted-contact-access.md +20 -0
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +1 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -120
- package/src/__tests__/access-request-decision.test.ts +0 -1
- package/src/__tests__/actor-token-service.test.ts +1099 -0
- package/src/__tests__/agent-loop.test.ts +51 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -5
- package/src/__tests__/assistant-id-boundary-guard.test.ts +415 -0
- package/src/__tests__/call-controller.test.ts +49 -0
- package/src/__tests__/call-pointer-message-composer.test.ts +171 -0
- package/src/__tests__/call-pointer-messages.test.ts +93 -3
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +42 -0
- package/src/__tests__/call-routes-http.test.ts +0 -25
- package/src/__tests__/callback-handoff-copy.test.ts +186 -0
- package/src/__tests__/channel-approval-routes.test.ts +133 -12
- package/src/__tests__/channel-guardian.test.ts +0 -86
- package/src/__tests__/channel-readiness-service.test.ts +10 -16
- package/src/__tests__/checker.test.ts +33 -12
- package/src/__tests__/config-schema.test.ts +6 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +410 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +256 -0
- package/src/__tests__/conversation-routes.test.ts +12 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/daemon-server-session-init.test.ts +4 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +39 -13
- package/src/__tests__/guardian-dispatch.test.ts +8 -0
- package/src/__tests__/guardian-outbound-http.test.ts +4 -5
- package/src/__tests__/guardian-question-mode.test.ts +200 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +178 -0
- package/src/__tests__/guardian-routing-state.test.ts +525 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +2 -0
- package/src/__tests__/handlers-telegram-config.test.ts +0 -83
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +55 -0
- package/src/__tests__/headless-browser-navigate.test.ts +2 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
- package/src/__tests__/ingress-routes-http.test.ts +55 -0
- package/src/__tests__/ipc-snapshot.test.ts +18 -51
- package/src/__tests__/non-member-access-request.test.ts +159 -9
- package/src/__tests__/notification-decision-fallback.test.ts +129 -4
- package/src/__tests__/notification-decision-strategy.test.ts +106 -2
- package/src/__tests__/notification-guardian-path.test.ts +3 -0
- package/src/__tests__/recording-intent-handler.test.ts +1 -0
- package/src/__tests__/relay-server.test.ts +1475 -33
- package/src/__tests__/send-endpoint-busy.test.ts +5 -0
- package/src/__tests__/session-agent-loop.test.ts +1 -0
- package/src/__tests__/session-confirmation-signals.test.ts +523 -0
- package/src/__tests__/session-init.benchmark.test.ts +0 -2
- package/src/__tests__/session-runtime-assembly.test.ts +4 -1
- package/src/__tests__/session-surfaces-task-progress.test.ts +44 -1
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +81 -2
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -1
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +21 -2
- package/src/__tests__/tool-grant-request-escalation.test.ts +333 -27
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +678 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1064 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +11 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/trusted-contact-verification.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +2 -13
- package/src/__tests__/twilio-routes.test.ts +4 -3
- package/src/__tests__/update-bulletin.test.ts +0 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-decision-primitive.ts +12 -3
- package/src/approvals/guardian-request-resolvers.ts +169 -11
- package/src/calls/call-constants.ts +29 -0
- package/src/calls/call-controller.ts +11 -3
- package/src/calls/call-domain.ts +33 -11
- package/src/calls/call-pointer-message-composer.ts +154 -0
- package/src/calls/call-pointer-messages.ts +106 -27
- package/src/calls/guardian-dispatch.ts +4 -2
- package/src/calls/relay-server.ts +921 -112
- package/src/calls/twilio-config.ts +4 -11
- package/src/calls/twilio-routes.ts +4 -6
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-session-bridge.ts +4 -3
- package/src/cli/core-commands.ts +7 -4
- package/src/cli.ts +5 -4
- package/src/config/bundled-skills/agentmail/SKILL.md +4 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +309 -10
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -1
- package/src/config/bundled-skills/email-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +105 -81
- package/src/config/bundled-skills/messaging/SKILL.md +61 -12
- package/src/config/bundled-skills/messaging/TOOLS.json +58 -0
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +6 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +35 -0
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +52 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +30 -39
- package/src/config/bundled-skills/twitter/SKILL.md +3 -3
- package/src/config/bundled-skills/vercel-token-setup/SKILL.md +215 -0
- package/src/config/calls-schema.ts +36 -0
- package/src/config/env.ts +22 -0
- package/src/config/feature-flag-registry.json +8 -8
- package/src/config/schema.ts +2 -2
- package/src/config/skills.ts +11 -0
- package/src/config/system-prompt.ts +11 -1
- package/src/config/templates/SOUL.md +2 -0
- package/src/config/vellum-skills/sms-setup/SKILL.md +71 -82
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +8 -1
- package/src/config/vellum-skills/twilio-setup/SKILL.md +88 -73
- package/src/daemon/call-pointer-generators.ts +59 -0
- package/src/daemon/computer-use-session.ts +2 -5
- package/src/daemon/handlers/apps.ts +76 -20
- package/src/daemon/handlers/config-channels.ts +9 -61
- package/src/daemon/handlers/config-inbox.ts +11 -3
- package/src/daemon/handlers/config-ingress.ts +28 -3
- package/src/daemon/handlers/config-telegram.ts +12 -0
- package/src/daemon/handlers/config.ts +2 -6
- package/src/daemon/handlers/index.ts +2 -1
- package/src/daemon/handlers/pairing.ts +2 -0
- package/src/daemon/handlers/publish.ts +11 -46
- package/src/daemon/handlers/sessions.ts +59 -5
- package/src/daemon/handlers/shared.ts +17 -2
- package/src/daemon/ipc-contract/apps.ts +1 -0
- package/src/daemon/ipc-contract/inbox.ts +4 -0
- package/src/daemon/ipc-contract/integrations.ts +1 -97
- package/src/daemon/ipc-contract/messages.ts +47 -1
- package/src/daemon/ipc-contract/notifications.ts +11 -0
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +17 -0
- package/src/daemon/server.ts +16 -2
- package/src/daemon/session-agent-loop-handlers.ts +20 -0
- package/src/daemon/session-agent-loop.ts +24 -12
- package/src/daemon/session-lifecycle.ts +1 -1
- package/src/daemon/session-process.ts +11 -1
- package/src/daemon/session-runtime-assembly.ts +6 -1
- package/src/daemon/session-surfaces.ts +32 -3
- package/src/daemon/session.ts +88 -1
- package/src/daemon/tool-side-effects.ts +22 -0
- package/src/home-base/prebuilt/brain-graph.html +1483 -0
- package/src/home-base/prebuilt/index.html +40 -0
- package/src/inbound/platform-callback-registration.ts +157 -0
- package/src/memory/canonical-guardian-store.ts +1 -1
- package/src/memory/conversation-crud.ts +2 -1
- package/src/memory/conversation-title-service.ts +16 -2
- package/src/memory/db-init.ts +8 -0
- package/src/memory/delivery-crud.ts +2 -1
- package/src/memory/guardian-action-store.ts +2 -1
- package/src/memory/guardian-approvals.ts +3 -2
- package/src/memory/ingress-invite-store.ts +12 -2
- package/src/memory/ingress-member-store.ts +4 -3
- package/src/memory/migrations/038-actor-token-records.ts +39 -0
- package/src/memory/migrations/124-voice-invite-display-metadata.ts +14 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema.ts +26 -5
- package/src/messaging/provider-types.ts +24 -0
- package/src/messaging/provider.ts +7 -0
- package/src/messaging/providers/gmail/adapter.ts +127 -0
- package/src/messaging/providers/sms/adapter.ts +40 -37
- package/src/notifications/adapters/macos.ts +45 -2
- package/src/notifications/broadcaster.ts +16 -0
- package/src/notifications/copy-composer.ts +50 -2
- package/src/notifications/decision-engine.ts +22 -9
- package/src/notifications/destination-resolver.ts +16 -2
- package/src/notifications/emit-signal.ts +18 -9
- package/src/notifications/guardian-question-mode.ts +419 -0
- package/src/notifications/signal.ts +14 -3
- package/src/permissions/checker.ts +13 -1
- package/src/permissions/prompter.ts +14 -0
- package/src/providers/anthropic/client.ts +20 -0
- package/src/providers/provider-send-message.ts +15 -3
- package/src/runtime/access-request-helper.ts +82 -4
- package/src/runtime/actor-token-service.ts +234 -0
- package/src/runtime/actor-token-store.ts +236 -0
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-scope.ts +10 -0
- package/src/runtime/channel-approvals.ts +5 -3
- package/src/runtime/channel-readiness-service.ts +23 -64
- package/src/runtime/channel-readiness-types.ts +3 -4
- package/src/runtime/channel-retry-sweep.ts +4 -1
- package/src/runtime/confirmation-request-guardian-bridge.ts +197 -0
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-context-resolver.ts +82 -0
- package/src/runtime/guardian-outbound-actions.ts +5 -7
- package/src/runtime/guardian-reply-router.ts +67 -30
- package/src/runtime/guardian-vellum-migration.ts +57 -0
- package/src/runtime/http-server.ts +75 -31
- package/src/runtime/http-types.ts +13 -0
- package/src/runtime/ingress-service.ts +14 -0
- package/src/runtime/invite-redemption-service.ts +10 -1
- package/src/runtime/local-actor-identity.ts +76 -0
- package/src/runtime/middleware/actor-token.ts +271 -0
- package/src/runtime/middleware/twilio-validation.ts +2 -4
- package/src/runtime/routes/approval-routes.ts +82 -7
- package/src/runtime/routes/brain-graph-routes.ts +222 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-readiness-routes.ts +71 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -3
- package/src/runtime/routes/conversation-attention-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +142 -53
- package/src/runtime/routes/events-routes.ts +22 -8
- package/src/runtime/routes/guardian-action-routes.ts +45 -3
- package/src/runtime/routes/guardian-approval-interception.ts +29 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +145 -0
- package/src/runtime/routes/inbound-conversation.ts +4 -3
- package/src/runtime/routes/inbound-message-handler.ts +147 -5
- package/src/runtime/routes/ingress-routes.ts +2 -0
- package/src/runtime/routes/integration-routes.ts +7 -15
- package/src/runtime/routes/pairing-routes.ts +163 -0
- package/src/runtime/routes/twilio-routes.ts +934 -0
- package/src/runtime/tool-grant-request-helper.ts +3 -1
- package/src/security/oauth2.ts +27 -2
- package/src/security/token-manager.ts +46 -10
- package/src/tools/browser/browser-execution.ts +4 -3
- package/src/tools/browser/browser-handoff.ts +10 -18
- package/src/tools/browser/browser-manager.ts +80 -25
- package/src/tools/browser/browser-screencast.ts +35 -119
- package/src/tools/calls/call-start.ts +2 -1
- package/src/tools/permission-checker.ts +15 -4
- package/src/tools/terminal/parser.ts +12 -0
- package/src/tools/tool-approval-handler.ts +244 -19
- package/src/workspace/git-service.ts +19 -0
- package/src/__tests__/handlers-twilio-config.test.ts +0 -1928
- 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(
|
|
18
|
+
function resolveTwilioPhoneNumber(config: ReturnType<typeof loadConfig>, assistantId?: string): string {
|
|
19
19
|
if (assistantId) {
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
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(
|
|
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.
|
|
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
|
|
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
|
|
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(
|
|
255
|
+
const twilioConfig = getTwilioConfig();
|
|
258
256
|
let relayUrl: string;
|
|
259
257
|
try {
|
|
260
258
|
relayUrl = getTwilioRelayUrl(loadConfig());
|
package/src/calls/types.ts
CHANGED
|
@@ -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 ??
|
|
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(
|
|
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 ??
|
|
368
|
+
assistantId: opts.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
368
369
|
executionChannel: 'voice',
|
|
369
370
|
conversationId: opts.conversationId,
|
|
370
371
|
callSessionId: opts.callSessionId,
|
package/src/cli/core-commands.ts
CHANGED
|
@@ -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
|
|
111
|
-
.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
112
|
-
radial-gradient(ellipse at 80% 20%, color-mix(in srgb, var(--v-
|
|
113
|
-
radial-gradient(ellipse at 50% 80%, color-mix(in srgb, var(--v-
|
|
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-
|
|
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-
|
|
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">🗽 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>🎭 Broadway — 41 professional theaters in the Theater District</li>
|
|
1095
|
+
<li>🏛️ 80+ world-class museums including the Met and MoMA</li>
|
|
1096
|
+
<li>🍕 Over 27,000 restaurants spanning every cuisine on earth</li>
|
|
1097
|
+
<li>🌳 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>🏙️ 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>🌉 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
|
|
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
|
-
|
|
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
|
|
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
|
|