@yugabytedb/meko-mcp 2.3.0 → 2.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 (40) hide show
  1. package/README.md +72 -33
  2. package/assets/hooks-handlers/lib/capture-cleanup-cli.js +735 -74
  3. package/assets/hooks-handlers/lib/capture-notices.js +94 -1
  4. package/assets/hooks-handlers/lib/capture-state.js +111 -0
  5. package/assets/hooks-handlers/lib/capture.js +129 -4
  6. package/assets/hooks-handlers/lib/checkpoint-timer.js +394 -8
  7. package/assets/hooks-handlers/lib/opencode-handler.js +29 -6
  8. package/assets/scripts/meko-statusline.sh +17 -1
  9. package/assets/skills/meko-mcp-tools/SKILL.md +5 -5
  10. package/assets/skills/meko-mcp-tools/references/_sections.md +1 -1
  11. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +193 -9
  12. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +6 -3
  13. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +11 -6
  14. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +4 -4
  15. package/assets/skills/meko-mcp-tools/references/tools-overview.md +16 -11
  16. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +67 -8
  17. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +3 -1
  18. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +7 -7
  19. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +4 -4
  20. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
  21. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +193 -9
  22. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +10 -5
  23. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +4 -4
  24. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +16 -11
  25. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +67 -8
  26. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +3 -1
  27. package/assets/skills/meko-select-datapack-desktop/SKILL.md +91 -12
  28. package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +39 -7
  29. package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +8 -6
  30. package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +13 -1
  31. package/bin/create-meko-setup.mjs +117 -24
  32. package/lib/autofix.mjs +100 -0
  33. package/lib/browser-login.mjs +495 -0
  34. package/lib/clients/opencode.mjs +6 -1
  35. package/lib/migrate/datapack.mjs +254 -16
  36. package/lib/migrate/mcp-client.mjs +34 -0
  37. package/lib/migrate/orchestrator.mjs +1 -1
  38. package/lib/migrate.mjs +1 -1
  39. package/lib/validate.mjs +111 -22
  40. package/package.json +5 -5
@@ -7,7 +7,8 @@ import { dirname, join } from 'node:path';
7
7
 
8
8
  import { install, uninstall } from '../lib/installer.mjs';
9
9
  import { askChoice, askMultiChoice, askText, askSecret, askYesNo, close as closePrompts } from '../lib/prompt.mjs';
10
- import { banner, error, dim, info, warn } from '../lib/logger.mjs';
10
+ import { banner, error, dim, info, success, warn } from '../lib/logger.mjs';
11
+ import { BrowserLoginError, consoleUrlFor, loginAndCreateApiKey } from '../lib/browser-login.mjs';
11
12
  import {
12
13
  isPackagedInstall,
13
14
  MEKO_PRODUCTION_CONSOLE_URL,
@@ -20,6 +21,7 @@ import { startMigration, runChildMigration, readStatus, stopMigration } from '..
20
21
  import { checkLatest } from '../lib/version-check.mjs';
21
22
  import { runCaptureStatus } from '../lib/capture-status.mjs';
22
23
  import { captureCleanupArgs, runCaptureCleanup } from '../lib/capture-cleanup.mjs';
24
+ import { applyAutofixSetting, describeAutofix } from '../lib/autofix.mjs';
23
25
 
24
26
  // ---------------------------------------------------------------------------
25
27
  // Flag definitions
@@ -28,6 +30,7 @@ import { captureCleanupArgs, runCaptureCleanup } from '../lib/capture-cleanup.mj
28
30
  const options = {
29
31
  url: { type: 'string' },
30
32
  'api-key': { type: 'string' },
33
+ 'no-browser': { type: 'boolean', default: false },
31
34
  local: { type: 'boolean', default: false },
32
35
  scope: { type: 'string', default: 'user' },
33
36
  name: { type: 'string', default: 'meko' },
@@ -37,6 +40,8 @@ const options = {
37
40
  'skip-validation': { type: 'boolean', default: false },
38
41
  statusline: { type: 'boolean', default: false },
39
42
  'no-statusline': { type: 'boolean', default: false },
43
+ autofix: { type: 'boolean', default: false },
44
+ 'no-autofix': { type: 'boolean', default: false },
40
45
  'no-canary': { type: 'boolean', default: false },
41
46
  'dry-run': { type: 'boolean', default: false },
42
47
  'check-updates': { type: 'boolean', default: false },
@@ -96,7 +101,10 @@ Examples:
96
101
  # Interactive setup (recommended for first-time users):
97
102
  create-meko-setup
98
103
 
99
- # Cloud Meko for Claude Code (non-interactive):
104
+ # Cloud Meko for Claude Code (signs in through your browser):
105
+ create-meko-setup --url https://mcp.mekodata.ai/mcp --client claude-code
106
+
107
+ # Cloud Meko with an existing API key (unattended, e.g. CI):
100
108
  create-meko-setup --url https://mcp.mekodata.ai/mcp --api-key "$MEKO_API_KEY" --yes
101
109
 
102
110
  # Local Meko with Cursor config:
@@ -131,7 +139,10 @@ Options:
131
139
  untrusted IDE workspaces and non-managed CLI agents fall
132
140
  back to agent-driven persistence.
133
141
  --url <url> MCP server URL (from cloud.mekodata.ai > Datapacks > Connect)
134
- --api-key <key> API key for Cloud Meko authentication
142
+ --api-key <key> Use an existing Cloud Meko API key instead of signing in
143
+ through the browser. Also read from $MEKO_API_KEY.
144
+ Required when no terminal is attached (CI, scripts).
145
+ --no-browser Print the sign-in URL instead of opening a browser
135
146
  --local Use local Meko at http://localhost:8000/mcp
136
147
  --scope <scope> Config scope: user or project (default: user)
137
148
  codex always installs at user scope (no project-level
@@ -144,8 +155,13 @@ Options:
144
155
  Default: ON under --yes, prompted otherwise.
145
156
  Claude Code only.
146
157
  --no-statusline Skip statusline wiring even under --yes.
158
+ --no-autofix Turn off automatic capture repair: the capture timer
159
+ otherwise runs --capture-cleanup fix every 30 minutes
160
+ (MEKO_AUTOFIX_INTERVAL) when a session is stuck.
161
+ --autofix Turn automatic capture repair back on.
147
162
  --skip-validation Skip post-install validation
148
- --no-canary Skip the canary datapack_list call. The
163
+ --no-canary Skip the canary datapack_search (or, on older
164
+ servers, datapack_list) call. The
149
165
  connectivity + tools/list checks still run.
150
166
  --dry-run Show what would be done without making changes
151
167
  --check-updates Compare installed version against npm registry and exit
@@ -167,6 +183,10 @@ Capture health (durable outbox / recovery):
167
183
  Clean up stuck capture on this machine:
168
184
  status list sessions with unsent work and the
169
185
  background capture processes (read-only)
186
+ fix diagnose each stuck session and fix it:
187
+ repair, drain, or move unsent turns to a
188
+ new conversation; reports what only you
189
+ can fix (start here)
170
190
  stop stop SessionEnd workers and in-flight
171
191
  drains, and park all unsent work so no
172
192
  hook retries it (nothing is deleted)
@@ -295,6 +315,32 @@ function parseRate(raw) {
295
315
  return n;
296
316
  }
297
317
 
318
+ // Placeholder shown in --dry-run output in place of a key that browser
319
+ // sign-in would create. Never sent anywhere: dry runs make no changes.
320
+ const DRY_RUN_SIGN_IN_KEY = 'mko_tkn_<created-by-browser-sign-in>';
321
+
322
+ /**
323
+ * Sign in through the browser and create an API key for this install.
324
+ * Returns the key, or null after printing why sign-in failed.
325
+ */
326
+ async function signInWithBrowser({ url, noBrowser, dryRun }) {
327
+ if (dryRun) {
328
+ dim('Would sign in with your browser and create an API key (skipped: --dry-run).');
329
+ return DRY_RUN_SIGN_IN_KEY;
330
+ }
331
+ info('Signing in to Meko with your browser to create an API key...');
332
+ try {
333
+ const key = await loginAndCreateApiKey({ url, noBrowser, notify: (msg) => dim(msg) });
334
+ const expires = key.expiresAt ? ` (expires ${String(key.expiresAt).slice(0, 10)})` : '';
335
+ success(`Signed in. Created API key "${key.name}"${expires}.`);
336
+ dim(`Manage or revoke it at ${consoleUrlFor(url)} > Settings > API Keys.`);
337
+ return key.apiKey;
338
+ } catch (err) {
339
+ error(err instanceof BrowserLoginError ? err.message : `Browser sign-in failed: ${err?.message ?? err}`);
340
+ return null;
341
+ }
342
+ }
343
+
298
344
  // ---------------------------------------------------------------------------
299
345
  // Main
300
346
  // ---------------------------------------------------------------------------
@@ -340,7 +386,7 @@ async function main() {
340
386
  if (out.stderr) process.stderr.write(out.stderr);
341
387
  process.exit(out.code);
342
388
  }
343
- // --capture-cleanup <action>: inspect, stop, park, drain, or discard
389
+ // --capture-cleanup <action>: inspect, fix, stop, park, drain, or discard
344
390
  // stuck capture sessions. Exit codes: 0 done, 1 error, 2 partly done.
345
391
  if (flags['capture-cleanup'] !== undefined) {
346
392
  const out = runCaptureCleanup(
@@ -508,6 +554,10 @@ async function main() {
508
554
  // submitting an empty value at the full-flow prompt would re-ask the
509
555
  // same question.
510
556
  let promptedForApiKey = false;
557
+ // Set when the user picked "Sign in with browser" at the interactive auth
558
+ // prompt. Someone answered that prompt, so sign-in runs even when stdin
559
+ // is not a TTY (e.g. piped answers in the smoke harness).
560
+ let choseBrowserSignIn = false;
511
561
 
512
562
  // --local shortcut
513
563
  if (flags.local) {
@@ -591,7 +641,7 @@ async function main() {
591
641
  // When only Cloud is available, skip the prompt entirely — asking to
592
642
  // pick between one option is pointless UX.
593
643
  const connectionOptions = [
594
- { label: 'Cloud Meko', description: 'Hosted at mekodata.ai (requires an API key)' },
644
+ { label: 'Cloud Meko', description: 'Hosted at mekodata.ai (sign in with your browser)' },
595
645
  ];
596
646
  if (!isPackagedInstall()) {
597
647
  connectionOptions.push({
@@ -620,11 +670,24 @@ async function main() {
620
670
  suggestedUrl?.url ?? undefined,
621
671
  );
622
672
 
623
- // Step 3 — API key. Normalize: terminal paste can drag in trailing
624
- // whitespace/CR or accidental surrounding quotes, all of which would
625
- // sail straight into `Authorization: Bearer ...` and 401.
626
- apiKey = normalizeApiKey(await askSecret('API key:'));
627
- promptedForApiKey = true;
673
+ // Step 3 — authentication. Browser sign-in is the default and runs
674
+ // in the Cloud-URL check below; pasting a key is the advanced path.
675
+ // An existing --api-key or $MEKO_API_KEY skips the question.
676
+ if (!apiKey && !normalizeApiKey(process.env.MEKO_API_KEY)) {
677
+ const authChoice = await askChoice('How do you want to authenticate?', [
678
+ { label: 'Sign in with browser', description: 'Recommended. Creates an API key for you' },
679
+ { label: 'Paste an API key', description: `From ${consoleUrlFor(url)} > Settings > API Keys` },
680
+ ]);
681
+ if (authChoice === 'Paste an API key') {
682
+ // Normalize: terminal paste can drag in trailing whitespace/CR or
683
+ // accidental surrounding quotes, all of which would sail straight
684
+ // into `Authorization: Bearer ...` and 401.
685
+ apiKey = normalizeApiKey(await askSecret('API key:'));
686
+ promptedForApiKey = true;
687
+ } else {
688
+ choseBrowserSignIn = true;
689
+ }
690
+ }
628
691
  } else {
629
692
  url = 'http://localhost:8000/mcp';
630
693
  apiKey = null;
@@ -663,25 +726,38 @@ async function main() {
663
726
 
664
727
  // Validation — Cloud URL requires an API key.
665
728
  // Resolution order: --api-key (already in `apiKey`) → $MEKO_API_KEY →
666
- // interactive prompt (only on a TTY, not under --yes, and only if the
667
- // full interactive flow above didn't already ask). The prompt fallback
668
- // exists so a user who pastes the cloud one-liner from the UI (which
669
- // only shows --url and --client) isn't dead-ended on a hard error when
670
- // they forgot to supply the key. Skipping it after a full-flow prompt
671
- // avoids re-asking the same question when the user submitted empty.
729
+ // browser sign-in, which creates a key through the server's
730
+ // POST /api-keys. Sign-in also covers the cloud one-liner from the UI,
731
+ // which only shows --url and --client. It is skipped when the full
732
+ // interactive flow already took a pasted key (an empty paste is an
733
+ // error, not a silent switch to the browser), and whenever stdin is not
734
+ // a terminal, with or without --yes: a script or CI step may have nobody
735
+ // at the browser, and a 5-minute callback wait would stall it. The one
736
+ // exception is an explicit "Sign in with browser" answer at the prompt.
672
737
  if (isCloudUrl(url) && !apiKey) {
673
738
  apiKey = normalizeApiKey(process.env.MEKO_API_KEY);
739
+ let signInFailed = false;
674
740
  if (apiKey) {
675
741
  dim('Using API key from MEKO_API_KEY environment variable.');
676
- } else if (!flags.yes && !promptedForApiKey && process.stdin.isTTY) {
677
- dim('No --api-key or MEKO_API_KEY env var found; prompting for API key.');
678
- apiKey = normalizeApiKey(await askSecret('API key:'));
742
+ } else if (!promptedForApiKey && (process.stdin.isTTY || choseBrowserSignIn)) {
743
+ apiKey = await signInWithBrowser({
744
+ url,
745
+ noBrowser: flags['no-browser'],
746
+ dryRun: flags['dry-run'],
747
+ });
748
+ signInFailed = !apiKey;
679
749
  }
680
750
  if (!apiKey) {
681
- error(
682
- 'API key is required for Cloud Meko. ' +
683
- 'Provide --api-key, set MEKO_API_KEY env var, or run interactively.',
684
- );
751
+ let message;
752
+ if (signInFailed) {
753
+ message = `Browser sign-in did not complete. To use an existing key instead, create one at ${consoleUrlFor(url)} > Settings > API Keys and rerun with --api-key, or set MEKO_API_KEY.`;
754
+ } else if (promptedForApiKey) {
755
+ message = 'No API key entered. Rerun and choose Sign in with browser, or pass --api-key.';
756
+ } else {
757
+ message = 'API key is required for Cloud Meko. ' +
758
+ 'Provide --api-key, set MEKO_API_KEY env var, or run in a terminal to sign in with your browser.';
759
+ }
760
+ error(message);
685
761
  process.exit(1);
686
762
  }
687
763
  }
@@ -716,6 +792,23 @@ async function main() {
716
792
  }
717
793
  }
718
794
 
795
+ // Automatic capture repair runs inside the capture timer the hooks start,
796
+ // so installing the hooks deploys it. Save --autofix / --no-autofix once,
797
+ // for every client (they share one capture directory).
798
+ if (errors.length < clients.length) {
799
+ if (flags.autofix && flags['no-autofix']) {
800
+ warn('Both --autofix and --no-autofix were given; keeping the current setting.');
801
+ }
802
+ const enable =
803
+ flags['no-autofix'] && !flags.autofix ? false : flags.autofix && !flags['no-autofix'] ? true : undefined;
804
+ try {
805
+ const res = applyAutofixSetting({ enable, dryRun: flags['dry-run'], projectRoot });
806
+ info(describeAutofix(res.enabled));
807
+ } catch (err) {
808
+ warn(`Could not save the automatic repair setting: ${err.message}`);
809
+ }
810
+ }
811
+
719
812
  if (errors.length === clients.length) {
720
813
  process.exit(1);
721
814
  } else if (errors.length > 0) {
@@ -0,0 +1,100 @@
1
+ /**
2
+ * autofix.mjs: the installer side of automatic capture repair.
3
+ *
4
+ * The checkpoint timer runs `--capture-cleanup fix --auto` every
5
+ * MEKO_AUTOFIX_INTERVAL seconds. The on/off choice lives in
6
+ * autofix-config.json in the capture directory, which capture-state.js
7
+ * autofixSetting() reads. MEKO_AUTOFIX in the hook environment overrides it.
8
+ *
9
+ * Most clients share one capture directory, but OpenCode hooks keep the one
10
+ * saved in their hook env file at first install, so the file is written to
11
+ * every capture directory the installer knows about.
12
+ */
13
+
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
+ import { dirname, isAbsolute, join, resolve } from "node:path";
16
+
17
+ import { getCaptureRoot, getOpenCodeHookEnvPath } from "./config.mjs";
18
+
19
+ const CONFIG_FILE = "autofix-config.json";
20
+
21
+ function savedOpenCodeRoot(scope, projectRoot) {
22
+ try {
23
+ const file = getOpenCodeHookEnvPath(scope, projectRoot);
24
+ if (!existsSync(file)) return null;
25
+ const value = JSON.parse(readFileSync(file, "utf8")).MEKO_WATERMARK_DIR;
26
+ if (typeof value !== "string" || !value) return null;
27
+ return isAbsolute(value) ? value : resolve(value);
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ /** Every capture directory a client's timer may read the setting from. */
34
+ export function captureRoots({ projectRoot = process.cwd() } = {}) {
35
+ const roots = [getCaptureRoot()];
36
+ for (const scope of ["user", "project"]) {
37
+ const saved = savedOpenCodeRoot(scope, projectRoot);
38
+ if (saved && !roots.includes(saved)) roots.push(saved);
39
+ }
40
+ return roots;
41
+ }
42
+
43
+ export function autofixConfigPath() {
44
+ return join(getCaptureRoot(), CONFIG_FILE);
45
+ }
46
+
47
+ /** @returns {boolean|null} the saved choice in the main capture directory, or null. */
48
+ export function readAutofixSetting() {
49
+ try {
50
+ const cfg = JSON.parse(readFileSync(autofixConfigPath(), "utf8"));
51
+ return typeof cfg.enabled === "boolean" ? cfg.enabled : null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Save --autofix / --no-autofix in every capture directory. `enable`
59
+ * undefined keeps an earlier choice, so a plain reinstall doesn't turn
60
+ * repair back on.
61
+ *
62
+ * @param {{ enable?: boolean, dryRun?: boolean, projectRoot?: string }} opts
63
+ * @returns {{ enabled: boolean, changed: boolean, paths: string[] }}
64
+ */
65
+ export function applyAutofixSetting({ enable, dryRun = false, projectRoot } = {}) {
66
+ const saved = readAutofixSetting();
67
+ const paths = captureRoots({ projectRoot }).map((root) => join(root, CONFIG_FILE));
68
+ if (enable === undefined) {
69
+ return { enabled: saved === null ? true : saved, changed: false, paths };
70
+ }
71
+ if (!dryRun) {
72
+ for (const file of paths) {
73
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
74
+ writeFileSync(
75
+ file,
76
+ `${JSON.stringify({ enabled: enable, updated_at: new Date().toISOString() })}\n`,
77
+ { mode: 0o600 },
78
+ );
79
+ }
80
+ }
81
+ return { enabled: enable, changed: saved !== enable, paths };
82
+ }
83
+
84
+ /** Same rule as capture-state.js autofixIntervalSeconds(). */
85
+ function intervalSeconds(env) {
86
+ const n = Number.parseInt(env.MEKO_AUTOFIX_INTERVAL || "", 10);
87
+ return Math.max(60, Number.isFinite(n) ? n : 1800);
88
+ }
89
+
90
+ /** One line for the end of an install. */
91
+ export function describeAutofix(enabled, env = process.env) {
92
+ const envOverride = env.MEKO_AUTOFIX;
93
+ if (envOverride != null && envOverride !== "") {
94
+ return `Automatic capture repair follows MEKO_AUTOFIX=${envOverride} in this environment.`;
95
+ }
96
+ const minutes = Math.max(1, Math.round(intervalSeconds(env) / 60));
97
+ return enabled
98
+ ? `Automatic capture repair is on: every ${minutes} minute${minutes === 1 ? "" : "s"} the capture timer fixes stuck sessions (--capture-cleanup fix). Turn it off with --no-autofix.`
99
+ : "Automatic capture repair is off. Turn it on with --autofix, or run --capture-cleanup fix yourself.";
100
+ }