@yugabytedb/meko-mcp 1.0.8 → 1.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.
- package/README.md +242 -44
- package/assets/hooks-handlers/before-submit-prompt.sh +44 -0
- package/assets/hooks-handlers/cursor-session-start.sh +42 -0
- package/assets/hooks-handlers/lib/capture.js +457 -107
- package/assets/hooks-handlers/lib/package.json +1 -0
- package/assets/hooks-handlers/test_single_conversation.sh +1 -1
- package/assets/scripts/package-desktop-skill.sh +36 -0
- package/assets/skills/meko-mcp-tools/SKILL.md +167 -53
- package/assets/skills/meko-mcp-tools/references/_sections.md +1 -1
- package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +84 -22
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +89 -56
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +15 -7
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +17 -9
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +94 -5
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +52 -37
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +108 -37
- package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +6 -10
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +30 -18
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +55 -69
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +251 -0
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +150 -0
- package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +24 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +99 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +406 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +69 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +217 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +112 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +134 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-scope-permissions.md +75 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +155 -0
- package/bin/create-meko-setup.mjs +151 -22
- package/lib/clients/claude-code.mjs +378 -68
- package/lib/clients/claude-desktop.mjs +45 -8
- package/lib/clients/codex.mjs +1209 -0
- package/lib/clients/common.mjs +223 -1
- package/lib/clients/cursor.mjs +118 -13
- package/lib/clients/index.mjs +2 -0
- package/lib/config.mjs +56 -0
- package/lib/detect.mjs +52 -44
- package/lib/installer.mjs +11 -4
- package/lib/mcp-transport.mjs +61 -2
- package/lib/migrate/adapters/memory-md.mjs +13 -6
- package/lib/migrate/datapack.mjs +167 -0
- package/lib/migrate/id.mjs +127 -9
- package/lib/migrate/orchestrator.mjs +68 -10
- package/lib/migrate.mjs +346 -3
- package/lib/prompt.mjs +22 -4
- package/lib/validate.mjs +313 -9
- package/lib/version-check.mjs +178 -0
- package/package.json +2 -1
- package/scripts/bundle-assets.mjs +8 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Troubleshooting — Error Recovery and Retry Strategies
|
|
2
|
+
|
|
3
|
+
These patterns are extracted from real agent sessions. Follow them to avoid wasting tool calls and context window on repeated failures.
|
|
4
|
+
|
|
5
|
+
## General rules
|
|
6
|
+
|
|
7
|
+
1. **Never retry the identical failed call more than once.** If it fails twice with the same error, it's not transient — diagnose the cause.
|
|
8
|
+
2. **Distinguish transient vs persistent failures.** "Connection already closed" is transient (retry once). "Permission denied" is persistent (stop, don't retry).
|
|
9
|
+
3. **Don't guess parameters sequentially.** If a tool fails with one parameter format, don't try 4 variations. Check this skill's docs for the correct format first.
|
|
10
|
+
4. **Use your session's `agent_id` consistently.** For Claude Desktop the default is `claude_desktop`; for cross-project common facts use `meko_agent`. Other clients use `<client>:<repo-basename>` (e.g. `claude_code:meko-mcp-server`). Use `agent_id="meko_agent"` deliberately on `memory_search`/`memory_get_all` only when you want the common bucket — empty string also resolves to `meko_agent`, not a cross-agent fan-out. Don't switch between forms mid-session.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Connection errors: "connection already closed"
|
|
15
|
+
|
|
16
|
+
**Affects:** Memory tools (`memory_add`, `memory_search`, `memory_get_all`) most frequently. DB tools less often.
|
|
17
|
+
|
|
18
|
+
**Why it happens:** The Python memory subsystem uses a connection cache that doesn't recover from dropped connections (unlike the Go API server which has retry logic with 5 retries and 3-second delays).
|
|
19
|
+
|
|
20
|
+
**What to do:**
|
|
21
|
+
|
|
22
|
+
1. Retry the same call **once**.
|
|
23
|
+
2. If it fails again, the memory subsystem is unhealthy for this session — stop trying memory calls and tell the user. Do not waste tokens re-sending the same payload.
|
|
24
|
+
3. Direct-SQL fallback **does not apply** on cloud Free Tier (`db_run_read_only_query` returns `{"error": "not_available", "detail": "Free Tier: DB Tools access is not enabled. coming soon."}`). On self-hosted / paid deployments where DB tools are available, inspect memory rows via the multi-tenant schema — tables are namespaced by datapack, not by agent_id. The old `{agent_id}_mem0_collection` per-agent-table layout is gone.
|
|
25
|
+
|
|
26
|
+
**Anti-pattern from real sessions:** Agent called `memory_add` with 500+ tokens of text, got "connection already closed", retried the identical call 3 more times (each 500+ tokens). Wasted ~2000 tokens on guaranteed failures.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## RAG pipeline stuck at INIT
|
|
31
|
+
|
|
32
|
+
**Affects:** `knowledgebase_trigger_index_creation` — the build triggers but never completes.
|
|
33
|
+
|
|
34
|
+
**Symptoms:**
|
|
35
|
+
- `knowledgebase_check_index_status` returns empty `pipeline_details` and `pipeline_stats`
|
|
36
|
+
- Documents remain at `INIT` status indefinitely
|
|
37
|
+
- Work queue tasks are `IN_PROGRESS` with `lease_expires_at` in the past
|
|
38
|
+
|
|
39
|
+
**Diagnosis:** Query the work queue directly:
|
|
40
|
+
```
|
|
41
|
+
db_run_read_only_query(scope="read",
|
|
42
|
+
query="SELECT task_type, status, lease_expires_at FROM dist_rag.work_queue WHERE index_name = 'your_index' ORDER BY created_at DESC LIMIT 10")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Look for tasks stuck `IN_PROGRESS` with expired leases — these indicate crashed or hung workers.
|
|
46
|
+
|
|
47
|
+
**What to do:**
|
|
48
|
+
|
|
49
|
+
1. Poll status **at most 3 times** with 1-2 minute intervals
|
|
50
|
+
2. If no progress after 3 checks, **stop polling** — the pipeline is stuck
|
|
51
|
+
3. There is **no way to reset or retry stuck tasks via MCP tools**
|
|
52
|
+
4. Creating a new index with a different name (e.g., `_v2`) is a workaround, but if the workers themselves are down, this will also get stuck
|
|
53
|
+
5. Recommend the user check worker logs or restart the pipeline service
|
|
54
|
+
|
|
55
|
+
**Anti-pattern from real sessions:** Agent created indexes `v1`, `v2`, `v3` — all stuck with the same symptoms. Even making the S3 bucket public didn't help. The issue was worker infrastructure, not permissions.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## db_run_write_query guardrail error
|
|
60
|
+
|
|
61
|
+
**Error:** `'dict' object has no attribute 'guardrail_config'`
|
|
62
|
+
|
|
63
|
+
**Affects:** `db_run_write_query` in some deployments.
|
|
64
|
+
|
|
65
|
+
**What to do:**
|
|
66
|
+
|
|
67
|
+
1. Try the call once. If it fails with this specific error, write operations are unavailable in this deployment.
|
|
68
|
+
2. For reads, always use `db_run_read_only_query` (never `db_run_write_query` for SELECT).
|
|
69
|
+
3. Report the issue — it's a server-side bug, not a parameter problem.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## knowledgebase_add_source_to_index failure
|
|
74
|
+
|
|
75
|
+
**Error shape:** `{"error": "not_available", "detail": "Vector index creation is not available on the free tier."}`
|
|
76
|
+
|
|
77
|
+
You're on cloud Free Tier; the ingestion tools are gated off. There is no retry that will succeed. Point the user at the Meko UI's **Add Knowledge** file-upload flow (Datapack → Actions → Add Knowledge) instead.
|
|
78
|
+
|
|
79
|
+
**Error shape:** `{"error": "index_not_found", "detail": "No vector index found with name '...'"}`
|
|
80
|
+
|
|
81
|
+
The `index_name` doesn't exist in this datapack's `dist_rag.vector_indexes` table. Either you're targeting the wrong datapack (check `datapack_id`), or the index hasn't been created yet — call `knowledgebase_trigger_index_creation` first.
|
|
82
|
+
|
|
83
|
+
**Error shape:** tool returns `"build_triggered"` but `knowledgebase_check_index_status` shows documents stuck at `INIT` for >5 minutes.
|
|
84
|
+
|
|
85
|
+
Most common cause on self-hosted deployments: the worker can't reach the S3 bucket. `S3BucketCrawler()` uses boto3's default credential chain — check `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / IAM role / `~/.aws/credentials` on the worker host, not on the MCP server. Second cause: the URI isn't `s3://`-prefixed (`aws_source_processor.py` rejects other schemes with `ValueError("Invalid source URI")` async).
|
|
86
|
+
|
|
87
|
+
**Historical note:** the old `r_index_name` vs `r_index_id` parameter-mismatch bug is fixed on main — the MCP tool now resolves `index_name → index_id` internally via a `SELECT id FROM dist_rag.vector_indexes WHERE index_name = %s` step. Older docs referencing this bug are stale.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Scope parameter errors
|
|
92
|
+
|
|
93
|
+
**Error:** `Insufficient scope: 'all'. This tool requires 'read' or higher.`
|
|
94
|
+
|
|
95
|
+
The only valid scope values are: `"read"`, `"write"`, `"admin"`.
|
|
96
|
+
|
|
97
|
+
- Use `"read"` for all read operations (default choice)
|
|
98
|
+
- Use `"write"` for inserts, updates, creates, memory writes
|
|
99
|
+
- Use `"admin"` only for destructive deletes
|
|
100
|
+
|
|
101
|
+
**Never use:** `"all"`, `"readwrite"`, `"rw"`, or any other value.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## agent_id errors
|
|
106
|
+
|
|
107
|
+
On the Cloud multi-tenant schema, `agent_id` is a TEXT column value — not a PostgreSQL identifier — so arbitrary strings are accepted.
|
|
108
|
+
|
|
109
|
+
`agent_id` is **not a constant**. Cloud Meko supports multiple agents on one datapack, and the Cloud UI renders every `agent_id` as a badge on each row regardless of value. The canonical shape for new writes is `<client>:<repo-basename>` for coding agents (e.g. `claude_code:meko-mcp-server`), a bare client name for non-coding clients like Claude Desktop (`claude_desktop`), or `meko_agent` for the cross-project common bucket — see `tools-agent-id-conventions.md`.
|
|
110
|
+
|
|
111
|
+
### "My writes are landing in `meko_agent` instead of my agent's bucket"
|
|
112
|
+
|
|
113
|
+
If you call `memory_add` with empty / None / whitespace `agent_id`, the server does NOT reject the write. It silently resolves the value to `meko_agent` (the cross-project common bucket) and the row lands there. The Cloud UI then shows a `meko_agent` badge instead of your client's badge.
|
|
114
|
+
|
|
115
|
+
Fix: always pass a concrete non-empty `agent_id`. For Claude Desktop use `agent_id="claude_desktop"`; for hook-driven clients (Claude Code, Cursor) fetch the value the SessionStart hook injected into `additionalContext` and pass it verbatim. Use `agent_id="meko_agent"` only when you genuinely want a fact in the cross-project pool (user identity, global preferences).
|
|
116
|
+
|
|
117
|
+
### "My reads return fewer results than I expected"
|
|
118
|
+
|
|
119
|
+
`memory_search` and `memory_get_all` filter strictly on your passed `agent_id`. A memory written by `claude-code:<slug-A>` is invisible to a search with `agent_id="cursor:<slug-B>"`. If you need a broader view, call repeatedly across the agent_ids you want to cover, or point the user at the UI's Memory Summary page (which shows every row regardless of agent_id).
|
|
120
|
+
|
|
121
|
+
### "I'm seeing rows tagged with legacy agent_id values"
|
|
122
|
+
|
|
123
|
+
Pre-existing data may be tagged `"agent"`, `"claude_code"`, `"cursor:<slug>"`, or similar ad-hoc values. All remain readable. New writes should follow the canonical `<client>:<project-slug>` form; don't try to retag old rows.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Permission denied on data tables
|
|
128
|
+
|
|
129
|
+
**Error:** `permission denied for table customer_complaints`
|
|
130
|
+
|
|
131
|
+
**Why:** The MCP database user may not have read access to all tables, particularly data tables created by the provisioning system within a datapack.
|
|
132
|
+
|
|
133
|
+
**What to do:**
|
|
134
|
+
|
|
135
|
+
1. This is a server-side configuration issue — not fixable via MCP tools.
|
|
136
|
+
2. Don't retry. Don't try different query formulations.
|
|
137
|
+
3. Use `db_summarize_database` to see which tables you CAN access.
|
|
138
|
+
4. Report the permission gap to the Meko admin.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Safe-to-retry vs not
|
|
143
|
+
|
|
144
|
+
| Tool | Safe to retry? | Why |
|
|
145
|
+
|------|---------------|-----|
|
|
146
|
+
| All `read` scope tools | Yes | Reads are idempotent |
|
|
147
|
+
| `memory_add` | Yes (once) | Mem0 has dedup logic |
|
|
148
|
+
| `memory_update` | Yes (once) | Overwrites same ID |
|
|
149
|
+
| `memory_delete_by_id` | Yes | Deleting already-deleted is a no-op |
|
|
150
|
+
| `db_run_write_query` (INSERT) | **No** | May double-insert rows |
|
|
151
|
+
| `db_run_write_query` (CREATE TABLE) | **No** | "Table already exists" error |
|
|
152
|
+
| `knowledgebase_trigger_index_creation` | **No** | Creates duplicate indexes |
|
|
153
|
+
| `conversation_create` | **No** | Creates duplicate conversations |
|
|
154
|
+
| `conversation_add_message` with `seed` | Yes | Seed-based dedup prevents duplicates |
|
|
155
|
+
| `conversation_add_message` without `seed` | **No** | Creates duplicate messages |
|
|
@@ -7,11 +7,12 @@ 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 } from '../lib/logger.mjs';
|
|
10
|
+
import { banner, error, dim, info } from '../lib/logger.mjs';
|
|
11
11
|
import { isPackagedInstall, selfInvocation } from '../lib/clients/common.mjs';
|
|
12
12
|
import { getClientAdapters } from '../lib/clients/index.mjs';
|
|
13
13
|
import { parseClientList } from '../lib/clients/aliases.mjs';
|
|
14
14
|
import { startMigration, runChildMigration, readStatus, stopMigration } from '../lib/migrate.mjs';
|
|
15
|
+
import { checkLatest } from '../lib/version-check.mjs';
|
|
15
16
|
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
17
18
|
// Flag definitions
|
|
@@ -24,10 +25,12 @@ const options = {
|
|
|
24
25
|
scope: { type: 'string', default: 'user' },
|
|
25
26
|
name: { type: 'string', default: 'meko' },
|
|
26
27
|
client: { type: 'string', default: 'claude-code' },
|
|
27
|
-
'cursor-hook-mode': { type: 'string', default: '
|
|
28
|
+
'cursor-hook-mode': { type: 'string', default: 'native' },
|
|
28
29
|
'skip-skills': { type: 'boolean', default: false },
|
|
29
30
|
'skip-validation': { type: 'boolean', default: false },
|
|
31
|
+
'no-canary': { type: 'boolean', default: false },
|
|
30
32
|
'dry-run': { type: 'boolean', default: false },
|
|
33
|
+
'check-updates': { type: 'boolean', default: false },
|
|
31
34
|
uninstall: { type: 'boolean', default: false },
|
|
32
35
|
verbose: { type: 'boolean', default: false },
|
|
33
36
|
yes: { type: 'boolean', default: false },
|
|
@@ -43,6 +46,7 @@ const options = {
|
|
|
43
46
|
rate: { type: 'string' },
|
|
44
47
|
'no-extract': { type: 'boolean', default: false },
|
|
45
48
|
'run-id': { type: 'string' },
|
|
49
|
+
datapack: { type: 'string' },
|
|
46
50
|
'__migrate-child': { type: 'boolean', default: false },
|
|
47
51
|
};
|
|
48
52
|
|
|
@@ -59,7 +63,7 @@ function readVersion() {
|
|
|
59
63
|
return pkg.version;
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
const ALL_CLIENTS = ['claude-code', 'claude-desktop', 'cursor'];
|
|
66
|
+
const ALL_CLIENTS = ['claude-code', 'claude-desktop', 'cursor', 'codex'];
|
|
63
67
|
|
|
64
68
|
function printHelp() {
|
|
65
69
|
const text = `
|
|
@@ -86,22 +90,32 @@ Examples:
|
|
|
86
90
|
# Install on every supported client:
|
|
87
91
|
create-meko-setup --client all --local --yes
|
|
88
92
|
|
|
93
|
+
# Install for OpenAI Codex (CLI + Desktop App share the same config):
|
|
94
|
+
create-meko-setup --client codex --yes
|
|
95
|
+
|
|
89
96
|
# Local Meko (docker-compose stack, Claude Code):
|
|
90
97
|
create-meko-setup --local --yes
|
|
91
98
|
|
|
92
99
|
Options:
|
|
93
|
-
--client <list> Target clients: one or more of claude-code, claude-desktop, cursor.
|
|
100
|
+
--client <list> Target clients: one or more of claude-code, claude-desktop, cursor, codex.
|
|
94
101
|
"claude" is accepted as an alias for claude-code.
|
|
95
102
|
Comma-separated, or "all" to target every supported client (default: claude-code)
|
|
103
|
+
codex covers BOTH the OpenAI Codex CLI and the Codex Desktop App
|
|
104
|
+
(they share ~/.codex/config.toml).
|
|
96
105
|
--url <url> MCP server URL (from app.mekodev.com > Datapacks > Connect)
|
|
97
106
|
--api-key <key> API key for Cloud Meko authentication
|
|
98
107
|
--local Use local Meko at http://localhost:8000/mcp
|
|
99
108
|
--scope <scope> Config scope: user or project (default: user)
|
|
109
|
+
codex always installs at user scope (no project-level
|
|
110
|
+
config); --scope=project degrades with a warning.
|
|
100
111
|
--name <name> MCP server name in config (default: meko)
|
|
101
|
-
--cursor-hook-mode Cursor hook mode: native
|
|
112
|
+
--cursor-hook-mode Cursor hook mode: native or claude-compat (default: native)
|
|
102
113
|
--skip-skills Skip plugin and skills installation
|
|
103
114
|
--skip-validation Skip post-install validation
|
|
115
|
+
--no-canary Skip the canary datapack_list call. The
|
|
116
|
+
connectivity + tools/list checks still run.
|
|
104
117
|
--dry-run Show what would be done without making changes
|
|
118
|
+
--check-updates Compare installed version against npm registry and exit
|
|
105
119
|
--uninstall Remove Meko MCP server, skills, and env vars
|
|
106
120
|
--verbose Show detailed output
|
|
107
121
|
--yes Accept all defaults (non-interactive)
|
|
@@ -121,6 +135,10 @@ Migrate existing conversations and memories into Meko:
|
|
|
121
135
|
--rate <n> Max concurrent MCP calls during migration (default 4).
|
|
122
136
|
--force Ignore a live lockfile or re-ingest processed items.
|
|
123
137
|
--run-id <id> Target a specific run-id for --migrate-status.
|
|
138
|
+
--datapack <name|id> Migration destination datapack. Accepts a datapack
|
|
139
|
+
name or UUID. When omitted, the user's default
|
|
140
|
+
datapack is used (resolved at run time and printed
|
|
141
|
+
before the run starts).
|
|
124
142
|
`.trimStart();
|
|
125
143
|
|
|
126
144
|
console.log(text);
|
|
@@ -130,6 +148,31 @@ function isCloudUrl(url) {
|
|
|
130
148
|
return url && !url.includes('localhost') && !url.includes('127.0.0.1');
|
|
131
149
|
}
|
|
132
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Sanitize a raw API key from any source (CLI flag, env var, askSecret prompt).
|
|
153
|
+
*
|
|
154
|
+
* Strips surrounding whitespace and a single matching pair of surrounding
|
|
155
|
+
* quotes. The contaminated value would otherwise be concatenated into
|
|
156
|
+
* `Authorization: Bearer <key>` verbatim and 401 against any server. Common
|
|
157
|
+
* sources of contamination: a trailing newline from
|
|
158
|
+
* `export MEKO_API_KEY=$(cat token.txt)`, a literal `"..."` copied out of a
|
|
159
|
+
* `.env` file, or a stray space at the end of a paste.
|
|
160
|
+
*
|
|
161
|
+
* Returns `null` for null/undefined/empty so the existing `!apiKey` checks
|
|
162
|
+
* still funnel into the "no key supplied" path.
|
|
163
|
+
*/
|
|
164
|
+
function normalizeApiKey(value) {
|
|
165
|
+
if (value === null || value === undefined) return null;
|
|
166
|
+
let s = String(value).trim();
|
|
167
|
+
if (
|
|
168
|
+
(s.startsWith('"') && s.endsWith('"') && s.length >= 2) ||
|
|
169
|
+
(s.startsWith("'") && s.endsWith("'") && s.length >= 2)
|
|
170
|
+
) {
|
|
171
|
+
s = s.slice(1, -1).trim();
|
|
172
|
+
}
|
|
173
|
+
return s || null;
|
|
174
|
+
}
|
|
175
|
+
|
|
133
176
|
/**
|
|
134
177
|
* Parse --client into a deduplicated list of client IDs.
|
|
135
178
|
* Accepts a single value, a comma-separated list, or the keyword "all".
|
|
@@ -175,7 +218,7 @@ function detectInstalledClients() {
|
|
|
175
218
|
}
|
|
176
219
|
|
|
177
220
|
function isValidCursorHookMode(mode) {
|
|
178
|
-
return ['native', 'claude-compat'
|
|
221
|
+
return ['native', 'claude-compat'].includes(mode);
|
|
179
222
|
}
|
|
180
223
|
|
|
181
224
|
/**
|
|
@@ -208,9 +251,41 @@ async function main() {
|
|
|
208
251
|
// --version
|
|
209
252
|
if (flags.version) {
|
|
210
253
|
console.log(readVersion());
|
|
254
|
+
console.log('(run with --check-updates to compare against npm registry)');
|
|
211
255
|
process.exit(0);
|
|
212
256
|
}
|
|
213
257
|
|
|
258
|
+
// --check-updates — synchronous probe of the npm registry. Never fails:
|
|
259
|
+
// network errors produce "status: network error (<message>)" and exit 0.
|
|
260
|
+
if (flags['check-updates']) {
|
|
261
|
+
const current = readVersion();
|
|
262
|
+
const result = await checkLatest({ currentVersion: current });
|
|
263
|
+
console.log(`current: ${current}`);
|
|
264
|
+
if (result.latest) {
|
|
265
|
+
console.log(`latest: ${result.latest}`);
|
|
266
|
+
if (result.isOutdated) {
|
|
267
|
+
console.log('status: outdated — run `npx @yugabytedb/meko-mcp@latest` to upgrade');
|
|
268
|
+
} else {
|
|
269
|
+
console.log('status: up to date');
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
console.log('latest: unknown');
|
|
273
|
+
console.log(`status: network error (${result.error ?? 'unknown'})`);
|
|
274
|
+
}
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Kick off a background version check on normal runs. We intentionally don't
|
|
279
|
+
// await — if the registry is slow or unreachable we don't want to delay
|
|
280
|
+
// (or fail) the install. `background: true` unrefs the underlying socket so
|
|
281
|
+
// an in-flight probe never keeps Node alive after the install exits; the
|
|
282
|
+
// result is still consulted (via Promise.race with an already-resolved
|
|
283
|
+
// sentinel) in time for the post-install banner when it arrives early.
|
|
284
|
+
const versionCheckPromise = checkLatest({ currentVersion: readVersion(), background: true });
|
|
285
|
+
versionCheckPromise.catch(() => {
|
|
286
|
+
/* checkLatest never rejects, but belt-and-suspenders. */
|
|
287
|
+
});
|
|
288
|
+
|
|
214
289
|
// --- Migrate subcommand dispatch ---
|
|
215
290
|
|
|
216
291
|
// Detached child branch: re-entry inside the spawned process.
|
|
@@ -230,11 +305,12 @@ async function main() {
|
|
|
230
305
|
runId: flags['run-id'],
|
|
231
306
|
scope: flags.scope,
|
|
232
307
|
url: flags.url,
|
|
233
|
-
apiKey: process.env.MEKO_API_KEY
|
|
308
|
+
apiKey: normalizeApiKey(process.env.MEKO_API_KEY),
|
|
234
309
|
projectRoot,
|
|
235
310
|
noExtract: flags['no-extract'],
|
|
236
311
|
force: flags.force,
|
|
237
312
|
rate: childRate,
|
|
313
|
+
datapack: flags.datapack ?? null,
|
|
238
314
|
});
|
|
239
315
|
process.exit(code);
|
|
240
316
|
}
|
|
@@ -257,7 +333,7 @@ async function main() {
|
|
|
257
333
|
process.exit(1);
|
|
258
334
|
}
|
|
259
335
|
let url = flags.url ?? process.env.MEKO_MCP_URL ?? null;
|
|
260
|
-
let apiKey = flags['api-key'] ?? process.env.MEKO_API_KEY
|
|
336
|
+
let apiKey = normalizeApiKey(flags['api-key'] ?? process.env.MEKO_API_KEY);
|
|
261
337
|
if (flags.local) { url = 'http://localhost:8000/mcp'; apiKey = null; }
|
|
262
338
|
|
|
263
339
|
let parentRate;
|
|
@@ -274,6 +350,8 @@ async function main() {
|
|
|
274
350
|
noExtract: flags['no-extract'],
|
|
275
351
|
force: flags.force,
|
|
276
352
|
rate: parentRate,
|
|
353
|
+
nonInteractive: flags.yes,
|
|
354
|
+
datapack: flags.datapack ?? null,
|
|
277
355
|
});
|
|
278
356
|
console.log(out.message);
|
|
279
357
|
process.exit(out.ok ? 0 : 1);
|
|
@@ -289,6 +367,15 @@ async function main() {
|
|
|
289
367
|
process.exit(1);
|
|
290
368
|
}
|
|
291
369
|
|
|
370
|
+
// Validate --scope BEFORE any branch that interpolates it into a shell
|
|
371
|
+
// command. The --uninstall path now forwards --scope to
|
|
372
|
+
// `claude plugin uninstall --scope <X>`, so an invalid value must be
|
|
373
|
+
// caught here, not after it's already been handed to the shell.
|
|
374
|
+
if (!isValidScope(flags.scope)) {
|
|
375
|
+
error(`Unknown scope "${flags.scope}". Valid values: user, project.`);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
|
|
292
379
|
// --uninstall
|
|
293
380
|
if (flags.uninstall) {
|
|
294
381
|
for (const client of clients) {
|
|
@@ -303,19 +390,20 @@ async function main() {
|
|
|
303
390
|
}
|
|
304
391
|
process.exit(0);
|
|
305
392
|
}
|
|
306
|
-
|
|
307
|
-
if (!isValidScope(flags.scope)) {
|
|
308
|
-
error(`Unknown scope "${flags.scope}". Valid values: user, project.`);
|
|
309
|
-
process.exit(1);
|
|
310
|
-
}
|
|
311
393
|
if (!isValidCursorHookMode(flags['cursor-hook-mode'])) {
|
|
312
|
-
error(`Unknown --cursor-hook-mode "${flags['cursor-hook-mode']}". Valid values: native, claude-compat
|
|
394
|
+
error(`Unknown --cursor-hook-mode "${flags['cursor-hook-mode']}". Valid values: native, claude-compat.`);
|
|
313
395
|
process.exit(1);
|
|
314
396
|
}
|
|
315
397
|
|
|
316
398
|
let url = flags.url ?? null;
|
|
317
|
-
let apiKey = flags['api-key']
|
|
399
|
+
let apiKey = normalizeApiKey(flags['api-key']);
|
|
318
400
|
let skipSkills = flags['skip-skills'];
|
|
401
|
+
// Tracks whether the full interactive flow already prompted for the API
|
|
402
|
+
// key. The Cloud-URL fallback below should only run for the
|
|
403
|
+
// semi-interactive `--url ... --client ...` paste path — otherwise
|
|
404
|
+
// submitting an empty value at the full-flow prompt would re-ask the
|
|
405
|
+
// same question.
|
|
406
|
+
let promptedForApiKey = false;
|
|
319
407
|
|
|
320
408
|
// --local shortcut
|
|
321
409
|
if (flags.local) {
|
|
@@ -336,6 +424,7 @@ async function main() {
|
|
|
336
424
|
'claude-code': 'Claude Code',
|
|
337
425
|
'claude-desktop': 'Claude Desktop',
|
|
338
426
|
cursor: 'Cursor',
|
|
427
|
+
codex: 'OpenAI Codex (CLI + App)',
|
|
339
428
|
}[id];
|
|
340
429
|
return {
|
|
341
430
|
label: id,
|
|
@@ -391,8 +480,11 @@ async function main() {
|
|
|
391
480
|
// Step 2 — MCP URL
|
|
392
481
|
url = await askText('MCP server URL (from app.mekodev.com > Datapacks > Connect):');
|
|
393
482
|
|
|
394
|
-
// Step 3 — API key
|
|
395
|
-
|
|
483
|
+
// Step 3 — API key. Normalize: terminal paste can drag in trailing
|
|
484
|
+
// whitespace/CR or accidental surrounding quotes, all of which would
|
|
485
|
+
// sail straight into `Authorization: Bearer ...` and 401.
|
|
486
|
+
apiKey = normalizeApiKey(await askSecret('API key:'));
|
|
487
|
+
promptedForApiKey = true;
|
|
396
488
|
} else {
|
|
397
489
|
url = 'http://localhost:8000/mcp';
|
|
398
490
|
apiKey = null;
|
|
@@ -414,9 +506,22 @@ async function main() {
|
|
|
414
506
|
process.exit(1);
|
|
415
507
|
}
|
|
416
508
|
|
|
417
|
-
// Validation — Cloud URL requires an API key
|
|
509
|
+
// Validation — Cloud URL requires an API key.
|
|
510
|
+
// Resolution order: --api-key (already in `apiKey`) → $MEKO_API_KEY →
|
|
511
|
+
// interactive prompt (only on a TTY, not under --yes, and only if the
|
|
512
|
+
// full interactive flow above didn't already ask). The prompt fallback
|
|
513
|
+
// exists so a user who pastes the cloud one-liner from the UI (which
|
|
514
|
+
// only shows --url and --client) isn't dead-ended on a hard error when
|
|
515
|
+
// they forgot to supply the key. Skipping it after a full-flow prompt
|
|
516
|
+
// avoids re-asking the same question when the user submitted empty.
|
|
418
517
|
if (isCloudUrl(url) && !apiKey) {
|
|
419
|
-
apiKey = process.env.MEKO_API_KEY
|
|
518
|
+
apiKey = normalizeApiKey(process.env.MEKO_API_KEY);
|
|
519
|
+
if (apiKey) {
|
|
520
|
+
dim('Using API key from MEKO_API_KEY environment variable.');
|
|
521
|
+
} else if (!flags.yes && !promptedForApiKey && process.stdin.isTTY) {
|
|
522
|
+
dim('No --api-key or MEKO_API_KEY env var found; prompting for API key.');
|
|
523
|
+
apiKey = normalizeApiKey(await askSecret('API key:'));
|
|
524
|
+
}
|
|
420
525
|
if (!apiKey) {
|
|
421
526
|
error(
|
|
422
527
|
'API key is required for Cloud Meko. ' +
|
|
@@ -424,7 +529,6 @@ async function main() {
|
|
|
424
529
|
);
|
|
425
530
|
process.exit(1);
|
|
426
531
|
}
|
|
427
|
-
dim('Using API key from MEKO_API_KEY environment variable.');
|
|
428
532
|
}
|
|
429
533
|
|
|
430
534
|
// Install — loop through each selected client sequentially so failures
|
|
@@ -439,6 +543,7 @@ async function main() {
|
|
|
439
543
|
name: flags.name,
|
|
440
544
|
skipSkills,
|
|
441
545
|
skipValidation: flags['skip-validation'],
|
|
546
|
+
skipCanary: flags['no-canary'],
|
|
442
547
|
dryRun: flags['dry-run'],
|
|
443
548
|
verbose: flags.verbose,
|
|
444
549
|
yes: flags.yes,
|
|
@@ -472,6 +577,7 @@ async function main() {
|
|
|
472
577
|
apiKey,
|
|
473
578
|
scope: flags.scope,
|
|
474
579
|
projectRoot,
|
|
580
|
+
datapack: flags.datapack ?? null,
|
|
475
581
|
});
|
|
476
582
|
} else if (flags.yes) {
|
|
477
583
|
console.log('');
|
|
@@ -482,16 +588,35 @@ async function main() {
|
|
|
482
588
|
apiKey,
|
|
483
589
|
scope: flags.scope,
|
|
484
590
|
projectRoot,
|
|
591
|
+
datapack: flags.datapack ?? null,
|
|
485
592
|
});
|
|
486
593
|
}
|
|
487
594
|
}
|
|
595
|
+
|
|
596
|
+
// Upgrade hint — only print if the background probe finished in time.
|
|
597
|
+
// We race the check against a pre-resolved sentinel so an in-flight
|
|
598
|
+
// probe (slow network) is silently dropped instead of delaying exit.
|
|
599
|
+
try {
|
|
600
|
+
const raced = await Promise.race([
|
|
601
|
+
versionCheckPromise,
|
|
602
|
+
Promise.resolve({ timedOut: true }),
|
|
603
|
+
]);
|
|
604
|
+
if (raced && raced.isOutdated === true && raced.latest && raced.current) {
|
|
605
|
+
info(
|
|
606
|
+
`A newer version is available (${raced.latest}, you have ${raced.current}). ` +
|
|
607
|
+
'Re-run with `npx @yugabytedb/meko-mcp@latest`.',
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
} catch {
|
|
611
|
+
/* never reached — checkLatest doesn't reject — but don't block exit. */
|
|
612
|
+
}
|
|
488
613
|
}
|
|
489
614
|
|
|
490
615
|
/**
|
|
491
616
|
* Unattended migration — used by --yes --with-migrate.
|
|
492
617
|
* No prompts; errors are surfaced but do not fail the install.
|
|
493
618
|
*/
|
|
494
|
-
async function runUnattendedMigration({ url, apiKey, scope, projectRoot }) {
|
|
619
|
+
async function runUnattendedMigration({ url, apiKey, scope, projectRoot, datapack }) {
|
|
495
620
|
try {
|
|
496
621
|
const out = await startMigration({
|
|
497
622
|
url,
|
|
@@ -499,6 +624,8 @@ async function runUnattendedMigration({ url, apiKey, scope, projectRoot }) {
|
|
|
499
624
|
scope,
|
|
500
625
|
projectRoot,
|
|
501
626
|
scriptPath: __filename,
|
|
627
|
+
nonInteractive: true,
|
|
628
|
+
datapack: datapack ?? null,
|
|
502
629
|
});
|
|
503
630
|
console.log('');
|
|
504
631
|
console.log(out.message);
|
|
@@ -516,7 +643,7 @@ async function runUnattendedMigration({ url, apiKey, scope, projectRoot }) {
|
|
|
516
643
|
* whether to kick off the detached migration. Any error here is non-fatal —
|
|
517
644
|
* install has already succeeded.
|
|
518
645
|
*/
|
|
519
|
-
async function maybeOfferMigration({ url, apiKey, scope, projectRoot }) {
|
|
646
|
+
async function maybeOfferMigration({ url, apiKey, scope, projectRoot, datapack }) {
|
|
520
647
|
try {
|
|
521
648
|
const dry = await startMigration({
|
|
522
649
|
url,
|
|
@@ -525,6 +652,7 @@ async function maybeOfferMigration({ url, apiKey, scope, projectRoot }) {
|
|
|
525
652
|
projectRoot,
|
|
526
653
|
scriptPath: __filename,
|
|
527
654
|
dryRun: true,
|
|
655
|
+
datapack: datapack ?? null,
|
|
528
656
|
});
|
|
529
657
|
const s = dry?.message?.match(/Claude Code sessions: *(\d+)/);
|
|
530
658
|
const c = dry?.message?.match(/Cursor sessions: *(\d+)/);
|
|
@@ -559,6 +687,7 @@ async function maybeOfferMigration({ url, apiKey, scope, projectRoot }) {
|
|
|
559
687
|
scope,
|
|
560
688
|
projectRoot,
|
|
561
689
|
scriptPath: __filename,
|
|
690
|
+
datapack: datapack ?? null,
|
|
562
691
|
});
|
|
563
692
|
console.log(out.message);
|
|
564
693
|
} catch (err) {
|