stoaken 0.4.0 → 0.5.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 (3) hide show
  1. package/README.md +18 -5
  2. package/dist/index.js +145 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -32,15 +32,28 @@ npx stoaken init --client cursor
32
32
  | `copilot` | `.vscode/mcp.json` (`servers.stoaken`, VS Code agent mode) |
33
33
  | `claude-desktop` | Nothing: prints the steps to add a custom connector (Customize > Connectors) |
34
34
  | `v0` | Nothing: prints the steps to add a custom MCP server in v0 (+ > MCPs) |
35
+ | `windsurf` | `.devin/mcp_config.json` (`mcpServers.stoaken`, URL only). Windsurf is now Devin Desktop; this is for its Devin Local agent and Devin CLI |
36
+ | `zed` | `.zed/settings.json` (`context_servers.stoaken`, URL only; OAuth). With an API key: nothing, prints how to add it to your user settings |
37
+ | `gemini-cli` | `.gemini/settings.json` (`mcpServers.stoaken.httpUrl`; loads in trusted folders only) |
38
+ | `jetbrains` | `.junie/mcp/mcp.json` (`mcpServers.stoaken`, URL only; Junie). With an API key: nothing, prints how to add it to `~/.junie/mcp/mcp.json` |
39
+ | `cline` | Nothing: prints the steps for Cline's Remote Servers tab |
40
+ | `lovable` | Nothing: prints the steps to add a custom MCP server (Connectors > Add custom) |
41
+ | `bolt` | Nothing: prints the steps to add a custom connector (+ > Connectors) |
42
+ | `replit` | Nothing: prints a one-click Replit install link and the manual steps |
35
43
 
36
44
  init merges: other servers in the file are kept and only the `stoaken` entry is replaced. If the
37
- file isn't plain JSON (for example `.vscode/mcp.json` with comments) it is left alone and you're told.
38
- No secret goes into these files, so they are safe to commit:
45
+ file isn't plain JSON (for example `.vscode/mcp.json` or `.zed/settings.json` with comments) it is
46
+ left alone and you're told. No secret goes into these files, so they are safe to commit:
39
47
 
40
48
  - Signed in with the browser: the entry has just the URL, and the tool signs you in itself with
41
- OAuth (Claude Code `/mcp`, Cursor Connect, `codex mcp login stoaken`, VS Code Start).
49
+ OAuth (Claude Code `/mcp`, Cursor Connect, `codex mcp login stoaken`, VS Code Start,
50
+ `/mcp auth stoaken` in Gemini CLI, Authenticate in Devin Local, Zed's sign-in prompt, Junie's
51
+ `/mcp` Authorize).
42
52
  - Pasted an API key: the entry reads the `STOAKEN_API_KEY` environment variable (VS Code instead
43
- asks for the key once and stores it securely).
53
+ asks for the key once and stores it securely). Devin Local, Zed and Junie document no variable
54
+ expansion in headers, so init never writes the key for them: Devin gets a gitignored
55
+ `.devin/mcp_config.local.json` override (`devin mcp add -s local ... -H`), and Zed and Junie are
56
+ told to use a user-level file outside the repo.
44
57
 
45
58
  ## Authentication
46
59
 
@@ -101,7 +114,7 @@ Bitbucket Pipelines, Azure Pipelines and Vercel builds: see /docs/webhooks#ci an
101
114
 
102
115
  ## What this connects to
103
116
 
104
- Once set up, any MCP-capable client (Claude Code, Claude Desktop, Cursor, Codex, Copilot, v0) or a direct HTTP client can reach Stoaken's MCP server at `/mcp`. Its main tools (full list at /docs/mcp):
117
+ Once set up, any MCP-capable client (Claude Code, Claude Desktop, Cursor, Codex, Copilot, v0, Devin Desktop, Zed, Gemini CLI, Junie, Cline, Lovable, Bolt, Replit) or a direct HTTP client can reach Stoaken's MCP server at `/mcp`. Its main tools (full list at /docs/mcp):
105
118
 
106
119
  - `list_brands` -- every Brand you hold at least Viewer on.
107
120
  - `get_theme_tokens(themeId)` -- a Theme's cascade-merged token tree.
package/dist/index.js CHANGED
@@ -214,7 +214,7 @@ async function listBrands(serverUrl, credential) {
214
214
  const transport = new StreamableHTTPClientTransport(new URL("/mcp", serverUrl), {
215
215
  requestInit: { headers: { Authorization: `Bearer ${credential}` } }
216
216
  });
217
- const client = new Client({ name: "stoaken-cli", version: "0.4.0" });
217
+ const client = new Client({ name: "stoaken-cli", version: "0.5.0" });
218
218
  await client.connect(transport);
219
219
  try {
220
220
  const result = await client.callTool({ name: "list_brands", arguments: {} });
@@ -229,7 +229,22 @@ async function listBrands(serverUrl, credential) {
229
229
  }
230
230
 
231
231
  // src/client-config.ts
232
- var CLIENTS = ["claude-code", "claude-desktop", "cursor", "codex", "copilot", "v0"];
232
+ var CLIENTS = [
233
+ "claude-code",
234
+ "claude-desktop",
235
+ "cursor",
236
+ "codex",
237
+ "copilot",
238
+ "v0",
239
+ "windsurf",
240
+ "zed",
241
+ "gemini-cli",
242
+ "jetbrains",
243
+ "cline",
244
+ "lovable",
245
+ "bolt",
246
+ "replit"
247
+ ];
233
248
  var SERVER_NAME = "stoaken";
234
249
  var API_KEY_ENV = "STOAKEN_API_KEY";
235
250
  var CLIENT_LABELS = {
@@ -238,17 +253,43 @@ var CLIENT_LABELS = {
238
253
  cursor: "Cursor",
239
254
  codex: "OpenAI Codex",
240
255
  copilot: "GitHub Copilot (VS Code)",
241
- v0: "v0 (Vercel)"
256
+ v0: "v0 (Vercel)",
257
+ windsurf: "Windsurf / Devin Desktop",
258
+ zed: "Zed",
259
+ "gemini-cli": "Gemini CLI",
260
+ jetbrains: "JetBrains (Junie)",
261
+ cline: "Cline",
262
+ lovable: "Lovable",
263
+ bolt: "Bolt (bolt.new)",
264
+ replit: "Replit Agent"
265
+ };
266
+ var JSON_CLIENTS = ["claude-code", "cursor", "copilot", "windsurf", "zed", "gemini-cli", "jetbrains"];
267
+ var SERVERS_KEY = {
268
+ "claude-code": "mcpServers",
269
+ cursor: "mcpServers",
270
+ copilot: "servers",
271
+ windsurf: "mcpServers",
272
+ zed: "context_servers",
273
+ "gemini-cli": "mcpServers",
274
+ jetbrains: "mcpServers"
242
275
  };
243
276
  function isClient(value) {
244
277
  return CLIENTS.includes(value);
245
278
  }
279
+ function isJsonClient(client) {
280
+ return JSON_CLIENTS.includes(client);
281
+ }
246
282
  function mcpUrl(serverUrl) {
247
283
  return `${serverUrl.replace(/\/+$/, "")}/mcp`;
248
284
  }
285
+ function replitInstallLink(serverUrl) {
286
+ const json = JSON.stringify({ displayName: "Stoaken", baseUrl: mcpUrl(serverUrl) });
287
+ return `https://replit.com/integrations?mcp=${Buffer.from(json, "utf8").toString("base64")}`;
288
+ }
249
289
  function planFor(client, auth, serverUrl) {
250
290
  const url = mcpUrl(serverUrl);
251
291
  const keyStep = `Set ${API_KEY_ENV} in your shell to an API key from Settings > API keys (never commit it).`;
292
+ const oauthInstead = "Or run init again, sign in with the browser, and use OAuth instead.";
252
293
  switch (client) {
253
294
  case "claude-code":
254
295
  return {
@@ -268,7 +309,8 @@ function planFor(client, auth, serverUrl) {
268
309
  case "copilot":
269
310
  return {
270
311
  file: ".vscode/mcp.json",
271
- nextSteps: auth === "oauth" ? ["In VS Code open .vscode/mcp.json, click Start above stoaken and sign in in the browser.", "Use Copilot Chat in Agent mode."] : ["In VS Code open .vscode/mcp.json, click Start above stoaken and paste an API key when asked (VS Code stores it securely).", "Use Copilot Chat in Agent mode."]
312
+ nextSteps: auth === "oauth" ? ["In VS Code open .vscode/mcp.json, click Start above stoaken and sign in in the browser.", "Use Copilot Chat in Agent mode."] : ["In VS Code open .vscode/mcp.json, click Start above stoaken and paste an API key when asked (VS Code stores it securely).", "Use Copilot Chat in Agent mode."],
313
+ keyNote: "VS Code asks for the key once and stores it securely"
272
314
  };
273
315
  case "claude-desktop":
274
316
  return {
@@ -290,10 +332,104 @@ function planFor(client, auth, serverUrl) {
290
332
  "v0 uses the tools while it generates; the app it builds cannot call them."
291
333
  ]
292
334
  };
335
+ case "windsurf":
336
+ return {
337
+ file: ".devin/mcp_config.json",
338
+ nextSteps: auth === "oauth" ? [
339
+ "Open this project in Devin Desktop (formerly Windsurf) with the Devin Local agent. Devin CLI reads the same file.",
340
+ "When stoaken shows Needs auth in the MCP list, click Authenticate (or run: devin mcp login stoaken)."
341
+ ] : [
342
+ keyStep,
343
+ `Devin documents no variable expansion in MCP headers, so add the key to the gitignored local override: devin mcp add -s local stoaken ${url} -H "Authorization: Bearer $${API_KEY_ENV}"`,
344
+ "That override replaces the project entry for you only. The legacy Cascade agent ignores project files (see the guide)."
345
+ ],
346
+ keyNote: "the key goes in the gitignored `.devin/mcp_config.local.json` override"
347
+ };
348
+ case "zed":
349
+ return auth === "oauth" ? {
350
+ file: ".zed/settings.json",
351
+ nextSteps: [
352
+ "Trust this project in Zed: in Restricted Mode project settings and their MCP servers don't load.",
353
+ "Zed asks you to sign in to stoaken (MCP OAuth). Settings > AI > MCP Servers shows a green dot when it's active."
354
+ ]
355
+ } : {
356
+ file: null,
357
+ nextSteps: [
358
+ "Zed documents no variable expansion in headers, so the key can't go in a project file.",
359
+ `Run "zed: open settings file" and add under "context_servers": "stoaken": { "url": "${url}", "headers": { "Authorization": "Bearer <API key>" } }`,
360
+ oauthInstead
361
+ ]
362
+ };
363
+ case "gemini-cli":
364
+ return {
365
+ file: ".gemini/settings.json",
366
+ nextSteps: auth === "oauth" ? ["Trust this folder (gemini trust): untrusted folders ignore .gemini/settings.json.", "In Gemini CLI run /mcp auth stoaken to sign in in the browser."] : [keyStep, "Trust this folder (gemini trust): untrusted folders ignore .gemini/settings.json."]
367
+ };
368
+ case "jetbrains":
369
+ return auth === "oauth" ? {
370
+ file: ".junie/mcp/mcp.json",
371
+ nextSteps: [
372
+ "Junie in the IDE: Settings > Tools > Junie > MCP Settings lists stoaken and its status.",
373
+ "Junie CLI: run /mcp, select stoaken, choose Authorize and sign in in the browser."
374
+ ]
375
+ } : {
376
+ file: null,
377
+ nextSteps: [
378
+ "Junie has no secret storage or variable expansion for MCP configs, so the key can't go in a project file.",
379
+ `In your user file ~/.junie/mcp/mcp.json, under "mcpServers", add "stoaken": { "url": "${url}", "headers": { "Authorization": "Bearer <API key>" } }`,
380
+ oauthInstead
381
+ ]
382
+ };
383
+ case "cline":
384
+ return {
385
+ file: null,
386
+ nextSteps: [
387
+ "Cline keeps MCP servers in its own settings, not a project file.",
388
+ ...auth === "oauth" ? [
389
+ `Cline panel > MCP Servers > Remote Servers: name stoaken, URL ${url}, transport Streamable HTTP, Add Server.`,
390
+ "If it asks you to authenticate, sign in in the browser (Cline CLI: cline mcp > Authorize OAuth)."
391
+ ] : [
392
+ `Cline panel > MCP Servers > Configure > Configure MCP Servers, and add under "mcpServers": "stoaken": { "type": "streamableHttp", "url": "${url}", "headers": { "Authorization": "Bearer <API key>" } }`
393
+ ]
394
+ ]
395
+ };
396
+ case "lovable":
397
+ return {
398
+ file: null,
399
+ nextSteps: [
400
+ "Lovable adds MCP servers in its own UI, not through a project file.",
401
+ "Connectors > Add custom > MCP server. Server name: Stoaken.",
402
+ `Server URL: ${url}. Authentication: OAuth (Add & authorize), or Bearer token with an API key.`,
403
+ "The connection is yours alone and only for Lovable's chat; the app it publishes can't call it."
404
+ ]
405
+ };
406
+ case "bolt":
407
+ return {
408
+ file: null,
409
+ nextSteps: [
410
+ "Bolt adds MCP servers in its own UI, not through a project file.",
411
+ "In the chatbox: + > Connectors > Manage connectors > Custom MCP server.",
412
+ `Name: Stoaken. URL: ${url}. Transport type: HTTP. Authentication: MCP OAuth.`,
413
+ "Then turn the connector on for the project (+ > Connectors)."
414
+ ]
415
+ };
416
+ case "replit":
417
+ return {
418
+ file: null,
419
+ nextSteps: [
420
+ "Replit adds MCP servers in its own UI, not through a project file.",
421
+ `Open ${replitInstallLink(serverUrl)} (or Integrations > MCP Servers > + Add MCP server, URL ${url}).`,
422
+ auth === "oauth" ? "Click Test & save and sign in when Replit runs the OAuth flow." : "Under Advanced settings add the header Authorization: Bearer <API key>, then Test & save."
423
+ ]
424
+ };
293
425
  }
294
426
  }
295
427
  function jsonEntry(client, auth, serverUrl) {
296
428
  const url = mcpUrl(serverUrl);
429
+ if (client === "windsurf" || client === "zed" || client === "jetbrains") return { url };
430
+ if (client === "gemini-cli") {
431
+ return auth === "oauth" ? { httpUrl: url } : { httpUrl: url, headers: { Authorization: `Bearer \${${API_KEY_ENV}}` } };
432
+ }
297
433
  if (client === "claude-code") {
298
434
  return auth === "oauth" ? { type: "http", url } : { type: "http", url, headers: { Authorization: `Bearer \${${API_KEY_ENV}}` } };
299
435
  }
@@ -325,8 +461,9 @@ var COPILOT_INPUT = {
325
461
  };
326
462
  function mergeJsonConfig(client, existing, auth, serverUrl) {
327
463
  const file = planFor(client, auth, serverUrl).file;
464
+ if (file === null) throw new ConfigMergeError(`${CLIENT_LABELS[client]} gets no project file with an API key; follow the steps instead.`);
328
465
  const doc = parseObject(existing, file);
329
- const key = client === "copilot" ? "servers" : "mcpServers";
466
+ const key = SERVERS_KEY[client];
330
467
  const current = doc[key];
331
468
  if (current !== void 0 && (typeof current !== "object" || current === null || Array.isArray(current))) {
332
469
  throw new ConfigMergeError(`${file} has a "${key}" that isn't an object, so it was left alone.`);
@@ -377,7 +514,7 @@ file -- it's already in \`.gitignore\`).
377
514
  }
378
515
  const { client, auth } = target;
379
516
  const plan = planFor(client, auth, serverUrl);
380
- const how = auth === "oauth" ? "the client signs in with OAuth" : `it reads the \`${API_KEY_ENV}\` environment variable`;
517
+ const how = auth === "oauth" ? "the client signs in with OAuth" : plan.keyNote ?? `it reads the \`${API_KEY_ENV}\` environment variable`;
381
518
  const where = plan.file ? `It is configured for ${CLIENT_LABELS[client]} in \`${plan.file}\` (no secret in that file: ${how}).` : `${CLIENT_LABELS[client]} connects through its own settings, not a project file.`;
382
519
  return `${where}
383
520
 
@@ -557,6 +694,7 @@ async function runInit(options) {
557
694
  const path = join2(cwd, plan.file);
558
695
  const existingText = existsSync2(path) ? await readFile2(path, "utf8") : null;
559
696
  try {
697
+ if (client !== "codex" && !isJsonClient(client)) throw new Error(`No file writer for ${client}.`);
560
698
  const next = client === "codex" ? mergeCodexToml(existingText, auth, serverUrl) : mergeJsonConfig(client, existingText, auth, serverUrl);
561
699
  await mkdir2(dirname(path), { recursive: true });
562
700
  await writeFile2(path, next, "utf8");
@@ -590,7 +728,7 @@ async function call(serverUrl, token, path) {
590
728
  let response;
591
729
  try {
592
730
  response = await fetch(new URL(path, serverUrl), {
593
- headers: { Authorization: `Bearer ${token}`, "User-Agent": "stoaken-cli/0.4.0" }
731
+ headers: { Authorization: `Bearer ${token}`, "User-Agent": "stoaken-cli/0.5.0" }
594
732
  });
595
733
  } catch (error) {
596
734
  throw new Error(`Couldn't reach ${serverUrl}: ${error instanceof Error ? error.message : String(error)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stoaken",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Stoaken CLI -- set up a project for MCP/API access, and export your design tokens as code.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",