@ashlr/stack-core 0.1.1 → 0.3.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.
Files changed (111) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +26 -15
  3. package/package.json +1 -1
  4. package/src/__tests__/_harness.ts +67 -7
  5. package/src/__tests__/agent-onboard.test.ts +279 -0
  6. package/src/__tests__/api-key-providers.test.ts +6 -2
  7. package/src/__tests__/base-urls.test.ts +208 -0
  8. package/src/__tests__/catalog-index.test.ts +14 -20
  9. package/src/__tests__/catalog.test.ts +31 -0
  10. package/src/__tests__/cloudflare-provider.test.ts +32 -10
  11. package/src/__tests__/completion-ci.test.ts +239 -23
  12. package/src/__tests__/config-own-services.test.ts +183 -0
  13. package/src/__tests__/config-split.test.ts +272 -2
  14. package/src/__tests__/config.test.ts +94 -4
  15. package/src/__tests__/credential-staging.test.ts +660 -0
  16. package/src/__tests__/deps.test.ts +2 -1
  17. package/src/__tests__/detect-source.test.ts +183 -4
  18. package/src/__tests__/fixtures/stack-manifest.v1.json +10 -0
  19. package/src/__tests__/github-provider.test.ts +3 -2
  20. package/src/__tests__/helpers.test.ts +24 -4
  21. package/src/__tests__/http.test.ts +2 -2
  22. package/src/__tests__/import-integration.test.ts +18 -4
  23. package/src/__tests__/inference.test.ts +57 -13
  24. package/src/__tests__/interactive-prompt.test.ts +106 -0
  25. package/src/__tests__/mcp-mirror.test.ts +116 -0
  26. package/src/__tests__/neon-provider.test.ts +136 -3
  27. package/src/__tests__/new-providers.test.ts +470 -0
  28. package/src/__tests__/no-orphan-modules.test.ts +114 -0
  29. package/src/__tests__/oauth-port.test.ts +84 -0
  30. package/src/__tests__/openai-provider.test.ts +1 -1
  31. package/src/__tests__/phantom-contract.test.ts +177 -0
  32. package/src/__tests__/phantom-harness.test.ts +226 -8
  33. package/src/__tests__/phantom-process.test.ts +135 -0
  34. package/src/__tests__/phantom-project-preflight.test.ts +118 -0
  35. package/src/__tests__/phantom-wire.test.ts +403 -22
  36. package/src/__tests__/pipeline-integration.test.ts +33 -1
  37. package/src/__tests__/pipeline-recovery.test.ts +29 -28
  38. package/src/__tests__/pipeline-rollback.test.ts +509 -0
  39. package/src/__tests__/pipeline.test.ts +3 -1
  40. package/src/__tests__/pm-detect.test.ts +113 -0
  41. package/src/__tests__/provider-deprovision.test.ts +269 -0
  42. package/src/__tests__/provider-parity.test.ts +46 -0
  43. package/src/__tests__/publish-script.test.ts +704 -0
  44. package/src/__tests__/recipe.test.ts +27 -2
  45. package/src/__tests__/recommend-apply-e2e.test.ts +29 -27
  46. package/src/__tests__/registry.test.ts +11 -2
  47. package/src/__tests__/release-artifacts.test.ts +55 -0
  48. package/src/__tests__/scan-missing-dir.test.ts +45 -0
  49. package/src/__tests__/secret-redaction.test.ts +10 -10
  50. package/src/__tests__/security.test.ts +9 -10
  51. package/src/__tests__/stack-manifest.test.ts +38 -0
  52. package/src/__tests__/supabase-provider.test.ts +6 -4
  53. package/src/__tests__/swap.test.ts +114 -0
  54. package/src/__tests__/telemetry.test.ts +354 -0
  55. package/src/__tests__/template-litellm-gateway.test.ts +70 -0
  56. package/src/__tests__/template-temporal-worker-phantom.test.ts +45 -0
  57. package/src/__tests__/templates.test.ts +1 -1
  58. package/src/__tests__/turso-provider.test.ts +2 -5
  59. package/src/agent-onboard.ts +346 -0
  60. package/src/ai/catalog-index.ts +68 -10
  61. package/src/ai/circuit-breaker.ts +3 -6
  62. package/src/ai/cost-tracker.ts +6 -15
  63. package/src/ai/inference.ts +19 -11
  64. package/src/ai/phantom-wire.ts +73 -58
  65. package/src/ai/recipe.ts +69 -0
  66. package/src/base-urls.ts +71 -0
  67. package/src/catalog.ts +281 -2
  68. package/src/config.ts +31 -18
  69. package/src/detect-source.ts +50 -9
  70. package/src/detect.ts +13 -5
  71. package/src/errors.ts +6 -0
  72. package/src/http.ts +6 -5
  73. package/src/index.ts +16 -0
  74. package/src/mcp-writer.ts +97 -2
  75. package/src/oauth.ts +85 -53
  76. package/src/phantom-process.ts +72 -0
  77. package/src/phantom.ts +319 -30
  78. package/src/pipeline.ts +177 -68
  79. package/src/pm-detect.ts +56 -0
  80. package/src/providers/_api-key.ts +18 -17
  81. package/src/providers/_base.ts +28 -1
  82. package/src/providers/_credential-stage.ts +237 -0
  83. package/src/providers/_helpers.ts +55 -9
  84. package/src/providers/anthropic.ts +2 -1
  85. package/src/providers/auth0.ts +27 -0
  86. package/src/providers/aws.ts +32 -33
  87. package/src/providers/cloudflare.ts +18 -37
  88. package/src/providers/convex.ts +1 -2
  89. package/src/providers/datadog.ts +29 -0
  90. package/src/providers/digitalocean.ts +30 -0
  91. package/src/providers/gcp.ts +36 -0
  92. package/src/providers/github.ts +37 -35
  93. package/src/providers/grafana.ts +29 -0
  94. package/src/providers/hetzner.ts +29 -0
  95. package/src/providers/index.ts +13 -1
  96. package/src/providers/launchdarkly.ts +33 -0
  97. package/src/providers/mixpanel.ts +37 -0
  98. package/src/providers/neon.ts +50 -26
  99. package/src/providers/openai.ts +2 -1
  100. package/src/providers/plausible.ts +26 -0
  101. package/src/providers/sentry.ts +17 -17
  102. package/src/providers/stripe.ts +1 -2
  103. package/src/providers/supabase.ts +66 -37
  104. package/src/providers/turso.ts +49 -38
  105. package/src/providers/vercel.ts +50 -20
  106. package/src/providers/workos.ts +27 -0
  107. package/src/registry.ts +7 -11
  108. package/src/stack-manifest.ts +39 -0
  109. package/src/swap.ts +108 -0
  110. package/src/telemetry.ts +267 -0
  111. package/src/templates.ts +6 -2
package/src/catalog.ts CHANGED
@@ -23,13 +23,14 @@ export interface ProviderRef {
23
23
  | "Cloud"
24
24
  | "AI"
25
25
  | "Analytics"
26
+ | "Observability"
26
27
  | "Errors"
27
28
  | "Payments"
28
29
  | "Code"
29
30
  | "Tickets"
30
31
  | "Email"
31
32
  | "Auth"
32
- | "Features";
33
+ | "FeatureFlags";
33
34
  authKind: CatalogAuthKind;
34
35
  /** Canonical .env names this provider writes into Phantom. */
35
36
  secrets: string[];
@@ -42,6 +43,13 @@ export interface ProviderRef {
42
43
  preview?: boolean;
43
44
  detail: string;
44
45
  };
46
+ /**
47
+ * When true, the adapter only validates and stores the key — no upstream
48
+ * resource is created or configured. Absence of this flag means the adapter
49
+ * performs real provisioning (project/database/repo creation, token minting,
50
+ * etc.). v0.2 will bring provisioning to key-only providers over time.
51
+ */
52
+ keyOnly?: boolean;
45
53
  /** Dashboard URL `stack open <name>` lands on. */
46
54
  dashboard: string;
47
55
  /** Upstream reference docs URL for when something goes wrong. */
@@ -50,6 +58,14 @@ export interface ProviderRef {
50
58
  howTo: string;
51
59
  /** Free-form note about v1 limitations or caveats. */
52
60
  notes?: string;
61
+ /**
62
+ * npm packages to install in the consuming project after `stack add <name>`
63
+ * succeeds — the SDK(s) you'd `import` from to actually use this provider.
64
+ * `stack add` prompts the user to install these with their detected package
65
+ * manager (bun / pnpm / npm / yarn). Leave empty for providers with no first-
66
+ * party SDK (e.g. shell-script-only providers or REST-only adapters).
67
+ */
68
+ sdkPackages?: string[];
53
69
  }
54
70
 
55
71
  /** Back-compat re-export for call sites that still use `AuthKind`. */
@@ -74,6 +90,7 @@ export const PROVIDERS_REF: ProviderRef[] = [
74
90
  howTo: "Browser OAuth (PKCE) via the Ashlr Stack app.",
75
91
  notes:
76
92
  "v1 provisions a project and fetches service keys. Database password is auto-generated and stored in Phantom.",
93
+ sdkPackages: ["@supabase/supabase-js"],
77
94
  },
78
95
  {
79
96
  name: "neon",
@@ -82,9 +99,16 @@ export const PROVIDERS_REF: ProviderRef[] = [
82
99
  authKind: "pat",
83
100
  secrets: ["NEON_API_KEY", "DATABASE_URL"],
84
101
  blurb: "Serverless Postgres. Creates a project and pools the connection string.",
102
+ mcp: {
103
+ name: "neon",
104
+ preview: true,
105
+ detail:
106
+ "@neondatabase/mcp-server-neon with NEON_API_KEY piped from Phantom — branch, migrate, and query from the agent.",
107
+ },
85
108
  dashboard: "https://console.neon.tech",
86
109
  docs: "https://neon.tech/docs/reference/api-reference",
87
110
  howTo: "Paste a personal API key from https://console.neon.tech/app/settings/api-keys.",
111
+ sdkPackages: ["@neondatabase/serverless"],
88
112
  },
89
113
  {
90
114
  name: "turso",
@@ -96,6 +120,7 @@ export const PROVIDERS_REF: ProviderRef[] = [
96
120
  dashboard: "https://app.turso.tech",
97
121
  docs: "https://docs.turso.tech",
98
122
  howTo: "Paste a platform token from https://app.turso.tech/account/api-tokens.",
123
+ sdkPackages: ["@libsql/client"],
99
124
  },
100
125
  {
101
126
  name: "convex",
@@ -104,9 +129,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
104
129
  authKind: "api_key",
105
130
  secrets: ["CONVEX_DEPLOY_KEY"],
106
131
  blurb: "Reactive backend-as-a-service. Deploy key stored in Phantom.",
132
+ keyOnly: true,
107
133
  dashboard: "https://dashboard.convex.dev",
108
134
  docs: "https://docs.convex.dev",
109
135
  howTo: "Create a deploy key in the Convex dashboard and paste it.",
136
+ sdkPackages: ["convex"],
110
137
  },
111
138
  {
112
139
  name: "upstash",
@@ -115,9 +142,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
115
142
  authKind: "api_key",
116
143
  secrets: ["UPSTASH_MANAGEMENT_TOKEN"],
117
144
  blurb: "Serverless Redis + Kafka. Management token stored in Phantom.",
145
+ keyOnly: true,
118
146
  dashboard: "https://console.upstash.com",
119
147
  docs: "https://upstash.com/docs/devops/developer-api",
120
148
  howTo: "Generate a management token from the Upstash console.",
149
+ sdkPackages: ["@upstash/redis"],
121
150
  },
122
151
  {
123
152
  name: "firebase",
@@ -126,13 +155,28 @@ export const PROVIDERS_REF: ProviderRef[] = [
126
155
  authKind: "api_key",
127
156
  secrets: ["FIREBASE_SERVICE_ACCOUNT_JSON"],
128
157
  blurb: "Realtime DB + Auth. Service-account JSON stored verbatim in Phantom.",
158
+ keyOnly: true,
129
159
  dashboard: "https://console.firebase.google.com",
130
160
  docs: "https://firebase.google.com/docs/admin/setup",
131
161
  howTo:
132
162
  "Paste the full service-account JSON from Firebase → Project settings → Service accounts.",
163
+ sdkPackages: ["firebase"],
133
164
  },
134
165
 
135
166
  // ── Deploy ────────────────────────────────────────────────────────────────
167
+ {
168
+ name: "modal",
169
+ displayName: "Modal",
170
+ category: "Deploy",
171
+ authKind: "api_key",
172
+ secrets: ["MODAL_TOKEN"],
173
+ blurb: "Serverless compute for AI + data. Token stored in Phantom.",
174
+ keyOnly: true,
175
+ dashboard: "https://modal.com",
176
+ docs: "https://modal.com/docs",
177
+ howTo: "Create a token in the Modal dashboard and paste it.",
178
+ sdkPackages: [],
179
+ },
136
180
  {
137
181
  name: "vercel",
138
182
  displayName: "Vercel",
@@ -140,9 +184,16 @@ export const PROVIDERS_REF: ProviderRef[] = [
140
184
  authKind: "pat",
141
185
  secrets: ["VERCEL_TOKEN"],
142
186
  blurb: "Frontend platform. Stores a scoped access token for deploys + env sync.",
187
+ mcp: {
188
+ name: "vercel",
189
+ preview: true,
190
+ detail:
191
+ "Vercel MCP server with VERCEL_TOKEN piped from Phantom — list deployments, tail logs, sync env.",
192
+ },
143
193
  dashboard: "https://vercel.com/dashboard",
144
194
  docs: "https://vercel.com/docs/rest-api",
145
195
  howTo: "Paste a personal access token from https://vercel.com/account/tokens.",
196
+ sdkPackages: [],
146
197
  },
147
198
  {
148
199
  name: "railway",
@@ -151,9 +202,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
151
202
  authKind: "api_key",
152
203
  secrets: ["RAILWAY_TOKEN"],
153
204
  blurb: "Infra from a repo. Project-level token stored in Phantom.",
205
+ keyOnly: true,
154
206
  dashboard: "https://railway.app/dashboard",
155
207
  docs: "https://docs.railway.app/reference/public-api",
156
208
  howTo: "Paste an API token from https://railway.app/account/tokens.",
209
+ sdkPackages: [],
157
210
  },
158
211
  {
159
212
  name: "fly",
@@ -162,9 +215,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
162
215
  authKind: "api_key",
163
216
  secrets: ["FLY_API_TOKEN"],
164
217
  blurb: "VMs at the edge. Machines API token stored in Phantom.",
218
+ keyOnly: true,
165
219
  dashboard: "https://fly.io/dashboard",
166
220
  docs: "https://fly.io/docs/machines/api",
167
221
  howTo: "Paste an API token from https://fly.io/user/personal_access_tokens.",
222
+ sdkPackages: [],
168
223
  },
169
224
  {
170
225
  name: "cloudflare",
@@ -173,9 +228,16 @@ export const PROVIDERS_REF: ProviderRef[] = [
173
228
  authKind: "pat",
174
229
  secrets: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
175
230
  blurb: "Workers, R2, D1. Account id inferred from the token's scope.",
231
+ mcp: {
232
+ name: "cloudflare",
233
+ preview: true,
234
+ detail:
235
+ "Cloudflare MCP (Workers Bindings + R2 + D1) with CLOUDFLARE_API_TOKEN piped from Phantom.",
236
+ },
176
237
  dashboard: "https://dash.cloudflare.com",
177
238
  docs: "https://developers.cloudflare.com/api",
178
239
  howTo: "Create a scoped API token at https://dash.cloudflare.com/profile/api-tokens.",
240
+ sdkPackages: [],
179
241
  },
180
242
  {
181
243
  name: "render",
@@ -184,9 +246,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
184
246
  authKind: "api_key",
185
247
  secrets: ["RENDER_API_KEY"],
186
248
  blurb: "Zero-config hosting. API key stored in Phantom.",
249
+ keyOnly: true,
187
250
  dashboard: "https://dashboard.render.com",
188
251
  docs: "https://api-docs.render.com",
189
252
  howTo: "Generate a key at https://dashboard.render.com/u/settings#api-keys.",
253
+ sdkPackages: [],
190
254
  },
191
255
 
192
256
  // ── Cloud ─────────────────────────────────────────────────────────────────
@@ -197,10 +261,12 @@ export const PROVIDERS_REF: ProviderRef[] = [
197
261
  authKind: "api_key",
198
262
  secrets: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"],
199
263
  blurb: "S3, Lambda, RDS. IAM access keys stored in Phantom.",
264
+ keyOnly: true,
200
265
  dashboard: "https://console.aws.amazon.com",
201
266
  docs: "https://docs.aws.amazon.com/iam/",
202
267
  howTo: "Create an IAM access key pair in the AWS console and paste both halves.",
203
268
  notes: "v1 stores the keys only — no resource provisioning. Scope the IAM policy narrowly.",
269
+ sdkPackages: [],
204
270
  },
205
271
 
206
272
  // ── AI ────────────────────────────────────────────────────────────────────
@@ -211,9 +277,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
211
277
  authKind: "api_key",
212
278
  secrets: ["OPENAI_API_KEY"],
213
279
  blurb: "GPT, Realtime, embeddings. Key verified against /v1/models on paste.",
280
+ keyOnly: true,
214
281
  dashboard: "https://platform.openai.com",
215
282
  docs: "https://platform.openai.com/docs/api-reference",
216
283
  howTo: "Paste a secret key from https://platform.openai.com/api-keys.",
284
+ sdkPackages: ["openai"],
217
285
  },
218
286
  {
219
287
  name: "anthropic",
@@ -222,9 +290,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
222
290
  authKind: "api_key",
223
291
  secrets: ["ANTHROPIC_API_KEY"],
224
292
  blurb: "Claude models + MCP. Key verified against the Messages API on paste.",
293
+ keyOnly: true,
225
294
  dashboard: "https://console.anthropic.com",
226
295
  docs: "https://docs.anthropic.com/en/api",
227
296
  howTo: "Paste a secret key from https://console.anthropic.com/settings/keys.",
297
+ sdkPackages: ["@anthropic-ai/sdk"],
228
298
  },
229
299
  {
230
300
  name: "xai",
@@ -233,9 +303,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
233
303
  authKind: "api_key",
234
304
  secrets: ["XAI_API_KEY"],
235
305
  blurb: "Grok + tool use. Key verified on paste.",
306
+ keyOnly: true,
236
307
  dashboard: "https://console.x.ai",
237
308
  docs: "https://docs.x.ai/api",
238
309
  howTo: "Paste a key from https://console.x.ai.",
310
+ sdkPackages: ["openai"],
239
311
  },
240
312
  {
241
313
  name: "deepseek",
@@ -244,9 +316,38 @@ export const PROVIDERS_REF: ProviderRef[] = [
244
316
  authKind: "api_key",
245
317
  secrets: ["DEEPSEEK_API_KEY"],
246
318
  blurb: "Open-weight models. Key verified on paste.",
319
+ keyOnly: true,
247
320
  dashboard: "https://platform.deepseek.com",
248
321
  docs: "https://api-docs.deepseek.com",
249
322
  howTo: "Paste a key from https://platform.deepseek.com/api_keys.",
323
+ sdkPackages: ["openai"],
324
+ },
325
+ {
326
+ name: "replicate",
327
+ displayName: "Replicate",
328
+ category: "AI",
329
+ authKind: "pat",
330
+ secrets: ["REPLICATE_API_TOKEN"],
331
+ blurb: "Open-source model inference with per-second billing. API token stored in Phantom.",
332
+ keyOnly: true,
333
+ dashboard: "https://replicate.com",
334
+ docs: "https://replicate.com/docs/reference/http",
335
+ howTo: "Create a token at https://replicate.com/account/api-tokens.",
336
+ sdkPackages: ["replicate"],
337
+ },
338
+ {
339
+ name: "braintrust",
340
+ displayName: "Braintrust",
341
+ category: "AI",
342
+ authKind: "api_key",
343
+ secrets: ["BRAINTRUST_API_KEY"],
344
+ blurb:
345
+ "LLM evals + observability + prompt playground. Verified against /v1/organization on paste.",
346
+ keyOnly: true,
347
+ dashboard: "https://www.braintrust.dev/app",
348
+ docs: "https://www.braintrust.dev/docs",
349
+ howTo: "Create a key at https://www.braintrust.dev/app/settings/api-keys.",
350
+ sdkPackages: ["braintrust"],
250
351
  },
251
352
 
252
353
  // ── Analytics ─────────────────────────────────────────────────────────────
@@ -257,6 +358,7 @@ export const PROVIDERS_REF: ProviderRef[] = [
257
358
  authKind: "api_key",
258
359
  secrets: ["POSTHOG_PERSONAL_API_KEY"],
259
360
  blurb: "Product analytics + flags. Personal API key stored in Phantom.",
361
+ keyOnly: true,
260
362
  mcp: {
261
363
  name: "posthog",
262
364
  detail:
@@ -265,6 +367,7 @@ export const PROVIDERS_REF: ProviderRef[] = [
265
367
  dashboard: "https://app.posthog.com",
266
368
  docs: "https://posthog.com/docs/api",
267
369
  howTo: "Create a personal API key at https://app.posthog.com/me/settings (scope: all).",
370
+ sdkPackages: ["posthog-node"],
268
371
  },
269
372
 
270
373
  // ── Errors ────────────────────────────────────────────────────────────────
@@ -274,7 +377,8 @@ export const PROVIDERS_REF: ProviderRef[] = [
274
377
  category: "Errors",
275
378
  authKind: "pat",
276
379
  secrets: ["SENTRY_AUTH_TOKEN", "SENTRY_DSN", "SENTRY_ORG", "SENTRY_PROJECT"],
277
- blurb: "Error + performance tracking. Creates a project and fetches its DSN.",
380
+ blurb:
381
+ "Error + performance tracking. Attaches to an existing project and reads its DSN when available.",
278
382
  mcp: {
279
383
  name: "sentry",
280
384
  detail:
@@ -284,6 +388,7 @@ export const PROVIDERS_REF: ProviderRef[] = [
284
388
  docs: "https://docs.sentry.io/api/",
285
389
  howTo:
286
390
  "Paste an auth token from https://sentry.io/settings/account/api/auth-tokens/ (scopes: project:read, project:write).",
391
+ sdkPackages: ["@sentry/node"],
287
392
  },
288
393
 
289
394
  // ── Payments ──────────────────────────────────────────────────────────────
@@ -294,12 +399,20 @@ export const PROVIDERS_REF: ProviderRef[] = [
294
399
  authKind: "api_key",
295
400
  secrets: ["STRIPE_SECRET_KEY"],
296
401
  blurb: "Billing, subscriptions, tax. Restricted / test-mode secret key stored in Phantom.",
402
+ keyOnly: true,
403
+ mcp: {
404
+ name: "stripe",
405
+ preview: true,
406
+ detail:
407
+ "@stripe/mcp with STRIPE_SECRET_KEY piped from Phantom — create products, prices, and inspect events from the agent.",
408
+ },
297
409
  dashboard: "https://dashboard.stripe.com",
298
410
  docs: "https://docs.stripe.com/api",
299
411
  howTo:
300
412
  "Create a restricted key at https://dashboard.stripe.com/apikeys (use test mode for development).",
301
413
  notes:
302
414
  "v1 only stores the key. Full Stripe Connect / account linking lands when we register the Ashlr OAuth app.",
415
+ sdkPackages: ["stripe"],
303
416
  },
304
417
 
305
418
  // ── Code ──────────────────────────────────────────────────────────────────
@@ -318,6 +431,7 @@ export const PROVIDERS_REF: ProviderRef[] = [
318
431
  dashboard: "https://github.com",
319
432
  docs: "https://docs.github.com/en/rest",
320
433
  howTo: "Browser device-code flow — enter the code GitHub shows you, authorise, done.",
434
+ sdkPackages: ["octokit"],
321
435
  },
322
436
 
323
437
  // ── Tickets ───────────────────────────────────────────────────────────────
@@ -328,6 +442,7 @@ export const PROVIDERS_REF: ProviderRef[] = [
328
442
  authKind: "api_key",
329
443
  secrets: ["LINEAR_API_KEY"],
330
444
  blurb: "Issues, projects, cycles. Personal API key stored in Phantom.",
445
+ keyOnly: true,
331
446
  mcp: {
332
447
  name: "linear",
333
448
  detail: "mcp-linear with LINEAR_API_KEY piped from Phantom.",
@@ -335,6 +450,7 @@ export const PROVIDERS_REF: ProviderRef[] = [
335
450
  dashboard: "https://linear.app",
336
451
  docs: "https://developers.linear.app/docs",
337
452
  howTo: "Create a personal API key at https://linear.app/settings/api.",
453
+ sdkPackages: ["@linear/sdk"],
338
454
  },
339
455
 
340
456
  // ── Email ─────────────────────────────────────────────────────────────────
@@ -345,9 +461,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
345
461
  authKind: "api_key",
346
462
  secrets: ["RESEND_API_KEY"],
347
463
  blurb: "Transactional + React Email. API key stored in Phantom.",
464
+ keyOnly: true,
348
465
  dashboard: "https://resend.com",
349
466
  docs: "https://resend.com/docs",
350
467
  howTo: "Paste an API key from https://resend.com/api-keys.",
468
+ sdkPackages: ["resend"],
351
469
  },
352
470
  {
353
471
  name: "sendgrid",
@@ -356,9 +474,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
356
474
  authKind: "api_key",
357
475
  secrets: ["SENDGRID_API_KEY"],
358
476
  blurb: "High-volume transactional mail. API key verified against /v3/scopes on paste.",
477
+ keyOnly: true,
359
478
  dashboard: "https://app.sendgrid.com",
360
479
  docs: "https://docs.sendgrid.com/api-reference",
361
480
  howTo: "Paste an API key from https://app.sendgrid.com/settings/api_keys.",
481
+ sdkPackages: ["@sendgrid/mail"],
362
482
  },
363
483
  {
364
484
  name: "mailgun",
@@ -367,9 +487,11 @@ export const PROVIDERS_REF: ProviderRef[] = [
367
487
  authKind: "api_key",
368
488
  secrets: ["MAILGUN_API_KEY"],
369
489
  blurb: "API-first email + routing. Key verified against /v3/domains on paste (HTTP Basic).",
490
+ keyOnly: true,
370
491
  dashboard: "https://app.mailgun.com",
371
492
  docs: "https://documentation.mailgun.com/docs/mailgun/api-reference/",
372
493
  howTo: "Paste an API key from https://app.mailgun.com/settings/api_security.",
494
+ sdkPackages: ["mailgun.js", "form-data"],
373
495
  },
374
496
  {
375
497
  name: "postmark",
@@ -379,12 +501,147 @@ export const PROVIDERS_REF: ProviderRef[] = [
379
501
  secrets: ["POSTMARK_ACCOUNT_TOKEN"],
380
502
  blurb:
381
503
  "Deliverability-focused transactional. Account token verified against /servers on paste.",
504
+ keyOnly: true,
382
505
  dashboard: "https://account.postmarkapp.com",
383
506
  docs: "https://postmarkapp.com/developer/api/overview",
384
507
  howTo: "Paste an account token from https://account.postmarkapp.com/api_tokens.",
508
+ sdkPackages: ["postmark"],
509
+ },
510
+
511
+ // ── Analytics ─────────────────────────────────────────────────────────────
512
+ {
513
+ name: "mixpanel",
514
+ displayName: "Mixpanel",
515
+ category: "Analytics",
516
+ authKind: "api_key",
517
+ secrets: ["MIXPANEL_PROJECT_TOKEN", "MIXPANEL_API_SECRET"],
518
+ blurb: "Product analytics. Project token + optional API secret stored in Phantom.",
519
+ keyOnly: true,
520
+ dashboard: "https://mixpanel.com",
521
+ docs: "https://developer.mixpanel.com/reference/overview",
522
+ howTo: "Find your project token and API secret in Mixpanel → Settings → Project Settings.",
523
+ sdkPackages: ["mixpanel"],
524
+ },
525
+ {
526
+ name: "plausible",
527
+ displayName: "Plausible",
528
+ category: "Analytics",
529
+ authKind: "api_key",
530
+ secrets: ["PLAUSIBLE_API_KEY", "PLAUSIBLE_SITE_ID"],
531
+ blurb: "Privacy-friendly web analytics. API key verified against the stats API.",
532
+ keyOnly: true,
533
+ dashboard: "https://plausible.io",
534
+ docs: "https://plausible.io/docs/stats-api",
535
+ howTo: "Generate an API key at https://plausible.io/settings and note your site ID.",
536
+ sdkPackages: [],
537
+ },
538
+
539
+ // ── Observability ─────────────────────────────────────────────────────────
540
+ {
541
+ name: "datadog",
542
+ displayName: "Datadog",
543
+ category: "Observability",
544
+ authKind: "api_key",
545
+ secrets: ["DD_API_KEY"],
546
+ blurb:
547
+ "Metrics, traces, logs. Current adapter validates an API key via /api/v1/validate and stores that key only.",
548
+ keyOnly: true,
549
+ dashboard: "https://app.datadoghq.com",
550
+ docs: "https://docs.datadoghq.com/api/latest/",
551
+ howTo:
552
+ "Create an API key at https://app.datadoghq.com/organization-settings/api-keys. This adapter validates and stores that key only.",
553
+ sdkPackages: ["dd-trace"],
554
+ },
555
+ {
556
+ name: "grafana",
557
+ displayName: "Grafana",
558
+ category: "Observability",
559
+ authKind: "pat",
560
+ secrets: ["GRAFANA_API_KEY", "GRAFANA_URL"],
561
+ blurb: "Dashboards + alerting. Service-account token verified against your Grafana instance.",
562
+ keyOnly: true,
563
+ dashboard: "https://grafana.com",
564
+ docs: "https://grafana.com/docs/grafana/latest/developers/http_api/",
565
+ howTo:
566
+ "Create a service-account token in Grafana → Administration → Service accounts, and provide your Grafana base URL.",
567
+ sdkPackages: [],
568
+ },
569
+
570
+ // ── Cloud ─────────────────────────────────────────────────────────────────
571
+ {
572
+ name: "gcp",
573
+ displayName: "GCP",
574
+ category: "Cloud",
575
+ authKind: "api_key",
576
+ secrets: ["GCP_SERVICE_ACCOUNT_JSON", "GCP_PROJECT_ID"],
577
+ blurb: "Google Cloud Platform. Service-account JSON + project ID stored in Phantom.",
578
+ keyOnly: true,
579
+ dashboard: "https://console.cloud.google.com",
580
+ docs: "https://cloud.google.com/docs/authentication/getting-started",
581
+ howTo:
582
+ "Download a service-account JSON key from GCP → IAM & Admin → Service Accounts and paste the full JSON.",
583
+ notes:
584
+ "v1 validates JSON shape and project_id field only — live project endpoint verification requires an OAuth2 token exchange; deferred to v0.2.",
585
+ sdkPackages: [],
586
+ },
587
+ {
588
+ name: "digitalocean",
589
+ displayName: "DigitalOcean",
590
+ category: "Cloud",
591
+ authKind: "pat",
592
+ secrets: ["DIGITALOCEAN_TOKEN"],
593
+ blurb:
594
+ "Droplets, Kubernetes, managed databases. Personal access token verified via /v2/account.",
595
+ keyOnly: true,
596
+ dashboard: "https://cloud.digitalocean.com",
597
+ docs: "https://docs.digitalocean.com/reference/api/api-reference/",
598
+ howTo: "Create a personal access token at https://cloud.digitalocean.com/account/api/tokens.",
599
+ sdkPackages: [],
600
+ },
601
+ {
602
+ name: "hetzner",
603
+ displayName: "Hetzner",
604
+ category: "Cloud",
605
+ authKind: "api_key",
606
+ secrets: ["HETZNER_API_TOKEN"],
607
+ blurb: "Affordable European cloud servers. API token verified via /v1/locations.",
608
+ keyOnly: true,
609
+ dashboard: "https://console.hetzner.cloud",
610
+ docs: "https://docs.hetzner.cloud/",
611
+ howTo: "Create an API token in Hetzner Cloud Console → Project → Security → API Tokens.",
612
+ sdkPackages: [],
385
613
  },
386
614
 
387
615
  // ── Auth ──────────────────────────────────────────────────────────────────
616
+ {
617
+ name: "auth0",
618
+ displayName: "Auth0",
619
+ category: "Auth",
620
+ authKind: "api_key",
621
+ secrets: ["AUTH0_DOMAIN"],
622
+ blurb:
623
+ "Identity platform. Current adapter checks a domain's discovery endpoint and stores only the domain.",
624
+ keyOnly: true,
625
+ dashboard: "https://manage.auth0.com",
626
+ docs: "https://auth0.com/docs/api/management/v2",
627
+ howTo:
628
+ "Enter your Auth0 tenant domain, such as your-tenant.us.auth0.com. This adapter checks its discovery response and stores only that domain.",
629
+ notes: "Browser SDK: @auth0/auth0-spa-js. Server SDK: auth0 (bundled).",
630
+ sdkPackages: ["auth0"],
631
+ },
632
+ {
633
+ name: "workos",
634
+ displayName: "WorkOS",
635
+ category: "Auth",
636
+ authKind: "api_key",
637
+ secrets: ["WORKOS_API_KEY", "WORKOS_CLIENT_ID"],
638
+ blurb: "Enterprise SSO + Directory Sync. API key verified via GET /organizations.",
639
+ keyOnly: true,
640
+ dashboard: "https://dashboard.workos.com",
641
+ docs: "https://workos.com/docs/reference/api",
642
+ howTo: "Find your API key and client ID in the WorkOS dashboard → API Keys.",
643
+ sdkPackages: ["@workos-inc/node"],
644
+ },
388
645
  {
389
646
  name: "clerk",
390
647
  displayName: "Clerk",
@@ -392,9 +649,28 @@ export const PROVIDERS_REF: ProviderRef[] = [
392
649
  authKind: "api_key",
393
650
  secrets: ["CLERK_SECRET_KEY"],
394
651
  blurb: "Drop-in auth + users. Secret key stored in Phantom.",
652
+ keyOnly: true,
395
653
  dashboard: "https://dashboard.clerk.com",
396
654
  docs: "https://clerk.com/docs",
397
655
  howTo: "Paste a secret key from https://dashboard.clerk.com (API Keys).",
656
+ sdkPackages: ["@clerk/backend"],
657
+ },
658
+
659
+ // ── FeatureFlags ──────────────────────────────────────────────────────────
660
+ {
661
+ name: "launchdarkly",
662
+ displayName: "LaunchDarkly",
663
+ category: "FeatureFlags",
664
+ authKind: "api_key",
665
+ secrets: ["LAUNCHDARKLY_SDK_KEY", "LAUNCHDARKLY_API_TOKEN"],
666
+ blurb:
667
+ "Feature flags + experimentation. SDK key + API token verified via /api/v2/caller-identity.",
668
+ keyOnly: true,
669
+ dashboard: "https://app.launchdarkly.com",
670
+ docs: "https://apidocs.launchdarkly.com/",
671
+ howTo:
672
+ "Find your SDK key in LaunchDarkly → Environments and create an API token at LaunchDarkly → Account settings → Authorization.",
673
+ sdkPackages: ["launchdarkly-node-server-sdk"],
398
674
  },
399
675
  ];
400
676
 
@@ -404,13 +680,16 @@ export const PROVIDER_CATEGORIES = [
404
680
  "Cloud",
405
681
  "AI",
406
682
  "Analytics",
683
+ "Observability",
407
684
  "Errors",
408
685
  "Payments",
409
686
  "Code",
410
687
  "Tickets",
411
688
  "Email",
412
689
  "Auth",
690
+ "FeatureFlags",
413
691
  ] as const;
692
+ // If you see "Features" anywhere, it's stale — no provider currently maps to it.
414
693
 
415
694
  export function groupByCategory(): Record<string, ProviderRef[]> {
416
695
  const out: Record<string, ProviderRef[]> = {};