@yugabytedb/meko-mcp 1.0.3

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 (51) hide show
  1. package/README.md +505 -0
  2. package/assets/hooks-handlers/lib/capture.js +863 -0
  3. package/assets/hooks-handlers/lib/checkpoint-timer.js +72 -0
  4. package/assets/hooks-handlers/pre-compact.sh +3 -0
  5. package/assets/hooks-handlers/session-end.sh +27 -0
  6. package/assets/hooks-handlers/session-start.sh +24 -0
  7. package/assets/hooks-handlers/test_single_conversation.sh +169 -0
  8. package/assets/skills/meko-mcp-tools/SKILL.md +192 -0
  9. package/assets/skills/meko-mcp-tools/references/_sections.md +31 -0
  10. package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +39 -0
  11. package/assets/skills/meko-mcp-tools/references/tools-conversation-dedup.md +49 -0
  12. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +373 -0
  13. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +56 -0
  14. package/assets/skills/meko-mcp-tools/references/tools-db-query-patterns.md +38 -0
  15. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +61 -0
  16. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +128 -0
  17. package/assets/skills/meko-mcp-tools/references/tools-overview.md +97 -0
  18. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +63 -0
  19. package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +79 -0
  20. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +143 -0
  21. package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +93 -0
  22. package/assets/skills/meko-mcp-tools/references/tools-write-guardrails.md +30 -0
  23. package/bin/create-meko-setup.mjs +499 -0
  24. package/lib/claude-code.mjs +14 -0
  25. package/lib/clients/claude-code.mjs +265 -0
  26. package/lib/clients/claude-desktop.mjs +256 -0
  27. package/lib/clients/common.mjs +107 -0
  28. package/lib/clients/cursor.mjs +332 -0
  29. package/lib/clients/index.mjs +14 -0
  30. package/lib/config.mjs +369 -0
  31. package/lib/detect.mjs +125 -0
  32. package/lib/installer.mjs +255 -0
  33. package/lib/logger.mjs +97 -0
  34. package/lib/mcp-transport.mjs +204 -0
  35. package/lib/migrate/adapters/claude-code.mjs +165 -0
  36. package/lib/migrate/adapters/cursor.mjs +221 -0
  37. package/lib/migrate/adapters/memory-md.mjs +230 -0
  38. package/lib/migrate/claude-parser.mjs +306 -0
  39. package/lib/migrate/daemon.mjs +67 -0
  40. package/lib/migrate/id.mjs +83 -0
  41. package/lib/migrate/lockfile.mjs +121 -0
  42. package/lib/migrate/logger.mjs +79 -0
  43. package/lib/migrate/mcp-client.mjs +224 -0
  44. package/lib/migrate/orchestrator.mjs +443 -0
  45. package/lib/migrate/progress.mjs +76 -0
  46. package/lib/migrate/state.mjs +177 -0
  47. package/lib/migrate.mjs +328 -0
  48. package/lib/prompt.mjs +212 -0
  49. package/lib/validate.mjs +259 -0
  50. package/package.json +49 -0
  51. package/scripts/bundle-assets.mjs +49 -0
@@ -0,0 +1,499 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseArgs } from 'node:util';
4
+ import { readFileSync } from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { dirname, join } from 'node:path';
7
+
8
+ import { install, uninstall } from '../lib/installer.mjs';
9
+ import { askChoice, askText, askSecret, askYesNo, close as closePrompts } from '../lib/prompt.mjs';
10
+ import { banner, error, dim } from '../lib/logger.mjs';
11
+ import { startMigration, runChildMigration, readStatus, stopMigration } from '../lib/migrate.mjs';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Flag definitions
15
+ // ---------------------------------------------------------------------------
16
+
17
+ const options = {
18
+ url: { type: 'string' },
19
+ 'api-key': { type: 'string' },
20
+ local: { type: 'boolean', default: false },
21
+ scope: { type: 'string', default: 'user' },
22
+ name: { type: 'string', default: 'meko' },
23
+ client: { type: 'string', default: 'claude-code' },
24
+ 'cursor-hook-mode': { type: 'string', default: 'hybrid' },
25
+ 'skip-skills': { type: 'boolean', default: false },
26
+ 'skip-validation': { type: 'boolean', default: false },
27
+ 'dry-run': { type: 'boolean', default: false },
28
+ uninstall: { type: 'boolean', default: false },
29
+ verbose: { type: 'boolean', default: false },
30
+ yes: { type: 'boolean', default: false },
31
+ help: { type: 'boolean', short: 'h', default: false },
32
+ version: { type: 'boolean', short: 'v', default: false },
33
+
34
+ // --- Migrate subcommand ---
35
+ migrate: { type: 'boolean', default: false },
36
+ 'migrate-status': { type: 'boolean', default: false },
37
+ 'migrate-stop': { type: 'boolean', default: false },
38
+ 'with-migrate': { type: 'boolean', default: false },
39
+ force: { type: 'boolean', default: false },
40
+ rate: { type: 'string' },
41
+ 'no-extract': { type: 'boolean', default: false },
42
+ 'run-id': { type: 'string' },
43
+ '__migrate-child': { type: 'boolean', default: false },
44
+ };
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Helpers
48
+ // ---------------------------------------------------------------------------
49
+
50
+ const __filename = fileURLToPath(import.meta.url);
51
+ const __dirname = dirname(__filename);
52
+
53
+ function readVersion() {
54
+ const pkgPath = join(__dirname, '..', 'package.json');
55
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
56
+ return pkg.version;
57
+ }
58
+
59
+ const ALL_CLIENTS = ['claude-code', 'claude-desktop', 'cursor'];
60
+
61
+ function printHelp() {
62
+ const text = `
63
+ Meko Setup for Claude Clients and Cursor
64
+
65
+ Install and configure Meko MCP Server in one command.
66
+
67
+ Usage:
68
+ create-meko-setup [options]
69
+
70
+ Examples:
71
+ # Interactive setup (recommended for first-time users):
72
+ create-meko-setup
73
+
74
+ # Cloud Meko for Claude Code (non-interactive):
75
+ create-meko-setup --url https://your-mcp.mekodev.com/mcp --api-key "$MEKO_API_KEY" --yes
76
+
77
+ # Local Meko with Cursor config:
78
+ create-meko-setup --client cursor --local --scope project --yes
79
+
80
+ # Install on both Claude Code and Claude Desktop at once:
81
+ create-meko-setup --client claude-code,claude-desktop --local --yes
82
+
83
+ # Install on every supported client:
84
+ create-meko-setup --client all --local --yes
85
+
86
+ # Local Meko (docker-compose stack, Claude Code):
87
+ create-meko-setup --local --yes
88
+
89
+ Options:
90
+ --client <list> Target clients: one or more of claude-code, claude-desktop, cursor.
91
+ Comma-separated, or "all" to target every supported client (default: claude-code)
92
+ --url <url> MCP server URL (from app.mekodev.com > Datapacks > Connect)
93
+ --api-key <key> API key for Cloud Meko authentication
94
+ --local Use local Meko at http://localhost:8000/mcp
95
+ --scope <scope> Config scope: user or project (default: user)
96
+ --name <name> MCP server name in config (default: meko)
97
+ --cursor-hook-mode Cursor hook mode: native, claude-compat, hybrid (default: hybrid)
98
+ --skip-skills Skip plugin and skills installation
99
+ --skip-validation Skip post-install validation
100
+ --dry-run Show what would be done without making changes
101
+ --uninstall Remove Meko MCP server, skills, and env vars
102
+ --verbose Show detailed output
103
+ --yes Accept all defaults (non-interactive)
104
+ -h, --help Show this help message
105
+ -v, --version Show version
106
+
107
+ Migrate existing conversations and memories into Meko:
108
+ --migrate Start a detached migration of existing Claude Code
109
+ and Cursor conversations + CLAUDE.md memories.
110
+ Honors --scope (user or project) and --dry-run.
111
+ --with-migrate When combined with --yes, kick off --migrate at the
112
+ end of a successful install without prompting
113
+ (CI-friendly: --local --yes --with-migrate).
114
+ --migrate-status Print the status of the latest migration run.
115
+ --migrate-stop Stop the running migration (SIGTERM, then SIGKILL).
116
+ --no-extract Skip memory extraction; replay conversations only.
117
+ --rate <n> Max concurrent MCP calls during migration (default 4).
118
+ --force Ignore a live lockfile or re-ingest processed items.
119
+ --run-id <id> Target a specific run-id for --migrate-status.
120
+ `.trimStart();
121
+
122
+ console.log(text);
123
+ }
124
+
125
+ function isCloudUrl(url) {
126
+ return url && !url.includes('localhost') && !url.includes('127.0.0.1');
127
+ }
128
+
129
+ /**
130
+ * Parse --client into a deduplicated list of client IDs.
131
+ * Accepts a single value, a comma-separated list, or the keyword "all".
132
+ * Returns { clients, invalid } where invalid lists any unknown tokens.
133
+ */
134
+ function parseClients(raw) {
135
+ const trimmed = (raw ?? '').trim();
136
+ if (!trimmed) return { clients: ['claude-code'], invalid: [] };
137
+ if (trimmed.toLowerCase() === 'all') return { clients: [...ALL_CLIENTS], invalid: [] };
138
+
139
+ const tokens = trimmed.split(',').map((t) => t.trim()).filter(Boolean);
140
+ const invalid = tokens.filter((t) => !ALL_CLIENTS.includes(t));
141
+ const clients = [...new Set(tokens.filter((t) => ALL_CLIENTS.includes(t)))];
142
+ return { clients, invalid };
143
+ }
144
+
145
+ function isValidScope(scope) {
146
+ return ['user', 'project'].includes(scope);
147
+ }
148
+
149
+ function isValidCursorHookMode(mode) {
150
+ return ['native', 'claude-compat', 'hybrid'].includes(mode);
151
+ }
152
+
153
+ /**
154
+ * Parse --rate as a positive integer, or throw with a helpful message.
155
+ * `undefined` passes through (= use the default).
156
+ */
157
+ function parseRate(raw) {
158
+ if (raw === undefined || raw === null || raw === '') return undefined;
159
+ const n = Number(raw);
160
+ if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
161
+ throw new Error(`Invalid --rate "${raw}": must be a positive integer.`);
162
+ }
163
+ return n;
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Main
168
+ // ---------------------------------------------------------------------------
169
+
170
+ async function main() {
171
+ const { values: flags } = parseArgs({ options, strict: true });
172
+ const projectRoot = process.cwd();
173
+
174
+ // --help
175
+ if (flags.help) {
176
+ printHelp();
177
+ process.exit(0);
178
+ }
179
+
180
+ // --version
181
+ if (flags.version) {
182
+ console.log(readVersion());
183
+ process.exit(0);
184
+ }
185
+
186
+ // --- Migrate subcommand dispatch ---
187
+
188
+ // Detached child branch: re-entry inside the spawned process.
189
+ if (flags['__migrate-child']) {
190
+ if (!flags['run-id']) {
191
+ error('--__migrate-child requires --run-id');
192
+ process.exit(1);
193
+ }
194
+ if (!flags.url) {
195
+ error('--__migrate-child requires --url');
196
+ process.exit(1);
197
+ }
198
+ let childRate;
199
+ try { childRate = parseRate(flags.rate); }
200
+ catch (err) { error(err.message); process.exit(1); }
201
+ const code = await runChildMigration({
202
+ runId: flags['run-id'],
203
+ scope: flags.scope,
204
+ url: flags.url,
205
+ apiKey: process.env.MEKO_API_KEY ?? null,
206
+ projectRoot,
207
+ noExtract: flags['no-extract'],
208
+ force: flags.force,
209
+ rate: childRate,
210
+ });
211
+ process.exit(code);
212
+ }
213
+
214
+ if (flags['migrate-status']) {
215
+ const out = readStatus({ runId: flags['run-id'] });
216
+ console.log(out.message);
217
+ process.exit(out.ok ? 0 : 1);
218
+ }
219
+
220
+ if (flags['migrate-stop']) {
221
+ const out = await stopMigration();
222
+ console.log(out.message);
223
+ process.exit(out.ok ? 0 : 1);
224
+ }
225
+
226
+ if (flags.migrate) {
227
+ if (!isValidScope(flags.scope)) {
228
+ error(`Unknown scope "${flags.scope}". Valid values: user, project.`);
229
+ process.exit(1);
230
+ }
231
+ let url = flags.url ?? process.env.MEKO_MCP_URL ?? null;
232
+ let apiKey = flags['api-key'] ?? process.env.MEKO_API_KEY ?? null;
233
+ if (flags.local) { url = 'http://localhost:8000/mcp'; apiKey = null; }
234
+
235
+ let parentRate;
236
+ try { parentRate = parseRate(flags.rate); }
237
+ catch (err) { error(err.message); process.exit(1); }
238
+
239
+ const out = await startMigration({
240
+ url,
241
+ apiKey,
242
+ scope: flags.scope,
243
+ projectRoot,
244
+ scriptPath: __filename,
245
+ dryRun: flags['dry-run'],
246
+ noExtract: flags['no-extract'],
247
+ force: flags.force,
248
+ rate: parentRate,
249
+ });
250
+ console.log(out.message);
251
+ process.exit(out.ok ? 0 : 1);
252
+ }
253
+
254
+ const { clients, invalid } = parseClients(flags.client);
255
+ if (invalid.length > 0) {
256
+ error(`Unknown client(s): ${invalid.join(', ')}. Valid values: ${ALL_CLIENTS.join(', ')}, all.`);
257
+ process.exit(1);
258
+ }
259
+ if (clients.length === 0) {
260
+ error(`--client must name at least one of: ${ALL_CLIENTS.join(', ')}, all.`);
261
+ process.exit(1);
262
+ }
263
+
264
+ // --uninstall
265
+ if (flags.uninstall) {
266
+ for (const client of clients) {
267
+ await uninstall({
268
+ client,
269
+ name: flags.name,
270
+ scope: flags.scope,
271
+ verbose: flags.verbose,
272
+ projectRoot,
273
+ cursorHookMode: flags['cursor-hook-mode'],
274
+ });
275
+ }
276
+ process.exit(0);
277
+ }
278
+
279
+ if (!isValidScope(flags.scope)) {
280
+ error(`Unknown scope "${flags.scope}". Valid values: user, project.`);
281
+ process.exit(1);
282
+ }
283
+ if (!isValidCursorHookMode(flags['cursor-hook-mode'])) {
284
+ error(`Unknown --cursor-hook-mode "${flags['cursor-hook-mode']}". Valid values: native, claude-compat, hybrid.`);
285
+ process.exit(1);
286
+ }
287
+
288
+ let url = flags.url ?? null;
289
+ let apiKey = flags['api-key'] ?? null;
290
+ let skipSkills = flags['skip-skills'];
291
+
292
+ // --local shortcut
293
+ if (flags.local) {
294
+ url = 'http://localhost:8000/mcp';
295
+ apiKey = null;
296
+ }
297
+
298
+ // Interactive mode: URL not provided and not auto-accepting defaults
299
+ if (!url && !flags.yes) {
300
+ banner();
301
+
302
+ // Step 1 — connection type
303
+ const connectionType = await askChoice('Connection type:', [
304
+ { label: 'Cloud Meko', description: 'Hosted at mekodev.com (requires URL + API key)' },
305
+ { label: 'Local Meko', description: 'Local docker stack at localhost:8000' },
306
+ ]);
307
+
308
+ if (connectionType === 'Cloud Meko') {
309
+ // Step 2 — MCP URL
310
+ url = await askText('MCP server URL (from app.mekodev.com > Datapacks > Connect):');
311
+
312
+ // Step 3 — API key
313
+ apiKey = await askSecret('API key:');
314
+ } else {
315
+ url = 'http://localhost:8000/mcp';
316
+ apiKey = null;
317
+ }
318
+
319
+ // Step 4 — skills
320
+ const installSkills = await askYesNo(
321
+ 'Install Meko agent skills plugin? (behavioral guides for MCP tools)',
322
+ true,
323
+ );
324
+ if (!installSkills) {
325
+ skipSkills = true;
326
+ }
327
+ }
328
+
329
+ // Validation — URL must be set by now
330
+ if (!url) {
331
+ error('MCP server URL is required. Provide --url, --local, or run interactively.');
332
+ process.exit(1);
333
+ }
334
+
335
+ // Validation — Cloud URL requires an API key
336
+ if (isCloudUrl(url) && !apiKey) {
337
+ apiKey = process.env.MEKO_API_KEY ?? null;
338
+ if (!apiKey) {
339
+ error(
340
+ 'API key is required for Cloud Meko. ' +
341
+ 'Provide --api-key, set MEKO_API_KEY env var, or run interactively.',
342
+ );
343
+ process.exit(1);
344
+ }
345
+ dim('Using API key from MEKO_API_KEY environment variable.');
346
+ }
347
+
348
+ // Install — loop through each selected client sequentially so failures
349
+ // for one client don't abort the others.
350
+ const errors = [];
351
+ for (const client of clients) {
352
+ try {
353
+ await install({
354
+ url,
355
+ apiKey,
356
+ scope: flags.scope,
357
+ name: flags.name,
358
+ skipSkills,
359
+ skipValidation: flags['skip-validation'],
360
+ dryRun: flags['dry-run'],
361
+ verbose: flags.verbose,
362
+ yes: flags.yes,
363
+ client,
364
+ projectRoot,
365
+ cursorHookMode: flags['cursor-hook-mode'],
366
+ });
367
+ } catch (err) {
368
+ errors.push({ client, err });
369
+ error(`Install for ${client} failed: ${err.message ?? err}`);
370
+ }
371
+ }
372
+
373
+ if (errors.length === clients.length) {
374
+ process.exit(1);
375
+ } else if (errors.length > 0) {
376
+ // Partial success — exit non-zero so CI notices, but keep the successful
377
+ // installs in place.
378
+ process.exit(2);
379
+ }
380
+
381
+ // Post-install migration handling:
382
+ // - --yes --with-migrate → kick off a detached migration, no prompt
383
+ // - --yes (no --with-migrate) → silent; print a one-liner tip
384
+ // - interactive (not --yes), TTY → dry-run enumerate, prompt Y/n
385
+ // - --dry-run install → skip entirely (we don't want side effects)
386
+ if (!flags['dry-run']) {
387
+ if (flags['with-migrate']) {
388
+ await runUnattendedMigration({
389
+ url,
390
+ apiKey,
391
+ scope: flags.scope,
392
+ projectRoot,
393
+ });
394
+ } else if (flags.yes) {
395
+ console.log('');
396
+ dim('Tip: run `create-meko-setup --migrate` to import existing conversations and memories.');
397
+ } else if (process.stdin.isTTY) {
398
+ await maybeOfferMigration({
399
+ url,
400
+ apiKey,
401
+ scope: flags.scope,
402
+ projectRoot,
403
+ });
404
+ }
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Unattended migration — used by --yes --with-migrate.
410
+ * No prompts; errors are surfaced but do not fail the install.
411
+ */
412
+ async function runUnattendedMigration({ url, apiKey, scope, projectRoot }) {
413
+ try {
414
+ const out = await startMigration({
415
+ url,
416
+ apiKey,
417
+ scope,
418
+ projectRoot,
419
+ scriptPath: __filename,
420
+ });
421
+ console.log('');
422
+ console.log(out.message);
423
+ if (!out.ok) {
424
+ // Don't fail the install; the user can retry with --migrate.
425
+ dim('Migration did not start cleanly. Install succeeded; try `create-meko-setup --migrate` later.');
426
+ }
427
+ } catch (err) {
428
+ dim(`Migration could not be started: ${err.message ?? err}`);
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Dry-run enumerate the user's local data; if anything is migratable, ask
434
+ * whether to kick off the detached migration. Any error here is non-fatal —
435
+ * install has already succeeded.
436
+ */
437
+ async function maybeOfferMigration({ url, apiKey, scope, projectRoot }) {
438
+ try {
439
+ const dry = await startMigration({
440
+ url,
441
+ apiKey,
442
+ scope,
443
+ projectRoot,
444
+ scriptPath: __filename,
445
+ dryRun: true,
446
+ });
447
+ const s = dry?.message?.match(/Claude Code sessions: *(\d+)/);
448
+ const c = dry?.message?.match(/Cursor sessions: *(\d+)/);
449
+ const m = dry?.message?.match(/Memory items \(MD\): *(\d+)/);
450
+ const counts = {
451
+ claude: s ? Number(s[1]) : 0,
452
+ cursor: c ? Number(c[1]) : 0,
453
+ memory: m ? Number(m[1]) : 0,
454
+ };
455
+ const total = counts.claude + counts.cursor + counts.memory;
456
+ if (total === 0) return;
457
+
458
+ console.log('');
459
+ dim(
460
+ `Found existing data to migrate: ` +
461
+ `${counts.claude} Claude Code sessions, ` +
462
+ `${counts.cursor} Cursor sessions, ` +
463
+ `${counts.memory} memory items.`,
464
+ );
465
+ const proceed = await askYesNo(
466
+ 'Migrate existing conversations and memories into Meko now? ' +
467
+ '(runs in the background; check with --migrate-status)',
468
+ true,
469
+ );
470
+ if (!proceed) {
471
+ dim('Skipped. Run `create-meko-setup --migrate` later when you\'re ready.');
472
+ return;
473
+ }
474
+ const out = await startMigration({
475
+ url,
476
+ apiKey,
477
+ scope,
478
+ projectRoot,
479
+ scriptPath: __filename,
480
+ });
481
+ console.log(out.message);
482
+ } catch (err) {
483
+ // Don't fail the whole install if the migration prompt hiccups.
484
+ dim(`Skipped migration offer: ${err.message ?? err}`);
485
+ }
486
+ }
487
+
488
+ // ---------------------------------------------------------------------------
489
+ // Entry
490
+ // ---------------------------------------------------------------------------
491
+
492
+ try {
493
+ await main();
494
+ } catch (err) {
495
+ error(err.message ?? err);
496
+ process.exit(1);
497
+ } finally {
498
+ closePrompts();
499
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Backward-compatible exports.
3
+ * Legacy callers importing ./claude-code.mjs continue to work, but all logic
4
+ * now routes through the multi-client installer with Claude Code selected.
5
+ */
6
+ import { install as installGeneric, uninstall as uninstallGeneric } from "./installer.mjs";
7
+
8
+ export async function install(options) {
9
+ return installGeneric({ ...options, client: "claude-code" });
10
+ }
11
+
12
+ export async function uninstall(options = {}) {
13
+ return uninstallGeneric({ ...options, client: "claude-code" });
14
+ }