@vellumai/assistant 0.4.34 → 0.4.36
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 +4 -1
- 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 +91 -43
- 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 +806 -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 +491 -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} +133 -242
- 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 +177 -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 +175 -145
- 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 +12 -1
- package/src/memory/llm-usage-store.ts +186 -0
- package/src/memory/migrations/026-guardian-verification-sessions.ts +28 -9
- package/src/memory/migrations/027a-guardian-bootstrap-token.ts +16 -3
- package/src/memory/migrations/038-actor-token-records.ts +8 -1
- package/src/memory/migrations/039-actor-refresh-token-records.ts +11 -2
- package/src/memory/migrations/110-channel-guardian.ts +27 -6
- package/src/memory/migrations/112-assistant-inbox.ts +39 -15
- package/src/memory/migrations/114-notifications.ts +37 -15
- package/src/memory/migrations/117-conversation-attention.ts +33 -9
- 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/migrations/schema-introspection.ts +18 -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 +5 -9
- package/src/runtime/http-types.ts +13 -1
- 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/guardian-bootstrap-routes.ts +1 -1
- 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/surface-content-routes.ts +104 -0
- package/src/runtime/routes/twilio-routes.ts +32 -1
- package/src/runtime/routes/usage-routes.ts +114 -0
- package/src/runtime/tool-grant-request-helper.ts +2 -1
- package/src/security/encrypted-store.ts +9 -5
- package/src/security/keychain-broker-client.ts +393 -0
- package/src/security/secure-keys.ts +106 -321
- package/src/tools/apps/executors.ts +73 -0
- package/src/tools/browser/auto-navigate.ts +15 -6
- package/src/tools/browser/chrome-cdp.ts +211 -0
- package/src/tools/browser/network-recorder.test.ts +83 -0
- package/src/tools/browser/network-recorder.ts +8 -7
- package/src/tools/browser/x-auto-navigate.ts +12 -6
- package/src/tools/credentials/policy-types.ts +24 -0
- package/src/tools/credentials/vault.ts +22 -27
- package/src/tools/network/script-proxy/session-manager.ts +47 -3
- package/src/tools/permission-checker.ts +1 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +1 -2
- package/src/tools/watch/watch-state.ts +2 -0
- package/src/__tests__/key-migration.test.ts +0 -240
- package/src/__tests__/keychain.test.ts +0 -286
- package/src/cli/core-commands.ts +0 -899
- package/src/security/keychain-to-encrypted-migration.ts +0 -66
- package/src/security/keychain.ts +0 -490
package/src/cli/influencer.ts
CHANGED
|
@@ -61,6 +61,21 @@ export function registerInfluencerCommand(program: Command): void {
|
|
|
61
61
|
)
|
|
62
62
|
.option("--json", "Machine-readable JSON output");
|
|
63
63
|
|
|
64
|
+
inf.addHelpText(
|
|
65
|
+
"after",
|
|
66
|
+
`
|
|
67
|
+
Researches influencers via the Chrome extension relay, which automates
|
|
68
|
+
browsing on each platform. The user must be logged into each target
|
|
69
|
+
platform (Instagram, TikTok, X/Twitter) in Chrome for the relay to work.
|
|
70
|
+
|
|
71
|
+
Supported platforms: instagram, tiktok, twitter (X).
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
$ vellum influencer search "fitness coach" --platforms instagram,tiktok
|
|
75
|
+
$ vellum influencer profile natgeo --platform instagram
|
|
76
|
+
$ vellum influencer compare instagram:nike twitter:nike tiktok:nike`,
|
|
77
|
+
);
|
|
78
|
+
|
|
64
79
|
// =========================================================================
|
|
65
80
|
// search — search for influencers across platforms
|
|
66
81
|
// =========================================================================
|
|
@@ -88,6 +103,27 @@ export function registerInfluencerCommand(program: Command): void {
|
|
|
88
103
|
)
|
|
89
104
|
.option("--limit <n>", "Max results per platform", "10")
|
|
90
105
|
.option("--verified", "Only return verified accounts")
|
|
106
|
+
.addHelpText(
|
|
107
|
+
"after",
|
|
108
|
+
`
|
|
109
|
+
Arguments:
|
|
110
|
+
query Search query — niche, topic, or keywords (e.g. "fitness coach")
|
|
111
|
+
|
|
112
|
+
--platforms filters which platforms to search. Defaults to all three:
|
|
113
|
+
instagram, tiktok, twitter. Provide a comma-separated list to narrow.
|
|
114
|
+
|
|
115
|
+
--min-followers and --max-followers accept human-friendly notation:
|
|
116
|
+
10k = 10,000 1.5m = 1,500,000 100k = 100,000
|
|
117
|
+
Plain integers are also accepted (e.g. 50000).
|
|
118
|
+
|
|
119
|
+
--limit caps the number of results returned per platform (default: 10).
|
|
120
|
+
--verified restricts results to verified/blue-check accounts only.
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
$ vellum influencer search "vegan food" --min-followers 10k --max-followers 1m
|
|
124
|
+
$ vellum influencer search "tech reviewer" --platforms tiktok --limit 5 --verified
|
|
125
|
+
$ vellum influencer search "streetwear" --platforms instagram,twitter --min-followers 50k`,
|
|
126
|
+
)
|
|
91
127
|
.action(
|
|
92
128
|
async (
|
|
93
129
|
query: string,
|
|
@@ -147,6 +183,23 @@ export function registerInfluencerCommand(program: Command): void {
|
|
|
147
183
|
"Platform (instagram, tiktok, or twitter)",
|
|
148
184
|
"instagram",
|
|
149
185
|
)
|
|
186
|
+
.addHelpText(
|
|
187
|
+
"after",
|
|
188
|
+
`
|
|
189
|
+
Arguments:
|
|
190
|
+
username The influencer's handle without the @ prefix (e.g. "natgeo", not "@natgeo")
|
|
191
|
+
|
|
192
|
+
--platform selects which platform to look up. Defaults to instagram.
|
|
193
|
+
Valid values: instagram, tiktok, twitter.
|
|
194
|
+
|
|
195
|
+
Returns detailed profile data including follower count, bio, engagement
|
|
196
|
+
metrics, and recent post statistics.
|
|
197
|
+
|
|
198
|
+
Examples:
|
|
199
|
+
$ vellum influencer profile natgeo --platform instagram
|
|
200
|
+
$ vellum influencer profile charlidamelio --platform tiktok
|
|
201
|
+
$ vellum influencer profile elonmusk --platform twitter`,
|
|
202
|
+
)
|
|
150
203
|
.action(
|
|
151
204
|
async (username: string, opts: { platform: string }, cmd: Command) => {
|
|
152
205
|
await run(cmd, async () => {
|
|
@@ -187,6 +240,25 @@ export function registerInfluencerCommand(program: Command): void {
|
|
|
187
240
|
"<influencers...>",
|
|
188
241
|
"Space-separated list of platform:username pairs (e.g. instagram:nike twitter:nike tiktok:nike)",
|
|
189
242
|
)
|
|
243
|
+
.addHelpText(
|
|
244
|
+
"after",
|
|
245
|
+
`
|
|
246
|
+
Arguments:
|
|
247
|
+
influencers Space-separated platform:username pairs (e.g. instagram:nike twitter:nike)
|
|
248
|
+
|
|
249
|
+
Each argument must be in platform:username format. Valid platforms:
|
|
250
|
+
instagram, tiktok, twitter. If no platform prefix is provided, defaults
|
|
251
|
+
to instagram.
|
|
252
|
+
|
|
253
|
+
Returns side-by-side profile data for all specified influencers,
|
|
254
|
+
useful for comparing follower counts, engagement rates, and content
|
|
255
|
+
metrics across platforms or between competing accounts.
|
|
256
|
+
|
|
257
|
+
Examples:
|
|
258
|
+
$ vellum influencer compare instagram:nike twitter:nike tiktok:nike
|
|
259
|
+
$ vellum influencer compare instagram:natgeo instagram:discoverearth
|
|
260
|
+
$ vellum influencer compare tiktok:charlidamelio tiktok:addisonre`,
|
|
261
|
+
)
|
|
190
262
|
.action(async (influencers: string[], _opts: unknown, cmd: Command) => {
|
|
191
263
|
await run(cmd, async () => {
|
|
192
264
|
const parsed = influencers.map((inf) => {
|
package/src/cli/integrations.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
mintEdgeRelayToken,
|
|
11
11
|
} from "../runtime/auth/token-service.js";
|
|
12
12
|
|
|
13
|
-
type IngressChannel = "telegram" | "voice" | "sms";
|
|
14
13
|
type GuardianChannel = "telegram" | "voice" | "sms";
|
|
15
14
|
|
|
16
15
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
@@ -20,7 +19,7 @@ function asRecord(value: unknown): Record<string, unknown> {
|
|
|
20
19
|
return value as Record<string, unknown>;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
function shouldOutputJson(cmd: Command): boolean {
|
|
22
|
+
export function shouldOutputJson(cmd: Command): boolean {
|
|
24
23
|
let current: Command | null = cmd;
|
|
25
24
|
while (current) {
|
|
26
25
|
if ((current.opts() as { json?: boolean }).json) return true;
|
|
@@ -29,7 +28,7 @@ function shouldOutputJson(cmd: Command): boolean {
|
|
|
29
28
|
return false;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
function writeOutput(cmd: Command, payload: unknown): void {
|
|
31
|
+
export function writeOutput(cmd: Command, payload: unknown): void {
|
|
33
32
|
const compact = shouldOutputJson(cmd);
|
|
34
33
|
process.stdout.write(
|
|
35
34
|
compact
|
|
@@ -49,7 +48,9 @@ function getGatewayToken(): string {
|
|
|
49
48
|
return mintEdgeRelayToken();
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
function toQueryString(
|
|
51
|
+
export function toQueryString(
|
|
52
|
+
params: Record<string, string | undefined>,
|
|
53
|
+
): string {
|
|
53
54
|
const query = new URLSearchParams();
|
|
54
55
|
for (const [key, value] of Object.entries(params)) {
|
|
55
56
|
if (value) query.set(key, value);
|
|
@@ -107,7 +108,7 @@ function readVoiceConfig(): {
|
|
|
107
108
|
// CLI-specific gateway helper — uses GATEWAY_AUTH_TOKEN env var for out-of-process
|
|
108
109
|
// access. See runtime/gateway-internal-client.ts for daemon-internal usage which
|
|
109
110
|
// mints fresh tokens.
|
|
110
|
-
async function gatewayGet(path: string): Promise<unknown> {
|
|
111
|
+
export async function gatewayGet(path: string): Promise<unknown> {
|
|
111
112
|
const gatewayBase = getGatewayInternalBaseUrl();
|
|
112
113
|
const token = getGatewayToken();
|
|
113
114
|
|
|
@@ -141,7 +142,46 @@ async function gatewayGet(path: string): Promise<unknown> {
|
|
|
141
142
|
return parsed;
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
async function
|
|
145
|
+
export async function gatewayPost(
|
|
146
|
+
path: string,
|
|
147
|
+
body: unknown,
|
|
148
|
+
): Promise<unknown> {
|
|
149
|
+
const gatewayBase = getGatewayInternalBaseUrl();
|
|
150
|
+
const token = getGatewayToken();
|
|
151
|
+
|
|
152
|
+
const response = await fetch(`${gatewayBase}${path}`, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: {
|
|
155
|
+
"Content-Type": "application/json",
|
|
156
|
+
Accept: "application/json",
|
|
157
|
+
Authorization: `Bearer ${token}`,
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify(body),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const rawBody = await response.text();
|
|
163
|
+
let parsed: unknown = { ok: false, error: rawBody };
|
|
164
|
+
|
|
165
|
+
if (rawBody.length > 0) {
|
|
166
|
+
try {
|
|
167
|
+
parsed = JSON.parse(rawBody) as unknown;
|
|
168
|
+
} catch {
|
|
169
|
+
parsed = { ok: false, error: rawBody };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
const message =
|
|
175
|
+
typeof parsed === "object" && parsed && "error" in parsed
|
|
176
|
+
? String((parsed as { error?: unknown }).error)
|
|
177
|
+
: `Gateway request failed (${response.status})`;
|
|
178
|
+
throw new Error(`${message} [${response.status}]`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return parsed;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function runRead(
|
|
145
185
|
cmd: Command,
|
|
146
186
|
reader: () => Promise<unknown>,
|
|
147
187
|
): Promise<void> {
|
|
@@ -155,70 +195,64 @@ async function runRead(
|
|
|
155
195
|
}
|
|
156
196
|
}
|
|
157
197
|
|
|
158
|
-
export function registerContactsCommand(program: Command): void {
|
|
159
|
-
const contacts = program
|
|
160
|
-
.command("contacts")
|
|
161
|
-
.description("Manage and query the contact graph")
|
|
162
|
-
.option("--json", "Machine-readable compact JSON output");
|
|
163
|
-
|
|
164
|
-
contacts
|
|
165
|
-
.command("list")
|
|
166
|
-
.description("List contacts (calls /v1/contacts)")
|
|
167
|
-
.option("--role <role>", "Filter by role (default: contact)", "contact")
|
|
168
|
-
.option("--limit <limit>", "Maximum number of contacts to return")
|
|
169
|
-
.option("--query <query>", "Search query to filter contacts")
|
|
170
|
-
.action(
|
|
171
|
-
async (
|
|
172
|
-
opts: {
|
|
173
|
-
role?: string;
|
|
174
|
-
limit?: string;
|
|
175
|
-
query?: string;
|
|
176
|
-
},
|
|
177
|
-
cmd: Command,
|
|
178
|
-
) => {
|
|
179
|
-
const query = toQueryString({
|
|
180
|
-
role: opts.role,
|
|
181
|
-
limit: opts.limit,
|
|
182
|
-
query: opts.query,
|
|
183
|
-
});
|
|
184
|
-
await runRead(cmd, async () => gatewayGet(`/v1/contacts${query}`));
|
|
185
|
-
},
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
contacts
|
|
189
|
-
.command("invites")
|
|
190
|
-
.description("List contact invites")
|
|
191
|
-
.option("--source-channel <sourceChannel>", "Filter by source channel")
|
|
192
|
-
.option("--status <status>", "Filter by invite status")
|
|
193
|
-
.action(
|
|
194
|
-
async (
|
|
195
|
-
opts: { sourceChannel?: IngressChannel; status?: string },
|
|
196
|
-
cmd: Command,
|
|
197
|
-
) => {
|
|
198
|
-
const query = toQueryString({
|
|
199
|
-
sourceChannel: opts.sourceChannel,
|
|
200
|
-
status: opts.status,
|
|
201
|
-
});
|
|
202
|
-
await runRead(cmd, async () =>
|
|
203
|
-
gatewayGet(`/v1/contacts/invites${query}`),
|
|
204
|
-
);
|
|
205
|
-
},
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
198
|
export function registerIntegrationsCommand(program: Command): void {
|
|
210
199
|
const integrations = program
|
|
211
200
|
.command("integrations")
|
|
212
201
|
.description("Read integration and ingress status through the gateway API")
|
|
213
202
|
.option("--json", "Machine-readable compact JSON output");
|
|
214
203
|
|
|
204
|
+
integrations.addHelpText(
|
|
205
|
+
"after",
|
|
206
|
+
`
|
|
207
|
+
Reads integration configuration and status through the gateway API. The
|
|
208
|
+
assistant must be running for most subcommands (telegram, twilio, guardian)
|
|
209
|
+
since they query the gateway. Exceptions: "ingress config" and "voice config"
|
|
210
|
+
read from the local config file and do not require the gateway.
|
|
211
|
+
|
|
212
|
+
Integration categories:
|
|
213
|
+
telegram Telegram bot configuration and webhook status
|
|
214
|
+
twilio Twilio credentials, phone numbers, and SMS compliance
|
|
215
|
+
guardian Guardian trust verification system for contacts
|
|
216
|
+
ingress Public ingress URL and local gateway target (config-only)
|
|
217
|
+
voice Voice/call readiness and ElevenLabs voice ID (config-only)
|
|
218
|
+
|
|
219
|
+
Examples:
|
|
220
|
+
$ vellum integrations telegram config
|
|
221
|
+
$ vellum integrations twilio numbers
|
|
222
|
+
$ vellum integrations guardian status --channel sms`,
|
|
223
|
+
);
|
|
224
|
+
|
|
215
225
|
const telegram = integrations
|
|
216
226
|
.command("telegram")
|
|
217
227
|
.description("Telegram integration status");
|
|
218
228
|
|
|
229
|
+
telegram.addHelpText(
|
|
230
|
+
"after",
|
|
231
|
+
`
|
|
232
|
+
Checks the Telegram bot configuration status through the gateway API.
|
|
233
|
+
Requires the assistant to be running.
|
|
234
|
+
|
|
235
|
+
Examples:
|
|
236
|
+
$ vellum integrations telegram config
|
|
237
|
+
$ vellum integrations telegram config --json`,
|
|
238
|
+
);
|
|
239
|
+
|
|
219
240
|
telegram
|
|
220
241
|
.command("config")
|
|
221
242
|
.description("Get Telegram integration configuration status")
|
|
243
|
+
.addHelpText(
|
|
244
|
+
"after",
|
|
245
|
+
`
|
|
246
|
+
Returns the Telegram bot token status, webhook URL, and bot username from
|
|
247
|
+
the gateway. Requires the assistant to be running.
|
|
248
|
+
|
|
249
|
+
The response includes whether a bot token is configured, the current webhook
|
|
250
|
+
endpoint, and the bot's Telegram username.
|
|
251
|
+
|
|
252
|
+
Examples:
|
|
253
|
+
$ vellum integrations telegram config
|
|
254
|
+
$ vellum integrations telegram config --json`,
|
|
255
|
+
)
|
|
222
256
|
.action(async (_opts: unknown, cmd: Command) => {
|
|
223
257
|
await runRead(cmd, async () =>
|
|
224
258
|
gatewayGet("/v1/integrations/telegram/config"),
|
|
@@ -229,10 +263,38 @@ export function registerIntegrationsCommand(program: Command): void {
|
|
|
229
263
|
.command("guardian")
|
|
230
264
|
.description("Guardian verification status");
|
|
231
265
|
|
|
266
|
+
guardian.addHelpText(
|
|
267
|
+
"after",
|
|
268
|
+
`
|
|
269
|
+
Guardian is the trust verification system for contacts. It tracks whether
|
|
270
|
+
contacts on each channel have completed identity verification. Requires
|
|
271
|
+
the assistant to be running.
|
|
272
|
+
|
|
273
|
+
Examples:
|
|
274
|
+
$ vellum integrations guardian status
|
|
275
|
+
$ vellum integrations guardian status --channel voice`,
|
|
276
|
+
);
|
|
277
|
+
|
|
232
278
|
guardian
|
|
233
279
|
.command("status")
|
|
234
280
|
.description("Get guardian status for a channel")
|
|
235
281
|
.option("--channel <channel>", "Channel: telegram|voice|sms", "telegram")
|
|
282
|
+
.addHelpText(
|
|
283
|
+
"after",
|
|
284
|
+
`
|
|
285
|
+
Returns the guardian verification state for the specified channel. Requires
|
|
286
|
+
the assistant to be running.
|
|
287
|
+
|
|
288
|
+
The --channel flag accepts: telegram, voice, sms. Defaults to telegram if
|
|
289
|
+
not specified. The response includes whether guardian verification is active
|
|
290
|
+
and the current verification state for that channel.
|
|
291
|
+
|
|
292
|
+
Examples:
|
|
293
|
+
$ vellum integrations guardian status
|
|
294
|
+
$ vellum integrations guardian status --channel telegram
|
|
295
|
+
$ vellum integrations guardian status --channel voice
|
|
296
|
+
$ vellum integrations guardian status --channel sms --json`,
|
|
297
|
+
)
|
|
236
298
|
.action(async (opts: { channel?: GuardianChannel }, cmd: Command) => {
|
|
237
299
|
const channel = opts.channel ?? "telegram";
|
|
238
300
|
await runRead(cmd, async () =>
|
|
@@ -246,9 +308,40 @@ export function registerIntegrationsCommand(program: Command): void {
|
|
|
246
308
|
.command("twilio")
|
|
247
309
|
.description("Twilio integration status");
|
|
248
310
|
|
|
311
|
+
twilio.addHelpText(
|
|
312
|
+
"after",
|
|
313
|
+
`
|
|
314
|
+
Covers Twilio credential status, phone number management, and SMS regulatory
|
|
315
|
+
compliance. All subcommands require the assistant to be running since they
|
|
316
|
+
query the gateway API.
|
|
317
|
+
|
|
318
|
+
Subcommands:
|
|
319
|
+
config Check Twilio credential status and phone number configuration
|
|
320
|
+
numbers List all Twilio incoming phone numbers
|
|
321
|
+
sms compliance Check SMS regulatory compliance status
|
|
322
|
+
|
|
323
|
+
Examples:
|
|
324
|
+
$ vellum integrations twilio config
|
|
325
|
+
$ vellum integrations twilio numbers
|
|
326
|
+
$ vellum integrations twilio sms compliance`,
|
|
327
|
+
);
|
|
328
|
+
|
|
249
329
|
twilio
|
|
250
330
|
.command("config")
|
|
251
331
|
.description("Get Twilio credential and phone number status")
|
|
332
|
+
.addHelpText(
|
|
333
|
+
"after",
|
|
334
|
+
`
|
|
335
|
+
Checks the Twilio credential status and phone number configuration through
|
|
336
|
+
the gateway. Requires the assistant to be running.
|
|
337
|
+
|
|
338
|
+
The response includes whether the Twilio account SID and auth token are
|
|
339
|
+
configured, and the currently assigned phone number.
|
|
340
|
+
|
|
341
|
+
Examples:
|
|
342
|
+
$ vellum integrations twilio config
|
|
343
|
+
$ vellum integrations twilio config --json`,
|
|
344
|
+
)
|
|
252
345
|
.action(async (_opts: unknown, cmd: Command) => {
|
|
253
346
|
await runRead(cmd, async () =>
|
|
254
347
|
gatewayGet("/v1/integrations/twilio/config"),
|
|
@@ -258,6 +351,19 @@ export function registerIntegrationsCommand(program: Command): void {
|
|
|
258
351
|
twilio
|
|
259
352
|
.command("numbers")
|
|
260
353
|
.description("List Twilio incoming phone numbers")
|
|
354
|
+
.addHelpText(
|
|
355
|
+
"after",
|
|
356
|
+
`
|
|
357
|
+
Lists all incoming phone numbers associated with the configured Twilio
|
|
358
|
+
account. Requires the assistant to be running.
|
|
359
|
+
|
|
360
|
+
Returns an array of phone number objects with their SID, phone number,
|
|
361
|
+
friendly name, and capabilities.
|
|
362
|
+
|
|
363
|
+
Examples:
|
|
364
|
+
$ vellum integrations twilio numbers
|
|
365
|
+
$ vellum integrations twilio numbers --json`,
|
|
366
|
+
)
|
|
261
367
|
.action(async (_opts: unknown, cmd: Command) => {
|
|
262
368
|
await runRead(cmd, async () =>
|
|
263
369
|
gatewayGet("/v1/integrations/twilio/numbers"),
|
|
@@ -266,9 +372,36 @@ export function registerIntegrationsCommand(program: Command): void {
|
|
|
266
372
|
|
|
267
373
|
const twilioSms = twilio.command("sms").description("Twilio SMS status");
|
|
268
374
|
|
|
375
|
+
twilioSms.addHelpText(
|
|
376
|
+
"after",
|
|
377
|
+
`
|
|
378
|
+
Covers SMS regulatory compliance for the configured Twilio account. All
|
|
379
|
+
subcommands require the assistant to be running since they query the gateway API.
|
|
380
|
+
|
|
381
|
+
Subcommands:
|
|
382
|
+
compliance Check SMS regulatory compliance status
|
|
383
|
+
|
|
384
|
+
Examples:
|
|
385
|
+
$ vellum integrations twilio sms compliance
|
|
386
|
+
$ vellum integrations twilio sms compliance --json`,
|
|
387
|
+
);
|
|
388
|
+
|
|
269
389
|
twilioSms
|
|
270
390
|
.command("compliance")
|
|
271
391
|
.description("Get Twilio SMS compliance status")
|
|
392
|
+
.addHelpText(
|
|
393
|
+
"after",
|
|
394
|
+
`
|
|
395
|
+
Checks the SMS regulatory compliance status for the configured Twilio
|
|
396
|
+
account. Requires the assistant to be running.
|
|
397
|
+
|
|
398
|
+
Returns the current compliance state, including whether the account is
|
|
399
|
+
approved for SMS messaging and any outstanding compliance requirements.
|
|
400
|
+
|
|
401
|
+
Examples:
|
|
402
|
+
$ vellum integrations twilio sms compliance
|
|
403
|
+
$ vellum integrations twilio sms compliance --json`,
|
|
404
|
+
)
|
|
272
405
|
.action(async (_opts: unknown, cmd: Command) => {
|
|
273
406
|
await runRead(cmd, async () =>
|
|
274
407
|
gatewayGet("/v1/integrations/twilio/sms/compliance"),
|
|
@@ -278,6 +411,16 @@ export function registerIntegrationsCommand(program: Command): void {
|
|
|
278
411
|
twilio
|
|
279
412
|
.command("sms-compliance")
|
|
280
413
|
.description('Alias for "vellum integrations twilio sms compliance"')
|
|
414
|
+
.addHelpText(
|
|
415
|
+
"after",
|
|
416
|
+
`
|
|
417
|
+
Shortcut alias for "vellum integrations twilio sms compliance". Prefer
|
|
418
|
+
the canonical path for scripts and documentation.
|
|
419
|
+
|
|
420
|
+
Examples:
|
|
421
|
+
$ vellum integrations twilio sms-compliance
|
|
422
|
+
$ vellum integrations twilio sms compliance # canonical form`,
|
|
423
|
+
)
|
|
281
424
|
.action(async (_opts: unknown, cmd: Command) => {
|
|
282
425
|
await runRead(cmd, async () =>
|
|
283
426
|
gatewayGet("/v1/integrations/twilio/sms/compliance"),
|
|
@@ -288,18 +431,66 @@ export function registerIntegrationsCommand(program: Command): void {
|
|
|
288
431
|
.command("ingress")
|
|
289
432
|
.description("Trusted contact membership and invite status");
|
|
290
433
|
|
|
434
|
+
ingress.addHelpText(
|
|
435
|
+
"after",
|
|
436
|
+
`
|
|
437
|
+
Shows the public ingress URL and local gateway target URL. Reads from the
|
|
438
|
+
local config file and does not require the gateway to be running.
|
|
439
|
+
|
|
440
|
+
Examples:
|
|
441
|
+
$ vellum integrations ingress config`,
|
|
442
|
+
);
|
|
443
|
+
|
|
291
444
|
ingress
|
|
292
445
|
.command("config")
|
|
293
446
|
.description("Get public ingress URL and local gateway target")
|
|
447
|
+
.addHelpText(
|
|
448
|
+
"after",
|
|
449
|
+
`
|
|
450
|
+
Shows the public ingress URL and the local gateway target URL. Reads from
|
|
451
|
+
the local config file and does not require the gateway to be running.
|
|
452
|
+
|
|
453
|
+
The response includes whether ingress is enabled, the configured public base
|
|
454
|
+
URL (if any), and the local gateway target address. Ingress is considered
|
|
455
|
+
enabled if explicitly set to true or if a publicBaseUrl is configured.
|
|
456
|
+
|
|
457
|
+
Examples:
|
|
458
|
+
$ vellum integrations ingress config
|
|
459
|
+
$ vellum integrations ingress config --json`,
|
|
460
|
+
)
|
|
294
461
|
.action(async (_opts: unknown, cmd: Command) => {
|
|
295
462
|
await runRead(cmd, async () => readIngressConfig());
|
|
296
463
|
});
|
|
297
464
|
|
|
298
465
|
const voice = integrations.command("voice").description("Voice setup status");
|
|
299
466
|
|
|
467
|
+
voice.addHelpText(
|
|
468
|
+
"after",
|
|
469
|
+
`
|
|
470
|
+
Shows voice and call readiness configuration. Reads from the local config
|
|
471
|
+
file and does not require the gateway to be running.
|
|
472
|
+
|
|
473
|
+
Examples:
|
|
474
|
+
$ vellum integrations voice config`,
|
|
475
|
+
);
|
|
476
|
+
|
|
300
477
|
voice
|
|
301
478
|
.command("config")
|
|
302
479
|
.description("Get voice and call readiness config")
|
|
480
|
+
.addHelpText(
|
|
481
|
+
"after",
|
|
482
|
+
`
|
|
483
|
+
Shows voice and call readiness status. Reads from the local config file and
|
|
484
|
+
does not require the gateway to be running.
|
|
485
|
+
|
|
486
|
+
The response includes whether calls are enabled, the active ElevenLabs voice
|
|
487
|
+
ID (falls back to default if not configured), whether a custom voice ID is
|
|
488
|
+
set, and whether the default voice is in use.
|
|
489
|
+
|
|
490
|
+
Examples:
|
|
491
|
+
$ vellum integrations voice config
|
|
492
|
+
$ vellum integrations voice config --json`,
|
|
493
|
+
)
|
|
303
494
|
.action(async (_opts: unknown, cmd: Command) => {
|
|
304
495
|
await runRead(cmd, async () => readVoiceConfig());
|
|
305
496
|
});
|
package/src/cli/keys.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import { API_KEY_PROVIDERS } from "../config/loader.js";
|
|
4
|
+
import {
|
|
5
|
+
deleteSecureKey,
|
|
6
|
+
getSecureKey,
|
|
7
|
+
setSecureKey,
|
|
8
|
+
} from "../security/secure-keys.js";
|
|
9
|
+
import { getCliLogger } from "../util/logger.js";
|
|
10
|
+
|
|
11
|
+
const log = getCliLogger("cli");
|
|
12
|
+
|
|
13
|
+
export function registerKeysCommand(program: Command): void {
|
|
14
|
+
const keys = program
|
|
15
|
+
.command("keys")
|
|
16
|
+
.description("Manage API keys in secure storage");
|
|
17
|
+
|
|
18
|
+
keys.addHelpText(
|
|
19
|
+
"after",
|
|
20
|
+
`
|
|
21
|
+
Keys are stored in secure local storage and are never written to disk in
|
|
22
|
+
plaintext. Each key is identified by provider name.
|
|
23
|
+
|
|
24
|
+
Known providers: ${API_KEY_PROVIDERS.join(", ")}
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
$ vellum keys list
|
|
28
|
+
$ vellum keys set anthropic sk-ant-...
|
|
29
|
+
$ vellum keys delete openai`,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
keys
|
|
33
|
+
.command("list")
|
|
34
|
+
.description("List all stored API key names")
|
|
35
|
+
.addHelpText(
|
|
36
|
+
"after",
|
|
37
|
+
`
|
|
38
|
+
Checks each known provider (${API_KEY_PROVIDERS.join(", ")}) and prints the
|
|
39
|
+
names of providers that have a stored key. Providers without a stored key are
|
|
40
|
+
omitted from the output.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
$ vellum keys list`,
|
|
44
|
+
)
|
|
45
|
+
.action(() => {
|
|
46
|
+
const stored: string[] = [];
|
|
47
|
+
for (const provider of API_KEY_PROVIDERS) {
|
|
48
|
+
const value = getSecureKey(provider);
|
|
49
|
+
if (value) stored.push(provider);
|
|
50
|
+
}
|
|
51
|
+
if (stored.length === 0) {
|
|
52
|
+
log.info("No API keys stored");
|
|
53
|
+
} else {
|
|
54
|
+
for (const name of stored) {
|
|
55
|
+
log.info(` ${name}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
keys
|
|
61
|
+
.command("set <provider> <key>")
|
|
62
|
+
.description("Store an API key (e.g. vellum keys set anthropic sk-ant-...)")
|
|
63
|
+
.addHelpText(
|
|
64
|
+
"after",
|
|
65
|
+
`
|
|
66
|
+
Arguments:
|
|
67
|
+
provider Provider name (e.g. anthropic, openai, gemini)
|
|
68
|
+
key The API key value to store
|
|
69
|
+
|
|
70
|
+
If a key already exists for the given provider, it is silently overwritten.
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
$ vellum keys set anthropic sk-ant-abc123
|
|
74
|
+
$ vellum keys set openai sk-proj-xyz789
|
|
75
|
+
$ vellum keys set fireworks fw-abc123`,
|
|
76
|
+
)
|
|
77
|
+
.action((provider: string, key: string) => {
|
|
78
|
+
if (setSecureKey(provider, key)) {
|
|
79
|
+
log.info(`Stored API key for "${provider}"`);
|
|
80
|
+
} else {
|
|
81
|
+
log.error(`Failed to store API key for "${provider}"`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
keys
|
|
87
|
+
.command("delete <provider>")
|
|
88
|
+
.description("Delete a stored API key")
|
|
89
|
+
.addHelpText(
|
|
90
|
+
"after",
|
|
91
|
+
`
|
|
92
|
+
Arguments:
|
|
93
|
+
provider Provider name whose key should be removed from secure storage
|
|
94
|
+
|
|
95
|
+
Removes the API key for the given provider from secure local storage. If
|
|
96
|
+
no key exists for the provider, exits with an error.
|
|
97
|
+
|
|
98
|
+
Examples:
|
|
99
|
+
$ vellum keys delete openai
|
|
100
|
+
$ vellum keys delete anthropic`,
|
|
101
|
+
)
|
|
102
|
+
.action((provider: string) => {
|
|
103
|
+
const result = deleteSecureKey(provider);
|
|
104
|
+
if (result === "deleted") {
|
|
105
|
+
log.info(`Deleted API key for "${provider}"`);
|
|
106
|
+
} else if (result === "error") {
|
|
107
|
+
log.error(`Failed to delete API key for "${provider}": storage error`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
} else {
|
|
110
|
+
log.error(`No API key found for "${provider}"`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|