@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.
- package/README.md +505 -0
- package/assets/hooks-handlers/lib/capture.js +863 -0
- package/assets/hooks-handlers/lib/checkpoint-timer.js +72 -0
- package/assets/hooks-handlers/pre-compact.sh +3 -0
- package/assets/hooks-handlers/session-end.sh +27 -0
- package/assets/hooks-handlers/session-start.sh +24 -0
- package/assets/hooks-handlers/test_single_conversation.sh +169 -0
- package/assets/skills/meko-mcp-tools/SKILL.md +192 -0
- package/assets/skills/meko-mcp-tools/references/_sections.md +31 -0
- package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +39 -0
- package/assets/skills/meko-mcp-tools/references/tools-conversation-dedup.md +49 -0
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +373 -0
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +56 -0
- package/assets/skills/meko-mcp-tools/references/tools-db-query-patterns.md +38 -0
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +61 -0
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +128 -0
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +97 -0
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +63 -0
- package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +79 -0
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +143 -0
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +93 -0
- package/assets/skills/meko-mcp-tools/references/tools-write-guardrails.md +30 -0
- package/bin/create-meko-setup.mjs +499 -0
- package/lib/claude-code.mjs +14 -0
- package/lib/clients/claude-code.mjs +265 -0
- package/lib/clients/claude-desktop.mjs +256 -0
- package/lib/clients/common.mjs +107 -0
- package/lib/clients/cursor.mjs +332 -0
- package/lib/clients/index.mjs +14 -0
- package/lib/config.mjs +369 -0
- package/lib/detect.mjs +125 -0
- package/lib/installer.mjs +255 -0
- package/lib/logger.mjs +97 -0
- package/lib/mcp-transport.mjs +204 -0
- package/lib/migrate/adapters/claude-code.mjs +165 -0
- package/lib/migrate/adapters/cursor.mjs +221 -0
- package/lib/migrate/adapters/memory-md.mjs +230 -0
- package/lib/migrate/claude-parser.mjs +306 -0
- package/lib/migrate/daemon.mjs +67 -0
- package/lib/migrate/id.mjs +83 -0
- package/lib/migrate/lockfile.mjs +121 -0
- package/lib/migrate/logger.mjs +79 -0
- package/lib/migrate/mcp-client.mjs +224 -0
- package/lib/migrate/orchestrator.mjs +443 -0
- package/lib/migrate/progress.mjs +76 -0
- package/lib/migrate/state.mjs +177 -0
- package/lib/migrate.mjs +328 -0
- package/lib/prompt.mjs +212 -0
- package/lib/validate.mjs +259 -0
- package/package.json +49 -0
- 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
|
+
}
|