@yugabytedb/meko-mcp 2.1.0 → 2.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 (70) hide show
  1. package/README.md +233 -40
  2. package/assets/hooks-handlers/codex-session-end.sh +4 -69
  3. package/assets/hooks-handlers/cursor-session-start.sh +19 -1
  4. package/assets/hooks-handlers/lib/capture-cleanup-cli.js +1473 -0
  5. package/assets/hooks-handlers/lib/capture-notices.js +438 -0
  6. package/assets/hooks-handlers/lib/capture-state.js +2173 -0
  7. package/assets/hooks-handlers/lib/capture-status-cli.js +86 -0
  8. package/assets/hooks-handlers/lib/capture.js +2782 -403
  9. package/assets/hooks-handlers/lib/checkpoint-timer.js +485 -81
  10. package/assets/hooks-handlers/lib/journal-bridge.js +218 -0
  11. package/assets/hooks-handlers/lib/kiro-handler.js +760 -0
  12. package/assets/hooks-handlers/lib/opencode-handler.js +620 -0
  13. package/assets/hooks-handlers/lib/opencode-normalizer.js +145 -0
  14. package/assets/hooks-handlers/lib/terminal-handoff.js +427 -0
  15. package/assets/hooks-handlers/session-end.sh +3 -34
  16. package/assets/opencode-plugin/meko.js +213 -0
  17. package/assets/scripts/meko-statusline.sh +123 -16
  18. package/assets/skills/meko-mcp-tools/SKILL.md +116 -126
  19. package/assets/skills/meko-mcp-tools/references/_sections.md +3 -3
  20. package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +25 -18
  21. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +99 -24
  22. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +1 -1
  23. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +53 -6
  24. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +25 -79
  25. package/assets/skills/meko-mcp-tools/references/tools-overview.md +37 -26
  26. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +14 -8
  27. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +66 -1
  28. package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +7 -40
  29. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +56 -20
  30. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +1 -1
  31. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
  32. package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +15 -8
  33. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +99 -36
  34. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +22 -5
  35. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +18 -14
  36. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +39 -28
  37. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +14 -8
  38. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +67 -1
  39. package/assets/skills/meko-select-datapack-desktop/SKILL.md +4 -4
  40. package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +8 -8
  41. package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +7 -5
  42. package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +4 -4
  43. package/bin/create-meko-setup.mjs +113 -12
  44. package/lib/capture-cleanup.mjs +63 -0
  45. package/lib/capture-status.mjs +67 -0
  46. package/lib/clients/aliases.mjs +5 -0
  47. package/lib/clients/claude-code.mjs +1 -0
  48. package/lib/clients/claude-desktop.mjs +120 -5
  49. package/lib/clients/codex.mjs +85 -37
  50. package/lib/clients/common.mjs +7 -0
  51. package/lib/clients/index.mjs +4 -0
  52. package/lib/clients/kiro.mjs +705 -0
  53. package/lib/clients/opencode.mjs +532 -0
  54. package/lib/config.mjs +158 -1
  55. package/lib/installer.mjs +18 -1
  56. package/lib/migrate/adapters/claude-code.mjs +11 -2
  57. package/lib/migrate/adapters/codex.mjs +497 -0
  58. package/lib/migrate/adapters/memory-md.mjs +27 -8
  59. package/lib/migrate/adapters/opencode.mjs +148 -0
  60. package/lib/migrate/claude-parser.mjs +153 -9
  61. package/lib/migrate/id.mjs +217 -10
  62. package/lib/migrate/mcp-client.mjs +25 -1
  63. package/lib/migrate/opencode-normalizer.mjs +132 -0
  64. package/lib/migrate/orchestrator.mjs +299 -56
  65. package/lib/migrate/progress.mjs +239 -5
  66. package/lib/migrate/registry.mjs +72 -0
  67. package/lib/migrate.mjs +100 -10
  68. package/lib/prompt.mjs +16 -1
  69. package/package.json +4 -4
  70. package/scripts/bundle-assets.mjs +4 -0
@@ -18,6 +18,101 @@ import {
18
18
  writeInstallerMarker,
19
19
  } from "./common.mjs";
20
20
 
21
+ /**
22
+ * Pinned top-level `mcp-remote` version.
23
+ *
24
+ * Pinning the top-level package (rather than leaving a bare `mcp-remote` that
25
+ * floats onto whatever npm's `latest` tag points to) reduces — but does not
26
+ * eliminate — startup drift: `mcp-remote@0.1.38` still declares transitive
27
+ * ranges like `open@^10.1.0` and `undici@^7.12.0`, and a fresh `npx` cache
28
+ * resolves the newest matching version of each with no lockfile consulted, so
29
+ * a future transitive release can still change startup behavior. The Node
30
+ * floor enforced below is what keeps today's tree runnable: every *currently
31
+ * published* undici 7 release declares `engines.node >= 20.18.1`, so rejecting
32
+ * Node under that floor covers the dependency set that resolves today. It is
33
+ * not a future-proof guarantee — a later in-range 7.x release could raise its
34
+ * own engine floor or introduce another incompatibility, which is exactly the
35
+ * residual drift the paragraph above acknowledges. Bump this pin deliberately
36
+ * after verifying the new release still starts on the supported Node floor.
37
+ */
38
+ export const MCP_REMOTE_VERSION = "0.1.38";
39
+
40
+ /** Pinned npm spec used in the registered command, e.g. `mcp-remote@0.1.38`. */
41
+ export const MCP_REMOTE_PACKAGE = `mcp-remote@${MCP_REMOTE_VERSION}`;
42
+
43
+ /**
44
+ * Minimum Node.js version required for the registered mcp-remote bridge to
45
+ * start. `mcp-remote@0.1.38` depends on `undici@^7.12.0`, and every undici 7
46
+ * release declares `engines.node >= 20.18.1`. npm's engine check is only a
47
+ * warning by default, so nothing stops a below-floor Node from resolving the
48
+ * tree and then crashing the bridge — we hard-fail preflight here instead of
49
+ * writing a config that cannot start. Bump this in lockstep with the pinned
50
+ * `mcp-remote` version above (and the advertised floors in package.json,
51
+ * mcpb/manifest.json, and the docs) after verifying the new dependency tree's
52
+ * engine requirement.
53
+ */
54
+ export const MIN_NODE_VERSION = "20.18.1";
55
+
56
+ /**
57
+ * Structured form of {@link MIN_NODE_VERSION}, parsed once so the preflight can
58
+ * compare major/minor/patch without re-parsing on every call.
59
+ */
60
+ export const MIN_NODE = parseNodeVersion(MIN_NODE_VERSION);
61
+
62
+ /**
63
+ * Parse a `process.version`-style string into its numeric {major, minor, patch}
64
+ * components (e.g. "v20.18.1" -> {major:20, minor:18, patch:1}). A missing
65
+ * minor/patch defaults to 0. Returns null when unparseable.
66
+ *
67
+ * The regex is end-anchored and rejects any trailing characters, including a
68
+ * semver prerelease/build suffix (`-rc.1`, `-nightly`, `+build`). A prerelease
69
+ * like `20.18.1-rc.1` sorts *below* the stable `20.18.1` under semver, so we
70
+ * must not silently discard the suffix and treat it as the stable release —
71
+ * that would let a below-floor prerelease pass the preflight. Returning null
72
+ * here routes such versions through `nodeMeetsFloor`'s not-satisfied path.
73
+ *
74
+ * @param {string} version
75
+ * @returns {{major:number, minor:number, patch:number}|null}
76
+ */
77
+ export function parseNodeVersion(version) {
78
+ const match = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(String(version));
79
+ if (!match) return null;
80
+ const major = Number(match[1]);
81
+ const minor = Number(match[2] ?? 0);
82
+ const patch = Number(match[3] ?? 0);
83
+ if (![major, minor, patch].every(Number.isFinite)) return null;
84
+ return { major, minor, patch };
85
+ }
86
+
87
+ /**
88
+ * Does `version` meet the {@link MIN_NODE} floor (>= major.minor.patch)?
89
+ *
90
+ * Three cases:
91
+ * - A clean `X.Y.Z` at/above the floor → true; below → false.
92
+ * - A *version-shaped* string that isn't a clean release — a prerelease
93
+ * (`20.18.1-rc.1`) or trailing garbage (`20.19.0-night,`) — → false. Under
94
+ * semver a prerelease sorts below its release, so it does NOT satisfy
95
+ * `>=20.18.1`; we reject rather than accept an ambiguous runtime.
96
+ * - Input that isn't version-shaped at all → true. We stay lenient on strings
97
+ * we can't read (the same stance the previous major-only check took);
98
+ * `process.version` never produces these, so this only guards odd stubs.
99
+ *
100
+ * @param {string} version
101
+ * @returns {boolean}
102
+ */
103
+ export function nodeMeetsFloor(version) {
104
+ const parsed = parseNodeVersion(version);
105
+ if (parsed === null) {
106
+ // Version-shaped (leading digits, optionally `v`-prefixed) but not a clean
107
+ // X.Y.Z — i.e. it carried a prerelease/build suffix or trailing chars.
108
+ // Treat as not satisfying the floor. Truly non-version input is lenient.
109
+ return !/^v?\d/.test(String(version));
110
+ }
111
+ if (parsed.major !== MIN_NODE.major) return parsed.major > MIN_NODE.major;
112
+ if (parsed.minor !== MIN_NODE.minor) return parsed.minor > MIN_NODE.minor;
113
+ return parsed.patch >= MIN_NODE.patch;
114
+ }
115
+
21
116
  /**
22
117
  * Claude Desktop adapter.
23
118
  *
@@ -31,7 +126,7 @@ import {
31
126
  *
32
127
  * "meko": {
33
128
  * "command": "npx",
34
- * "args": ["-y", "mcp-remote", "<url>", "--transport", "http-only",
129
+ * "args": ["-y", "mcp-remote@<pinned>", "<url>", "--transport", "http-only",
35
130
  * "--header", "User-Agent:meko-mcp-installer/1.0 (...)",
36
131
  * "--header", "Authorization:Bearer ${MEKO_API_KEY}"],
37
132
  * "env": { "MEKO_API_KEY": "<key>" }
@@ -53,6 +148,7 @@ export function createClaudeDesktopAdapter(options = {}) {
53
148
  const assetRootResolver = options.getAssetRoot ?? getAssetRoot;
54
149
  const packageRunner = options.runPackageScript ?? defaultRunPackageScript;
55
150
  const npxProber = options.probeNpx ?? defaultProbeNpx;
151
+ const nodeVersionResolver = options.getNodeVersion ?? (() => process.version);
56
152
 
57
153
  return {
58
154
  id: "claude-desktop",
@@ -61,12 +157,27 @@ export function createClaudeDesktopAdapter(options = {}) {
61
157
  return configPathResolver();
62
158
  },
63
159
  async detectPrerequisites({ log, verbose } = {}) {
160
+ // The registered mcp-remote bridge pulls in undici@^7, whose every
161
+ // release declares engines.node >= 20.18.1; below that floor the bridge
162
+ // crashes at startup and Claude Desktop shows "Server disconnected". A
163
+ // major-only check would wave through Node 20.0.0–20.18.0, so we parse
164
+ // the full version and reject anything below the exact floor here,
165
+ // before writing a config that Claude Desktop cannot start.
166
+ const nodeVersion = nodeVersionResolver();
167
+ if (!nodeMeetsFloor(nodeVersion)) {
168
+ throw new Error(
169
+ `Node >= ${MIN_NODE_VERSION} is required for the mcp-remote bridge that ` +
170
+ `Claude Desktop launches (detected ${nodeVersion}). Older Node ` +
171
+ `versions crash the bridge at startup and Claude Desktop shows ` +
172
+ `"Server disconnected". Upgrade Node.js and re-run the installer.`,
173
+ );
174
+ }
64
175
  const npx = npxProber();
65
176
  if (!npx.found) {
66
177
  throw new Error(
67
178
  "npx (from Node.js) is required so Claude Desktop can launch the " +
68
- "mcp-remote stdio bridge. Install Node.js 18+ (which includes npx) " +
69
- "and re-run the installer.",
179
+ `mcp-remote stdio bridge. Install Node.js ${MIN_NODE_VERSION}+ (which ` +
180
+ "includes npx) and re-run the installer.",
70
181
  );
71
182
  }
72
183
  if (verbose && log?.dim) {
@@ -283,7 +394,7 @@ export function createClaudeDesktopAdapter(options = {}) {
283
394
  export function buildMcpRemoteEntry({ url, apiKey }) {
284
395
  const args = [
285
396
  "-y",
286
- "mcp-remote",
397
+ MCP_REMOTE_PACKAGE,
287
398
  url,
288
399
  "--transport",
289
400
  "http-only",
@@ -309,7 +420,11 @@ export function getMcpRemoteEntryUrl(entry) {
309
420
  }
310
421
  if (typeof entry?.url === "string") return entry.url;
311
422
  if (!Array.isArray(entry?.args)) return undefined;
312
- const remoteIndex = entry.args.indexOf("mcp-remote");
423
+ // Match both the bare `mcp-remote` (legacy entries) and the pinned
424
+ // `mcp-remote@<version>` spec the installer now writes.
425
+ const remoteIndex = entry.args.findIndex(
426
+ (arg) => arg === "mcp-remote" || (typeof arg === "string" && arg.startsWith("mcp-remote@")),
427
+ );
313
428
  const candidate = remoteIndex >= 0 ? entry.args[remoteIndex + 1] : undefined;
314
429
  return typeof candidate === "string" ? candidate : undefined;
315
430
  }
@@ -97,6 +97,7 @@ const MEKO_HOOK_SCRIPTS = [
97
97
  * getAgentsMdPath?: () => string,
98
98
  * getHooksPath?: () => string,
99
99
  * getSkillsRoot?: () => string,
100
+ * resolveSkillSource?: (name: string) => string,
100
101
  * resolveHooksRoot?: () => string,
101
102
  * getHooksInstallRoot?: () => string,
102
103
  * runCommand?: (cmd: string) => string,
@@ -113,6 +114,8 @@ export function createCodexAdapter(options = {}) {
113
114
  const resolveHooksPath = options.getHooksPath ?? getCodexHooksPath;
114
115
  const resolveMekoEnvFilePath =
115
116
  options.getMekoEnvFilePath ?? getCodexMekoEnvFilePath;
117
+ const resolveSkillSourcePath =
118
+ options.resolveSkillSource ?? resolveSkillSource;
116
119
  const resolveHooksRootPath = options.resolveHooksRoot ?? resolveHooksRoot;
117
120
  const resolveHooksInstallRoot =
118
121
  options.getHooksInstallRoot ?? getCodexHooksInstallRoot;
@@ -251,7 +254,7 @@ export function createCodexAdapter(options = {}) {
251
254
  const next = removeCodexConfigBlock(raw, name);
252
255
  if (next !== raw) writeWithBackup(path, next);
253
256
  },
254
- async registerServer({ name, url, apiKey, dryRun, verbose, log, scope, yes }) {
257
+ async registerServer({ name, url, apiKey, dryRun, verbose, log, scope }) {
255
258
  const path = settingsPathFor(scope, log);
256
259
  const useApiKey = apiKey ? "MEKO_API_KEY" : null;
257
260
  const cliCmd = buildCliRegisterCommand(name, url, useApiKey);
@@ -350,35 +353,6 @@ export function createCodexAdapter(options = {}) {
350
353
  // Cloud auth UX: env-var-only model. The key is never written to
351
354
  // config.toml; the user's runtime env supplies it. Print actionable
352
355
  // instructions and (when interactive) offer to wire things up.
353
- if (apiKey) {
354
- const platform = surfaces.app?.platform ?? process.platform;
355
- const hasApp = Boolean(surfaces.app);
356
- const shellRc = detectShellRc();
357
- printCloudAuthInstructions({ shellRc, hasApp, platform, log });
358
- if (!yes) {
359
- await maybeOfferRcAppend({
360
- apiKey,
361
- shellRc,
362
- promptUser,
363
- log,
364
- });
365
- if (hasApp && platform === "darwin") {
366
- await maybeOfferLaunchctlSetenv({
367
- apiKey,
368
- promptUser,
369
- runCommand,
370
- log,
371
- });
372
- }
373
- } else if (hasApp && platform === "darwin") {
374
- setLaunchctlMekoApiKey({
375
- apiKey,
376
- runCommand,
377
- log,
378
- });
379
- }
380
- }
381
-
382
356
  if (surfaces.app) {
383
357
  log.dim?.(" Restart Codex.app to attach the new MCP server.");
384
358
  }
@@ -424,6 +398,61 @@ export function createCodexAdapter(options = {}) {
424
398
  );
425
399
  }
426
400
  },
401
+ /**
402
+ * Cloud auth UX: env-var-only model. The key is never written to
403
+ * config.toml, so the user's runtime env must supply it. This is a
404
+ * separate adapter step (not part of registerServer) because an
405
+ * interactive reinstall where the user declines the overwrite prompt
406
+ * skips registration entirely - and the launchd env would otherwise
407
+ * never be refreshed after a key rotation. See
408
+ * yugabyte/meko-mcp-server#337.
409
+ */
410
+ async syncCloudAuthEnvironment({ apiKey, dryRun, log, yes }) {
411
+ if (!apiKey || dryRun) return;
412
+ const platform = surfaces.app?.platform ?? process.platform;
413
+ const hasApp = Boolean(surfaces.app);
414
+ const shellRc = detectShellRc();
415
+ printCloudAuthInstructions({ shellRc, hasApp, platform, log });
416
+ if (!yes) {
417
+ // Offer launchctl BEFORE rc-append, and isolate the rc-append
418
+ // failure mode. `maybeOfferRcAppend` throws when the shell rc is
419
+ // fish and the API key contains a `'` (fish single-quoting can't
420
+ // escape it). If that threw first, the launchctl offer - the
421
+ // yugabyte/meko-mcp-server#337 fix - would never run.
422
+ //
423
+ // macOS: offer launchctl setenv regardless of whether Codex.app
424
+ // is present. Spotlight- and Alfred-launched Codex CLI reads the
425
+ // launchd env too. Login-session inheritance means every
426
+ // GUI-launched process in this session then sees MEKO_API_KEY
427
+ // (visible via `launchctl getenv`, `ps e` on descendants,
428
+ // crash reports); the prompt copy calls that out so the choice
429
+ // is informed.
430
+ if (platform === "darwin") {
431
+ await maybeOfferLaunchctlSetenv({
432
+ apiKey,
433
+ promptUser,
434
+ runCommand,
435
+ log,
436
+ });
437
+ }
438
+ try {
439
+ await maybeOfferRcAppend({
440
+ apiKey,
441
+ shellRc,
442
+ promptUser,
443
+ log,
444
+ });
445
+ } catch (err) {
446
+ log?.warn?.(` Skipping shell rc append: ${err.message}`);
447
+ }
448
+ } else if (platform === "darwin") {
449
+ setLaunchctlMekoApiKey({
450
+ apiKey,
451
+ runCommand,
452
+ log,
453
+ });
454
+ }
455
+ },
427
456
  installSkills({ dryRun, log, scope, name }) {
428
457
  void scope; // Codex skills are user-scope only.
429
458
  const serverName = name ?? "meko";
@@ -439,7 +468,7 @@ export function createCodexAdapter(options = {}) {
439
468
 
440
469
  mkdirSync(skillsRoot, { recursive: true });
441
470
  for (const skill of SKILLS) {
442
- const source = resolveSkillSource(skill.name);
471
+ const source = resolveSkillSourcePath(skill.name);
443
472
  const destination = join(skillsRoot, skill.name);
444
473
 
445
474
  if (existsSync(destination)) {
@@ -517,7 +546,7 @@ export function createCodexAdapter(options = {}) {
517
546
  removePath(dest);
518
547
  continue;
519
548
  }
520
- const expectedSource = resolveSkillSource(skill.name);
549
+ const expectedSource = resolveSkillSourcePath(skill.name);
521
550
  if (isOwnSymlink(dest, expectedSource)) removePath(dest);
522
551
  }
523
552
  results.push({
@@ -1314,7 +1343,7 @@ function renderAgentsMdBlock(version, name) {
1314
1343
  `${AGENTS_MD_START} v${version}`,
1315
1344
  "## Meko memory (auto-managed by meko installer)",
1316
1345
  "",
1317
- `You have a persistent memory store via the \`${name}\` MCP server. At the start of every conversation, call \`${toolPrefix}memory_search\` for relevant context. When the user shares facts, preferences, or decisions worth remembering, call \`${toolPrefix}memory_add\`. Skill details: ~/.agents/skills/meko-mcp-tools/SKILL.md`,
1346
+ `You have a persistent memory store via the \`${name}\` MCP server. At the start of every conversation, call \`${toolPrefix}context_search\` for relevant context: it returns personal memory, shared datapack knowledge, and past conversations in one call. Use \`${toolPrefix}memory_search\` or \`${toolPrefix}knowledgebase_search\` when you need a single source. When the user shares facts, preferences, or decisions worth remembering, call \`${toolPrefix}memory_add\`. Skill details: ~/.agents/skills/meko-mcp-tools/SKILL.md`,
1318
1347
  "",
1319
1348
  "Cloud auth requires `MEKO_API_KEY` exported in your shell (or, for the desktop app on macOS, set via `launchctl setenv`).",
1320
1349
  AGENTS_MD_END,
@@ -1347,9 +1376,16 @@ function printCloudAuthInstructions({ shellRc, hasApp, platform, log }) {
1347
1376
  log.dim?.(` fish: set -gx MEKO_API_KEY '<your-meko-api-key>'`);
1348
1377
  }
1349
1378
  log.dim?.(` Then open a new terminal (or run "exec $SHELL" / "exec fish").`);
1350
- if (hasApp && platform === "darwin") {
1379
+ if (platform === "darwin") {
1380
+ // Explain launchd for both Codex.app and CLI-only macOS installs.
1381
+ // GUI-launched Codex (Finder, Dock, Spotlight, Alfred, `open -a`) reads
1382
+ // launchd's env, not the shell rc, so a fresh install or a rotated key
1383
+ // needs `launchctl setenv` to propagate. Shell-launched CLI still
1384
+ // reads its shell rc.
1351
1385
  log.warn?.(
1352
- "macOS Codex.app launched from Finder/Dock does NOT inherit shell rc — set it via launchctl too:",
1386
+ hasApp
1387
+ ? "macOS Codex.app launched from Finder/Dock does NOT inherit shell rc - set it via launchctl too:"
1388
+ : "macOS Codex CLI launched from Spotlight/Alfred/`open` does NOT inherit shell rc - set it via launchctl too:",
1353
1389
  );
1354
1390
  log.dim?.(
1355
1391
  ` launchctl setenv MEKO_API_KEY '<your-meko-api-key>' # current login session`,
@@ -1357,6 +1393,9 @@ function printCloudAuthInstructions({ shellRc, hasApp, platform, log }) {
1357
1393
  log.dim?.(
1358
1394
  " (For permanence across reboots, add a ~/Library/LaunchAgents/com.meko.env.plist.)",
1359
1395
  );
1396
+ log.warn?.(
1397
+ " launchctl setenv writes to the login session's env, which every subsequently GUI-launched process inherits (visible via `launchctl getenv`, `ps e` on descendants, and crash reports).",
1398
+ );
1360
1399
  }
1361
1400
  }
1362
1401
 
@@ -1440,9 +1479,15 @@ export async function maybeOfferLaunchctlSetenv({
1440
1479
  runCommand,
1441
1480
  log,
1442
1481
  }) {
1482
+ // Default to Yes: this is the setting that makes GUI-launched Codex on
1483
+ // macOS see the fresh key. Users who habitually hit Enter at the prompt
1484
+ // otherwise leave launchd empty and Codex authenticates with whatever key
1485
+ // it had at first start. See yugabyte/meko-mcp-server#337. The prompt
1486
+ // names both Codex.app and Spotlight-launched CLI so a user without the
1487
+ // desktop app understands why the offer applies to them.
1443
1488
  const accept = await promptUser(
1444
- "Run `launchctl setenv MEKO_API_KEY ...` so Codex.app sees the key now?",
1445
- false,
1489
+ "Run `launchctl setenv MEKO_API_KEY ...` so GUI-launched Codex (App, Spotlight, Alfred, `open`) sees the key now? (writes the key into the login session env, inherited by every subsequently GUI-launched process)",
1490
+ true,
1446
1491
  );
1447
1492
  if (!accept) return { set: false };
1448
1493
  return setLaunchctlMekoApiKey({ apiKey, runCommand, log });
@@ -1460,6 +1505,9 @@ export function setLaunchctlMekoApiKey({ apiKey, runCommand, log }) {
1460
1505
  try {
1461
1506
  runCommand(`launchctl setenv MEKO_API_KEY ${shellQuote(apiKey)}`);
1462
1507
  log?.dim?.(" Set MEKO_API_KEY via launchctl (current login session).");
1508
+ log?.warn?.(
1509
+ " Fully quit any running Codex (Cmd-Q, or `pkill -x codex` for the CLI) and relaunch — a process already running will keep its previous MEKO_API_KEY.",
1510
+ );
1463
1511
  return { set: true };
1464
1512
  } catch (err) {
1465
1513
  log?.warn?.(" launchctl setenv failed; run it manually if Codex.app needs cloud auth.");
@@ -88,6 +88,13 @@ export function resolveHooksRoot() {
88
88
  return join(getAssetRoot(), "hooks-handlers");
89
89
  }
90
90
 
91
+ /**
92
+ * @returns {string} absolute path to the installer-owned OpenCode plugin source
93
+ */
94
+ export function resolveOpenCodePluginSource() {
95
+ return join(getAssetRoot(), "opencode-plugin", "meko.js");
96
+ }
97
+
91
98
  /**
92
99
  * @returns {string} absolute path to the statusline script source.
93
100
  * Lives under skills/scripts/ in source, mirrored under assets/scripts/ in
@@ -2,6 +2,8 @@ import { createClaudeCodeAdapter } from "./claude-code.mjs";
2
2
  import { createClaudeDesktopAdapter } from "./claude-desktop.mjs";
3
3
  import { createCodexAdapter } from "./codex.mjs";
4
4
  import { createCursorAdapter } from "./cursor.mjs";
5
+ import { createKiroAdapter } from "./kiro.mjs";
6
+ import { createOpenCodeAdapter } from "./opencode.mjs";
5
7
 
6
8
  /**
7
9
  * @returns {Record<string, object>}
@@ -12,5 +14,7 @@ export function getClientAdapters() {
12
14
  "claude-desktop": createClaudeDesktopAdapter(),
13
15
  cursor: createCursorAdapter(),
14
16
  codex: createCodexAdapter(),
17
+ kiro: createKiroAdapter(),
18
+ opencode: createOpenCodeAdapter(),
15
19
  };
16
20
  }