@cleocode/contracts 2026.6.13 → 2026.6.15
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/dist/__tests__/operation-def.test.js +4 -1
- package/dist/__tests__/operation-def.test.js.map +1 -1
- package/dist/__tests__/provider-def.test.d.ts +12 -0
- package/dist/__tests__/provider-def.test.d.ts.map +1 -0
- package/dist/__tests__/provider-def.test.js +84 -0
- package/dist/__tests__/provider-def.test.js.map +1 -0
- package/dist/__tests__/service-provider.test.d.ts +17 -0
- package/dist/__tests__/service-provider.test.d.ts.map +1 -0
- package/dist/__tests__/service-provider.test.js +108 -0
- package/dist/__tests__/service-provider.test.js.map +1 -0
- package/dist/boundary.d.ts +52 -0
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +23 -0
- package/dist/boundary.js.map +1 -1
- package/dist/branch-lock.d.ts +4 -0
- package/dist/branch-lock.d.ts.map +1 -1
- package/dist/branch-lock.js.map +1 -1
- package/dist/conduit.d.ts +171 -0
- package/dist/conduit.d.ts.map +1 -1
- package/dist/config/index.d.ts +23 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +22 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config.d.ts +109 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts +21 -0
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts.map +1 -0
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js +84 -0
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js.map +1 -0
- package/dist/dispatch/identity.d.ts +1 -1
- package/dist/dispatch/identity.d.ts.map +1 -1
- package/dist/dispatch/identity.js +20 -0
- package/dist/dispatch/identity.js.map +1 -1
- package/dist/dispatch/operation-def.d.ts +20 -0
- package/dist/dispatch/operation-def.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +1030 -46
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/index.d.ts +17 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/lease-ipc/__tests__/freeze.test.js +92 -0
- package/dist/lease-ipc/__tests__/freeze.test.js.map +1 -1
- package/dist/lease-ipc/index.d.ts +2 -2
- package/dist/lease-ipc/index.d.ts.map +1 -1
- package/dist/lease-ipc/index.js +1 -1
- package/dist/lease-ipc/index.js.map +1 -1
- package/dist/lease-ipc/messages.d.ts +167 -3
- package/dist/lease-ipc/messages.d.ts.map +1 -1
- package/dist/lease-ipc/messages.js +93 -0
- package/dist/lease-ipc/messages.js.map +1 -1
- package/dist/llm/catalog-schema.d.ts +242 -0
- package/dist/llm/catalog-schema.d.ts.map +1 -0
- package/dist/llm/catalog-schema.js +153 -0
- package/dist/llm/catalog-schema.js.map +1 -0
- package/dist/llm/catalog-schema.json +139 -0
- package/dist/llm/index.d.ts +15 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +14 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/oauth.d.ts +16 -2
- package/dist/llm/oauth.d.ts.map +1 -1
- package/dist/llm/onboarding.d.ts +126 -0
- package/dist/llm/onboarding.d.ts.map +1 -0
- package/dist/llm/onboarding.js +34 -0
- package/dist/llm/onboarding.js.map +1 -0
- package/dist/llm/provider-def.d.ts +254 -0
- package/dist/llm/provider-def.d.ts.map +1 -0
- package/dist/llm/provider-def.js +93 -0
- package/dist/llm/provider-def.js.map +1 -0
- package/dist/llm/system-resolver.d.ts +42 -0
- package/dist/llm/system-resolver.d.ts.map +1 -1
- package/dist/operations/admin.d.ts +124 -1
- package/dist/operations/admin.d.ts.map +1 -1
- package/dist/operations/entities.d.ts +110 -0
- package/dist/operations/entities.d.ts.map +1 -0
- package/dist/operations/entities.js +695 -0
- package/dist/operations/entities.js.map +1 -0
- package/dist/operations/llm.d.ts +16 -1
- package/dist/operations/llm.d.ts.map +1 -1
- package/dist/operations/output-contracts-data.d.ts.map +1 -1
- package/dist/operations/output-contracts-data.js +230 -0
- package/dist/operations/output-contracts-data.js.map +1 -1
- package/dist/operations/service.d.ts +68 -0
- package/dist/operations/service.d.ts.map +1 -0
- package/dist/operations/service.js +336 -0
- package/dist/operations/service.js.map +1 -0
- package/dist/operations/tasks.d.ts +109 -0
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js.map +1 -1
- package/dist/operations/worktree.d.ts +11 -1
- package/dist/operations/worktree.d.ts.map +1 -1
- package/dist/playbook.d.ts +56 -0
- package/dist/playbook.d.ts.map +1 -1
- package/dist/release/plan.d.ts +5 -5
- package/dist/spawn.d.ts +70 -0
- package/dist/spawn.d.ts.map +1 -1
- package/dist/spawn.js +1 -0
- package/dist/spawn.js.map +1 -1
- package/dist/vault/service-provider.d.ts +343 -0
- package/dist/vault/service-provider.d.ts.map +1 -0
- package/dist/vault/service-provider.js +857 -0
- package/dist/vault/service-provider.js.map +1 -0
- package/dist/workgraph.d.ts +12 -12
- package/package.json +10 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/operation-def.test.ts +6 -1
- package/src/__tests__/provider-def.test.ts +93 -0
- package/src/__tests__/service-provider.test.ts +129 -0
- package/src/boundary.ts +69 -0
- package/src/branch-lock.ts +4 -0
- package/src/conduit.ts +200 -0
- package/src/config/index.ts +35 -0
- package/src/config.ts +116 -1
- package/src/dispatch/__tests__/tasks-gateway-crud-ops.test.ts +99 -0
- package/src/dispatch/identity.ts +20 -0
- package/src/dispatch/operation-def.ts +21 -0
- package/src/dispatch/operations-registry.ts +1062 -36
- package/src/index.ts +147 -2
- package/src/lease-ipc/__tests__/freeze.test.ts +96 -0
- package/src/lease-ipc/index.ts +12 -0
- package/src/lease-ipc/messages.ts +117 -0
- package/src/llm/catalog-schema.json +139 -0
- package/src/llm/catalog-schema.ts +184 -0
- package/src/llm/index.ts +29 -0
- package/src/llm/oauth.ts +16 -2
- package/src/llm/onboarding.ts +131 -0
- package/src/llm/provider-def.ts +291 -0
- package/src/llm/system-resolver.ts +45 -0
- package/src/operations/admin.ts +144 -0
- package/src/operations/entities.ts +758 -0
- package/src/operations/llm.ts +16 -0
- package/src/operations/output-contracts-data.ts +264 -0
- package/src/operations/service.ts +360 -0
- package/src/operations/tasks.ts +119 -0
- package/src/operations/worktree.ts +11 -0
- package/src/playbook.ts +56 -0
- package/src/spawn.ts +79 -0
- package/src/vault/service-provider.ts +1137 -0
|
@@ -0,0 +1,1137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal service-vault — declarative service-provider registry (types + DATA).
|
|
3
|
+
*
|
|
4
|
+
* EP-UNIVERSAL-SERVICE-VAULT (epic T11765 · saga SG-VAULT-CORE T10409 · M2 W1a ·
|
|
5
|
+
* task T11937). The **service** half of the universal vault: machine-wide OAuth /
|
|
6
|
+
* API credentials for third-party SERVICES (github, google, notion, figma, …) —
|
|
7
|
+
* distinct from the LLM-provider credential pool (`accounts`, T11709) which holds
|
|
8
|
+
* model-API keys. A {@link ServiceProviderDef} is a purely declarative description
|
|
9
|
+
* of one connectable service: its stable key, display label, auth shape, and the
|
|
10
|
+
* default OAuth endpoints/scopes the (later) OAuth dance (T11939) will drive.
|
|
11
|
+
*
|
|
12
|
+
* ## Why this lives in `@cleocode/contracts` (Gate 10 purity)
|
|
13
|
+
*
|
|
14
|
+
* This module is **types + frozen DATA only**. {@link SERVICE_PROVIDERS} is a
|
|
15
|
+
* `const` record literal (no bodied function, no zod schema, no type guard) — it
|
|
16
|
+
* is configuration data, exactly like a routing table, so it satisfies the
|
|
17
|
+
* contracts-purity gate (`lint-no-runtime-in-contracts`). The runtime that
|
|
18
|
+
* CONSUMES the registry (the store, the trust gate, the OAuth flow) lives in
|
|
19
|
+
* `@cleocode/core`; this file never imports from core and carries no side effects.
|
|
20
|
+
*
|
|
21
|
+
* ## Pattern, not census (T11937 vs T11938)
|
|
22
|
+
*
|
|
23
|
+
* Only **github** and **google** are seeded here, deliberately — they prove the
|
|
24
|
+
* declarative pattern (one PKCE provider with a fixed loopback, one classic
|
|
25
|
+
* web-OAuth provider) before the fan-out to ~40 services in the follow-up
|
|
26
|
+
* (T11938). Adding a service is a single DATA entry; no code change.
|
|
27
|
+
*
|
|
28
|
+
* @module contracts/vault/service-provider
|
|
29
|
+
* @task T11937
|
|
30
|
+
* @epic T11765
|
|
31
|
+
* @saga T10409
|
|
32
|
+
* @see ../llm/oauth.js — `ProviderOAuthConfig` / `OAuthMode` (reused here)
|
|
33
|
+
* @see ../llm/sealed-credential.js — `SealedCredential` (the egress handle a resolved service credential is wrapped in)
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type { ProviderOAuthConfig } from '../llm/oauth.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Auth shape a service uses to obtain credentials.
|
|
40
|
+
*
|
|
41
|
+
* - `oauth2` — classic OAuth 2.0 Authorization Code grant (e.g. github web flow).
|
|
42
|
+
* - `oauth2-pkce` — OAuth 2.0 Authorization Code + PKCE (RFC 7636), for CLI /
|
|
43
|
+
* headless flows (e.g. google installed-app flow).
|
|
44
|
+
* - `api-key` — a long-lived API token pasted/imported directly (no OAuth dance).
|
|
45
|
+
*
|
|
46
|
+
* The value drives which fields of {@link ServiceProviderDef.oauth} are required
|
|
47
|
+
* and which OAuth code path (T11939) runs for the service.
|
|
48
|
+
*
|
|
49
|
+
* @task T11937
|
|
50
|
+
*/
|
|
51
|
+
export const SERVICE_AUTH_KINDS = ['oauth2', 'oauth2-pkce', 'api-key'] as const;
|
|
52
|
+
|
|
53
|
+
/** TypeScript union derived from {@link SERVICE_AUTH_KINDS}. */
|
|
54
|
+
export type ServiceAuthKind = (typeof SERVICE_AUTH_KINDS)[number];
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Refresh configuration (T11938 BREADTH · T11939 W2 — driven by this DATA)
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Discriminant naming the refresh GRANT a provider uses (T11938 AC3).
|
|
62
|
+
*
|
|
63
|
+
* The OAuth flow (T11939) branches on this value to pick the refresh code path:
|
|
64
|
+
*
|
|
65
|
+
* - `refresh-token` — the standard OAuth 2.0 `grant_type=refresh_token` exchange
|
|
66
|
+
* (RFC 6749 §6), shaped by {@link RefreshConfig.bodyFormat} /
|
|
67
|
+
* {@link RefreshConfig.clientAuth}. The common case (google, atlassian, …).
|
|
68
|
+
* - `github-app` — a GitHub App installation token: sign an RS256 JWT with the
|
|
69
|
+
* app private key, then POST `/app/installations/{id}/access_tokens`.
|
|
70
|
+
* - `service-account-jwt` — a Google service-account: sign an RS256 JWT and
|
|
71
|
+
* exchange it via `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`.
|
|
72
|
+
* - `client-credentials` — the OAuth 2.0 `grant_type=client_credentials` grant
|
|
73
|
+
* (RFC 6749 §4.4), Basic-auth client id/secret (e.g. MongoDB Atlas SAs).
|
|
74
|
+
*
|
|
75
|
+
* @task T11938
|
|
76
|
+
*/
|
|
77
|
+
export const REFRESH_KINDS = [
|
|
78
|
+
'refresh-token',
|
|
79
|
+
'github-app',
|
|
80
|
+
'service-account-jwt',
|
|
81
|
+
'client-credentials',
|
|
82
|
+
] as const;
|
|
83
|
+
|
|
84
|
+
/** TypeScript union derived from {@link REFRESH_KINDS}. */
|
|
85
|
+
export type RefreshKind = (typeof REFRESH_KINDS)[number];
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Body encoding for a `refresh-token` token-endpoint POST.
|
|
89
|
+
*
|
|
90
|
+
* - `form` — `application/x-www-form-urlencoded` (OAuth 2.0 default; google).
|
|
91
|
+
* - `json` — `application/json` (required by Atlassian / Notion).
|
|
92
|
+
*
|
|
93
|
+
* @task T11938
|
|
94
|
+
*/
|
|
95
|
+
export type RefreshBodyFormat = 'form' | 'json';
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* How the OAuth client credentials are presented on a `refresh-token` POST.
|
|
99
|
+
*
|
|
100
|
+
* - `body` — `client_id` + `client_secret` carried IN the request body (default).
|
|
101
|
+
* - `basic-auth` — `Authorization: Basic base64(client_id:client_secret)` header
|
|
102
|
+
* (Notion, Supabase).
|
|
103
|
+
*
|
|
104
|
+
* @task T11938
|
|
105
|
+
*/
|
|
106
|
+
export type RefreshClientAuth = 'body' | 'basic-auth';
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Declarative refresh configuration for a `refresh-token` provider (T11938 AC1).
|
|
110
|
+
*
|
|
111
|
+
* Pure data: the token endpoint, the body encoding, and how the client
|
|
112
|
+
* credentials are presented. The OAuth flow (T11939) reads this to drive the
|
|
113
|
+
* `grant_type=refresh_token` exchange WITHOUT any per-provider code. The
|
|
114
|
+
* `kind` discriminant selects the broader grant (the three special variants
|
|
115
|
+
* — github-app / service-account-jwt / client-credentials — set `kind` and
|
|
116
|
+
* leave `bodyFormat`/`clientAuth` at their `form`/`body` defaults; their grant
|
|
117
|
+
* mechanics are not refresh-token-shaped).
|
|
118
|
+
*
|
|
119
|
+
* Cannibalized from onecli `apps.rs::RefreshConfig` (DATA only — the Rust
|
|
120
|
+
* `reqwest`/env-var resolution is re-implemented in TS in T11939).
|
|
121
|
+
*
|
|
122
|
+
* @task T11938
|
|
123
|
+
*/
|
|
124
|
+
export interface RefreshConfig {
|
|
125
|
+
/** Which refresh GRANT this provider uses ({@link RefreshKind}). */
|
|
126
|
+
readonly kind: RefreshKind;
|
|
127
|
+
/**
|
|
128
|
+
* Token endpoint the refresh exchange POSTs to. For `refresh-token` /
|
|
129
|
+
* `client-credentials`; the JWT variants use a fixed provider endpoint and may
|
|
130
|
+
* leave this as the canonical value for documentation.
|
|
131
|
+
*/
|
|
132
|
+
readonly tokenUrl: string;
|
|
133
|
+
/** Body encoding for a `refresh-token` exchange. Defaults to `form`. */
|
|
134
|
+
readonly bodyFormat?: RefreshBodyFormat;
|
|
135
|
+
/** Client-credential presentation for a `refresh-token` exchange. Defaults to `body`. */
|
|
136
|
+
readonly clientAuth?: RefreshClientAuth;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Host rules + header injection (T11938 BREADTH — declarative reach metadata)
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Auth strategy a host rule applies when a resolved credential is injected into
|
|
145
|
+
* an outbound request to a matched host.
|
|
146
|
+
*
|
|
147
|
+
* - `bearer` — `Authorization: Bearer <token>` (the OAuth default).
|
|
148
|
+
* - `basic-x-access-token` — `Authorization: Basic base64("x-access-token:<token>")`
|
|
149
|
+
* (GitHub git-over-HTTPS).
|
|
150
|
+
* - `header` — the token is injected via a named header rather than `Authorization`
|
|
151
|
+
* (paired with {@link ServiceProviderDef.credentialHeaders}).
|
|
152
|
+
*
|
|
153
|
+
* Declarative only in this milestone — the injecting proxy is a later epic.
|
|
154
|
+
*
|
|
155
|
+
* @task T11938
|
|
156
|
+
*/
|
|
157
|
+
export type HostAuthStrategy = 'bearer' | 'basic-x-access-token' | 'header';
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* One host-match rule declaring which upstream hosts a provider's credential is
|
|
161
|
+
* valid for and how it is injected (T11938 AC1/AC4 — every provider declares a
|
|
162
|
+
* non-empty {@link ServiceProviderDef.hostRules}).
|
|
163
|
+
*
|
|
164
|
+
* `host` is matched EXACTLY (no wildcards in this milestone). `pathPrefix`, when
|
|
165
|
+
* set, narrows the rule to requests under that path (e.g. the legacy
|
|
166
|
+
* `www.googleapis.com/gmail/` endpoint). Cannibalized from onecli
|
|
167
|
+
* `apps.rs::HostRule` (DATA only).
|
|
168
|
+
*
|
|
169
|
+
* @task T11938
|
|
170
|
+
*/
|
|
171
|
+
export interface ServiceHostRule {
|
|
172
|
+
/** Exact upstream host this rule matches (e.g. `'api.github.com'`). */
|
|
173
|
+
readonly host: string;
|
|
174
|
+
/** Optional path prefix narrowing the rule (e.g. `'/gmail/'`). */
|
|
175
|
+
readonly pathPrefix?: string;
|
|
176
|
+
/** How the credential is injected for a matched request. */
|
|
177
|
+
readonly strategy: HostAuthStrategy;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* One declarative injection action applied to an outbound request when a resolved
|
|
182
|
+
* service credential is injected at the harness tool boundary (T11940 · M2-W3).
|
|
183
|
+
*
|
|
184
|
+
* A discriminated union over the action `kind`. The injector
|
|
185
|
+
* ({@link import('@cleocode/core').injectServiceCredentials}) materializes the
|
|
186
|
+
* sealed credential's plaintext ONLY at the wire (inside the action's value
|
|
187
|
+
* resolver) and applies these mutations to a request descriptor — there is NO MITM
|
|
188
|
+
* proxy (the seam is the in-process tool boundary, T11940 AC4):
|
|
189
|
+
*
|
|
190
|
+
* - `set-header` — set `name` to the materialized value (overwriting any existing).
|
|
191
|
+
* - `replace-header` — set `name` to the value ONLY if the header is already
|
|
192
|
+
* present (a no-op when absent — used to swap a placeholder the tool emitted).
|
|
193
|
+
* - `remove-header` — delete `name` (e.g. strip a stale `Authorization` before
|
|
194
|
+
* re-injecting the vault credential).
|
|
195
|
+
* - `set-param` — set the query-string parameter `name` to the materialized value.
|
|
196
|
+
*
|
|
197
|
+
* The `valueSource` discriminates WHERE the injected value comes from:
|
|
198
|
+
* `'token'` (the decrypted access token), `'credential-field'` (a named field of
|
|
199
|
+
* the credential blob, e.g. an AWS access-key id), or `'metadata'` (a non-secret
|
|
200
|
+
* connection-metadata key, e.g. a Google project id). `remove-header` carries no
|
|
201
|
+
* `valueSource` (it injects nothing).
|
|
202
|
+
*
|
|
203
|
+
* @task T11940
|
|
204
|
+
*/
|
|
205
|
+
export type InjectionRule =
|
|
206
|
+
| {
|
|
207
|
+
/** Set a header to the injected value (overwrites any existing). */
|
|
208
|
+
readonly kind: 'set-header';
|
|
209
|
+
/** The header name to set. */
|
|
210
|
+
readonly name: string;
|
|
211
|
+
/** Where the injected value comes from. */
|
|
212
|
+
readonly valueSource: InjectionValueSource;
|
|
213
|
+
/** For `bearer`/`basic-x-access-token` token framing — how to wrap the value. */
|
|
214
|
+
readonly framing?: HostAuthStrategy;
|
|
215
|
+
}
|
|
216
|
+
| {
|
|
217
|
+
/** Set a header ONLY when it is already present (swap a placeholder). */
|
|
218
|
+
readonly kind: 'replace-header';
|
|
219
|
+
/** The header name to replace. */
|
|
220
|
+
readonly name: string;
|
|
221
|
+
/** Where the injected value comes from. */
|
|
222
|
+
readonly valueSource: InjectionValueSource;
|
|
223
|
+
/** For `bearer`/`basic-x-access-token` token framing — how to wrap the value. */
|
|
224
|
+
readonly framing?: HostAuthStrategy;
|
|
225
|
+
}
|
|
226
|
+
| {
|
|
227
|
+
/** Remove a header (injects no value). */
|
|
228
|
+
readonly kind: 'remove-header';
|
|
229
|
+
/** The header name to remove. */
|
|
230
|
+
readonly name: string;
|
|
231
|
+
}
|
|
232
|
+
| {
|
|
233
|
+
/** Set a query-string parameter to the injected value. */
|
|
234
|
+
readonly kind: 'set-param';
|
|
235
|
+
/** The query-parameter name to set. */
|
|
236
|
+
readonly name: string;
|
|
237
|
+
/** Where the injected value comes from. */
|
|
238
|
+
readonly valueSource: InjectionValueSource;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Where an {@link InjectionRule}'s injected value is sourced from.
|
|
243
|
+
*
|
|
244
|
+
* - `token` — the decrypted access token (the secret materialized at the wire).
|
|
245
|
+
* - `credential-field` — a named field of the credential blob (paired with the
|
|
246
|
+
* rule's `name` resolution via {@link ServiceProviderDef.credentialHeaders}).
|
|
247
|
+
* - `metadata` — a non-secret connection-metadata key.
|
|
248
|
+
*
|
|
249
|
+
* @task T11940
|
|
250
|
+
*/
|
|
251
|
+
export type InjectionValueSource = 'token' | 'credential-field' | 'metadata';
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Inject an HTTP header from a credential-blob field (e.g. a regional API key →
|
|
255
|
+
* `DD-API-KEY`). Declarative metadata; the injecting proxy is a later epic.
|
|
256
|
+
*
|
|
257
|
+
* @task T11938
|
|
258
|
+
*/
|
|
259
|
+
export interface CredentialHeaderRule {
|
|
260
|
+
/** The credential-blob field whose value is injected. */
|
|
261
|
+
readonly credentialField: string;
|
|
262
|
+
/** The HTTP header the value is injected as. */
|
|
263
|
+
readonly headerName: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Inject an HTTP header from a connection-metadata key (e.g. a project id →
|
|
268
|
+
* `x-goog-user-project`). Declarative metadata; the injecting proxy is a later
|
|
269
|
+
* epic.
|
|
270
|
+
*
|
|
271
|
+
* @task T11938
|
|
272
|
+
*/
|
|
273
|
+
export interface MetadataHeaderRule {
|
|
274
|
+
/** The non-secret metadata key whose value is injected. */
|
|
275
|
+
readonly metadataKey: string;
|
|
276
|
+
/** The HTTP header the value is injected as. */
|
|
277
|
+
readonly headerName: string;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Declarative description of one connectable third-party service.
|
|
282
|
+
*
|
|
283
|
+
* A `ServiceProviderDef` is pure configuration: it names a service, describes how
|
|
284
|
+
* it authenticates, and (for OAuth services) carries the default endpoints and
|
|
285
|
+
* scopes the OAuth flow will drive. It holds NO secret material — a user's actual
|
|
286
|
+
* tokens live encrypted in the `service_connections` table; BYOC client secrets
|
|
287
|
+
* (when the user brings their own OAuth app) live encrypted in `service_configs`.
|
|
288
|
+
*
|
|
289
|
+
* @task T11937
|
|
290
|
+
*/
|
|
291
|
+
export interface ServiceProviderDef {
|
|
292
|
+
/**
|
|
293
|
+
* Stable provider key (lowercase, kebab) — the join key against
|
|
294
|
+
* `service_connections.provider` / `service_configs.provider`. NEVER renamed
|
|
295
|
+
* once shipped (it is persisted in user rows). e.g. `'github'`, `'google'`.
|
|
296
|
+
*/
|
|
297
|
+
readonly provider: string;
|
|
298
|
+
/** Human-readable display name for UI / CLI output. e.g. `'GitHub'`. */
|
|
299
|
+
readonly displayName: string;
|
|
300
|
+
/** Auth shape this service uses ({@link ServiceAuthKind}). */
|
|
301
|
+
readonly authKind: ServiceAuthKind;
|
|
302
|
+
/**
|
|
303
|
+
* Default OAuth configuration (endpoints + scopes + the public client id of
|
|
304
|
+
* CLEO's first-party OAuth app). `undefined` for an `api-key`-only service.
|
|
305
|
+
*
|
|
306
|
+
* Reuses the LLM-layer {@link ProviderOAuthConfig} shape verbatim — the OAuth
|
|
307
|
+
* mechanics (PKCE vs classic, endpoints, redirect, extra params) are identical
|
|
308
|
+
* whether the credential is for a model API or a service API. When the user
|
|
309
|
+
* supplies their own client id/secret (BYOC, stored in `service_configs`),
|
|
310
|
+
* those OVERRIDE the `clientId`/`scope` declared here at flow time (T11939).
|
|
311
|
+
*/
|
|
312
|
+
readonly oauth?: ProviderOAuthConfig;
|
|
313
|
+
/**
|
|
314
|
+
* Default space-separated scopes requested when CLEO's first-party app is used
|
|
315
|
+
* and `oauth.scope` is not overridden by a BYOC `service_configs.settings`.
|
|
316
|
+
* Mirrors `oauth.scope`; kept as a top-level field so an `api-key` service can
|
|
317
|
+
* still document the access level a token is expected to carry.
|
|
318
|
+
*/
|
|
319
|
+
readonly defaultScopes?: string;
|
|
320
|
+
/**
|
|
321
|
+
* One-line description of what connecting this service enables. Surfaced in the
|
|
322
|
+
* connect UI / `cleo service list`. Non-secret.
|
|
323
|
+
*/
|
|
324
|
+
readonly description: string;
|
|
325
|
+
/**
|
|
326
|
+
* Non-empty list of host-match rules (T11938 AC1/AC4) declaring which upstream
|
|
327
|
+
* hosts this provider's resolved credential is valid for and how it is injected
|
|
328
|
+
* ({@link ServiceHostRule}). The registry-completeness test asserts EVERY
|
|
329
|
+
* provider carries at least one rule. Cannibalized from onecli
|
|
330
|
+
* `apps.rs::AppProvider.host_rules` (DATA only).
|
|
331
|
+
*/
|
|
332
|
+
readonly hostRules: readonly ServiceHostRule[];
|
|
333
|
+
/**
|
|
334
|
+
* Refresh configuration (T11938 AC1/AC3). Present for every `oauth2` /
|
|
335
|
+
* `oauth2-pkce` provider that can refresh; `undefined` for an `api-key` service
|
|
336
|
+
* (no token to refresh) or an OAuth service that only re-authorizes. The OAuth
|
|
337
|
+
* flow (T11939) reads {@link RefreshConfig.kind} to pick the refresh grant.
|
|
338
|
+
*/
|
|
339
|
+
readonly refresh?: RefreshConfig;
|
|
340
|
+
/**
|
|
341
|
+
* Optional header-injection rules sourced from the credential blob (T11938 AC1).
|
|
342
|
+
* Declarative reach metadata; the injecting proxy is a later epic.
|
|
343
|
+
*/
|
|
344
|
+
readonly credentialHeaders?: readonly CredentialHeaderRule[];
|
|
345
|
+
/**
|
|
346
|
+
* Optional header-injection rules sourced from connection metadata (T11938 AC1).
|
|
347
|
+
* Declarative reach metadata; the injecting proxy is a later epic.
|
|
348
|
+
*/
|
|
349
|
+
readonly metadataHeaders?: readonly MetadataHeaderRule[];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// Shared refresh configs (T11938) — frozen DATA reused across provider families.
|
|
354
|
+
// Mirror onecli `apps.rs` statics (GOOGLE_REFRESH, ATLASSIAN_REFRESH, …). These
|
|
355
|
+
// are `const` object literals (no runtime helper) so contracts-purity (Gate 10)
|
|
356
|
+
// stays green.
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
/** Shared refresh config for every Google OAuth API (form body, client-id/secret in body). */
|
|
360
|
+
const GOOGLE_REFRESH: RefreshConfig = {
|
|
361
|
+
kind: 'refresh-token',
|
|
362
|
+
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
363
|
+
bodyFormat: 'form',
|
|
364
|
+
clientAuth: 'body',
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/** Shared refresh config for the Atlassian OAuth APIs (Jira, Confluence) — JSON body. */
|
|
368
|
+
const ATLASSIAN_REFRESH: RefreshConfig = {
|
|
369
|
+
kind: 'refresh-token',
|
|
370
|
+
tokenUrl: 'https://auth.atlassian.com/oauth/token',
|
|
371
|
+
bodyFormat: 'json',
|
|
372
|
+
clientAuth: 'body',
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* The full service-provider census — **~40 declarative entries** (T11938 BREADTH).
|
|
377
|
+
*
|
|
378
|
+
* Spread into {@link SERVICE_PROVIDERS} after the github/google seeds. Each entry
|
|
379
|
+
* is PURE DATA cannibalized from onecli `apps.rs::APP_PROVIDERS` (host rules,
|
|
380
|
+
* refresh shape, display name) — no code, no runtime helper. Three special-case
|
|
381
|
+
* refresh GRANTS are declared as {@link RefreshKind} discriminants (T11938 AC3):
|
|
382
|
+
* `github-app` (GitHub App), `client-credentials` (MongoDB Atlas SAs). The OAuth
|
|
383
|
+
* flow (T11939) branches on `refresh.kind` to drive the right grant.
|
|
384
|
+
*
|
|
385
|
+
* Default OAuth `clientId`s are PLACEHOLDER public client ids (BYOC-overridden at
|
|
386
|
+
* flow time from `service_configs`); they are not secrets.
|
|
387
|
+
*
|
|
388
|
+
* @task T11938
|
|
389
|
+
*/
|
|
390
|
+
const SERVICE_PROVIDERS_BREADTH: Readonly<Record<string, ServiceProviderDef>> = {
|
|
391
|
+
// ── GitHub App (installation token via signed RS256 JWT — special refresh) ──
|
|
392
|
+
'github-app': {
|
|
393
|
+
provider: 'github-app',
|
|
394
|
+
displayName: 'GitHub App',
|
|
395
|
+
authKind: 'oauth2',
|
|
396
|
+
description: 'GitHub App installation access — short-lived installation tokens.',
|
|
397
|
+
defaultScopes: '',
|
|
398
|
+
refresh: {
|
|
399
|
+
kind: 'github-app',
|
|
400
|
+
tokenUrl: 'https://api.github.com/app/installations',
|
|
401
|
+
},
|
|
402
|
+
hostRules: [
|
|
403
|
+
{ host: 'api.github.com', strategy: 'bearer' },
|
|
404
|
+
{ host: 'github.com', strategy: 'basic-x-access-token' },
|
|
405
|
+
{ host: 'raw.githubusercontent.com', strategy: 'bearer' },
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
// ── Google API family (all share GOOGLE_REFRESH + the google PKCE oauth) ──
|
|
409
|
+
gmail: {
|
|
410
|
+
provider: 'gmail',
|
|
411
|
+
displayName: 'Gmail',
|
|
412
|
+
authKind: 'oauth2-pkce',
|
|
413
|
+
description: 'Read, search, and send mail via the Gmail API.',
|
|
414
|
+
defaultScopes: 'https://www.googleapis.com/auth/gmail.modify',
|
|
415
|
+
oauth: {
|
|
416
|
+
mode: 'pkce',
|
|
417
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
418
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
419
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
420
|
+
scope: 'https://www.googleapis.com/auth/gmail.modify',
|
|
421
|
+
redirectUri: 'http://127.0.0.1:7878/service/gmail/callback',
|
|
422
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
423
|
+
},
|
|
424
|
+
refresh: GOOGLE_REFRESH,
|
|
425
|
+
hostRules: [
|
|
426
|
+
{ host: 'gmail.googleapis.com', strategy: 'bearer' },
|
|
427
|
+
{ host: 'www.googleapis.com', pathPrefix: '/gmail/', strategy: 'bearer' },
|
|
428
|
+
],
|
|
429
|
+
},
|
|
430
|
+
'google-calendar': {
|
|
431
|
+
provider: 'google-calendar',
|
|
432
|
+
displayName: 'Google Calendar',
|
|
433
|
+
authKind: 'oauth2-pkce',
|
|
434
|
+
description: 'Manage calendars and events via the Google Calendar API.',
|
|
435
|
+
defaultScopes: 'https://www.googleapis.com/auth/calendar',
|
|
436
|
+
oauth: {
|
|
437
|
+
mode: 'pkce',
|
|
438
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
439
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
440
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
441
|
+
scope: 'https://www.googleapis.com/auth/calendar',
|
|
442
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-calendar/callback',
|
|
443
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
444
|
+
},
|
|
445
|
+
refresh: GOOGLE_REFRESH,
|
|
446
|
+
hostRules: [{ host: 'www.googleapis.com', pathPrefix: '/calendar/', strategy: 'bearer' }],
|
|
447
|
+
},
|
|
448
|
+
'google-drive': {
|
|
449
|
+
provider: 'google-drive',
|
|
450
|
+
displayName: 'Google Drive',
|
|
451
|
+
authKind: 'oauth2-pkce',
|
|
452
|
+
description: 'List, read, and write files via the Google Drive API.',
|
|
453
|
+
defaultScopes: 'https://www.googleapis.com/auth/drive',
|
|
454
|
+
oauth: {
|
|
455
|
+
mode: 'pkce',
|
|
456
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
457
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
458
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
459
|
+
scope: 'https://www.googleapis.com/auth/drive',
|
|
460
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-drive/callback',
|
|
461
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
462
|
+
},
|
|
463
|
+
refresh: GOOGLE_REFRESH,
|
|
464
|
+
hostRules: [
|
|
465
|
+
{ host: 'www.googleapis.com', pathPrefix: '/drive/', strategy: 'bearer' },
|
|
466
|
+
{ host: 'www.googleapis.com', pathPrefix: '/upload/drive/', strategy: 'bearer' },
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
'google-docs': {
|
|
470
|
+
provider: 'google-docs',
|
|
471
|
+
displayName: 'Google Docs',
|
|
472
|
+
authKind: 'oauth2-pkce',
|
|
473
|
+
description: 'Read and edit documents via the Google Docs API.',
|
|
474
|
+
defaultScopes: 'https://www.googleapis.com/auth/documents',
|
|
475
|
+
oauth: {
|
|
476
|
+
mode: 'pkce',
|
|
477
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
478
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
479
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
480
|
+
scope: 'https://www.googleapis.com/auth/documents',
|
|
481
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-docs/callback',
|
|
482
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
483
|
+
},
|
|
484
|
+
refresh: GOOGLE_REFRESH,
|
|
485
|
+
hostRules: [{ host: 'docs.googleapis.com', strategy: 'bearer' }],
|
|
486
|
+
},
|
|
487
|
+
'google-sheets': {
|
|
488
|
+
provider: 'google-sheets',
|
|
489
|
+
displayName: 'Google Sheets',
|
|
490
|
+
authKind: 'oauth2-pkce',
|
|
491
|
+
description: 'Read and write spreadsheets via the Google Sheets API.',
|
|
492
|
+
defaultScopes: 'https://www.googleapis.com/auth/spreadsheets',
|
|
493
|
+
oauth: {
|
|
494
|
+
mode: 'pkce',
|
|
495
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
496
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
497
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
498
|
+
scope: 'https://www.googleapis.com/auth/spreadsheets',
|
|
499
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-sheets/callback',
|
|
500
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
501
|
+
},
|
|
502
|
+
refresh: GOOGLE_REFRESH,
|
|
503
|
+
hostRules: [{ host: 'sheets.googleapis.com', strategy: 'bearer' }],
|
|
504
|
+
},
|
|
505
|
+
'google-slides': {
|
|
506
|
+
provider: 'google-slides',
|
|
507
|
+
displayName: 'Google Slides',
|
|
508
|
+
authKind: 'oauth2-pkce',
|
|
509
|
+
description: 'Read and edit presentations via the Google Slides API.',
|
|
510
|
+
defaultScopes: 'https://www.googleapis.com/auth/presentations',
|
|
511
|
+
oauth: {
|
|
512
|
+
mode: 'pkce',
|
|
513
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
514
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
515
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
516
|
+
scope: 'https://www.googleapis.com/auth/presentations',
|
|
517
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-slides/callback',
|
|
518
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
519
|
+
},
|
|
520
|
+
refresh: GOOGLE_REFRESH,
|
|
521
|
+
hostRules: [{ host: 'slides.googleapis.com', strategy: 'bearer' }],
|
|
522
|
+
},
|
|
523
|
+
'google-tasks': {
|
|
524
|
+
provider: 'google-tasks',
|
|
525
|
+
displayName: 'Google Tasks',
|
|
526
|
+
authKind: 'oauth2-pkce',
|
|
527
|
+
description: 'Manage task lists via the Google Tasks API.',
|
|
528
|
+
defaultScopes: 'https://www.googleapis.com/auth/tasks',
|
|
529
|
+
oauth: {
|
|
530
|
+
mode: 'pkce',
|
|
531
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
532
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
533
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
534
|
+
scope: 'https://www.googleapis.com/auth/tasks',
|
|
535
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-tasks/callback',
|
|
536
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
537
|
+
},
|
|
538
|
+
refresh: GOOGLE_REFRESH,
|
|
539
|
+
hostRules: [{ host: 'tasks.googleapis.com', strategy: 'bearer' }],
|
|
540
|
+
},
|
|
541
|
+
'google-forms': {
|
|
542
|
+
provider: 'google-forms',
|
|
543
|
+
displayName: 'Google Forms',
|
|
544
|
+
authKind: 'oauth2-pkce',
|
|
545
|
+
description: 'Read forms and responses via the Google Forms API.',
|
|
546
|
+
defaultScopes: 'https://www.googleapis.com/auth/forms.body',
|
|
547
|
+
oauth: {
|
|
548
|
+
mode: 'pkce',
|
|
549
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
550
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
551
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
552
|
+
scope: 'https://www.googleapis.com/auth/forms.body',
|
|
553
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-forms/callback',
|
|
554
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
555
|
+
},
|
|
556
|
+
refresh: GOOGLE_REFRESH,
|
|
557
|
+
hostRules: [{ host: 'forms.googleapis.com', strategy: 'bearer' }],
|
|
558
|
+
},
|
|
559
|
+
'google-classroom': {
|
|
560
|
+
provider: 'google-classroom',
|
|
561
|
+
displayName: 'Google Classroom',
|
|
562
|
+
authKind: 'oauth2-pkce',
|
|
563
|
+
description: 'Manage courses and coursework via the Google Classroom API.',
|
|
564
|
+
defaultScopes: 'https://www.googleapis.com/auth/classroom.courses',
|
|
565
|
+
oauth: {
|
|
566
|
+
mode: 'pkce',
|
|
567
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
568
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
569
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
570
|
+
scope: 'https://www.googleapis.com/auth/classroom.courses',
|
|
571
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-classroom/callback',
|
|
572
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
573
|
+
},
|
|
574
|
+
refresh: GOOGLE_REFRESH,
|
|
575
|
+
hostRules: [{ host: 'classroom.googleapis.com', strategy: 'bearer' }],
|
|
576
|
+
},
|
|
577
|
+
'google-admin': {
|
|
578
|
+
provider: 'google-admin',
|
|
579
|
+
displayName: 'Google Admin',
|
|
580
|
+
authKind: 'oauth2-pkce',
|
|
581
|
+
description: 'Directory and admin operations via the Google Admin SDK.',
|
|
582
|
+
defaultScopes: 'https://www.googleapis.com/auth/admin.directory.user',
|
|
583
|
+
oauth: {
|
|
584
|
+
mode: 'pkce',
|
|
585
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
586
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
587
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
588
|
+
scope: 'https://www.googleapis.com/auth/admin.directory.user',
|
|
589
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-admin/callback',
|
|
590
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
591
|
+
},
|
|
592
|
+
refresh: GOOGLE_REFRESH,
|
|
593
|
+
hostRules: [{ host: 'admin.googleapis.com', strategy: 'bearer' }],
|
|
594
|
+
},
|
|
595
|
+
'google-analytics': {
|
|
596
|
+
provider: 'google-analytics',
|
|
597
|
+
displayName: 'Google Analytics',
|
|
598
|
+
authKind: 'oauth2-pkce',
|
|
599
|
+
description: 'Query reports via the Google Analytics Data API.',
|
|
600
|
+
defaultScopes: 'https://www.googleapis.com/auth/analytics.readonly',
|
|
601
|
+
oauth: {
|
|
602
|
+
mode: 'pkce',
|
|
603
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
604
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
605
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
606
|
+
scope: 'https://www.googleapis.com/auth/analytics.readonly',
|
|
607
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-analytics/callback',
|
|
608
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
609
|
+
},
|
|
610
|
+
refresh: GOOGLE_REFRESH,
|
|
611
|
+
hostRules: [{ host: 'analyticsdata.googleapis.com', strategy: 'bearer' }],
|
|
612
|
+
},
|
|
613
|
+
'google-search-console': {
|
|
614
|
+
provider: 'google-search-console',
|
|
615
|
+
displayName: 'Google Search Console',
|
|
616
|
+
authKind: 'oauth2-pkce',
|
|
617
|
+
description: 'Query search analytics via the Google Search Console API.',
|
|
618
|
+
defaultScopes: 'https://www.googleapis.com/auth/webmasters.readonly',
|
|
619
|
+
oauth: {
|
|
620
|
+
mode: 'pkce',
|
|
621
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
622
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
623
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
624
|
+
scope: 'https://www.googleapis.com/auth/webmasters.readonly',
|
|
625
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-search-console/callback',
|
|
626
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
627
|
+
},
|
|
628
|
+
refresh: GOOGLE_REFRESH,
|
|
629
|
+
hostRules: [
|
|
630
|
+
{ host: 'searchconsole.googleapis.com', strategy: 'bearer' },
|
|
631
|
+
{ host: 'www.googleapis.com', pathPrefix: '/webmasters/', strategy: 'bearer' },
|
|
632
|
+
],
|
|
633
|
+
},
|
|
634
|
+
'google-meet': {
|
|
635
|
+
provider: 'google-meet',
|
|
636
|
+
displayName: 'Google Meet',
|
|
637
|
+
authKind: 'oauth2-pkce',
|
|
638
|
+
description: 'Manage meeting spaces and conference records via the Google Meet API.',
|
|
639
|
+
defaultScopes: 'https://www.googleapis.com/auth/meetings.space.created',
|
|
640
|
+
oauth: {
|
|
641
|
+
mode: 'pkce',
|
|
642
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
643
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
644
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
645
|
+
scope: 'https://www.googleapis.com/auth/meetings.space.created',
|
|
646
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-meet/callback',
|
|
647
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
648
|
+
},
|
|
649
|
+
refresh: GOOGLE_REFRESH,
|
|
650
|
+
hostRules: [{ host: 'meet.googleapis.com', strategy: 'bearer' }],
|
|
651
|
+
},
|
|
652
|
+
'google-photos': {
|
|
653
|
+
provider: 'google-photos',
|
|
654
|
+
displayName: 'Google Photos',
|
|
655
|
+
authKind: 'oauth2-pkce',
|
|
656
|
+
description: 'Read media items and albums via the Google Photos Library API.',
|
|
657
|
+
defaultScopes: 'https://www.googleapis.com/auth/photoslibrary.readonly',
|
|
658
|
+
oauth: {
|
|
659
|
+
mode: 'pkce',
|
|
660
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
661
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
662
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
663
|
+
scope: 'https://www.googleapis.com/auth/photoslibrary.readonly',
|
|
664
|
+
redirectUri: 'http://127.0.0.1:7878/service/google-photos/callback',
|
|
665
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
666
|
+
},
|
|
667
|
+
refresh: GOOGLE_REFRESH,
|
|
668
|
+
hostRules: [{ host: 'photoslibrary.googleapis.com', strategy: 'bearer' }],
|
|
669
|
+
},
|
|
670
|
+
youtube: {
|
|
671
|
+
provider: 'youtube',
|
|
672
|
+
displayName: 'YouTube',
|
|
673
|
+
authKind: 'oauth2-pkce',
|
|
674
|
+
description: 'Manage channels, videos, and playlists via the YouTube Data API.',
|
|
675
|
+
defaultScopes: 'https://www.googleapis.com/auth/youtube',
|
|
676
|
+
oauth: {
|
|
677
|
+
mode: 'pkce',
|
|
678
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
679
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
680
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
681
|
+
scope: 'https://www.googleapis.com/auth/youtube',
|
|
682
|
+
redirectUri: 'http://127.0.0.1:7878/service/youtube/callback',
|
|
683
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
684
|
+
},
|
|
685
|
+
refresh: GOOGLE_REFRESH,
|
|
686
|
+
hostRules: [{ host: 'www.googleapis.com', pathPrefix: '/youtube/', strategy: 'bearer' }],
|
|
687
|
+
},
|
|
688
|
+
'vertex-ai': {
|
|
689
|
+
provider: 'vertex-ai',
|
|
690
|
+
displayName: 'Vertex AI',
|
|
691
|
+
authKind: 'oauth2-pkce',
|
|
692
|
+
description: 'Call Google Cloud Vertex AI model endpoints.',
|
|
693
|
+
defaultScopes: 'https://www.googleapis.com/auth/cloud-platform',
|
|
694
|
+
oauth: {
|
|
695
|
+
mode: 'pkce',
|
|
696
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
697
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
698
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
699
|
+
scope: 'https://www.googleapis.com/auth/cloud-platform',
|
|
700
|
+
redirectUri: 'http://127.0.0.1:7878/service/vertex-ai/callback',
|
|
701
|
+
extraAuthParams: { access_type: 'offline', prompt: 'consent' },
|
|
702
|
+
},
|
|
703
|
+
refresh: GOOGLE_REFRESH,
|
|
704
|
+
hostRules: [
|
|
705
|
+
{ host: 'aiplatform.googleapis.com', strategy: 'bearer' },
|
|
706
|
+
{ host: 'oauth2.googleapis.com', strategy: 'bearer' },
|
|
707
|
+
],
|
|
708
|
+
metadataHeaders: [{ metadataKey: 'projectId', headerName: 'x-goog-user-project' }],
|
|
709
|
+
},
|
|
710
|
+
// ── Atlassian family (JSON refresh body) ──
|
|
711
|
+
jira: {
|
|
712
|
+
provider: 'jira',
|
|
713
|
+
displayName: 'Jira',
|
|
714
|
+
authKind: 'oauth2',
|
|
715
|
+
description: 'Issues, projects, and workflows via the Jira Cloud REST API.',
|
|
716
|
+
defaultScopes: 'read:jira-work write:jira-work offline_access',
|
|
717
|
+
oauth: {
|
|
718
|
+
mode: 'pkce',
|
|
719
|
+
clientId: 'cleo-atlassian-client-id',
|
|
720
|
+
authorizationEndpoint: 'https://auth.atlassian.com/authorize',
|
|
721
|
+
tokenEndpoint: 'https://auth.atlassian.com/oauth/token',
|
|
722
|
+
scope: 'read:jira-work write:jira-work offline_access',
|
|
723
|
+
redirectUri: 'http://127.0.0.1:7878/service/jira/callback',
|
|
724
|
+
extraAuthParams: { audience: 'api.atlassian.com', prompt: 'consent' },
|
|
725
|
+
},
|
|
726
|
+
refresh: ATLASSIAN_REFRESH,
|
|
727
|
+
hostRules: [{ host: 'api.atlassian.com', strategy: 'bearer' }],
|
|
728
|
+
},
|
|
729
|
+
confluence: {
|
|
730
|
+
provider: 'confluence',
|
|
731
|
+
displayName: 'Confluence',
|
|
732
|
+
authKind: 'oauth2',
|
|
733
|
+
description: 'Spaces, pages, and content via the Confluence Cloud REST API.',
|
|
734
|
+
defaultScopes: 'read:confluence-content.all write:confluence-content offline_access',
|
|
735
|
+
oauth: {
|
|
736
|
+
mode: 'pkce',
|
|
737
|
+
clientId: 'cleo-atlassian-client-id',
|
|
738
|
+
authorizationEndpoint: 'https://auth.atlassian.com/authorize',
|
|
739
|
+
tokenEndpoint: 'https://auth.atlassian.com/oauth/token',
|
|
740
|
+
scope: 'read:confluence-content.all write:confluence-content offline_access',
|
|
741
|
+
redirectUri: 'http://127.0.0.1:7878/service/confluence/callback',
|
|
742
|
+
extraAuthParams: { audience: 'api.atlassian.com', prompt: 'consent' },
|
|
743
|
+
},
|
|
744
|
+
refresh: ATLASSIAN_REFRESH,
|
|
745
|
+
hostRules: [{ host: 'api.atlassian.com', strategy: 'bearer' }],
|
|
746
|
+
},
|
|
747
|
+
// ── Notion (JSON body + Basic-auth client credentials) ──
|
|
748
|
+
notion: {
|
|
749
|
+
provider: 'notion',
|
|
750
|
+
displayName: 'Notion',
|
|
751
|
+
authKind: 'oauth2',
|
|
752
|
+
description: 'Databases, pages, and blocks via the Notion API.',
|
|
753
|
+
defaultScopes: '',
|
|
754
|
+
oauth: {
|
|
755
|
+
mode: 'pkce',
|
|
756
|
+
clientId: 'cleo-notion-client-id',
|
|
757
|
+
authorizationEndpoint: 'https://api.notion.com/v1/oauth/authorize',
|
|
758
|
+
tokenEndpoint: 'https://api.notion.com/v1/oauth/token',
|
|
759
|
+
scope: '',
|
|
760
|
+
redirectUri: 'http://127.0.0.1:7878/service/notion/callback',
|
|
761
|
+
extraAuthParams: { owner: 'user' },
|
|
762
|
+
},
|
|
763
|
+
refresh: {
|
|
764
|
+
kind: 'refresh-token',
|
|
765
|
+
tokenUrl: 'https://api.notion.com/v1/oauth/token',
|
|
766
|
+
bodyFormat: 'json',
|
|
767
|
+
clientAuth: 'basic-auth',
|
|
768
|
+
},
|
|
769
|
+
hostRules: [{ host: 'api.notion.com', strategy: 'bearer' }],
|
|
770
|
+
},
|
|
771
|
+
// ── Dropbox (form body) ──
|
|
772
|
+
dropbox: {
|
|
773
|
+
provider: 'dropbox',
|
|
774
|
+
displayName: 'Dropbox',
|
|
775
|
+
authKind: 'oauth2',
|
|
776
|
+
description: 'Files, folders, and sharing via the Dropbox API.',
|
|
777
|
+
defaultScopes: 'files.content.read files.content.write account_info.read',
|
|
778
|
+
oauth: {
|
|
779
|
+
mode: 'pkce',
|
|
780
|
+
clientId: 'cleo-dropbox-client-id',
|
|
781
|
+
authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize',
|
|
782
|
+
tokenEndpoint: 'https://api.dropboxapi.com/oauth2/token',
|
|
783
|
+
scope: 'files.content.read files.content.write account_info.read',
|
|
784
|
+
redirectUri: 'http://127.0.0.1:7878/service/dropbox/callback',
|
|
785
|
+
extraAuthParams: { token_access_type: 'offline' },
|
|
786
|
+
},
|
|
787
|
+
refresh: {
|
|
788
|
+
kind: 'refresh-token',
|
|
789
|
+
tokenUrl: 'https://api.dropboxapi.com/oauth2/token',
|
|
790
|
+
bodyFormat: 'form',
|
|
791
|
+
clientAuth: 'body',
|
|
792
|
+
},
|
|
793
|
+
hostRules: [
|
|
794
|
+
{ host: 'api.dropboxapi.com', strategy: 'bearer' },
|
|
795
|
+
{ host: 'content.dropboxapi.com', strategy: 'bearer' },
|
|
796
|
+
],
|
|
797
|
+
},
|
|
798
|
+
// ── LinkedIn (form body) ──
|
|
799
|
+
linkedin: {
|
|
800
|
+
provider: 'linkedin',
|
|
801
|
+
displayName: 'LinkedIn',
|
|
802
|
+
authKind: 'oauth2',
|
|
803
|
+
description: 'Profile, posts, and shares via the LinkedIn API.',
|
|
804
|
+
defaultScopes: 'r_liteprofile r_emailaddress w_member_social',
|
|
805
|
+
oauth: {
|
|
806
|
+
mode: 'pkce',
|
|
807
|
+
clientId: 'cleo-linkedin-client-id',
|
|
808
|
+
authorizationEndpoint: 'https://www.linkedin.com/oauth/v2/authorization',
|
|
809
|
+
tokenEndpoint: 'https://www.linkedin.com/oauth/v2/accessToken',
|
|
810
|
+
scope: 'r_liteprofile r_emailaddress w_member_social',
|
|
811
|
+
redirectUri: 'http://127.0.0.1:7878/service/linkedin/callback',
|
|
812
|
+
},
|
|
813
|
+
refresh: {
|
|
814
|
+
kind: 'refresh-token',
|
|
815
|
+
tokenUrl: 'https://www.linkedin.com/oauth/v2/accessToken',
|
|
816
|
+
bodyFormat: 'form',
|
|
817
|
+
clientAuth: 'body',
|
|
818
|
+
},
|
|
819
|
+
hostRules: [{ host: 'api.linkedin.com', strategy: 'bearer' }],
|
|
820
|
+
},
|
|
821
|
+
// ── Todoist (form body) ──
|
|
822
|
+
todoist: {
|
|
823
|
+
provider: 'todoist',
|
|
824
|
+
displayName: 'Todoist',
|
|
825
|
+
authKind: 'oauth2',
|
|
826
|
+
description: 'Tasks, projects, and labels via the Todoist REST API.',
|
|
827
|
+
defaultScopes: 'data:read_write',
|
|
828
|
+
oauth: {
|
|
829
|
+
mode: 'pkce',
|
|
830
|
+
clientId: 'cleo-todoist-client-id',
|
|
831
|
+
authorizationEndpoint: 'https://todoist.com/oauth/authorize',
|
|
832
|
+
tokenEndpoint: 'https://api.todoist.com/oauth/access_token',
|
|
833
|
+
scope: 'data:read_write',
|
|
834
|
+
redirectUri: 'http://127.0.0.1:7878/service/todoist/callback',
|
|
835
|
+
},
|
|
836
|
+
refresh: {
|
|
837
|
+
kind: 'refresh-token',
|
|
838
|
+
tokenUrl: 'https://api.todoist.com/oauth/access_token',
|
|
839
|
+
bodyFormat: 'form',
|
|
840
|
+
clientAuth: 'body',
|
|
841
|
+
},
|
|
842
|
+
hostRules: [{ host: 'api.todoist.com', strategy: 'bearer' }],
|
|
843
|
+
},
|
|
844
|
+
// ── Supabase Management API (form body + Basic-auth client credentials) ──
|
|
845
|
+
supabase: {
|
|
846
|
+
provider: 'supabase',
|
|
847
|
+
displayName: 'Supabase',
|
|
848
|
+
authKind: 'oauth2',
|
|
849
|
+
description: 'Projects and configuration via the Supabase Management API.',
|
|
850
|
+
defaultScopes: 'all',
|
|
851
|
+
oauth: {
|
|
852
|
+
mode: 'pkce',
|
|
853
|
+
clientId: 'cleo-supabase-client-id',
|
|
854
|
+
authorizationEndpoint: 'https://api.supabase.com/v1/oauth/authorize',
|
|
855
|
+
tokenEndpoint: 'https://api.supabase.com/v1/oauth/token',
|
|
856
|
+
scope: 'all',
|
|
857
|
+
redirectUri: 'http://127.0.0.1:7878/service/supabase/callback',
|
|
858
|
+
},
|
|
859
|
+
refresh: {
|
|
860
|
+
kind: 'refresh-token',
|
|
861
|
+
tokenUrl: 'https://api.supabase.com/v1/oauth/token',
|
|
862
|
+
bodyFormat: 'form',
|
|
863
|
+
clientAuth: 'basic-auth',
|
|
864
|
+
},
|
|
865
|
+
hostRules: [{ host: 'api.supabase.com', strategy: 'bearer' }],
|
|
866
|
+
},
|
|
867
|
+
// ── MongoDB Atlas (client_credentials grant — special refresh) ──
|
|
868
|
+
'mongodb-atlas': {
|
|
869
|
+
provider: 'mongodb-atlas',
|
|
870
|
+
displayName: 'MongoDB Atlas',
|
|
871
|
+
authKind: 'oauth2',
|
|
872
|
+
description: 'Clusters and projects via the MongoDB Atlas Administration API.',
|
|
873
|
+
defaultScopes: '',
|
|
874
|
+
refresh: {
|
|
875
|
+
kind: 'client-credentials',
|
|
876
|
+
tokenUrl: 'https://cloud.mongodb.com/api/oauth/token',
|
|
877
|
+
},
|
|
878
|
+
hostRules: [{ host: 'cloud.mongodb.com', strategy: 'bearer' }],
|
|
879
|
+
},
|
|
880
|
+
// ── monday.com (api-key — no OAuth refresh) ──
|
|
881
|
+
monday: {
|
|
882
|
+
provider: 'monday',
|
|
883
|
+
displayName: 'monday.com',
|
|
884
|
+
authKind: 'oauth2',
|
|
885
|
+
description: 'Boards, items, and updates via the monday.com GraphQL API.',
|
|
886
|
+
defaultScopes: 'me:read boards:read boards:write',
|
|
887
|
+
oauth: {
|
|
888
|
+
mode: 'pkce',
|
|
889
|
+
clientId: 'cleo-monday-client-id',
|
|
890
|
+
authorizationEndpoint: 'https://auth.monday.com/oauth2/authorize',
|
|
891
|
+
tokenEndpoint: 'https://auth.monday.com/oauth2/token',
|
|
892
|
+
scope: 'me:read boards:read boards:write',
|
|
893
|
+
redirectUri: 'http://127.0.0.1:7878/service/monday/callback',
|
|
894
|
+
},
|
|
895
|
+
hostRules: [{ host: 'api.monday.com', strategy: 'bearer' }],
|
|
896
|
+
},
|
|
897
|
+
// ── Resend (api-key) ──
|
|
898
|
+
resend: {
|
|
899
|
+
provider: 'resend',
|
|
900
|
+
displayName: 'Resend',
|
|
901
|
+
authKind: 'api-key',
|
|
902
|
+
description: 'Send transactional email via the Resend API (API-key auth).',
|
|
903
|
+
defaultScopes: 'full_access',
|
|
904
|
+
hostRules: [{ host: 'api.resend.com', strategy: 'bearer' }],
|
|
905
|
+
},
|
|
906
|
+
// ── Cloudflare (api-key) ──
|
|
907
|
+
cloudflare: {
|
|
908
|
+
provider: 'cloudflare',
|
|
909
|
+
displayName: 'Cloudflare',
|
|
910
|
+
authKind: 'api-key',
|
|
911
|
+
description: 'Zones, DNS, and Workers via the Cloudflare API (API-token auth).',
|
|
912
|
+
defaultScopes: '',
|
|
913
|
+
hostRules: [{ host: 'api.cloudflare.com', strategy: 'bearer' }],
|
|
914
|
+
},
|
|
915
|
+
// ── Fly.io (api-key) ──
|
|
916
|
+
flyio: {
|
|
917
|
+
provider: 'flyio',
|
|
918
|
+
displayName: 'Fly.io',
|
|
919
|
+
authKind: 'api-key',
|
|
920
|
+
description: 'Machines and apps via the Fly.io Machines API (API-token auth).',
|
|
921
|
+
defaultScopes: '',
|
|
922
|
+
hostRules: [
|
|
923
|
+
{ host: 'api.machines.dev', strategy: 'bearer' },
|
|
924
|
+
{ host: 'api.fly.io', strategy: 'bearer' },
|
|
925
|
+
],
|
|
926
|
+
},
|
|
927
|
+
// ── AWS (api-key/SigV4 — credential headers; no token refresh) ──
|
|
928
|
+
aws: {
|
|
929
|
+
provider: 'aws',
|
|
930
|
+
displayName: 'AWS',
|
|
931
|
+
authKind: 'api-key',
|
|
932
|
+
description: 'AWS service APIs signed with SigV4 (access-key credentials).',
|
|
933
|
+
defaultScopes: '',
|
|
934
|
+
hostRules: [{ host: 'amazonaws.com', strategy: 'header' }],
|
|
935
|
+
credentialHeaders: [{ credentialField: 'accessKeyId', headerName: 'x-amz-access-key-id' }],
|
|
936
|
+
},
|
|
937
|
+
// ── Figma (form body refresh) ──
|
|
938
|
+
figma: {
|
|
939
|
+
provider: 'figma',
|
|
940
|
+
displayName: 'Figma',
|
|
941
|
+
authKind: 'oauth2',
|
|
942
|
+
description: 'Files, projects, and comments via the Figma REST API.',
|
|
943
|
+
defaultScopes: 'file_read',
|
|
944
|
+
oauth: {
|
|
945
|
+
mode: 'pkce',
|
|
946
|
+
clientId: 'cleo-figma-client-id',
|
|
947
|
+
authorizationEndpoint: 'https://www.figma.com/oauth',
|
|
948
|
+
tokenEndpoint: 'https://api.figma.com/v1/oauth/token',
|
|
949
|
+
scope: 'file_read',
|
|
950
|
+
redirectUri: 'http://127.0.0.1:7878/service/figma/callback',
|
|
951
|
+
},
|
|
952
|
+
refresh: {
|
|
953
|
+
kind: 'refresh-token',
|
|
954
|
+
tokenUrl: 'https://api.figma.com/v1/oauth/refresh',
|
|
955
|
+
bodyFormat: 'form',
|
|
956
|
+
clientAuth: 'body',
|
|
957
|
+
},
|
|
958
|
+
hostRules: [{ host: 'api.figma.com', strategy: 'bearer' }],
|
|
959
|
+
},
|
|
960
|
+
// ── Linear (form body refresh) ──
|
|
961
|
+
linear: {
|
|
962
|
+
provider: 'linear',
|
|
963
|
+
displayName: 'Linear',
|
|
964
|
+
authKind: 'oauth2',
|
|
965
|
+
description: 'Issues, projects, and cycles via the Linear GraphQL API.',
|
|
966
|
+
defaultScopes: 'read write',
|
|
967
|
+
oauth: {
|
|
968
|
+
mode: 'pkce',
|
|
969
|
+
clientId: 'cleo-linear-client-id',
|
|
970
|
+
authorizationEndpoint: 'https://linear.app/oauth/authorize',
|
|
971
|
+
tokenEndpoint: 'https://api.linear.app/oauth/token',
|
|
972
|
+
scope: 'read write',
|
|
973
|
+
redirectUri: 'http://127.0.0.1:7878/service/linear/callback',
|
|
974
|
+
},
|
|
975
|
+
refresh: {
|
|
976
|
+
kind: 'refresh-token',
|
|
977
|
+
tokenUrl: 'https://api.linear.app/oauth/token',
|
|
978
|
+
bodyFormat: 'form',
|
|
979
|
+
clientAuth: 'body',
|
|
980
|
+
},
|
|
981
|
+
hostRules: [{ host: 'api.linear.app', strategy: 'bearer' }],
|
|
982
|
+
},
|
|
983
|
+
// ── Slack (form body refresh; token rotation) ──
|
|
984
|
+
slack: {
|
|
985
|
+
provider: 'slack',
|
|
986
|
+
displayName: 'Slack',
|
|
987
|
+
authKind: 'oauth2',
|
|
988
|
+
description: 'Channels, messages, and users via the Slack Web API.',
|
|
989
|
+
defaultScopes: 'channels:read chat:write users:read',
|
|
990
|
+
oauth: {
|
|
991
|
+
mode: 'pkce',
|
|
992
|
+
clientId: 'cleo-slack-client-id',
|
|
993
|
+
authorizationEndpoint: 'https://slack.com/oauth/v2/authorize',
|
|
994
|
+
tokenEndpoint: 'https://slack.com/api/oauth.v2.access',
|
|
995
|
+
scope: 'channels:read chat:write users:read',
|
|
996
|
+
redirectUri: 'http://127.0.0.1:7878/service/slack/callback',
|
|
997
|
+
},
|
|
998
|
+
refresh: {
|
|
999
|
+
kind: 'refresh-token',
|
|
1000
|
+
tokenUrl: 'https://slack.com/api/oauth.v2.access',
|
|
1001
|
+
bodyFormat: 'form',
|
|
1002
|
+
clientAuth: 'body',
|
|
1003
|
+
},
|
|
1004
|
+
hostRules: [{ host: 'slack.com', strategy: 'bearer' }],
|
|
1005
|
+
},
|
|
1006
|
+
// ── Discord (form body refresh) ──
|
|
1007
|
+
discord: {
|
|
1008
|
+
provider: 'discord',
|
|
1009
|
+
displayName: 'Discord',
|
|
1010
|
+
authKind: 'oauth2',
|
|
1011
|
+
description: 'Guilds, channels, and messages via the Discord API.',
|
|
1012
|
+
defaultScopes: 'identify guilds',
|
|
1013
|
+
oauth: {
|
|
1014
|
+
mode: 'pkce',
|
|
1015
|
+
clientId: 'cleo-discord-client-id',
|
|
1016
|
+
authorizationEndpoint: 'https://discord.com/oauth2/authorize',
|
|
1017
|
+
tokenEndpoint: 'https://discord.com/api/oauth2/token',
|
|
1018
|
+
scope: 'identify guilds',
|
|
1019
|
+
redirectUri: 'http://127.0.0.1:7878/service/discord/callback',
|
|
1020
|
+
},
|
|
1021
|
+
refresh: {
|
|
1022
|
+
kind: 'refresh-token',
|
|
1023
|
+
tokenUrl: 'https://discord.com/api/oauth2/token',
|
|
1024
|
+
bodyFormat: 'form',
|
|
1025
|
+
clientAuth: 'body',
|
|
1026
|
+
},
|
|
1027
|
+
hostRules: [{ host: 'discord.com', strategy: 'bearer' }],
|
|
1028
|
+
},
|
|
1029
|
+
// ── Sentry (form body refresh) ──
|
|
1030
|
+
sentry: {
|
|
1031
|
+
provider: 'sentry',
|
|
1032
|
+
displayName: 'Sentry',
|
|
1033
|
+
authKind: 'oauth2',
|
|
1034
|
+
description: 'Issues, events, and releases via the Sentry API.',
|
|
1035
|
+
defaultScopes: 'org:read project:read event:read',
|
|
1036
|
+
oauth: {
|
|
1037
|
+
mode: 'pkce',
|
|
1038
|
+
clientId: 'cleo-sentry-client-id',
|
|
1039
|
+
authorizationEndpoint: 'https://sentry.io/oauth/authorize/',
|
|
1040
|
+
tokenEndpoint: 'https://sentry.io/oauth/token/',
|
|
1041
|
+
scope: 'org:read project:read event:read',
|
|
1042
|
+
redirectUri: 'http://127.0.0.1:7878/service/sentry/callback',
|
|
1043
|
+
},
|
|
1044
|
+
refresh: {
|
|
1045
|
+
kind: 'refresh-token',
|
|
1046
|
+
tokenUrl: 'https://sentry.io/oauth/token/',
|
|
1047
|
+
bodyFormat: 'form',
|
|
1048
|
+
clientAuth: 'body',
|
|
1049
|
+
},
|
|
1050
|
+
hostRules: [{ host: 'sentry.io', strategy: 'bearer' }],
|
|
1051
|
+
},
|
|
1052
|
+
// ── Vercel (api-key — no OAuth refresh) ──
|
|
1053
|
+
vercel: {
|
|
1054
|
+
provider: 'vercel',
|
|
1055
|
+
displayName: 'Vercel',
|
|
1056
|
+
authKind: 'api-key',
|
|
1057
|
+
description: 'Deployments, projects, and domains via the Vercel REST API.',
|
|
1058
|
+
defaultScopes: '',
|
|
1059
|
+
hostRules: [{ host: 'api.vercel.com', strategy: 'bearer' }],
|
|
1060
|
+
},
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* The seeded service-provider registry — **github + google + the ~40-service
|
|
1065
|
+
* census** (T11937 seeds + T11938 breadth).
|
|
1066
|
+
*
|
|
1067
|
+
* Keyed by {@link ServiceProviderDef.provider}. This is the SSoT the store reads
|
|
1068
|
+
* when validating a `connect` and the OAuth flow (T11939) reads for default
|
|
1069
|
+
* endpoints/scopes. The two seeds prove the two OAuth shapes:
|
|
1070
|
+
*
|
|
1071
|
+
* - **github** — classic OAuth 2.0 web flow (`authKind: 'oauth2'`), fixed
|
|
1072
|
+
* loopback redirect; user grants `repo`/`read:user` scopes.
|
|
1073
|
+
* - **google** — installed-app OAuth 2.0 + PKCE (`authKind: 'oauth2-pkce'`),
|
|
1074
|
+
* loopback redirect; user grants offline-access + the requested API scopes.
|
|
1075
|
+
*
|
|
1076
|
+
* The `clientId` values are PLACEHOLDER public client ids for CLEO's first-party
|
|
1077
|
+
* OAuth apps — the real ids are wired (or BYOC-overridden) when the OAuth dance
|
|
1078
|
+
* lands in T11939. They are NOT secrets (a public OAuth client id is, by design,
|
|
1079
|
+
* embeddable in a distributed CLI).
|
|
1080
|
+
*
|
|
1081
|
+
* Fan-out to the full ~40-service census is the follow-up (T11938): add a DATA
|
|
1082
|
+
* entry here, nothing else.
|
|
1083
|
+
*
|
|
1084
|
+
* @task T11937
|
|
1085
|
+
*/
|
|
1086
|
+
export const SERVICE_PROVIDERS: Readonly<Record<string, ServiceProviderDef>> = {
|
|
1087
|
+
github: {
|
|
1088
|
+
provider: 'github',
|
|
1089
|
+
displayName: 'GitHub',
|
|
1090
|
+
authKind: 'oauth2',
|
|
1091
|
+
description: 'Repositories, issues, pull requests, and Actions on GitHub.',
|
|
1092
|
+
defaultScopes: 'repo read:user read:org',
|
|
1093
|
+
oauth: {
|
|
1094
|
+
mode: 'pkce',
|
|
1095
|
+
// PLACEHOLDER public client id for CLEO's first-party GitHub OAuth app —
|
|
1096
|
+
// real id (or a BYOC override from service_configs) wired in T11939.
|
|
1097
|
+
clientId: 'cleo-github-client-id',
|
|
1098
|
+
authorizationEndpoint: 'https://github.com/login/oauth/authorize',
|
|
1099
|
+
tokenEndpoint: 'https://github.com/login/oauth/access_token',
|
|
1100
|
+
scope: 'repo read:user read:org',
|
|
1101
|
+
redirectUri: 'http://127.0.0.1:7878/service/github/callback',
|
|
1102
|
+
},
|
|
1103
|
+
hostRules: [
|
|
1104
|
+
{ host: 'api.github.com', strategy: 'bearer' },
|
|
1105
|
+
{ host: 'github.com', strategy: 'basic-x-access-token' },
|
|
1106
|
+
{ host: 'raw.githubusercontent.com', strategy: 'bearer' },
|
|
1107
|
+
],
|
|
1108
|
+
},
|
|
1109
|
+
google: {
|
|
1110
|
+
provider: 'google',
|
|
1111
|
+
displayName: 'Google',
|
|
1112
|
+
authKind: 'oauth2-pkce',
|
|
1113
|
+
description: 'Gmail, Drive, Calendar, and other Google APIs.',
|
|
1114
|
+
defaultScopes: 'openid email profile https://www.googleapis.com/auth/drive.readonly',
|
|
1115
|
+
oauth: {
|
|
1116
|
+
mode: 'pkce',
|
|
1117
|
+
// PLACEHOLDER public client id for CLEO's first-party Google installed-app
|
|
1118
|
+
// OAuth client — real id (or a BYOC override) wired in T11939.
|
|
1119
|
+
clientId: 'cleo-google-client-id.apps.googleusercontent.com',
|
|
1120
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
1121
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
1122
|
+
scope: 'openid email profile https://www.googleapis.com/auth/drive.readonly',
|
|
1123
|
+
redirectUri: 'http://127.0.0.1:7878/service/google/callback',
|
|
1124
|
+
extraAuthParams: {
|
|
1125
|
+
// Google requires these to return a refresh token on the first consent.
|
|
1126
|
+
access_type: 'offline',
|
|
1127
|
+
prompt: 'consent',
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
refresh: GOOGLE_REFRESH,
|
|
1131
|
+
hostRules: [
|
|
1132
|
+
{ host: 'www.googleapis.com', strategy: 'bearer' },
|
|
1133
|
+
{ host: 'accounts.google.com', strategy: 'bearer' },
|
|
1134
|
+
],
|
|
1135
|
+
},
|
|
1136
|
+
...SERVICE_PROVIDERS_BREADTH,
|
|
1137
|
+
};
|