@yugabytedb/meko-mcp 2.0.0 → 2.2.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 (65) hide show
  1. package/README.md +183 -33
  2. package/assets/hooks-handlers/codex-session-end.sh +13 -0
  3. package/assets/hooks-handlers/cursor-session-start.sh +19 -1
  4. package/assets/hooks-handlers/lib/capture-notices.js +321 -0
  5. package/assets/hooks-handlers/lib/capture-state.js +2079 -0
  6. package/assets/hooks-handlers/lib/capture-status-cli.js +82 -0
  7. package/assets/hooks-handlers/lib/capture.js +1653 -225
  8. package/assets/hooks-handlers/lib/checkpoint-timer.js +455 -50
  9. package/assets/hooks-handlers/lib/kiro-handler.js +738 -0
  10. package/assets/hooks-handlers/lib/terminal-handoff.js +418 -0
  11. package/assets/hooks-handlers/session-end.sh +3 -24
  12. package/assets/hooks-handlers/session-start.sh +8 -1
  13. package/assets/hooks-handlers/test_checkpoint_timer.sh +162 -0
  14. package/assets/scripts/meko-statusline.sh +41 -1
  15. package/assets/scripts/package-desktop-skill.sh +32 -1
  16. package/assets/skills/meko-mcp-tools/SKILL.md +90 -137
  17. package/assets/skills/meko-mcp-tools/references/_sections.md +3 -5
  18. package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +31 -28
  19. package/assets/skills/meko-mcp-tools/references/tools-conversation-dedup.md +5 -7
  20. package/assets/skills/meko-mcp-tools/references/tools-conversation-reasoning.md +50 -0
  21. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +69 -49
  22. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +6 -6
  23. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +29 -5
  24. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +29 -92
  25. package/assets/skills/meko-mcp-tools/references/tools-overview.md +42 -40
  26. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +1 -2
  27. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +18 -11
  28. package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +10 -9
  29. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +51 -44
  30. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +6 -6
  31. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +2 -2
  32. package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +34 -25
  33. package/assets/skills/meko-mcp-tools-desktop/references/tools-conversation-reasoning.md +50 -0
  34. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +63 -37
  35. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +4 -4
  36. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +19 -22
  37. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +42 -40
  38. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +1 -2
  39. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +3 -17
  40. package/assets/skills/meko-select-datapack-desktop/SKILL.md +31 -21
  41. package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +8 -8
  42. package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +41 -0
  43. package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +1 -1
  44. package/bin/create-meko-setup.mjs +93 -16
  45. package/lib/capture-status.mjs +67 -0
  46. package/lib/clients/aliases.mjs +5 -0
  47. package/lib/clients/claude-code.mjs +8 -4
  48. package/lib/clients/claude-desktop.mjs +133 -4
  49. package/lib/clients/codex.mjs +29 -11
  50. package/lib/clients/common.mjs +2 -0
  51. package/lib/clients/cursor.mjs +7 -2
  52. package/lib/clients/index.mjs +2 -0
  53. package/lib/clients/kiro.mjs +705 -0
  54. package/lib/config.mjs +90 -0
  55. package/lib/detect.mjs +7 -2
  56. package/lib/installer.mjs +1 -0
  57. package/lib/migrate/adapters/codex.mjs +490 -0
  58. package/lib/migrate/id.mjs +28 -3
  59. package/lib/migrate/orchestrator.mjs +34 -7
  60. package/lib/migrate.mjs +4 -2
  61. package/lib/prompt.mjs +17 -2
  62. package/lib/url-selection.mjs +55 -0
  63. package/package.json +4 -4
  64. package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +0 -71
  65. package/assets/skills/meko-mcp-tools-desktop/references/tools-scope-permissions.md +0 -71
@@ -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) {
@@ -99,9 +210,11 @@ export function createClaudeDesktopAdapter(options = {}) {
99
210
  const fromMcpb =
100
211
  /\bClaude\b[^ ]*\bExtensions\b/i.test(commandJoined) ||
101
212
  /mcp-remote-launcher\.js/.test(commandJoined);
213
+ const url = getMcpRemoteEntryUrl(entry);
102
214
  return {
103
215
  found: true,
104
216
  status: fromMcpb ? "Configured (via .mcpb extension)" : "Configured",
217
+ ...(url ? { url } : {}),
105
218
  };
106
219
  },
107
220
  removeRegistration(name) {
@@ -281,7 +394,7 @@ export function createClaudeDesktopAdapter(options = {}) {
281
394
  export function buildMcpRemoteEntry({ url, apiKey }) {
282
395
  const args = [
283
396
  "-y",
284
- "mcp-remote",
397
+ MCP_REMOTE_PACKAGE,
285
398
  url,
286
399
  "--transport",
287
400
  "http-only",
@@ -300,6 +413,22 @@ export function buildMcpRemoteEntry({ url, apiKey }) {
300
413
  return entry;
301
414
  }
302
415
 
416
+ /** Read the remote URL from installer-managed or MCPB Claude Desktop entries. */
417
+ export function getMcpRemoteEntryUrl(entry) {
418
+ if (typeof entry?.env?.MEKO_MCP_URL === "string") {
419
+ return entry.env.MEKO_MCP_URL;
420
+ }
421
+ if (typeof entry?.url === "string") return entry.url;
422
+ if (!Array.isArray(entry?.args)) return undefined;
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
+ );
428
+ const candidate = remoteIndex >= 0 ? entry.args[remoteIndex + 1] : undefined;
429
+ return typeof candidate === "string" ? candidate : undefined;
430
+ }
431
+
303
432
  function defaultRunPackageScript(scriptPath) {
304
433
  if (!statSync(scriptPath).isFile()) {
305
434
  throw new Error(`Not a file: ${scriptPath}`);
@@ -77,14 +77,19 @@ const RC_FENCE_END = "# <<< meko mcp <<<";
77
77
  const SKILLS = [{ name: "meko-mcp-tools" }];
78
78
  // Codex hook timeouts are in SECONDS (Duration::from_secs in
79
79
  // codex-rs/hooks/src/engine/command_runner.rs), not milliseconds like Claude
80
- // Code / Cursor. Default is 600s if omitted; we cap SessionStart/SessionEnd
81
- // at 15s/30s to match the Claude Code values logically.
80
+ // Code / Cursor. Codex hard-caps SessionEnd at three seconds, so its dedicated
81
+ // handler only spools input and launches the actual capture in the background.
82
82
  const MEKO_HOOKS = [
83
83
  { event: "SessionStart", script: "session-start.sh", timeout: 15 },
84
84
  { event: "PreCompact", script: "pre-compact.sh" },
85
- { event: "SessionEnd", script: "session-end.sh", timeout: 30 },
85
+ { event: "SessionEnd", script: "codex-session-end.sh", timeout: 3 },
86
+ ];
87
+ // Include the former synchronous Codex SessionEnd handler so reinstall prunes
88
+ // it instead of leaving both old and new handlers active.
89
+ const MEKO_HOOK_SCRIPTS = [
90
+ ...MEKO_HOOKS.map((hook) => hook.script),
91
+ "session-end.sh",
86
92
  ];
87
- const MEKO_HOOK_SCRIPTS = MEKO_HOOKS.map((hook) => hook.script);
88
93
 
89
94
  /**
90
95
  * @param {{
@@ -92,6 +97,7 @@ const MEKO_HOOK_SCRIPTS = MEKO_HOOKS.map((hook) => hook.script);
92
97
  * getAgentsMdPath?: () => string,
93
98
  * getHooksPath?: () => string,
94
99
  * getSkillsRoot?: () => string,
100
+ * resolveSkillSource?: (name: string) => string,
95
101
  * resolveHooksRoot?: () => string,
96
102
  * getHooksInstallRoot?: () => string,
97
103
  * runCommand?: (cmd: string) => string,
@@ -108,6 +114,8 @@ export function createCodexAdapter(options = {}) {
108
114
  const resolveHooksPath = options.getHooksPath ?? getCodexHooksPath;
109
115
  const resolveMekoEnvFilePath =
110
116
  options.getMekoEnvFilePath ?? getCodexMekoEnvFilePath;
117
+ const resolveSkillSourcePath =
118
+ options.resolveSkillSource ?? resolveSkillSource;
111
119
  const resolveHooksRootPath = options.resolveHooksRoot ?? resolveHooksRoot;
112
120
  const resolveHooksInstallRoot =
113
121
  options.getHooksInstallRoot ?? getCodexHooksInstallRoot;
@@ -196,10 +204,11 @@ export function createCodexAdapter(options = {}) {
196
204
  surfaces.app = app?.found ? app : null;
197
205
  }
198
206
 
207
+ let cliFound = false;
199
208
  if (surfaces.cli) {
200
209
  try {
201
210
  runCommand(`codex mcp get ${shellQuote(name)}`);
202
- return { found: true, status: "Configured" };
211
+ cliFound = true;
203
212
  } catch {
204
213
  // fall through to TOML grep — `codex mcp get` exits non-zero when
205
214
  // the entry isn't present, but might also fail for unrelated
@@ -209,8 +218,17 @@ export function createCodexAdapter(options = {}) {
209
218
  }
210
219
  const path = settingsPathFor(scope, null);
211
220
  const raw = safeReadFile(path);
212
- if (raw === null) return { found: false, status: "" };
213
- return parseCodexConfigForServer(raw, name).found
221
+ if (raw !== null) {
222
+ const parsed = parseCodexConfigForServer(raw, name);
223
+ if (parsed.found) {
224
+ return {
225
+ found: true,
226
+ status: "Configured",
227
+ ...(parsed.url ? { url: parsed.url } : {}),
228
+ };
229
+ }
230
+ }
231
+ return cliFound
214
232
  ? { found: true, status: "Configured" }
215
233
  : { found: false, status: "" };
216
234
  },
@@ -424,7 +442,7 @@ export function createCodexAdapter(options = {}) {
424
442
 
425
443
  mkdirSync(skillsRoot, { recursive: true });
426
444
  for (const skill of SKILLS) {
427
- const source = resolveSkillSource(skill.name);
445
+ const source = resolveSkillSourcePath(skill.name);
428
446
  const destination = join(skillsRoot, skill.name);
429
447
 
430
448
  if (existsSync(destination)) {
@@ -502,7 +520,7 @@ export function createCodexAdapter(options = {}) {
502
520
  removePath(dest);
503
521
  continue;
504
522
  }
505
- const expectedSource = resolveSkillSource(skill.name);
523
+ const expectedSource = resolveSkillSourcePath(skill.name);
506
524
  if (isOwnSymlink(dest, expectedSource)) removePath(dest);
507
525
  }
508
526
  results.push({
@@ -640,7 +658,7 @@ function upsertCodexMekoHooks(hooksPath, hooksRoot, { envFilePath } = {}) {
640
658
  function buildCodexHookCommand(scriptPath, { envFilePath } = {}) {
641
659
  const parts = [];
642
660
  if (envFilePath) parts.push(`. ${shellQuote(envFilePath)}`);
643
- parts.push(`bash ${shellQuote(scriptPath)}`);
661
+ parts.push(`MEKO_HOOK_CLIENT=codex bash ${shellQuote(scriptPath)}`);
644
662
  return parts.join(" && ");
645
663
  }
646
664
 
@@ -1299,7 +1317,7 @@ function renderAgentsMdBlock(version, name) {
1299
1317
  `${AGENTS_MD_START} v${version}`,
1300
1318
  "## Meko memory (auto-managed by meko installer)",
1301
1319
  "",
1302
- `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`,
1320
+ `You have a persistent memory store via the \`${name}\` MCP server. At the start of every conversation, call \`${toolPrefix}memory_search\` for relevant personal context; use \`${toolPrefix}knowledgebase_search\` when the request concerns shared datapack knowledge. When the user shares facts, preferences, or decisions worth remembering, call \`${toolPrefix}memory_add\`. Skill details: ~/.agents/skills/meko-mcp-tools/SKILL.md`,
1303
1321
  "",
1304
1322
  "Cloud auth requires `MEKO_API_KEY` exported in your shell (or, for the desktop app on macOS, set via `launchctl setenv`).",
1305
1323
  AGENTS_MD_END,
@@ -16,6 +16,8 @@ import { dirname, join, resolve } from "node:path";
16
16
  import { fileURLToPath } from "node:url";
17
17
 
18
18
  export const MEKO_MCP_USER_AGENT = "meko-mcp-installer/1.0";
19
+ export const MEKO_PRODUCTION_MCP_URL = "https://mcp.mekodata.ai/mcp";
20
+ export const MEKO_PRODUCTION_CONSOLE_URL = "https://cloud.mekodata.ai";
19
21
 
20
22
  /**
21
23
  * @returns {string} Absolute repository root path.
@@ -75,8 +75,13 @@ export function createCursorAdapter(customPaths = {}) {
75
75
  detectExisting(name, { scope, projectRoot }) {
76
76
  const path = paths.getMcpPath(scope, projectRoot);
77
77
  const cfg = readJsonFile(path);
78
- const found = Boolean(cfg?.mcpServers?.[name]);
79
- return { found, status: found ? "Configured" : "" };
78
+ const entry = cfg?.mcpServers?.[name];
79
+ const found = Boolean(entry);
80
+ return {
81
+ found,
82
+ status: found ? "Configured" : "",
83
+ ...(typeof entry?.url === "string" ? { url: entry.url } : {}),
84
+ };
80
85
  },
81
86
  removeRegistration(name, { scope, projectRoot }) {
82
87
  const mcpPath = paths.getMcpPath(scope, projectRoot);
@@ -2,6 +2,7 @@ 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";
5
6
 
6
7
  /**
7
8
  * @returns {Record<string, object>}
@@ -12,5 +13,6 @@ export function getClientAdapters() {
12
13
  "claude-desktop": createClaudeDesktopAdapter(),
13
14
  cursor: createCursorAdapter(),
14
15
  codex: createCodexAdapter(),
16
+ kiro: createKiroAdapter(),
15
17
  };
16
18
  }