@vellumai/assistant 0.4.48 → 0.4.49
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/ARCHITECTURE.md +2 -2
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +15 -6
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-metadata-store.test.ts +64 -73
- package/src/__tests__/credential-security-invariants.test.ts +13 -7
- package/src/__tests__/credential-vault-unit.test.ts +280 -49
- package/src/__tests__/credential-vault.test.ts +138 -16
- package/src/__tests__/credentials-cli.test.ts +71 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +32 -51
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +1 -1
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-channel-config.test.ts +67 -3
- package/src/__tests__/slack-share-routes.test.ts +17 -19
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +0 -16
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +1 -6
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +2 -7
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +18 -12
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +101 -15
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/loader.ts +0 -6
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +49 -46
- package/src/daemon/handlers/config-telegram.ts +32 -16
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +36 -68
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +2 -9
- package/src/daemon/session.ts +138 -15
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/logfire.ts +16 -5
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +4 -4
- package/src/messaging/providers/gmail/client.ts +82 -2
- package/src/messaging/providers/gmail/people-client.ts +10 -10
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
- package/src/messaging/providers/whatsapp/adapter.ts +11 -8
- package/src/messaging/registry.ts +2 -32
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +126 -25
- package/src/oauth/byo-connection.ts +22 -6
- package/src/oauth/connect-orchestrator.ts +113 -57
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +35 -11
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +29 -0
- package/src/oauth/platform-connection.ts +6 -5
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +28 -40
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/retry.ts +1 -27
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/http-server.ts +8 -6
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -7
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/settings-routes.ts +55 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -9
- package/src/security/credential-key.ts +0 -156
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +119 -46
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +17 -121
- package/src/tools/credentials/vault.ts +94 -167
- package/src/tools/registry.ts +2 -7
- package/src/tools/skills/load.ts +62 -3
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/watcher/providers/google-calendar.ts +2 -1
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1026
- package/src/daemon/ride-shotgun-handler.ts +0 -569
- package/src/oauth/provider-base-urls.ts +0 -21
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
- package/src/runtime/telegram-streaming-delivery.ts +0 -393
- package/src/tools/computer-use/request-computer-control.ts +0 -56
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
disconnectOAuthProvider,
|
|
5
|
+
getConnection,
|
|
6
|
+
getConnectionByProvider,
|
|
7
|
+
listConnections,
|
|
8
|
+
} from "../../../oauth/oauth-store.js";
|
|
9
|
+
import { credentialKey } from "../../../security/credential-key.js";
|
|
10
|
+
import { deleteSecureKeyAsync } from "../../../security/secure-keys.js";
|
|
11
|
+
import { withValidToken } from "../../../security/token-manager.js";
|
|
12
|
+
import {
|
|
13
|
+
assertMetadataWritable,
|
|
14
|
+
deleteCredentialMetadata,
|
|
15
|
+
} from "../../../tools/credentials/metadata-store.js";
|
|
16
|
+
import { getCliLogger } from "../../logger.js";
|
|
17
|
+
import { shouldOutputJson, writeOutput } from "../../output.js";
|
|
18
|
+
|
|
19
|
+
const log = getCliLogger("cli");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Keys that may contain secrets in an OAuth token endpoint response.
|
|
23
|
+
* These are stripped from the `metadata` field before CLI output to prevent
|
|
24
|
+
* token leakage via shell history, logs, or agent transcript capture.
|
|
25
|
+
*/
|
|
26
|
+
const REDACTED_METADATA_KEYS = new Set([
|
|
27
|
+
"access_token",
|
|
28
|
+
"refresh_token",
|
|
29
|
+
"id_token",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/** Recursively strip secret-bearing keys from a parsed metadata object. */
|
|
33
|
+
function redactMetadata(obj: Record<string, unknown>): Record<string, unknown> {
|
|
34
|
+
const result: Record<string, unknown> = {};
|
|
35
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
36
|
+
if (REDACTED_METADATA_KEYS.has(key)) {
|
|
37
|
+
result[key] = "[REDACTED]";
|
|
38
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
39
|
+
result[key] = redactMetadata(value as Record<string, unknown>);
|
|
40
|
+
} else {
|
|
41
|
+
result[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Parse stored JSON string fields and convert timestamps for a connection row. */
|
|
48
|
+
function formatConnectionRow(row: ReturnType<typeof getConnection>) {
|
|
49
|
+
if (!row) return row;
|
|
50
|
+
const parsed = row.metadata ? JSON.parse(row.metadata) : null;
|
|
51
|
+
return {
|
|
52
|
+
...row,
|
|
53
|
+
grantedScopes: row.grantedScopes ? JSON.parse(row.grantedScopes) : [],
|
|
54
|
+
metadata: parsed ? redactMetadata(parsed) : null,
|
|
55
|
+
hasRefreshToken: row.hasRefreshToken === 1,
|
|
56
|
+
createdAt: new Date(row.createdAt).toISOString(),
|
|
57
|
+
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
58
|
+
expiresAt: row.expiresAt ? new Date(row.expiresAt).toISOString() : null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function registerConnectionCommands(oauth: Command): void {
|
|
63
|
+
const connections = oauth
|
|
64
|
+
.command("connections")
|
|
65
|
+
.description("Manage OAuth connections (active tokens and refresh state)");
|
|
66
|
+
|
|
67
|
+
connections.addHelpText(
|
|
68
|
+
"after",
|
|
69
|
+
`
|
|
70
|
+
Connections represent active OAuth sessions — an access token bound to a
|
|
71
|
+
provider through an app registration. Each connection tracks granted scopes,
|
|
72
|
+
token expiry, refresh token availability, account info, and status.
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
$ assistant oauth connections list
|
|
76
|
+
$ assistant oauth connections list --provider integration:gmail
|
|
77
|
+
$ assistant oauth connections get --id <uuid>
|
|
78
|
+
$ assistant oauth connections get --provider integration:gmail
|
|
79
|
+
$ assistant oauth connections token integration:twitter`,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// connections list
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
connections
|
|
87
|
+
.command("list")
|
|
88
|
+
.description("List all OAuth connections")
|
|
89
|
+
.option(
|
|
90
|
+
"--provider <key>",
|
|
91
|
+
"Filter by provider key (e.g. integration:gmail)",
|
|
92
|
+
)
|
|
93
|
+
.addHelpText(
|
|
94
|
+
"after",
|
|
95
|
+
`
|
|
96
|
+
Lists all OAuth connections, optionally filtered by provider key.
|
|
97
|
+
|
|
98
|
+
Each connection shows its ID, provider, account info, granted scopes, token
|
|
99
|
+
expiry, refresh token availability, and status.
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
$ assistant oauth connections list
|
|
103
|
+
$ assistant oauth connections list --provider integration:gmail`,
|
|
104
|
+
)
|
|
105
|
+
.action((opts: { provider?: string }, cmd: Command) => {
|
|
106
|
+
try {
|
|
107
|
+
const rows = listConnections(opts.provider).map(formatConnectionRow);
|
|
108
|
+
|
|
109
|
+
if (!shouldOutputJson(cmd)) {
|
|
110
|
+
log.info(`Found ${rows.length} connection(s)`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
writeOutput(cmd, rows);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
116
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// connections get
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
connections
|
|
126
|
+
.command("get")
|
|
127
|
+
.description("Look up an OAuth connection by ID or provider")
|
|
128
|
+
.option("--id <id>", "Connection ID (UUID)")
|
|
129
|
+
.option(
|
|
130
|
+
"--provider <key>",
|
|
131
|
+
"Provider key (returns most recent active connection)",
|
|
132
|
+
)
|
|
133
|
+
.addHelpText(
|
|
134
|
+
"after",
|
|
135
|
+
`
|
|
136
|
+
Two lookup modes are supported:
|
|
137
|
+
|
|
138
|
+
1. By connection ID:
|
|
139
|
+
$ assistant oauth connections get --id <uuid>
|
|
140
|
+
|
|
141
|
+
2. By provider (returns the most recent active connection):
|
|
142
|
+
$ assistant oauth connections get --provider integration:gmail
|
|
143
|
+
|
|
144
|
+
At least --id or --provider must be specified.`,
|
|
145
|
+
)
|
|
146
|
+
.action((opts: { id?: string; provider?: string }, cmd: Command) => {
|
|
147
|
+
try {
|
|
148
|
+
let row;
|
|
149
|
+
|
|
150
|
+
if (opts.id) {
|
|
151
|
+
row = getConnection(opts.id);
|
|
152
|
+
} else if (opts.provider) {
|
|
153
|
+
row = getConnectionByProvider(opts.provider);
|
|
154
|
+
} else {
|
|
155
|
+
writeOutput(cmd, {
|
|
156
|
+
ok: false,
|
|
157
|
+
error: "Provide --id or --provider",
|
|
158
|
+
});
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!row) {
|
|
164
|
+
writeOutput(cmd, { ok: false, error: "Connection not found" });
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
writeOutput(cmd, formatConnectionRow(row));
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
172
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// connections token <provider-key>
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
connections
|
|
182
|
+
.command("token <provider-key>")
|
|
183
|
+
.description(
|
|
184
|
+
"Print a valid OAuth access token for a provider, refreshing if expired",
|
|
185
|
+
)
|
|
186
|
+
.addHelpText(
|
|
187
|
+
"after",
|
|
188
|
+
`
|
|
189
|
+
Arguments:
|
|
190
|
+
provider-key Provider key (e.g. integration:gmail, integration:twitter)
|
|
191
|
+
|
|
192
|
+
Returns a valid OAuth access token for the given provider. If the stored token
|
|
193
|
+
is expired or near-expiry, it is refreshed automatically before being returned.
|
|
194
|
+
|
|
195
|
+
In human mode, prints the bare token to stdout (suitable for shell substitution).
|
|
196
|
+
In JSON mode (--json), prints {"ok": true, "token": "..."}.
|
|
197
|
+
|
|
198
|
+
Exits with code 1 if no access token exists or refresh fails.
|
|
199
|
+
|
|
200
|
+
Examples:
|
|
201
|
+
$ assistant oauth connections token integration:twitter
|
|
202
|
+
$ assistant oauth connections token integration:gmail --json`,
|
|
203
|
+
)
|
|
204
|
+
.action(async (providerKey: string, _opts: unknown, cmd: Command) => {
|
|
205
|
+
try {
|
|
206
|
+
const token = await withValidToken(providerKey, async (t) => t);
|
|
207
|
+
if (shouldOutputJson(cmd)) {
|
|
208
|
+
writeOutput(cmd, { ok: true, token });
|
|
209
|
+
} else {
|
|
210
|
+
process.stdout.write(token + "\n");
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
214
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
215
|
+
process.exitCode = 1;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// connections disconnect <provider-key>
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
connections
|
|
224
|
+
.command("disconnect <provider-key>")
|
|
225
|
+
.description(
|
|
226
|
+
"Disconnect an OAuth integration and remove all associated credentials",
|
|
227
|
+
)
|
|
228
|
+
.addHelpText(
|
|
229
|
+
"after",
|
|
230
|
+
`
|
|
231
|
+
Arguments:
|
|
232
|
+
provider-key The full provider key (e.g. integration:gmail, integration:slack)
|
|
233
|
+
|
|
234
|
+
Removes the OAuth connection, tokens, and any legacy credential metadata for
|
|
235
|
+
the provider. The <provider-key> argument is the full provider key as-is — it
|
|
236
|
+
is not parsed through service:field splitting.
|
|
237
|
+
|
|
238
|
+
Legacy credential keys for common fields (access_token, refresh_token,
|
|
239
|
+
client_id, client_secret) are also cleaned up if present.
|
|
240
|
+
|
|
241
|
+
Examples:
|
|
242
|
+
$ assistant oauth connections disconnect integration:gmail
|
|
243
|
+
$ assistant oauth connections disconnect integration:slack`,
|
|
244
|
+
)
|
|
245
|
+
.action(async (providerKey: string, _opts: unknown, cmd: Command) => {
|
|
246
|
+
try {
|
|
247
|
+
assertMetadataWritable();
|
|
248
|
+
|
|
249
|
+
let cleanedUp = false;
|
|
250
|
+
|
|
251
|
+
// 1. Disconnect the OAuth connection (new-format keys + connection row)
|
|
252
|
+
const oauthResult = await disconnectOAuthProvider(providerKey);
|
|
253
|
+
if (oauthResult === "error") {
|
|
254
|
+
writeOutput(cmd, {
|
|
255
|
+
ok: false,
|
|
256
|
+
error: `Failed to disconnect OAuth provider "${providerKey}" — please try again`,
|
|
257
|
+
});
|
|
258
|
+
process.exitCode = 1;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (oauthResult === "disconnected") cleanedUp = true;
|
|
262
|
+
|
|
263
|
+
// 2. Clean up legacy credential keys for common fields
|
|
264
|
+
const legacyFields = [
|
|
265
|
+
"access_token",
|
|
266
|
+
"refresh_token",
|
|
267
|
+
"client_id",
|
|
268
|
+
"client_secret",
|
|
269
|
+
];
|
|
270
|
+
for (const field of legacyFields) {
|
|
271
|
+
const key = credentialKey(providerKey, field);
|
|
272
|
+
const result = await deleteSecureKeyAsync(key);
|
|
273
|
+
if (result === "deleted") cleanedUp = true;
|
|
274
|
+
|
|
275
|
+
const metaDeleted = deleteCredentialMetadata(providerKey, field);
|
|
276
|
+
if (metaDeleted) cleanedUp = true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!cleanedUp) {
|
|
280
|
+
writeOutput(cmd, {
|
|
281
|
+
ok: false,
|
|
282
|
+
error: `No OAuth connection or credentials found for "${providerKey}"`,
|
|
283
|
+
});
|
|
284
|
+
process.exitCode = 1;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
writeOutput(cmd, { ok: true, service: providerKey });
|
|
289
|
+
|
|
290
|
+
if (!shouldOutputJson(cmd)) {
|
|
291
|
+
log.info(`Disconnected ${providerKey}`);
|
|
292
|
+
}
|
|
293
|
+
} catch (err) {
|
|
294
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
295
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
296
|
+
process.exitCode = 1;
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import { registerAppCommands } from "./apps.js";
|
|
4
|
+
import { registerConnectionCommands } from "./connections.js";
|
|
5
|
+
import { registerProviderCommands } from "./providers.js";
|
|
6
|
+
|
|
7
|
+
export function registerOAuthCommand(program: Command): void {
|
|
8
|
+
const oauth = program
|
|
9
|
+
.command("oauth")
|
|
10
|
+
.description("Manage OAuth providers, apps, connections, and tokens")
|
|
11
|
+
.option("--json", "Machine-readable compact JSON output");
|
|
12
|
+
|
|
13
|
+
oauth.addHelpText(
|
|
14
|
+
"after",
|
|
15
|
+
`
|
|
16
|
+
The oauth command group manages the full OAuth lifecycle:
|
|
17
|
+
|
|
18
|
+
providers Protocol-level configurations (auth URLs, scopes, endpoints)
|
|
19
|
+
apps Client credentials (client ID / secret pairs)
|
|
20
|
+
connections Active token grants per provider (list, get, token)
|
|
21
|
+
|
|
22
|
+
Providers are seeded on startup for built-in integrations. Apps and connections
|
|
23
|
+
are created during the OAuth authorization flow or can be managed manually via
|
|
24
|
+
their respective subcommands.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
$ assistant oauth connections token integration:twitter
|
|
28
|
+
$ assistant oauth connections list
|
|
29
|
+
$ assistant oauth connections get --provider integration:gmail
|
|
30
|
+
$ assistant oauth providers list
|
|
31
|
+
$ assistant oauth providers get integration:gmail
|
|
32
|
+
$ assistant oauth providers register --provider-key custom:myapi --auth-url https://example.com/auth --token-url https://example.com/token`,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// providers — subcommand group
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
registerProviderCommands(oauth);
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// apps — subcommand group
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
registerAppCommands(oauth);
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// connections — subcommand group (includes token)
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
registerConnectionCommands(oauth);
|
|
52
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { type Command, InvalidArgumentError } from "commander";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getProvider,
|
|
5
|
+
listProviders,
|
|
6
|
+
registerProvider,
|
|
7
|
+
} from "../../../oauth/oauth-store.js";
|
|
8
|
+
import { getCliLogger } from "../../logger.js";
|
|
9
|
+
import { shouldOutputJson, writeOutput } from "../../output.js";
|
|
10
|
+
|
|
11
|
+
const log = getCliLogger("cli");
|
|
12
|
+
|
|
13
|
+
/** Parse stored JSON string fields into their native types. */
|
|
14
|
+
function parseProviderRow(row: ReturnType<typeof getProvider>) {
|
|
15
|
+
if (!row) return row;
|
|
16
|
+
return {
|
|
17
|
+
...row,
|
|
18
|
+
defaultScopes: row.defaultScopes ? JSON.parse(row.defaultScopes) : [],
|
|
19
|
+
scopePolicy: row.scopePolicy ? JSON.parse(row.scopePolicy) : {},
|
|
20
|
+
extraParams: row.extraParams ? JSON.parse(row.extraParams) : null,
|
|
21
|
+
createdAt: new Date(row.createdAt).toISOString(),
|
|
22
|
+
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function registerProviderCommands(oauth: Command): void {
|
|
27
|
+
const providers = oauth
|
|
28
|
+
.command("providers")
|
|
29
|
+
.description(
|
|
30
|
+
"Manage OAuth provider configurations (auth URLs, scopes, endpoints)",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
providers.addHelpText(
|
|
34
|
+
"after",
|
|
35
|
+
`
|
|
36
|
+
Providers define the protocol-level configuration for an OAuth integration:
|
|
37
|
+
authorization URL, token URL, default scopes, and other endpoint details.
|
|
38
|
+
|
|
39
|
+
They are seeded on startup for built-in integrations (e.g. Gmail, Twitter,
|
|
40
|
+
Slack) but can also be registered dynamically via the "register" subcommand.
|
|
41
|
+
|
|
42
|
+
Each provider is identified by a provider key (e.g. "integration:gmail").`,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// providers list
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
providers
|
|
50
|
+
.command("list")
|
|
51
|
+
.description("List all registered OAuth providers")
|
|
52
|
+
.option(
|
|
53
|
+
"--provider-key <key>",
|
|
54
|
+
'Filter by provider key substring (case-insensitive). Comma-separated values are OR\'d (e.g. "gmail,google")',
|
|
55
|
+
)
|
|
56
|
+
.addHelpText(
|
|
57
|
+
"after",
|
|
58
|
+
`
|
|
59
|
+
Returns registered OAuth providers, including both built-in providers
|
|
60
|
+
seeded at startup and any dynamically registered via "providers register".
|
|
61
|
+
|
|
62
|
+
When --provider-key is specified, only providers whose key contains the
|
|
63
|
+
given substring (case-insensitive) are returned. Multiple substrings can
|
|
64
|
+
be OR'd together using commas (e.g. "gmail,google" matches any provider
|
|
65
|
+
whose key contains "gmail" OR "google"). Without the flag, all providers
|
|
66
|
+
are listed.
|
|
67
|
+
|
|
68
|
+
Each provider row includes its key, auth URL, token URL, default scopes,
|
|
69
|
+
and configuration timestamps.
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
$ assistant oauth providers list
|
|
73
|
+
$ assistant oauth providers list --provider-key gmail
|
|
74
|
+
$ assistant oauth providers list --provider-key "gmail,google"
|
|
75
|
+
$ assistant oauth providers list --provider-key slack --json`,
|
|
76
|
+
)
|
|
77
|
+
.action((opts: { providerKey?: string }, cmd: Command) => {
|
|
78
|
+
try {
|
|
79
|
+
let rows = listProviders().map(parseProviderRow);
|
|
80
|
+
|
|
81
|
+
if (opts.providerKey) {
|
|
82
|
+
const needles = opts.providerKey
|
|
83
|
+
.split(",")
|
|
84
|
+
.map((n) => n.trim().toLowerCase())
|
|
85
|
+
.filter(Boolean);
|
|
86
|
+
rows = rows.filter(
|
|
87
|
+
(r) =>
|
|
88
|
+
r &&
|
|
89
|
+
needles.some((needle) =>
|
|
90
|
+
r.providerKey.toLowerCase().includes(needle),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!shouldOutputJson(cmd)) {
|
|
96
|
+
log.info(`Found ${rows.length} provider(s)`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
writeOutput(cmd, rows);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
102
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// providers get <provider-key>
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
providers
|
|
112
|
+
.command("get <provider-key>")
|
|
113
|
+
.description("Show details of a specific OAuth provider")
|
|
114
|
+
.addHelpText(
|
|
115
|
+
"after",
|
|
116
|
+
`
|
|
117
|
+
Arguments:
|
|
118
|
+
provider-key The full provider key (e.g. "integration:gmail").
|
|
119
|
+
Must match the key used during registration or seeding.
|
|
120
|
+
|
|
121
|
+
Returns the full provider configuration including auth URL, token URL,
|
|
122
|
+
default scopes, scope policy, and extra parameters. Exits with code 1
|
|
123
|
+
if the provider key is not found.
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
$ assistant oauth providers get integration:gmail
|
|
127
|
+
$ assistant oauth providers get integration:twitter --json`,
|
|
128
|
+
)
|
|
129
|
+
.action((providerKey: string, _opts: unknown, cmd: Command) => {
|
|
130
|
+
try {
|
|
131
|
+
const row = getProvider(providerKey);
|
|
132
|
+
|
|
133
|
+
if (!row) {
|
|
134
|
+
writeOutput(cmd, {
|
|
135
|
+
ok: false,
|
|
136
|
+
error: `Provider not found: ${providerKey}`,
|
|
137
|
+
});
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
writeOutput(cmd, parseProviderRow(row));
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
145
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// providers register
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
providers
|
|
155
|
+
.command("register")
|
|
156
|
+
.description("Register a new OAuth provider configuration")
|
|
157
|
+
.requiredOption("--provider-key <key>", "Provider key")
|
|
158
|
+
.requiredOption("--auth-url <url>", "Authorization endpoint URL")
|
|
159
|
+
.requiredOption("--token-url <url>", "Token endpoint URL")
|
|
160
|
+
.option("--base-url <url>", "API base URL")
|
|
161
|
+
.option("--userinfo-url <url>", "Userinfo endpoint URL")
|
|
162
|
+
.option("--scopes <scopes>", "Comma-separated default scopes")
|
|
163
|
+
.option("--token-auth-method <method>", "Token endpoint auth method")
|
|
164
|
+
.option("--callback-transport <transport>", "Callback transport")
|
|
165
|
+
.option("--loopback-port <port>", "Loopback port", (value: string) => {
|
|
166
|
+
const port = parseInt(value, 10);
|
|
167
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
168
|
+
throw new InvalidArgumentError(
|
|
169
|
+
"Port must be a number between 1 and 65535",
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return port;
|
|
173
|
+
})
|
|
174
|
+
.addHelpText(
|
|
175
|
+
"after",
|
|
176
|
+
`
|
|
177
|
+
Arguments (via options):
|
|
178
|
+
--provider-key Unique identifier for this provider (e.g. "integration:custom-service").
|
|
179
|
+
Must not collide with an existing provider key.
|
|
180
|
+
--auth-url The OAuth authorization endpoint URL.
|
|
181
|
+
--token-url The OAuth token endpoint URL.
|
|
182
|
+
--base-url Optional API base URL for the service.
|
|
183
|
+
--userinfo-url Optional OpenID Connect userinfo endpoint.
|
|
184
|
+
--scopes Comma-separated list of default scopes (e.g. "read,write,profile").
|
|
185
|
+
--token-auth-method How the client authenticates at the token endpoint
|
|
186
|
+
(e.g. "client_secret_post", "client_secret_basic").
|
|
187
|
+
--callback-transport Transport method for the OAuth callback.
|
|
188
|
+
--loopback-port Port number for the local loopback callback server (1-65535).
|
|
189
|
+
|
|
190
|
+
Registers a new OAuth provider configuration in the local store. This is
|
|
191
|
+
used for custom integrations not covered by the built-in provider seeds.
|
|
192
|
+
On success, returns the full provider row including generated timestamps.
|
|
193
|
+
|
|
194
|
+
Examples:
|
|
195
|
+
$ assistant oauth providers register \\
|
|
196
|
+
--provider-key integration:custom-api \\
|
|
197
|
+
--auth-url https://custom-api.example.com/oauth/authorize \\
|
|
198
|
+
--token-url https://custom-api.example.com/oauth/token
|
|
199
|
+
$ assistant oauth providers register \\
|
|
200
|
+
--provider-key integration:my-service \\
|
|
201
|
+
--auth-url https://my-service.com/auth \\
|
|
202
|
+
--token-url https://my-service.com/token \\
|
|
203
|
+
--scopes read,write --json`,
|
|
204
|
+
)
|
|
205
|
+
.action(
|
|
206
|
+
(
|
|
207
|
+
opts: {
|
|
208
|
+
providerKey: string;
|
|
209
|
+
authUrl: string;
|
|
210
|
+
tokenUrl: string;
|
|
211
|
+
baseUrl?: string;
|
|
212
|
+
userinfoUrl?: string;
|
|
213
|
+
scopes?: string;
|
|
214
|
+
tokenAuthMethod?: string;
|
|
215
|
+
callbackTransport?: string;
|
|
216
|
+
loopbackPort?: number;
|
|
217
|
+
},
|
|
218
|
+
cmd: Command,
|
|
219
|
+
) => {
|
|
220
|
+
try {
|
|
221
|
+
const row = registerProvider({
|
|
222
|
+
providerKey: opts.providerKey,
|
|
223
|
+
authUrl: opts.authUrl,
|
|
224
|
+
tokenUrl: opts.tokenUrl,
|
|
225
|
+
baseUrl: opts.baseUrl,
|
|
226
|
+
userinfoUrl: opts.userinfoUrl,
|
|
227
|
+
defaultScopes: opts.scopes ? opts.scopes.split(",") : [],
|
|
228
|
+
scopePolicy: {},
|
|
229
|
+
tokenEndpointAuthMethod: opts.tokenAuthMethod,
|
|
230
|
+
callbackTransport: opts.callbackTransport,
|
|
231
|
+
loopbackPort: opts.loopbackPort,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
writeOutput(cmd, parseProviderRow(row));
|
|
235
|
+
} catch (err) {
|
|
236
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
237
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
}
|