@vellumai/assistant 0.4.35 → 0.4.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/ARCHITECTURE.md +44 -49
- package/README.md +32 -20
- package/docs/architecture/keychain-broker.md +186 -0
- package/docs/architecture/security.md +110 -116
- package/docs/runbook-trusted-contacts.md +2 -2
- package/docs/skills.md +25 -25
- package/package.json +5 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +11 -2
- package/src/__tests__/actor-token-service.test.ts +1 -0
- package/src/__tests__/amazon-cdp-integration.test.ts +74 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +38 -9
- package/src/__tests__/assistant-id-boundary-guard.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/bundle-scanner.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +102 -102
- package/src/__tests__/channel-invite-transport.test.ts +155 -256
- package/src/__tests__/channel-readiness-routes.test.ts +336 -0
- package/src/__tests__/checker.test.ts +6 -6
- package/src/__tests__/chrome-cdp.test.ts +350 -0
- package/src/__tests__/computer-use-session-lifecycle.test.ts +3 -3
- package/src/__tests__/computer-use-session-working-dir.test.ts +86 -52
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +1 -1
- package/src/__tests__/config-loader-migration.test.ts +85 -0
- package/src/__tests__/conversation-pairing.test.ts +370 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +1 -10
- package/src/__tests__/credential-broker-server-use.test.ts +1 -10
- package/src/__tests__/credential-security-e2e.test.ts +7 -1
- package/src/__tests__/credential-security-invariants.test.ts +14 -20
- package/src/__tests__/credential-vault-unit.test.ts +1 -11
- package/src/__tests__/credential-vault.test.ts +5 -19
- package/src/__tests__/credentials-cli.test.ts +814 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +23 -4
- package/src/__tests__/email-invite-adapter.test.ts +78 -0
- package/src/__tests__/email-service-config-fallback.test.ts +102 -0
- package/src/__tests__/encrypted-store.test.ts +6 -6
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +5 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +70 -12
- package/src/__tests__/guardian-outbound-http.test.ts +53 -47
- package/src/__tests__/handle-user-message-secret-resume.test.ts +23 -0
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +32 -23
- package/src/__tests__/handlers-telegram-config.test.ts +8 -2
- package/src/__tests__/handlers-twitter-config.test.ts +2 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +108 -7
- package/src/__tests__/ingress-reconcile.test.ts +6 -0
- package/src/__tests__/intent-routing.test.ts +23 -4
- package/src/__tests__/invite-routes-http.test.ts +12 -0
- package/src/__tests__/ipc-snapshot.test.ts +8 -2
- package/src/__tests__/keychain-broker-client.test.ts +543 -0
- package/src/__tests__/llm-usage-store.test.ts +344 -0
- package/src/__tests__/mcp-client-auth.test.ts +2 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
- package/src/__tests__/migration-transport.test.ts +49 -0
- package/src/__tests__/notification-broadcaster.test.ts +205 -5
- package/src/__tests__/notification-deep-link.test.ts +365 -1
- package/src/__tests__/oauth-connect-handler.test.ts +2 -2
- package/src/__tests__/onboarding-starter-tasks.test.ts +17 -4
- package/src/__tests__/proxy-approval-callback.test.ts +1 -1
- package/src/__tests__/recording-handler.test.ts +1 -1
- package/src/__tests__/recording-intent-handler.test.ts +6 -1
- package/src/__tests__/recording-state-machine.test.ts +1 -1
- package/src/__tests__/relay-server.test.ts +9 -1
- package/src/__tests__/ride-shotgun-handler.test.ts +499 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +160 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +299 -2
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +8 -2
- package/src/__tests__/secure-keys.test.ts +175 -216
- package/src/__tests__/session-confirmation-signals.test.ts +1 -1
- package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/session-queue.test.ts +2 -1
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +2 -2
- package/src/__tests__/skill-feature-flags-integration.test.ts +29 -4
- package/src/__tests__/skill-feature-flags.test.ts +12 -9
- package/src/__tests__/skill-load-feature-flag.test.ts +26 -5
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skills.test.ts +34 -4
- package/src/__tests__/slack-channel-config.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +26 -4
- package/src/__tests__/telegram-bot-username-resolution.test.ts +212 -0
- package/src/__tests__/telegram-invite-adapter.test.ts +164 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-permission-simulate-handler.test.ts +8 -2
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +9 -1
- package/src/__tests__/twitter-auth-handler.test.ts +2 -2
- package/src/__tests__/twitter-oauth-client.test.ts +1 -1
- package/src/__tests__/usage-routes.test.ts +339 -0
- package/src/__tests__/whatsapp-invite-adapter.test.ts +94 -0
- package/src/agent/loop.ts +3 -0
- package/src/amazon/checkout.ts +0 -1
- package/src/approvals/guardian-request-resolvers.ts +9 -1
- package/src/bundler/app-bundler.ts +28 -12
- package/src/bundler/bundle-scanner.ts +1 -1
- package/src/bundler/bundle-signer.ts +3 -3
- package/src/bundler/manifest.ts +1 -1
- package/src/bundler/signature-verifier.ts +3 -3
- package/src/channels/config.ts +1 -1
- package/src/cli/AGENTS.md +63 -0
- package/src/cli/__tests__/notifications.test.ts +470 -0
- package/src/cli/amazon.ts +344 -167
- package/src/cli/audit.ts +85 -0
- package/src/cli/autonomy.ts +369 -0
- package/src/cli/channels.ts +51 -0
- package/src/cli/completions.ts +208 -0
- package/src/cli/config.ts +220 -0
- package/src/cli/contacts.ts +471 -0
- package/src/cli/credentials.ts +564 -0
- package/src/cli/default-action.ts +14 -0
- package/src/cli/dev.ts +131 -0
- package/src/cli/doctor.ts +398 -0
- package/src/cli/email.ts +494 -0
- package/src/cli/influencer.ts +72 -0
- package/src/cli/integrations.ts +248 -57
- package/src/cli/keys.ts +114 -0
- package/src/cli/map.ts +46 -54
- package/src/cli/mcp.ts +111 -3
- package/src/cli/{config-commands.ts → memory.ts} +134 -245
- package/src/cli/notifications.ts +407 -0
- package/src/cli/program.ts +65 -0
- package/src/cli/reference.ts +48 -0
- package/src/cli/sequence.ts +154 -0
- package/src/cli/sessions.ts +262 -0
- package/src/cli/trust.ts +175 -0
- package/src/cli/twitter.ts +323 -106
- package/src/config/__tests__/build-cli-reference-section.test.ts +49 -0
- package/src/config/bundled-skills/amazon/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/TOOLS.json +26 -0
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +13 -0
- package/src/config/bundled-skills/contacts/SKILL.md +178 -10
- package/src/config/bundled-skills/doordash/doordash-cli.ts +23 -168
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +135 -34
- package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
- package/src/config/bundled-skills/twilio-setup/SKILL.md +70 -17
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/core-schema.ts +7 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +26 -0
- package/src/config/schema.ts +4 -0
- package/src/config/skill-state.ts +0 -13
- package/src/config/system-prompt.ts +27 -0
- package/src/contacts/contact-store.ts +25 -0
- package/src/daemon/computer-use-session.ts +1 -1
- package/src/daemon/handlers/apps.ts +1 -0
- package/src/daemon/handlers/config-channels.ts +3 -3
- package/src/daemon/handlers/config-dispatch.ts +29 -0
- package/src/daemon/handlers/config-inbox.ts +4 -3
- package/src/daemon/handlers/config.ts +3 -43
- package/src/daemon/handlers/contacts.ts +34 -0
- package/src/daemon/handlers/index.ts +17 -3
- package/src/daemon/handlers/session-user-message.ts +7 -0
- package/src/daemon/handlers/sessions.ts +21 -2
- package/src/daemon/handlers/shared.ts +17 -0
- package/src/daemon/ipc-contract/apps.ts +2 -0
- package/src/daemon/ipc-contract/computer-use.ts +9 -0
- package/src/daemon/ipc-contract/contacts.ts +3 -3
- package/src/daemon/ipc-contract/inbox.ts +2 -0
- package/src/daemon/ipc-contract/messages.ts +4 -0
- package/src/daemon/ipc-contract/sessions.ts +8 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/ride-shotgun-handler.ts +139 -25
- package/src/daemon/session-agent-loop-handlers.ts +100 -0
- package/src/daemon/session-agent-loop.ts +72 -0
- package/src/daemon/session-tool-setup.ts +7 -0
- package/src/daemon/session.ts +23 -1
- package/src/daemon/tool-side-effects.ts +39 -1
- package/src/email/service.ts +59 -2
- package/src/index.ts +2 -60
- package/src/mcp/mcp-oauth-provider.ts +90 -8
- package/src/media/app-icon-generator.ts +86 -0
- package/src/memory/db-init.ts +11 -0
- package/src/memory/llm-usage-store.ts +186 -0
- package/src/memory/migrations/137-usage-dashboard-indexes.ts +26 -0
- package/src/memory/migrations/139-drop-usage-composite-indexes.ts +30 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/shared-app-links-store.ts +1 -1
- package/src/messaging/registry.ts +27 -0
- package/src/notifications/README.md +79 -70
- package/src/notifications/broadcaster.ts +2 -1
- package/src/notifications/conversation-pairing.ts +147 -13
- package/src/notifications/copy-composer.ts +7 -3
- package/src/notifications/destination-resolver.ts +14 -1
- package/src/notifications/emit-signal.ts +3 -2
- package/src/notifications/signal.ts +105 -1
- package/src/notifications/types.ts +16 -0
- package/src/permissions/checker.ts +29 -3
- package/src/permissions/prompter.ts +11 -3
- package/src/runtime/access-request-helper.ts +2 -1
- package/src/runtime/auth/route-policy.ts +7 -1
- package/src/runtime/channel-invite-transport.ts +40 -63
- package/src/runtime/channel-invite-transports/email.ts +13 -39
- package/src/runtime/channel-invite-transports/slack.ts +5 -34
- package/src/runtime/channel-invite-transports/sms.ts +8 -29
- package/src/runtime/channel-invite-transports/telegram.ts +69 -28
- package/src/runtime/channel-invite-transports/voice.ts +0 -7
- package/src/runtime/channel-invite-transports/whatsapp.ts +43 -0
- package/src/runtime/channel-readiness-service.ts +202 -45
- package/src/runtime/confirmation-request-guardian-bridge.ts +2 -1
- package/src/runtime/guardian-outbound-actions.ts +8 -5
- package/src/runtime/http-server.ts +2 -0
- package/src/runtime/invite-instruction-generator.ts +178 -0
- package/src/runtime/invite-service.ts +22 -25
- package/src/runtime/migrations/migration-transport.ts +13 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +8 -7
- package/src/runtime/routes/channel-readiness-routes.ts +30 -11
- package/src/runtime/routes/contact-routes.ts +54 -26
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -1
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +2 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +2 -1
- package/src/runtime/routes/integration-routes.ts +1 -1
- package/src/runtime/routes/invite-routes.ts +1 -1
- package/src/runtime/routes/secret-routes.ts +31 -7
- package/src/runtime/routes/twilio-routes.ts +32 -1
- package/src/runtime/routes/usage-routes.ts +114 -0
- package/src/runtime/tool-grant-request-helper.ts +2 -1
- package/src/security/encrypted-store.ts +9 -5
- package/src/security/keychain-broker-client.ts +393 -0
- package/src/security/secure-keys.ts +106 -321
- package/src/tools/apps/executors.ts +73 -0
- package/src/tools/browser/auto-navigate.ts +15 -6
- package/src/tools/browser/chrome-cdp.ts +211 -0
- package/src/tools/browser/network-recorder.test.ts +83 -0
- package/src/tools/browser/network-recorder.ts +8 -7
- package/src/tools/browser/x-auto-navigate.ts +12 -6
- package/src/tools/credentials/policy-types.ts +24 -0
- package/src/tools/credentials/vault.ts +22 -27
- package/src/tools/network/script-proxy/session-manager.ts +47 -3
- package/src/tools/permission-checker.ts +1 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +1 -2
- package/src/tools/watch/watch-state.ts +2 -0
- package/src/__tests__/key-migration.test.ts +0 -240
- package/src/__tests__/keychain.test.ts +0 -286
- package/src/cli/core-commands.ts +0 -899
- package/src/security/keychain-to-encrypted-migration.ts +0 -66
- package/src/security/keychain.ts +0 -490
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getAssistantContactMetadata,
|
|
5
|
+
getContact,
|
|
6
|
+
listContacts,
|
|
7
|
+
mergeContacts,
|
|
8
|
+
searchContacts,
|
|
9
|
+
} from "../contacts/contact-store.js";
|
|
10
|
+
import type { ContactRole } from "../contacts/types.js";
|
|
11
|
+
import { initializeDb } from "../memory/db.js";
|
|
12
|
+
import {
|
|
13
|
+
createIngressInvite,
|
|
14
|
+
listIngressInvites,
|
|
15
|
+
redeemIngressInvite,
|
|
16
|
+
redeemVoiceInviteCode,
|
|
17
|
+
revokeIngressInvite,
|
|
18
|
+
} from "../runtime/invite-service.js";
|
|
19
|
+
import { writeOutput } from "./integrations.js";
|
|
20
|
+
|
|
21
|
+
export function registerContactsCommand(program: Command): void {
|
|
22
|
+
const contacts = program
|
|
23
|
+
.command("contacts")
|
|
24
|
+
.description("Manage and query the contact graph")
|
|
25
|
+
.option("--json", "Machine-readable compact JSON output");
|
|
26
|
+
|
|
27
|
+
contacts.addHelpText(
|
|
28
|
+
"after",
|
|
29
|
+
`
|
|
30
|
+
Contacts represent people and entities the assistant interacts with. Each
|
|
31
|
+
contact is identified by a UUID, has a role (contact or guardian), and
|
|
32
|
+
can be linked to external identifiers — phone numbers,
|
|
33
|
+
Telegram IDs, email addresses — via channel memberships. The contact graph
|
|
34
|
+
is the source of truth for identity resolution across all channels.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
$ vellum contacts list
|
|
38
|
+
$ vellum contacts get abc-123
|
|
39
|
+
$ vellum contacts merge keep-id merge-id
|
|
40
|
+
$ vellum contacts invites list`,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
contacts
|
|
44
|
+
.command("list")
|
|
45
|
+
.description("List contacts")
|
|
46
|
+
.option("--role <role>", "Filter by role (default: contact)", "contact")
|
|
47
|
+
.option("--limit <limit>", "Maximum number of contacts to return")
|
|
48
|
+
.option("--query <query>", "Search query to filter contacts")
|
|
49
|
+
.addHelpText(
|
|
50
|
+
"after",
|
|
51
|
+
`
|
|
52
|
+
Lists contacts with optional filtering. The --role flag accepts: contact
|
|
53
|
+
or guardian (defaults to contact). The --limit flag sets
|
|
54
|
+
the maximum number of results (defaults to 50).
|
|
55
|
+
|
|
56
|
+
When --query is provided, a full-text search is performed across contact
|
|
57
|
+
names and linked external identifiers (phone numbers, emails, Telegram
|
|
58
|
+
usernames). Without --query, returns all contacts matching the role filter.
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
$ vellum contacts list
|
|
62
|
+
$ vellum contacts list --role guardian
|
|
63
|
+
$ vellum contacts list --query "john" --limit 10
|
|
64
|
+
$ vellum contacts list --role guardian --json`,
|
|
65
|
+
)
|
|
66
|
+
.action(
|
|
67
|
+
async (
|
|
68
|
+
opts: {
|
|
69
|
+
role?: string;
|
|
70
|
+
limit?: string;
|
|
71
|
+
query?: string;
|
|
72
|
+
},
|
|
73
|
+
cmd: Command,
|
|
74
|
+
) => {
|
|
75
|
+
try {
|
|
76
|
+
initializeDb();
|
|
77
|
+
const role = opts.role as ContactRole | undefined;
|
|
78
|
+
const limit = opts.limit ? Number(opts.limit) : undefined;
|
|
79
|
+
|
|
80
|
+
const effectiveLimit = limit ?? 50;
|
|
81
|
+
|
|
82
|
+
const results = opts.query
|
|
83
|
+
? searchContacts({ query: opts.query, role, limit: effectiveLimit })
|
|
84
|
+
: listContacts(effectiveLimit, role);
|
|
85
|
+
|
|
86
|
+
writeOutput(cmd, { ok: true, contacts: results });
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
89
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
contacts
|
|
96
|
+
.command("get <id>")
|
|
97
|
+
.description("Get a contact by ID")
|
|
98
|
+
.addHelpText(
|
|
99
|
+
"after",
|
|
100
|
+
`
|
|
101
|
+
Arguments:
|
|
102
|
+
id UUID of the contact to retrieve
|
|
103
|
+
|
|
104
|
+
Returns the full contact record including role, display name, and all
|
|
105
|
+
channel memberships (phone numbers, Telegram IDs, email addresses, etc.).
|
|
106
|
+
For assistant-type contacts, additional assistant metadata is included.
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
$ vellum contacts get 7a3b1c2d-4e5f-6789-abcd-ef0123456789
|
|
110
|
+
$ vellum contacts get abc-123 --json`,
|
|
111
|
+
)
|
|
112
|
+
.action(async (id: string, _opts: unknown, cmd: Command) => {
|
|
113
|
+
try {
|
|
114
|
+
initializeDb();
|
|
115
|
+
const contact = getContact(id);
|
|
116
|
+
if (!contact) {
|
|
117
|
+
writeOutput(cmd, { ok: false, error: "Contact not found" });
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const assistantMeta =
|
|
122
|
+
contact.contactType === "assistant"
|
|
123
|
+
? getAssistantContactMetadata(contact.id)
|
|
124
|
+
: undefined;
|
|
125
|
+
writeOutput(cmd, {
|
|
126
|
+
ok: true,
|
|
127
|
+
contact,
|
|
128
|
+
assistantMetadata: assistantMeta ?? undefined,
|
|
129
|
+
});
|
|
130
|
+
} catch (err) {
|
|
131
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
132
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
contacts
|
|
138
|
+
.command("merge <keepId> <mergeId>")
|
|
139
|
+
.description("Merge two contacts")
|
|
140
|
+
.addHelpText(
|
|
141
|
+
"after",
|
|
142
|
+
`
|
|
143
|
+
Arguments:
|
|
144
|
+
keepId UUID of the surviving contact that will absorb the other
|
|
145
|
+
mergeId UUID of the contact to be merged and deleted
|
|
146
|
+
|
|
147
|
+
All channel memberships, conversation history, and metadata from mergeId
|
|
148
|
+
are transferred to keepId. After the merge, mergeId is permanently deleted.
|
|
149
|
+
This operation is irreversible.
|
|
150
|
+
|
|
151
|
+
Examples:
|
|
152
|
+
$ vellum contacts merge 7a3b1c2d-4e5f-6789-abcd-ef0123456789 9f8e7d6c-5b4a-3210-fedc-ba9876543210
|
|
153
|
+
$ vellum contacts merge keep-id merge-id --json`,
|
|
154
|
+
)
|
|
155
|
+
.action(
|
|
156
|
+
async (keepId: string, mergeId: string, _opts: unknown, cmd: Command) => {
|
|
157
|
+
try {
|
|
158
|
+
initializeDb();
|
|
159
|
+
const contact = mergeContacts(keepId, mergeId);
|
|
160
|
+
writeOutput(cmd, { ok: true, contact });
|
|
161
|
+
} catch (err) {
|
|
162
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const invites = contacts
|
|
170
|
+
.command("invites")
|
|
171
|
+
.description("Manage contact invites");
|
|
172
|
+
|
|
173
|
+
invites.addHelpText(
|
|
174
|
+
"after",
|
|
175
|
+
`
|
|
176
|
+
Invites are tokens that grant channel access when redeemed. Each invite is
|
|
177
|
+
tied to a source channel (telegram, voice, sms, email, whatsapp) and can
|
|
178
|
+
optionally have usage limits, expiration, and notes. When redeemed, the
|
|
179
|
+
invite creates a channel membership linking a contact to an external
|
|
180
|
+
identifier on the source channel.
|
|
181
|
+
|
|
182
|
+
Examples:
|
|
183
|
+
$ vellum contacts invites list
|
|
184
|
+
$ vellum contacts invites create --source-channel telegram
|
|
185
|
+
$ vellum contacts invites revoke abc-123
|
|
186
|
+
$ vellum contacts invites redeem --token xyz-789 --source-channel telegram --external-user-id 12345`,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
invites
|
|
190
|
+
.command("list", { isDefault: true })
|
|
191
|
+
.description("List invites")
|
|
192
|
+
.option("--source-channel <sourceChannel>", "Filter by source channel")
|
|
193
|
+
.option("--status <status>", "Filter by invite status")
|
|
194
|
+
.addHelpText(
|
|
195
|
+
"after",
|
|
196
|
+
`
|
|
197
|
+
Lists all invites with optional filtering by source channel or status.
|
|
198
|
+
Returns invite tokens, their source channels, usage counts, and expiration.
|
|
199
|
+
|
|
200
|
+
Examples:
|
|
201
|
+
$ vellum contacts invites list
|
|
202
|
+
$ vellum contacts invites list --source-channel telegram
|
|
203
|
+
$ vellum contacts invites list --status active
|
|
204
|
+
$ vellum contacts invites list --source-channel voice --json`,
|
|
205
|
+
)
|
|
206
|
+
.action(
|
|
207
|
+
async (
|
|
208
|
+
opts: { sourceChannel?: string; status?: string },
|
|
209
|
+
cmd: Command,
|
|
210
|
+
) => {
|
|
211
|
+
try {
|
|
212
|
+
initializeDb();
|
|
213
|
+
const result = listIngressInvites({
|
|
214
|
+
sourceChannel: opts.sourceChannel,
|
|
215
|
+
status: opts.status,
|
|
216
|
+
});
|
|
217
|
+
if (result.ok) {
|
|
218
|
+
writeOutput(cmd, { ok: true, invites: result.data });
|
|
219
|
+
} else {
|
|
220
|
+
writeOutput(cmd, result);
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
224
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
225
|
+
process.exitCode = 1;
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
invites
|
|
231
|
+
.command("create")
|
|
232
|
+
.description("Create a new invite")
|
|
233
|
+
.requiredOption(
|
|
234
|
+
"--source-channel <channel>",
|
|
235
|
+
"Source channel (e.g. telegram, voice, sms, email, whatsapp)",
|
|
236
|
+
)
|
|
237
|
+
.option("--note <note>", "Optional note")
|
|
238
|
+
.option("--max-uses <n>", "Max redemptions")
|
|
239
|
+
.option("--expires-in-ms <ms>", "Expiry duration in milliseconds")
|
|
240
|
+
.option(
|
|
241
|
+
"--contact-name <name>",
|
|
242
|
+
"Contact name for personalizing instructions",
|
|
243
|
+
)
|
|
244
|
+
.option(
|
|
245
|
+
"--expected-external-user-id <id>",
|
|
246
|
+
"E.164 phone number (required for voice invites)",
|
|
247
|
+
)
|
|
248
|
+
.option("--friend-name <name>", "Friend name (required for voice invites)")
|
|
249
|
+
.option(
|
|
250
|
+
"--guardian-name <name>",
|
|
251
|
+
"Guardian name (required for voice invites)",
|
|
252
|
+
)
|
|
253
|
+
.addHelpText(
|
|
254
|
+
"after",
|
|
255
|
+
`
|
|
256
|
+
Creates a new invite token for the specified source channel. The --source-channel
|
|
257
|
+
flag is required and must be one of: telegram, voice, sms, email, whatsapp.
|
|
258
|
+
|
|
259
|
+
Optional fields:
|
|
260
|
+
--note Free-text note attached to the invite
|
|
261
|
+
--max-uses Maximum number of times the invite can be redeemed
|
|
262
|
+
--expires-in-ms Expiry duration in milliseconds from creation
|
|
263
|
+
--contact-name Name used to personalize invite instructions
|
|
264
|
+
|
|
265
|
+
Voice invites require three additional fields:
|
|
266
|
+
--expected-external-user-id E.164 phone number of the expected caller (e.g. +15551234567)
|
|
267
|
+
--friend-name Name the contact uses for the assistant's owner
|
|
268
|
+
--guardian-name Name of the guardian associated with this invite
|
|
269
|
+
|
|
270
|
+
Examples:
|
|
271
|
+
$ vellum contacts invites create --source-channel telegram --note "For Alice" --max-uses 1
|
|
272
|
+
$ vellum contacts invites create --source-channel voice --expected-external-user-id "+15551234567" --friend-name "Alice" --guardian-name "Bob" --contact-name "Alice Smith"`,
|
|
273
|
+
)
|
|
274
|
+
.action(
|
|
275
|
+
async (
|
|
276
|
+
opts: {
|
|
277
|
+
sourceChannel: string;
|
|
278
|
+
note?: string;
|
|
279
|
+
maxUses?: string;
|
|
280
|
+
expiresInMs?: string;
|
|
281
|
+
contactName?: string;
|
|
282
|
+
expectedExternalUserId?: string;
|
|
283
|
+
friendName?: string;
|
|
284
|
+
guardianName?: string;
|
|
285
|
+
},
|
|
286
|
+
cmd: Command,
|
|
287
|
+
) => {
|
|
288
|
+
try {
|
|
289
|
+
const maxUses = opts.maxUses ? Number(opts.maxUses) : undefined;
|
|
290
|
+
if (maxUses !== undefined && !Number.isFinite(maxUses)) {
|
|
291
|
+
writeOutput(cmd, {
|
|
292
|
+
ok: false,
|
|
293
|
+
error: `--max-uses must be a number, got: ${opts.maxUses}`,
|
|
294
|
+
});
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const expiresInMs = opts.expiresInMs
|
|
299
|
+
? Number(opts.expiresInMs)
|
|
300
|
+
: undefined;
|
|
301
|
+
if (expiresInMs !== undefined && !Number.isFinite(expiresInMs)) {
|
|
302
|
+
writeOutput(cmd, {
|
|
303
|
+
ok: false,
|
|
304
|
+
error: `--expires-in-ms must be a number, got: ${opts.expiresInMs}`,
|
|
305
|
+
});
|
|
306
|
+
process.exitCode = 1;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
initializeDb();
|
|
310
|
+
const result = await createIngressInvite({
|
|
311
|
+
sourceChannel: opts.sourceChannel,
|
|
312
|
+
note: opts.note,
|
|
313
|
+
maxUses,
|
|
314
|
+
expiresInMs,
|
|
315
|
+
contactName: opts.contactName,
|
|
316
|
+
expectedExternalUserId: opts.expectedExternalUserId,
|
|
317
|
+
friendName: opts.friendName,
|
|
318
|
+
guardianName: opts.guardianName,
|
|
319
|
+
});
|
|
320
|
+
if (result.ok) {
|
|
321
|
+
writeOutput(cmd, { ok: true, invite: result.data });
|
|
322
|
+
} else {
|
|
323
|
+
writeOutput(cmd, result);
|
|
324
|
+
}
|
|
325
|
+
if (!result.ok) {
|
|
326
|
+
process.exitCode = 1;
|
|
327
|
+
}
|
|
328
|
+
} catch (err) {
|
|
329
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
330
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
331
|
+
process.exitCode = 1;
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
invites
|
|
337
|
+
.command("revoke <inviteId>")
|
|
338
|
+
.description("Revoke an active invite")
|
|
339
|
+
.addHelpText(
|
|
340
|
+
"after",
|
|
341
|
+
`
|
|
342
|
+
Arguments:
|
|
343
|
+
inviteId UUID of the invite to revoke
|
|
344
|
+
|
|
345
|
+
Revokes an active invite so it can no longer be redeemed. Already-redeemed
|
|
346
|
+
channel memberships are not affected. Returns the updated invite record.
|
|
347
|
+
|
|
348
|
+
Examples:
|
|
349
|
+
$ vellum contacts invites revoke 7a3b1c2d-4e5f-6789-abcd-ef0123456789
|
|
350
|
+
$ vellum contacts invites revoke abc-123 --json`,
|
|
351
|
+
)
|
|
352
|
+
.action(async (inviteId: string, _opts: unknown, cmd: Command) => {
|
|
353
|
+
try {
|
|
354
|
+
initializeDb();
|
|
355
|
+
const result = revokeIngressInvite(inviteId);
|
|
356
|
+
if (result.ok) {
|
|
357
|
+
writeOutput(cmd, { ok: true, invite: result.data });
|
|
358
|
+
} else {
|
|
359
|
+
writeOutput(cmd, result);
|
|
360
|
+
}
|
|
361
|
+
if (!result.ok) process.exitCode = 1;
|
|
362
|
+
} catch (err) {
|
|
363
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
364
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
365
|
+
process.exitCode = 1;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
invites
|
|
370
|
+
.command("redeem")
|
|
371
|
+
.description("Redeem an invite via token or voice code")
|
|
372
|
+
.option("--token <token>", "Invite token")
|
|
373
|
+
.option("--source-channel <channel>", "Channel for redemption")
|
|
374
|
+
.option("--external-user-id <id>", "External user ID")
|
|
375
|
+
.option("--external-chat-id <id>", "External chat ID")
|
|
376
|
+
.option("--code <code>", "6-digit voice code")
|
|
377
|
+
.option(
|
|
378
|
+
"--caller-external-user-id <phone>",
|
|
379
|
+
"E.164 phone number for voice code redemption",
|
|
380
|
+
)
|
|
381
|
+
.option("--assistant-id <id>", "Assistant ID for voice code redemption")
|
|
382
|
+
.addHelpText(
|
|
383
|
+
"after",
|
|
384
|
+
`
|
|
385
|
+
Two redemption modes:
|
|
386
|
+
|
|
387
|
+
1. Token-based redemption: Provide --token, --source-channel, and at
|
|
388
|
+
least one of --external-user-id or --external-chat-id. Creates a
|
|
389
|
+
channel membership linking the contact to the external identifier.
|
|
390
|
+
|
|
391
|
+
2. Voice-code-based redemption: Provide --code (6-digit code) and
|
|
392
|
+
--caller-external-user-id (E.164 phone number). Optionally include
|
|
393
|
+
--assistant-id to scope the redemption to a specific assistant.
|
|
394
|
+
|
|
395
|
+
Examples:
|
|
396
|
+
$ vellum contacts invites redeem --token xyz-789 --source-channel telegram --external-user-id 12345
|
|
397
|
+
$ vellum contacts invites redeem --code 123456 --caller-external-user-id "+15551234567"
|
|
398
|
+
$ vellum contacts invites redeem --code 654321 --caller-external-user-id "+15559876543" --assistant-id asst-abc --json`,
|
|
399
|
+
)
|
|
400
|
+
.action(
|
|
401
|
+
async (
|
|
402
|
+
opts: {
|
|
403
|
+
token?: string;
|
|
404
|
+
sourceChannel?: string;
|
|
405
|
+
externalUserId?: string;
|
|
406
|
+
externalChatId?: string;
|
|
407
|
+
code?: string;
|
|
408
|
+
callerExternalUserId?: string;
|
|
409
|
+
assistantId?: string;
|
|
410
|
+
},
|
|
411
|
+
cmd: Command,
|
|
412
|
+
) => {
|
|
413
|
+
try {
|
|
414
|
+
initializeDb();
|
|
415
|
+
if (opts.code) {
|
|
416
|
+
if (!opts.callerExternalUserId) {
|
|
417
|
+
writeOutput(cmd, {
|
|
418
|
+
ok: false,
|
|
419
|
+
error:
|
|
420
|
+
"--caller-external-user-id is required for voice code redemption",
|
|
421
|
+
});
|
|
422
|
+
process.exitCode = 1;
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const result = redeemVoiceInviteCode({
|
|
426
|
+
code: opts.code,
|
|
427
|
+
callerExternalUserId: opts.callerExternalUserId,
|
|
428
|
+
sourceChannel: "voice",
|
|
429
|
+
...(opts.assistantId ? { assistantId: opts.assistantId } : {}),
|
|
430
|
+
});
|
|
431
|
+
if (result.ok) {
|
|
432
|
+
writeOutput(cmd, {
|
|
433
|
+
ok: true,
|
|
434
|
+
type: result.type,
|
|
435
|
+
memberId: result.memberId,
|
|
436
|
+
...(result.type === "redeemed"
|
|
437
|
+
? { inviteId: result.inviteId }
|
|
438
|
+
: {}),
|
|
439
|
+
});
|
|
440
|
+
} else {
|
|
441
|
+
writeOutput(cmd, { ok: false, error: result.reason });
|
|
442
|
+
process.exitCode = 1;
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
const result = redeemIngressInvite({
|
|
446
|
+
token: opts.token,
|
|
447
|
+
sourceChannel: opts.sourceChannel,
|
|
448
|
+
...(opts.externalUserId
|
|
449
|
+
? { externalUserId: opts.externalUserId }
|
|
450
|
+
: {}),
|
|
451
|
+
...(opts.externalChatId
|
|
452
|
+
? { externalChatId: opts.externalChatId }
|
|
453
|
+
: {}),
|
|
454
|
+
});
|
|
455
|
+
if (result.ok) {
|
|
456
|
+
writeOutput(cmd, { ok: true, invite: result.data });
|
|
457
|
+
} else {
|
|
458
|
+
writeOutput(cmd, result);
|
|
459
|
+
}
|
|
460
|
+
if (!result.ok) {
|
|
461
|
+
process.exitCode = 1;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
} catch (err) {
|
|
465
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
466
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
467
|
+
process.exitCode = 1;
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
);
|
|
471
|
+
}
|