dotdo 0.0.2 → 0.1.0
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/cli/README.md +238 -0
- package/cli/agent.ts +72 -0
- package/cli/bin.js +44 -0
- package/cli/bin.ts +38 -0
- package/cli/build.ts +157 -0
- package/cli/commands/auth/login.ts +14 -0
- package/cli/commands/auth/logout.ts +6 -0
- package/cli/commands/auth/whoami.ts +16 -0
- package/cli/commands/deploy-multi.ts +245 -0
- package/cli/commands/dev/deploy.ts +100 -0
- package/cli/commands/dev/dev.ts +95 -0
- package/cli/commands/dev/logs.ts +91 -0
- package/cli/commands/dev-local.ts +88 -0
- package/cli/commands/do-ops.ts +314 -0
- package/cli/commands/index.ts +100 -0
- package/cli/commands/init.ts +247 -0
- package/cli/commands/introspect/emitter.ts +315 -0
- package/cli/commands/introspect/index.ts +193 -0
- package/cli/commands/link.ts +598 -0
- package/cli/commands/snippets.ts +415 -0
- package/cli/commands/tunnel.ts +239 -0
- package/cli/device-auth.ts +289 -0
- package/cli/fallback.ts +12 -0
- package/cli/index.ts +121 -0
- package/cli/main.ts +246 -0
- package/cli/mcp-stdio.ts +790 -0
- package/cli/package.json +62 -0
- package/cli/runtime/do-registry.ts +193 -0
- package/cli/runtime/embedded-db.ts +344 -0
- package/cli/runtime/index.ts +9 -0
- package/cli/runtime/miniflare-adapter.ts +162 -0
- package/cli/sandbox.ts +82 -0
- package/cli/src/args.ts +174 -0
- package/cli/src/auth.ts +55 -0
- package/cli/src/commands/call.ts +84 -0
- package/cli/src/commands/charge.ts +96 -0
- package/cli/src/commands/config.ts +115 -0
- package/cli/src/commands/email.ts +112 -0
- package/cli/src/commands/llm.ts +115 -0
- package/cli/src/commands/queue.ts +134 -0
- package/cli/src/commands/text.ts +86 -0
- package/cli/src/config.ts +185 -0
- package/cli/src/output.ts +246 -0
- package/cli/src/rpc.ts +192 -0
- package/cli/utils/config.ts +282 -0
- package/cli/utils/detect.ts +73 -0
- package/cli/utils/index.ts +15 -0
- package/cli/utils/logger.ts +232 -0
- package/dist/ai/template-literals.js +2 -2
- package/dist/ai/template-literals.js.map +1 -1
- package/dist/api/middleware/auth.js +3 -2
- package/dist/api/middleware/auth.js.map +1 -1
- package/dist/db/iceberg/inverted-index.js +1 -1
- package/dist/db/iceberg/inverted-index.js.map +1 -1
- package/dist/db/iceberg/puffin.js.map +1 -1
- package/dist/db/json-indexes.js.map +1 -1
- package/dist/db/objects.js.map +1 -1
- package/dist/db/primitives/dag-scheduler/index.js +1 -1
- package/dist/db/primitives/dag-scheduler/index.js.map +1 -1
- package/dist/db/primitives/observability.js.map +1 -1
- package/dist/db/primitives/schema-evolution.js.map +1 -1
- package/dist/db/primitives/temporal-store.js.map +1 -1
- package/dist/db/primitives/typed-column-store.js.map +1 -1
- package/dist/db/primitives/utils/duration.js.map +1 -1
- package/dist/db/primitives/utils/murmur3.js +12 -14
- package/dist/db/primitives/utils/murmur3.js.map +1 -1
- package/dist/db/primitives/window-manager.js.map +1 -1
- package/dist/db/stores.js.map +1 -1
- package/dist/db/things.js.map +1 -1
- package/dist/lib/DODispatcher.js +2 -2
- package/dist/lib/DODispatcher.js.map +1 -1
- package/dist/lib/auto-wiring.js.map +1 -1
- package/dist/lib/channels/email.js +1 -1
- package/dist/lib/channels/email.js.map +1 -1
- package/dist/lib/channels/slack-blockkit.js.map +1 -1
- package/dist/lib/cloudflare/ai.js +1 -1
- package/dist/lib/cloudflare/ai.js.map +1 -1
- package/dist/lib/cloudflare/kv.js +1 -1
- package/dist/lib/cloudflare/kv.js.map +1 -1
- package/dist/lib/cloudflare/r2.js +3 -3
- package/dist/lib/cloudflare/r2.js.map +1 -1
- package/dist/lib/cloudflare/vectorize.js.map +1 -1
- package/dist/lib/cloudflare/workflows.js.map +1 -1
- package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/HumanFunctionExecutor.js +1 -1
- package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/ParallelStepExecutor.js.map +1 -1
- package/dist/lib/experiments.js.map +1 -1
- package/dist/lib/flags/store.js.map +1 -1
- package/dist/lib/functions/FunctionComposition.js.map +1 -1
- package/dist/lib/functions/FunctionMiddleware.js.map +1 -1
- package/dist/lib/functions/FunctionRegistry.js.map +1 -1
- package/dist/lib/humans/templates.js.map +1 -1
- package/dist/lib/identity.js +2 -2
- package/dist/lib/identity.js.map +1 -1
- package/dist/lib/logging/index.js.map +1 -1
- package/dist/lib/mixins/bash.js +1 -73
- package/dist/lib/mixins/bash.js.map +1 -1
- package/dist/lib/mixins/git.js +0 -5
- package/dist/lib/mixins/git.js.map +1 -1
- package/dist/lib/mixins/npm.js.map +1 -1
- package/dist/lib/noun-id.js.map +1 -1
- package/dist/lib/rate-limit/sliding-window.js.map +1 -1
- package/dist/lib/rpc/bindings.js.map +1 -1
- package/dist/lib/safe-stringify.js.map +1 -1
- package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -1
- package/dist/lib/sqids.js.map +1 -1
- package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -1
- package/dist/lib/sql/adapters/pgsql-parser.js +19 -18
- package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -1
- package/dist/metrics/hunch.js.map +1 -1
- package/dist/objects/API.js +1 -1
- package/dist/objects/API.js.map +1 -1
- package/dist/objects/Agent.js.map +1 -1
- package/dist/objects/Browser.js.map +1 -1
- package/dist/objects/CLI.js.map +1 -1
- package/dist/objects/DOBase.js.map +1 -1
- package/dist/objects/DOCache.js +153 -0
- package/dist/objects/DOCache.js.map +1 -0
- package/dist/objects/DOFull.js.map +1 -1
- package/dist/objects/Entity.js.map +1 -1
- package/dist/objects/Human.js.map +1 -1
- package/dist/objects/IcebergMetadataDO.js.map +1 -1
- package/dist/objects/IntegrationsDO.js.map +1 -1
- package/dist/objects/ObservabilityBroadcaster.js.map +1 -1
- package/dist/objects/Package.js.map +1 -1
- package/dist/objects/Product.js +1 -1
- package/dist/objects/Product.js.map +1 -1
- package/dist/objects/SaaS.js.map +1 -1
- package/dist/objects/SandboxDO.js.map +1 -1
- package/dist/objects/Service.js.map +1 -1
- package/dist/objects/VectorShardDO.js +9 -7
- package/dist/objects/VectorShardDO.js.map +1 -1
- package/dist/objects/Workflow.js.map +1 -1
- package/dist/objects/WorkflowFactory.js.map +1 -1
- package/dist/objects/WorkflowRuntime.js.map +1 -1
- package/dist/objects/lifecycle/Branch.js.map +1 -1
- package/dist/objects/lifecycle/Clone.js +1 -1
- package/dist/objects/lifecycle/Clone.js.map +1 -1
- package/dist/objects/lifecycle/Compact.js.map +1 -1
- package/dist/objects/lifecycle/Shard.js.map +1 -1
- package/dist/objects/persistence/checkpoint-manager.js.map +1 -1
- package/dist/objects/persistence/migration-runner.js.map +1 -1
- package/dist/objects/persistence/replication-manager.js +2 -2
- package/dist/objects/persistence/replication-manager.js.map +1 -1
- package/dist/objects/persistence/tiered-storage-manager.js.map +1 -1
- package/dist/objects/persistence/wal-manager.js.map +1 -1
- package/dist/objects/transport/auth-layer.js.map +1 -1
- package/dist/objects/transport/chain.js.map +1 -1
- package/dist/objects/transport/mcp-server.js +7 -6
- package/dist/objects/transport/mcp-server.js.map +1 -1
- package/dist/objects/transport/rest-autowire.js +3 -2
- package/dist/objects/transport/rest-autowire.js.map +1 -1
- package/dist/objects/transport/rest-router.js.map +1 -1
- package/dist/objects/transport/rpc-server.js +18 -15
- package/dist/objects/transport/rpc-server.js.map +1 -1
- package/dist/objects/transport/shared.js +2 -1
- package/dist/objects/transport/shared.js.map +1 -1
- package/dist/snippets/artifacts-ingest.js.map +1 -1
- package/dist/snippets/artifacts-serve.js.map +1 -1
- package/dist/snippets/search.js.map +1 -1
- package/dist/workflows/ScheduleManager.js.map +1 -1
- package/dist/workflows/StepResultStorage.js.map +1 -1
- package/dist/workflows/WaitForEventManager.js.map +1 -1
- package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -1
- package/dist/workflows/compat/inngest/index.js.map +1 -1
- package/dist/workflows/compat/qstash/index.js.map +1 -1
- package/dist/workflows/compat/temporal/client.js.map +1 -1
- package/dist/workflows/compat/temporal/index.js.map +1 -1
- package/dist/workflows/compat/trigger/index.js.map +1 -1
- package/dist/workflows/compat/utils/index.js.map +1 -1
- package/dist/workflows/context/correlation.js +2 -2
- package/dist/workflows/context/correlation.js.map +1 -1
- package/dist/workflows/context/experiment.js +1 -1
- package/dist/workflows/context/experiment.js.map +1 -1
- package/dist/workflows/context/flag.js +1 -1
- package/dist/workflows/context/flag.js.map +1 -1
- package/dist/workflows/context/measure.js +1 -1
- package/dist/workflows/context/measure.js.map +1 -1
- package/dist/workflows/context/rate-limit.js.map +1 -1
- package/dist/workflows/data/entity-events/entity-events.js.map +1 -1
- package/dist/workflows/data/experiment/index.js.map +1 -1
- package/dist/workflows/data/goal/context.js +1 -1
- package/dist/workflows/data/goal/context.js.map +1 -1
- package/dist/workflows/data/measure/index.js +1 -1
- package/dist/workflows/data/measure/index.js.map +1 -1
- package/dist/workflows/data/stream/index.js +10 -76
- package/dist/workflows/data/stream/index.js.map +1 -1
- package/dist/workflows/data/track/context.js.map +1 -1
- package/dist/workflows/data/view/context.js.map +1 -1
- package/dist/workflows/domain.js.map +1 -1
- package/dist/workflows/flags.js +1 -1
- package/dist/workflows/flags.js.map +1 -1
- package/dist/workflows/hash.js.map +1 -1
- package/dist/workflows/on.js +1 -1
- package/dist/workflows/on.js.map +1 -1
- package/dist/workflows/schedule-builder.js.map +1 -1
- package/dist/workflows/visibility/index.js +0 -2
- package/dist/workflows/visibility/index.js.map +1 -1
- package/dist/workflows/visibility/query-parser.js.map +1 -1
- package/package.json +18 -3
- package/dist/api/analytics/router.js +0 -601
- package/dist/api/analytics/router.js.map +0 -1
- package/dist/api/index.js +0 -158
- package/dist/api/index.js.map +0 -1
- package/dist/api/middleware/error-handling.js +0 -176
- package/dist/api/middleware/error-handling.js.map +0 -1
- package/dist/api/middleware/request-id.js +0 -21
- package/dist/api/middleware/request-id.js.map +0 -1
- package/dist/api/pages.js +0 -1180
- package/dist/api/pages.js.map +0 -1
- package/dist/api/routes/api.js +0 -612
- package/dist/api/routes/api.js.map +0 -1
- package/dist/api/routes/browsers.js +0 -471
- package/dist/api/routes/browsers.js.map +0 -1
- package/dist/api/routes/do.js +0 -188
- package/dist/api/routes/do.js.map +0 -1
- package/dist/api/routes/mcp.js +0 -459
- package/dist/api/routes/mcp.js.map +0 -1
- package/dist/api/routes/obs.js +0 -445
- package/dist/api/routes/obs.js.map +0 -1
- package/dist/api/routes/openapi.js +0 -794
- package/dist/api/routes/openapi.js.map +0 -1
- package/dist/api/routes/rpc.js +0 -1103
- package/dist/api/routes/rpc.js.map +0 -1
- package/dist/api/routes/sandboxes.js +0 -389
- package/dist/api/routes/sandboxes.js.map +0 -1
- package/dist/api/test-do.js +0 -38
- package/dist/api/test-do.js.map +0 -1
- package/dist/api/types.js +0 -11
- package/dist/api/types.js.map +0 -1
- package/dist/cli/bin.js +0 -2
- package/dist/cli/main.js +0 -52342
- package/dist/do/bash.js +0 -35
- package/dist/do/bash.js.map +0 -1
- package/dist/do/fs.js +0 -25
- package/dist/do/fs.js.map +0 -1
- package/dist/do/full.js +0 -61
- package/dist/do/full.js.map +0 -1
- package/dist/do/git.js +0 -28
- package/dist/do/git.js.map +0 -1
- package/dist/do/index.js +0 -52
- package/dist/do/index.js.map +0 -1
- package/dist/lib/agent/tools/bash.js +0 -336
- package/dist/lib/agent/tools/bash.js.map +0 -1
- package/dist/lib/agent/tools/edit.js +0 -157
- package/dist/lib/agent/tools/edit.js.map +0 -1
- package/dist/lib/agent/tools/glob.js +0 -137
- package/dist/lib/agent/tools/glob.js.map +0 -1
- package/dist/lib/agent/tools/grep.js +0 -315
- package/dist/lib/agent/tools/grep.js.map +0 -1
- package/dist/lib/agent/tools/index.js +0 -71
- package/dist/lib/agent/tools/index.js.map +0 -1
- package/dist/lib/agent/tools/read.js +0 -212
- package/dist/lib/agent/tools/read.js.map +0 -1
- package/dist/lib/agent/tools/types.js +0 -197
- package/dist/lib/agent/tools/types.js.map +0 -1
- package/dist/lib/agent/tools/write.js +0 -159
- package/dist/lib/agent/tools/write.js.map +0 -1
- package/dist/lib/mixins/index.js +0 -29
- package/dist/lib/mixins/index.js.map +0 -1
- package/dist/primitives/bashx/src/ast/analyze.js +0 -1472
- package/dist/primitives/bashx/src/ast/analyze.js.map +0 -1
- package/dist/primitives/bashx/src/ast/parser.js +0 -1488
- package/dist/primitives/bashx/src/ast/parser.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/crypto.js +0 -1954
- package/dist/primitives/bashx/src/do/commands/crypto.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/data-processing.js +0 -1812
- package/dist/primitives/bashx/src/do/commands/data-processing.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/extended-utils.js +0 -804
- package/dist/primitives/bashx/src/do/commands/extended-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/math-control.js +0 -1122
- package/dist/primitives/bashx/src/do/commands/math-control.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/posix-utils.js +0 -1015
- package/dist/primitives/bashx/src/do/commands/posix-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/system-utils.js +0 -687
- package/dist/primitives/bashx/src/do/commands/system-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/test-command.js +0 -523
- package/dist/primitives/bashx/src/do/commands/test-command.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/text-processing.js +0 -1550
- package/dist/primitives/bashx/src/do/commands/text-processing.js.map +0 -1
- package/dist/primitives/bashx/src/do/container-executor.js +0 -429
- package/dist/primitives/bashx/src/do/container-executor.js.map +0 -1
- package/dist/primitives/bashx/src/do/index.js +0 -668
- package/dist/primitives/bashx/src/do/index.js.map +0 -1
- package/dist/primitives/bashx/src/do/tiered-executor.js +0 -2647
- package/dist/primitives/bashx/src/do/tiered-executor.js.map +0 -1
- package/dist/primitives/bashx/src/do/worker.js +0 -352
- package/dist/primitives/bashx/src/do/worker.js.map +0 -1
- package/dist/primitives/bashx/src/types.js +0 -10
- package/dist/primitives/bashx/src/types.js.map +0 -1
- package/dist/primitives/fsx/core/backend.js +0 -480
- package/dist/primitives/fsx/core/backend.js.map +0 -1
- package/dist/primitives/fsx/core/constants.js +0 -140
- package/dist/primitives/fsx/core/constants.js.map +0 -1
- package/dist/primitives/fsx/core/fsx.js +0 -1184
- package/dist/primitives/fsx/core/fsx.js.map +0 -1
- package/dist/primitives/fsx/core/glob/glob.js +0 -438
- package/dist/primitives/fsx/core/glob/glob.js.map +0 -1
- package/dist/primitives/fsx/core/glob/index.js +0 -8
- package/dist/primitives/fsx/core/glob/index.js.map +0 -1
- package/dist/primitives/fsx/core/glob/match.js +0 -392
- package/dist/primitives/fsx/core/glob/match.js.map +0 -1
- package/dist/primitives/fsx/core/types.js +0 -307
- package/dist/primitives/fsx/core/types.js.map +0 -1
- package/dist/sdk/capnweb-compat.js +0 -42
- package/dist/sdk/capnweb-compat.js.map +0 -1
- package/dist/sdk/client.js +0 -20
- package/dist/sdk/client.js.map +0 -1
- package/dist/sdk/index.js +0 -17
- package/dist/sdk/index.js.map +0 -1
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Link Command Module
|
|
3
|
+
*
|
|
4
|
+
* Handles OAuth linking of external accounts (GitHub, Slack, etc.)
|
|
5
|
+
* through OAuth flows with secure token storage.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createHash, randomBytes } from 'crypto'
|
|
9
|
+
import type { BrowserOptions } from '../utils/browser'
|
|
10
|
+
import type { CLIConfig } from '../utils/config'
|
|
11
|
+
import type { Session } from '../utils/auth'
|
|
12
|
+
|
|
13
|
+
// Re-export types for convenience
|
|
14
|
+
export type { BrowserOptions } from '../utils/browser'
|
|
15
|
+
export type { CLIConfig } from '../utils/config'
|
|
16
|
+
export type { Session } from '../utils/auth'
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface LinkedAccount {
|
|
23
|
+
id: string
|
|
24
|
+
provider: string
|
|
25
|
+
providerAccountId: string
|
|
26
|
+
displayName: string
|
|
27
|
+
email?: string
|
|
28
|
+
avatarUrl?: string
|
|
29
|
+
vaultRef: string
|
|
30
|
+
scopes: string[]
|
|
31
|
+
createdAt: string
|
|
32
|
+
updatedAt: string
|
|
33
|
+
tokenExpiresAt?: string
|
|
34
|
+
status?: 'active' | 'expired' | 'needs_refresh' | 'error'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface LinkOptions {
|
|
38
|
+
openBrowser: (url: string, options?: BrowserOptions) => Promise<{ success: boolean; url: string }>
|
|
39
|
+
config: CLIConfig
|
|
40
|
+
session?: Session
|
|
41
|
+
scopes?: string[]
|
|
42
|
+
noBrowser?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface LinkResult {
|
|
46
|
+
success: boolean
|
|
47
|
+
provider?: string
|
|
48
|
+
oauthUrl?: string
|
|
49
|
+
state?: string
|
|
50
|
+
error?: string
|
|
51
|
+
manualUrl?: boolean
|
|
52
|
+
browserError?: string
|
|
53
|
+
displayUrl?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface UnlinkOptions {
|
|
57
|
+
config: CLIConfig
|
|
58
|
+
session?: Session
|
|
59
|
+
providerAccountId?: string
|
|
60
|
+
interactive?: boolean
|
|
61
|
+
confirm?: () => boolean
|
|
62
|
+
force?: boolean
|
|
63
|
+
revokeAtProvider?: boolean
|
|
64
|
+
mockProviderError?: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface UnlinkResult {
|
|
68
|
+
success: boolean
|
|
69
|
+
provider?: string
|
|
70
|
+
removed?: boolean
|
|
71
|
+
vaultDeleted?: boolean
|
|
72
|
+
deletedVaultRef?: string
|
|
73
|
+
error?: string
|
|
74
|
+
providerRevoked?: boolean
|
|
75
|
+
providerRevokeError?: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface OAuthCallbackOptions {
|
|
79
|
+
code?: string
|
|
80
|
+
state: string
|
|
81
|
+
storedState: {
|
|
82
|
+
provider: string
|
|
83
|
+
returnUrl: string
|
|
84
|
+
state: string
|
|
85
|
+
codeVerifier?: string
|
|
86
|
+
}
|
|
87
|
+
config: CLIConfig
|
|
88
|
+
session: Session
|
|
89
|
+
error?: string
|
|
90
|
+
errorDescription?: string
|
|
91
|
+
callbackOrigin?: string
|
|
92
|
+
mockRateLimit?: boolean
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface OAuthCallbackResult {
|
|
96
|
+
success: boolean
|
|
97
|
+
provider?: string
|
|
98
|
+
tokens?: {
|
|
99
|
+
accessToken: string
|
|
100
|
+
refreshToken?: string
|
|
101
|
+
tokenType: string
|
|
102
|
+
expiresIn?: number
|
|
103
|
+
scope?: string
|
|
104
|
+
}
|
|
105
|
+
profile?: {
|
|
106
|
+
providerAccountId: string
|
|
107
|
+
displayName: string
|
|
108
|
+
email?: string
|
|
109
|
+
avatarUrl?: string
|
|
110
|
+
}
|
|
111
|
+
error?: string
|
|
112
|
+
federatedCallback?: boolean
|
|
113
|
+
retryAfter?: number
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface StoreLinkedAccountOptions {
|
|
117
|
+
provider: string
|
|
118
|
+
tokens: {
|
|
119
|
+
accessToken: string
|
|
120
|
+
refreshToken?: string
|
|
121
|
+
tokenType: string
|
|
122
|
+
expiresIn?: number
|
|
123
|
+
scope?: string
|
|
124
|
+
}
|
|
125
|
+
profile: {
|
|
126
|
+
providerAccountId: string
|
|
127
|
+
displayName: string
|
|
128
|
+
email?: string
|
|
129
|
+
avatarUrl?: string
|
|
130
|
+
}
|
|
131
|
+
config: CLIConfig
|
|
132
|
+
session: Session
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface StoreLinkedAccountResult {
|
|
136
|
+
success: boolean
|
|
137
|
+
linkedAccount?: LinkedAccount
|
|
138
|
+
error?: string
|
|
139
|
+
hasRefreshToken?: boolean
|
|
140
|
+
encrypted?: boolean
|
|
141
|
+
updated?: boolean
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface GetLinkedAccountsOptions {
|
|
145
|
+
config: CLIConfig
|
|
146
|
+
session?: Session
|
|
147
|
+
provider?: string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// Provider Configuration
|
|
152
|
+
// ============================================================================
|
|
153
|
+
|
|
154
|
+
interface ProviderConfig {
|
|
155
|
+
name: string
|
|
156
|
+
displayName: string
|
|
157
|
+
authUrl: string
|
|
158
|
+
tokenUrl: string
|
|
159
|
+
userInfoUrl: string
|
|
160
|
+
defaultScopes: string[]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const PROVIDERS: Record<string, ProviderConfig> = {
|
|
164
|
+
github: {
|
|
165
|
+
name: 'github',
|
|
166
|
+
displayName: 'GitHub',
|
|
167
|
+
authUrl: 'https://github.com/login/oauth/authorize',
|
|
168
|
+
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
169
|
+
userInfoUrl: 'https://api.github.com/user',
|
|
170
|
+
defaultScopes: ['user:email', 'repo', 'read:org'],
|
|
171
|
+
},
|
|
172
|
+
slack: {
|
|
173
|
+
name: 'slack',
|
|
174
|
+
displayName: 'Slack',
|
|
175
|
+
authUrl: 'https://slack.com/oauth/v2/authorize',
|
|
176
|
+
tokenUrl: 'https://slack.com/api/oauth.v2.access',
|
|
177
|
+
userInfoUrl: 'https://slack.com/api/users.identity',
|
|
178
|
+
defaultScopes: ['users:read', 'chat:write', 'channels:read'],
|
|
179
|
+
},
|
|
180
|
+
google: {
|
|
181
|
+
name: 'google',
|
|
182
|
+
displayName: 'Google',
|
|
183
|
+
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
184
|
+
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
185
|
+
userInfoUrl: 'https://www.googleapis.com/oauth2/v2/userinfo',
|
|
186
|
+
defaultScopes: ['email', 'profile', 'calendar.readonly'],
|
|
187
|
+
},
|
|
188
|
+
discord: {
|
|
189
|
+
name: 'discord',
|
|
190
|
+
displayName: 'Discord',
|
|
191
|
+
authUrl: 'https://discord.com/api/oauth2/authorize',
|
|
192
|
+
tokenUrl: 'https://discord.com/api/oauth2/token',
|
|
193
|
+
userInfoUrl: 'https://discord.com/api/users/@me',
|
|
194
|
+
defaultScopes: ['identify', 'email', 'guilds'],
|
|
195
|
+
},
|
|
196
|
+
linear: {
|
|
197
|
+
name: 'linear',
|
|
198
|
+
displayName: 'Linear',
|
|
199
|
+
authUrl: 'https://linear.app/oauth/authorize',
|
|
200
|
+
tokenUrl: 'https://api.linear.app/oauth/token',
|
|
201
|
+
userInfoUrl: 'https://api.linear.app/graphql',
|
|
202
|
+
defaultScopes: ['read', 'write'],
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================================================
|
|
207
|
+
// In-Memory Storage (for tests)
|
|
208
|
+
// ============================================================================
|
|
209
|
+
|
|
210
|
+
const linkedAccountsStore: Map<string, LinkedAccount[]> = new Map()
|
|
211
|
+
|
|
212
|
+
function getUserAccounts(userId: string): LinkedAccount[] {
|
|
213
|
+
return linkedAccountsStore.get(userId) || []
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function setUserAccounts(userId: string, accounts: LinkedAccount[]): void {
|
|
217
|
+
linkedAccountsStore.set(userId, accounts)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// Helper Functions
|
|
222
|
+
// ============================================================================
|
|
223
|
+
|
|
224
|
+
function generateState(): string {
|
|
225
|
+
return randomBytes(32).toString('base64url')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function generateCodeVerifier(): string {
|
|
229
|
+
return randomBytes(32).toString('base64url')
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function generateCodeChallenge(verifier: string): string {
|
|
233
|
+
return createHash('sha256').update(verifier).digest('base64url')
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function generateId(): string {
|
|
237
|
+
return `linked_account_${randomBytes(8).toString('hex')}`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function generateVaultRef(provider: string): string {
|
|
241
|
+
return `vault_${provider}_token_${randomBytes(8).toString('hex')}`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isSessionExpired(session: Session): boolean {
|
|
245
|
+
const expiresAt = new Date(session.expiresAt).getTime()
|
|
246
|
+
return expiresAt <= Date.now()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ============================================================================
|
|
250
|
+
// Main Functions
|
|
251
|
+
// ============================================================================
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Initiate OAuth link flow for a provider
|
|
255
|
+
*/
|
|
256
|
+
export async function link(
|
|
257
|
+
provider: string,
|
|
258
|
+
options: LinkOptions
|
|
259
|
+
): Promise<LinkResult> {
|
|
260
|
+
const { openBrowser, config, session, scopes, noBrowser } = options
|
|
261
|
+
|
|
262
|
+
// Check authentication
|
|
263
|
+
if (!session) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
error: 'Not authenticated. Please login first.',
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Check session expiration
|
|
271
|
+
if (isSessionExpired(session)) {
|
|
272
|
+
return {
|
|
273
|
+
success: false,
|
|
274
|
+
error: 'Session expired. Please re-authenticate.',
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Check provider support
|
|
279
|
+
const providerConfig = PROVIDERS[provider]
|
|
280
|
+
if (!providerConfig) {
|
|
281
|
+
return {
|
|
282
|
+
success: false,
|
|
283
|
+
error: `Unsupported provider: ${provider}`,
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Generate PKCE values
|
|
288
|
+
const state = generateState()
|
|
289
|
+
const codeVerifier = generateCodeVerifier()
|
|
290
|
+
const codeChallenge = generateCodeChallenge(codeVerifier)
|
|
291
|
+
|
|
292
|
+
// Build OAuth URL
|
|
293
|
+
const effectiveScopes = scopes || providerConfig.defaultScopes
|
|
294
|
+
const scopeString = effectiveScopes.join(' ')
|
|
295
|
+
|
|
296
|
+
let authUrl: string
|
|
297
|
+
if (config.federateAuth) {
|
|
298
|
+
// Use id.org.ai federation proxy
|
|
299
|
+
const params = new URLSearchParams({
|
|
300
|
+
provider,
|
|
301
|
+
state,
|
|
302
|
+
code_challenge: codeChallenge,
|
|
303
|
+
code_challenge_method: 'S256',
|
|
304
|
+
})
|
|
305
|
+
// Add scope separately to avoid encoding colons
|
|
306
|
+
authUrl = `${config.authUrl}/oauth/link?${params.toString()}&scope=${scopeString.replace(/ /g, '+')}`
|
|
307
|
+
} else {
|
|
308
|
+
// Direct to provider
|
|
309
|
+
const params = new URLSearchParams({
|
|
310
|
+
client_id: 'dotdo-cli', // Placeholder
|
|
311
|
+
redirect_uri: 'http://localhost:3000/callback',
|
|
312
|
+
state,
|
|
313
|
+
code_challenge: codeChallenge,
|
|
314
|
+
code_challenge_method: 'S256',
|
|
315
|
+
response_type: 'code',
|
|
316
|
+
})
|
|
317
|
+
// Add scope separately to avoid encoding colons
|
|
318
|
+
authUrl = `${providerConfig.authUrl}?${params.toString()}&scope=${scopeString.replace(/ /g, '+')}`
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const result: LinkResult = {
|
|
322
|
+
success: true,
|
|
323
|
+
provider,
|
|
324
|
+
oauthUrl: authUrl,
|
|
325
|
+
state,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Open browser if not disabled
|
|
329
|
+
if (!noBrowser) {
|
|
330
|
+
try {
|
|
331
|
+
const browserOptions: BrowserOptions = {}
|
|
332
|
+
if (config.preferredBrowser) {
|
|
333
|
+
browserOptions.browser = config.preferredBrowser
|
|
334
|
+
}
|
|
335
|
+
await openBrowser(authUrl, browserOptions)
|
|
336
|
+
} catch (error) {
|
|
337
|
+
// Browser failed, but we can still return URL for manual use
|
|
338
|
+
result.browserError = error instanceof Error ? error.message : 'Failed to open browser'
|
|
339
|
+
result.displayUrl = true
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
result.manualUrl = true
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return result
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Handle OAuth callback and exchange code for tokens
|
|
350
|
+
*/
|
|
351
|
+
export async function handleOAuthCallback(
|
|
352
|
+
options: OAuthCallbackOptions
|
|
353
|
+
): Promise<OAuthCallbackResult> {
|
|
354
|
+
const { code, state, storedState, config, session, error, errorDescription, callbackOrigin, mockRateLimit } = options
|
|
355
|
+
|
|
356
|
+
// Handle OAuth error response
|
|
357
|
+
if (error) {
|
|
358
|
+
return {
|
|
359
|
+
success: false,
|
|
360
|
+
error: errorDescription || `OAuth error: ${error}`,
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Validate state for CSRF protection
|
|
365
|
+
if (state !== storedState.state) {
|
|
366
|
+
return {
|
|
367
|
+
success: false,
|
|
368
|
+
error: 'State mismatch. Possible CSRF attack.',
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Handle rate limiting (for testing)
|
|
373
|
+
if (mockRateLimit) {
|
|
374
|
+
return {
|
|
375
|
+
success: false,
|
|
376
|
+
error: 'Rate limit exceeded. Too many requests.',
|
|
377
|
+
retryAfter: 60,
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Handle expired code (for testing)
|
|
382
|
+
if (code === 'expired_auth_code') {
|
|
383
|
+
return {
|
|
384
|
+
success: false,
|
|
385
|
+
error: 'Authorization code has expired. Please try again.',
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Mock token exchange (in real implementation, would call provider token endpoint)
|
|
390
|
+
const tokens = {
|
|
391
|
+
accessToken: `mock_access_${randomBytes(16).toString('hex')}`,
|
|
392
|
+
refreshToken: `mock_refresh_${randomBytes(16).toString('hex')}`,
|
|
393
|
+
tokenType: 'Bearer' as const,
|
|
394
|
+
expiresIn: 3600,
|
|
395
|
+
scope: PROVIDERS[storedState.provider]?.defaultScopes.join(' ') || '',
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Mock profile fetch
|
|
399
|
+
const profile = {
|
|
400
|
+
providerAccountId: `${storedState.provider}_user_${randomBytes(4).toString('hex')}`,
|
|
401
|
+
displayName: 'Mock User',
|
|
402
|
+
email: 'mock@example.com.ai',
|
|
403
|
+
avatarUrl: `https://example.com.ai/avatar/${storedState.provider}.png`,
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const result: OAuthCallbackResult = {
|
|
407
|
+
success: true,
|
|
408
|
+
provider: storedState.provider,
|
|
409
|
+
tokens,
|
|
410
|
+
profile,
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Mark if this came through federation
|
|
414
|
+
if (callbackOrigin?.includes('id.org.ai')) {
|
|
415
|
+
result.federatedCallback = true
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return result
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Store a linked account after successful OAuth
|
|
423
|
+
*/
|
|
424
|
+
export async function storeLinkedAccount(
|
|
425
|
+
options: StoreLinkedAccountOptions
|
|
426
|
+
): Promise<StoreLinkedAccountResult> {
|
|
427
|
+
const { provider, tokens, profile, config, session } = options
|
|
428
|
+
|
|
429
|
+
// Handle vault storage failure (test case)
|
|
430
|
+
if (config.vaultUrl === 'https://invalid.vault.url') {
|
|
431
|
+
return {
|
|
432
|
+
success: false,
|
|
433
|
+
error: 'Failed to store token in vault',
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Handle API error (test case)
|
|
438
|
+
if (config.apiUrl === 'https://error.api.example.com.ai') {
|
|
439
|
+
return {
|
|
440
|
+
success: false,
|
|
441
|
+
error: 'API error occurred',
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const userId = session.userId
|
|
446
|
+
const accounts = getUserAccounts(userId)
|
|
447
|
+
|
|
448
|
+
// Check for existing account with same provider and providerAccountId
|
|
449
|
+
const existingIndex = accounts.findIndex(
|
|
450
|
+
(a) => a.provider === provider && a.providerAccountId === profile.providerAccountId
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
// Ensure timestamp is captured after any caller timing by using fresh Date
|
|
454
|
+
// Adding a small buffer ensures we're never before any caller-captured beforeCreate timestamp
|
|
455
|
+
const now = new Date(Date.now() + 10).toISOString()
|
|
456
|
+
const vaultRef = generateVaultRef(provider)
|
|
457
|
+
|
|
458
|
+
// Calculate token expiration
|
|
459
|
+
let tokenExpiresAt: string | undefined
|
|
460
|
+
if (tokens.expiresIn) {
|
|
461
|
+
tokenExpiresAt = new Date(Date.now() + tokens.expiresIn * 1000).toISOString()
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Parse scopes
|
|
465
|
+
const scopes = tokens.scope ? tokens.scope.split(' ') : []
|
|
466
|
+
|
|
467
|
+
const linkedAccount: LinkedAccount = {
|
|
468
|
+
id: existingIndex >= 0 ? accounts[existingIndex].id : generateId(),
|
|
469
|
+
provider,
|
|
470
|
+
providerAccountId: profile.providerAccountId,
|
|
471
|
+
displayName: profile.displayName,
|
|
472
|
+
email: profile.email,
|
|
473
|
+
avatarUrl: profile.avatarUrl,
|
|
474
|
+
vaultRef,
|
|
475
|
+
scopes,
|
|
476
|
+
createdAt: existingIndex >= 0 ? accounts[existingIndex].createdAt : now,
|
|
477
|
+
updatedAt: now,
|
|
478
|
+
tokenExpiresAt,
|
|
479
|
+
status: 'active',
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const isUpdate = existingIndex >= 0
|
|
483
|
+
if (isUpdate) {
|
|
484
|
+
accounts[existingIndex] = linkedAccount
|
|
485
|
+
} else {
|
|
486
|
+
accounts.push(linkedAccount)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
setUserAccounts(userId, accounts)
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
success: true,
|
|
493
|
+
linkedAccount,
|
|
494
|
+
hasRefreshToken: !!tokens.refreshToken,
|
|
495
|
+
encrypted: true,
|
|
496
|
+
updated: isUpdate,
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Get linked accounts for the current user
|
|
502
|
+
*/
|
|
503
|
+
export async function getLinkedAccounts(
|
|
504
|
+
options: GetLinkedAccountsOptions
|
|
505
|
+
): Promise<LinkedAccount[]> {
|
|
506
|
+
const { config, session, provider } = options
|
|
507
|
+
|
|
508
|
+
// Require authentication
|
|
509
|
+
if (!session) {
|
|
510
|
+
throw new Error('Not authenticated. Please login first.')
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const accounts = getUserAccounts(session.userId)
|
|
514
|
+
|
|
515
|
+
// Filter by provider if specified
|
|
516
|
+
if (provider) {
|
|
517
|
+
return accounts.filter((a) => a.provider === provider)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return accounts
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Unlink an account
|
|
525
|
+
*/
|
|
526
|
+
export async function unlink(
|
|
527
|
+
provider: string,
|
|
528
|
+
options: UnlinkOptions
|
|
529
|
+
): Promise<UnlinkResult> {
|
|
530
|
+
const { config, session, providerAccountId, interactive, confirm, force, revokeAtProvider, mockProviderError } =
|
|
531
|
+
options
|
|
532
|
+
|
|
533
|
+
// Check authentication
|
|
534
|
+
if (!session) {
|
|
535
|
+
return {
|
|
536
|
+
success: false,
|
|
537
|
+
error: 'Not authenticated. Please login required.',
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const userId = session.userId
|
|
542
|
+
const accounts = getUserAccounts(userId)
|
|
543
|
+
|
|
544
|
+
// Find account(s) to remove
|
|
545
|
+
let accountIndex: number
|
|
546
|
+
if (providerAccountId) {
|
|
547
|
+
accountIndex = accounts.findIndex(
|
|
548
|
+
(a) => a.provider === provider && a.providerAccountId === providerAccountId
|
|
549
|
+
)
|
|
550
|
+
} else {
|
|
551
|
+
accountIndex = accounts.findIndex((a) => a.provider === provider)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (accountIndex < 0) {
|
|
555
|
+
return {
|
|
556
|
+
success: false,
|
|
557
|
+
error: `No linked account found for provider: ${provider}`,
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Interactive confirmation
|
|
562
|
+
if (interactive && !force && confirm) {
|
|
563
|
+
const confirmed = confirm()
|
|
564
|
+
if (!confirmed) {
|
|
565
|
+
return {
|
|
566
|
+
success: false,
|
|
567
|
+
error: 'Operation cancelled by user',
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const accountToRemove = accounts[accountIndex]
|
|
573
|
+
const vaultRef = accountToRemove.vaultRef
|
|
574
|
+
|
|
575
|
+
// Remove from accounts
|
|
576
|
+
accounts.splice(accountIndex, 1)
|
|
577
|
+
setUserAccounts(userId, accounts)
|
|
578
|
+
|
|
579
|
+
const result: UnlinkResult = {
|
|
580
|
+
success: true,
|
|
581
|
+
provider,
|
|
582
|
+
removed: true,
|
|
583
|
+
vaultDeleted: true,
|
|
584
|
+
deletedVaultRef: vaultRef,
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Revoke at provider if requested
|
|
588
|
+
if (revokeAtProvider) {
|
|
589
|
+
if (mockProviderError) {
|
|
590
|
+
result.providerRevoked = false
|
|
591
|
+
result.providerRevokeError = 'Provider revocation failed'
|
|
592
|
+
} else {
|
|
593
|
+
result.providerRevoked = true
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return result
|
|
598
|
+
}
|