@yugabytedb/meko-mcp 1.2.0 → 2.1.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 +59 -25
- package/assets/hooks-handlers/before-submit-prompt.sh +2 -2
- package/assets/hooks-handlers/codex-session-end.sh +78 -0
- package/assets/hooks-handlers/lib/capture.js +366 -152
- package/assets/hooks-handlers/lib/checkpoint-timer.js +67 -13
- package/assets/hooks-handlers/session-end.sh +10 -0
- package/assets/hooks-handlers/session-start.sh +14 -3
- package/assets/hooks-handlers/test_checkpoint_timer.sh +162 -0
- package/assets/scripts/meko-statusline.sh +119 -0
- package/assets/scripts/package-desktop-skill.sh +64 -30
- package/assets/skills/meko-mcp-tools/SKILL.md +84 -86
- package/assets/skills/meko-mcp-tools/references/_sections.md +2 -4
- package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +29 -25
- package/assets/skills/meko-mcp-tools/references/tools-conversation-dedup.md +5 -7
- package/assets/skills/meko-mcp-tools/references/tools-conversation-reasoning.md +50 -0
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +126 -184
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +31 -37
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +10 -26
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +26 -30
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +60 -66
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +28 -93
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +5 -99
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +9 -8
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +138 -138
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +46 -16
- package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +2 -2
- package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +28 -24
- package/assets/skills/meko-mcp-tools-desktop/references/tools-conversation-reasoning.md +50 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +120 -174
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +16 -22
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +28 -34
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +60 -66
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +28 -93
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +4 -98
- package/assets/skills/meko-select-datapack-desktop/SKILL.md +208 -0
- package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +121 -0
- package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +41 -0
- package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +66 -0
- package/bin/create-meko-setup.mjs +74 -6
- package/lib/clients/claude-code.mjs +137 -6
- package/lib/clients/claude-desktop.mjs +51 -12
- package/lib/clients/codex.mjs +329 -13
- package/lib/clients/common.mjs +43 -1
- package/lib/clients/cursor.mjs +82 -21
- package/lib/config.mjs +26 -0
- package/lib/detect.mjs +7 -2
- package/lib/installer.mjs +24 -0
- package/lib/mcp-transport.mjs +5 -0
- package/lib/prompt.mjs +1 -1
- package/lib/url-selection.mjs +55 -0
- package/lib/validate.mjs +0 -1
- package/package.json +1 -1
- package/scripts/bundle-assets.mjs +8 -0
- package/assets/skills/meko-mcp-tools/references/tools-db-query-patterns.md +0 -38
- package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +0 -75
- package/assets/skills/meko-mcp-tools/references/tools-write-guardrails.md +0 -30
- package/assets/skills/meko-mcp-tools-desktop/references/tools-scope-permissions.md +0 -75
|
@@ -7,9 +7,14 @@ 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 } from '../lib/logger.mjs';
|
|
11
|
-
import {
|
|
10
|
+
import { banner, error, dim, info, warn } from '../lib/logger.mjs';
|
|
11
|
+
import {
|
|
12
|
+
isPackagedInstall,
|
|
13
|
+
MEKO_PRODUCTION_CONSOLE_URL,
|
|
14
|
+
selfInvocation,
|
|
15
|
+
} from '../lib/clients/common.mjs';
|
|
12
16
|
import { getClientAdapters } from '../lib/clients/index.mjs';
|
|
17
|
+
import { resolveSuggestedMcpUrl } from '../lib/url-selection.mjs';
|
|
13
18
|
import { parseClientList } from '../lib/clients/aliases.mjs';
|
|
14
19
|
import { startMigration, runChildMigration, readStatus, stopMigration } from '../lib/migrate.mjs';
|
|
15
20
|
import { checkLatest } from '../lib/version-check.mjs';
|
|
@@ -28,6 +33,8 @@ const options = {
|
|
|
28
33
|
'cursor-hook-mode': { type: 'string', default: 'native' },
|
|
29
34
|
'skip-skills': { type: 'boolean', default: false },
|
|
30
35
|
'skip-validation': { type: 'boolean', default: false },
|
|
36
|
+
statusline: { type: 'boolean', default: false },
|
|
37
|
+
'no-statusline': { type: 'boolean', default: false },
|
|
31
38
|
'no-canary': { type: 'boolean', default: false },
|
|
32
39
|
'dry-run': { type: 'boolean', default: false },
|
|
33
40
|
'check-updates': { type: 'boolean', default: false },
|
|
@@ -79,7 +86,7 @@ Examples:
|
|
|
79
86
|
create-meko-setup
|
|
80
87
|
|
|
81
88
|
# Cloud Meko for Claude Code (non-interactive):
|
|
82
|
-
create-meko-setup --url https://
|
|
89
|
+
create-meko-setup --url https://mcp.mekodata.ai/mcp --api-key "$MEKO_API_KEY" --yes
|
|
83
90
|
|
|
84
91
|
# Local Meko with Cursor config:
|
|
85
92
|
create-meko-setup --client cursor --local --scope project --yes
|
|
@@ -102,7 +109,7 @@ Options:
|
|
|
102
109
|
Comma-separated, or "all" to target every supported client (default: claude-code)
|
|
103
110
|
codex covers BOTH the OpenAI Codex CLI and the Codex Desktop App
|
|
104
111
|
(they share ~/.codex/config.toml).
|
|
105
|
-
--url <url> MCP server URL (from
|
|
112
|
+
--url <url> MCP server URL (from cloud.mekodata.ai > Datapacks > Connect)
|
|
106
113
|
--api-key <key> API key for Cloud Meko authentication
|
|
107
114
|
--local Use local Meko at http://localhost:8000/mcp
|
|
108
115
|
--scope <scope> Config scope: user or project (default: user)
|
|
@@ -111,6 +118,11 @@ Options:
|
|
|
111
118
|
--name <name> MCP server name in config (default: meko)
|
|
112
119
|
--cursor-hook-mode Cursor hook mode: native or claude-compat (default: native)
|
|
113
120
|
--skip-skills Skip plugin and skills installation
|
|
121
|
+
--statusline Wire the Meko datapack-pin indicator into Claude Code's
|
|
122
|
+
statusline (composes with any existing statusLine).
|
|
123
|
+
Default: ON under --yes, prompted otherwise.
|
|
124
|
+
Claude Code only.
|
|
125
|
+
--no-statusline Skip statusline wiring even under --yes.
|
|
114
126
|
--skip-validation Skip post-install validation
|
|
115
127
|
--no-canary Skip the canary datapack_list call. The
|
|
116
128
|
connectivity + tools/list checks still run.
|
|
@@ -398,6 +410,11 @@ async function main() {
|
|
|
398
410
|
let url = flags.url ?? null;
|
|
399
411
|
let apiKey = normalizeApiKey(flags['api-key']);
|
|
400
412
|
let skipSkills = flags['skip-skills'];
|
|
413
|
+
// Statusline: --no-statusline always wins. --statusline forces on. Otherwise
|
|
414
|
+
// default to on under --yes (programmatic), and prompt with Y default
|
|
415
|
+
// interactively. Resolved later after the interactive prompts since the
|
|
416
|
+
// flag may flip in the interactive flow.
|
|
417
|
+
let installStatusline = !flags['no-statusline'] && (flags.statusline || flags.yes);
|
|
401
418
|
// Tracks whether the full interactive flow already prompted for the API
|
|
402
419
|
// key. The Cloud-URL fallback below should only run for the
|
|
403
420
|
// semi-interactive `--url ... --client ...` paste path — otherwise
|
|
@@ -453,6 +470,28 @@ async function main() {
|
|
|
453
470
|
}
|
|
454
471
|
}
|
|
455
472
|
|
|
473
|
+
let suggestedUrl = null;
|
|
474
|
+
if (!url && !flags.local) {
|
|
475
|
+
suggestedUrl = resolveSuggestedMcpUrl({
|
|
476
|
+
adapters: getClientAdapters(),
|
|
477
|
+
clients,
|
|
478
|
+
name: flags.name,
|
|
479
|
+
scope: flags.scope,
|
|
480
|
+
projectRoot,
|
|
481
|
+
});
|
|
482
|
+
if (flags.yes) {
|
|
483
|
+
if (suggestedUrl.source === 'conflict') {
|
|
484
|
+
const conflicts = suggestedUrl.existingUrls.map((existingUrl) => `\n - ${existingUrl}`).join('');
|
|
485
|
+
error(
|
|
486
|
+
`Selected clients have different existing Meko URLs:${conflicts}\n` +
|
|
487
|
+
'Pass --url explicitly to choose which endpoint to install.',
|
|
488
|
+
);
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
url = suggestedUrl.url;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
456
495
|
// Interactive mode: URL not provided and not auto-accepting defaults
|
|
457
496
|
if (!url && !flags.yes) {
|
|
458
497
|
banner('Meko Setup');
|
|
@@ -463,7 +502,7 @@ async function main() {
|
|
|
463
502
|
// When only Cloud is available, skip the prompt entirely — asking to
|
|
464
503
|
// pick between one option is pointless UX.
|
|
465
504
|
const connectionOptions = [
|
|
466
|
-
{ label: 'Cloud Meko', description: 'Hosted at
|
|
505
|
+
{ label: 'Cloud Meko', description: 'Hosted at mekodata.ai (requires an API key)' },
|
|
467
506
|
];
|
|
468
507
|
if (!isPackagedInstall()) {
|
|
469
508
|
connectionOptions.push({
|
|
@@ -478,7 +517,19 @@ async function main() {
|
|
|
478
517
|
|
|
479
518
|
if (connectionType === 'Cloud Meko') {
|
|
480
519
|
// Step 2 — MCP URL
|
|
481
|
-
|
|
520
|
+
if (suggestedUrl?.source === 'existing') {
|
|
521
|
+
dim(`Using the existing Meko endpoint as the default: ${suggestedUrl.url}`);
|
|
522
|
+
} else if (suggestedUrl?.source === 'conflict') {
|
|
523
|
+
const conflicts = suggestedUrl.existingUrls.map((existingUrl) => `\n - ${existingUrl}`).join('');
|
|
524
|
+
warn(
|
|
525
|
+
`Selected clients use different Meko endpoints:${conflicts}\n` +
|
|
526
|
+
'Enter the endpoint to use for this install.',
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
url = await askText(
|
|
530
|
+
`MCP server URL (from ${MEKO_PRODUCTION_CONSOLE_URL} > Datapacks > Connect):`,
|
|
531
|
+
suggestedUrl?.url ?? undefined,
|
|
532
|
+
);
|
|
482
533
|
|
|
483
534
|
// Step 3 — API key. Normalize: terminal paste can drag in trailing
|
|
484
535
|
// whitespace/CR or accidental surrounding quotes, all of which would
|
|
@@ -498,6 +549,19 @@ async function main() {
|
|
|
498
549
|
if (!installSkills) {
|
|
499
550
|
skipSkills = true;
|
|
500
551
|
}
|
|
552
|
+
|
|
553
|
+
// Step 5 — statusline (Claude Code only). --no-statusline / --statusline
|
|
554
|
+
// skip the prompt; otherwise ask with Y default. The prompt only matters
|
|
555
|
+
// when at least one selected client is claude-code; for desktop / cursor /
|
|
556
|
+
// codex the install() orchestrator simply skips the step (the adapters
|
|
557
|
+
// don't expose installStatusline()).
|
|
558
|
+
if (!flags['no-statusline'] && !flags.statusline && clients.includes('claude-code')) {
|
|
559
|
+
installStatusline = await askYesNo(
|
|
560
|
+
'Show the active Meko datapack in Claude Code\'s statusline? ' +
|
|
561
|
+
'(composes with any existing statusLine — non-destructive)',
|
|
562
|
+
true,
|
|
563
|
+
);
|
|
564
|
+
}
|
|
501
565
|
}
|
|
502
566
|
|
|
503
567
|
// Validation — URL must be set by now
|
|
@@ -544,6 +608,10 @@ async function main() {
|
|
|
544
608
|
skipSkills,
|
|
545
609
|
skipValidation: flags['skip-validation'],
|
|
546
610
|
skipCanary: flags['no-canary'],
|
|
611
|
+
// Statusline is Claude Code only. For other clients the orchestrator
|
|
612
|
+
// skips the step regardless of the flag because the adapter doesn't
|
|
613
|
+
// expose installStatusline(); explicit guard keeps step counts honest.
|
|
614
|
+
installStatusline: client === 'claude-code' && installStatusline,
|
|
547
615
|
dryRun: flags['dry-run'],
|
|
548
616
|
verbose: flags.verbose,
|
|
549
617
|
yes: flags.yes,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
1
|
+
import { chmodSync, copyFileSync, mkdirSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
} from "../config.mjs";
|
|
15
15
|
import { detectClaude, detectExistingMeko, getClaudeCodeConfigPath } from "../detect.mjs";
|
|
16
16
|
import {
|
|
17
|
+
MEKO_MCP_USER_AGENT,
|
|
17
18
|
redactKey,
|
|
18
19
|
resolveHooksRoot,
|
|
20
|
+
resolveStatuslineSource,
|
|
19
21
|
run,
|
|
20
22
|
shellQuote,
|
|
21
23
|
writeInstallerMarker,
|
|
@@ -44,15 +46,23 @@ const MEKO_MARKETPLACE_NAME = "meko-agent-skills";
|
|
|
44
46
|
|
|
45
47
|
// Meko MCP tools to union into `permissions.allow` so Claude Code resolves
|
|
46
48
|
// them without the deferred-tools ToolSearch round-trip (issue #5). The set
|
|
47
|
-
// is intentionally tight —
|
|
48
|
-
// the meko-mcp-tools skill — to avoid bloating every user's
|
|
49
|
+
// is intentionally tight — the memory/conversation tools the agent actually
|
|
50
|
+
// reaches for per the meko-mcp-tools skill — to avoid bloating every user's
|
|
51
|
+
// settings.
|
|
52
|
+
//
|
|
53
|
+
// `memory_add` stays on this list even though the skill no longer tells the
|
|
54
|
+
// agent to fire it proactively (server-side extraction now captures
|
|
55
|
+
// conversationally-stated facts automatically). It's kept because the skill
|
|
56
|
+
// still uses memory_add for the narrow explicit-save cases — "remember this",
|
|
57
|
+
// output-only/tool-derived facts, and overwriting corrections — and pre-
|
|
58
|
+
// allowing it keeps those calls prompt-free. `conversation_add_message` is the
|
|
59
|
+
// tool that now drives capture-plus-extraction, so it must resolve instantly.
|
|
49
60
|
//
|
|
50
61
|
// NOTE: `permissions.allow` is verified to cover user-prompt bypass. Whether
|
|
51
62
|
// it also bypasses the deferred-tools schema-fetch gate must be re-verified
|
|
52
63
|
// empirically before this flag is considered load-bearing for #5. Tracked
|
|
53
64
|
// as Phase 10.1 in skills/TEST_PLAN.md.
|
|
54
65
|
const MEKO_ALWAYS_AVAILABLE_TOOLS = [
|
|
55
|
-
"mcp__meko__db_summarize_database",
|
|
56
66
|
"mcp__meko__memory_add",
|
|
57
67
|
"mcp__meko__memory_search",
|
|
58
68
|
"mcp__meko__memory_get_all",
|
|
@@ -125,7 +135,8 @@ export function createClaudeCodeAdapter(options = {}) {
|
|
|
125
135
|
buildRegisterCommand({ name, url, scope, apiKey }) {
|
|
126
136
|
let cmd =
|
|
127
137
|
`claude mcp add --transport http --scope ${shellQuote(scope)} ` +
|
|
128
|
-
`${shellQuote(name)} ${shellQuote(url)}
|
|
138
|
+
`${shellQuote(name)} ${shellQuote(url)} ` +
|
|
139
|
+
`--header ${shellQuote(`User-Agent: ${MEKO_MCP_USER_AGENT}`)}`;
|
|
129
140
|
if (apiKey) {
|
|
130
141
|
cmd += ` --header ${shellQuote(`Authorization: Bearer ${apiKey}`)}`;
|
|
131
142
|
}
|
|
@@ -207,14 +218,89 @@ export function createClaudeCodeAdapter(options = {}) {
|
|
|
207
218
|
);
|
|
208
219
|
}
|
|
209
220
|
},
|
|
221
|
+
/**
|
|
222
|
+
* Wire the Meko datapack-pin statusline contribution into ~/.claude/settings.json.
|
|
223
|
+
*
|
|
224
|
+
* Composable: if the user already has a `statusLine.command`, that command
|
|
225
|
+
* is preserved as the inner command and our wrapper appends `Meko: <name>`
|
|
226
|
+
* after its output. Idempotent: if our wrapper is already wired, no-op.
|
|
227
|
+
*
|
|
228
|
+
* Restore-on-uninstall: the original command is preserved in
|
|
229
|
+
* settings.json under `_meko.statusLine.previousCommand` so `uninstall()`
|
|
230
|
+
* can restore it. Absent that key (fresh install), uninstall removes the
|
|
231
|
+
* whole `statusLine` block.
|
|
232
|
+
*/
|
|
233
|
+
installStatusline({ dryRun, verbose, log, settingsPath }) {
|
|
234
|
+
const scriptSource = resolveStatuslineSource();
|
|
235
|
+
const scriptDest = join(homedir(), ".claude", "meko-statusline.sh");
|
|
236
|
+
|
|
237
|
+
const existing = readJsonFile(settingsPath) ?? {};
|
|
238
|
+
const existingCmd =
|
|
239
|
+
existing?.statusLine?.type === "command" && typeof existing?.statusLine?.command === "string"
|
|
240
|
+
? existing.statusLine.command.trim()
|
|
241
|
+
: "";
|
|
242
|
+
|
|
243
|
+
// Detect prior install of our wrapper. Re-wiring is a no-op for the
|
|
244
|
+
// command (already points at the wrapper); we still re-copy the script
|
|
245
|
+
// so a refreshed installer ships an updated copy.
|
|
246
|
+
const alreadyWired = existingCmd.includes("meko-statusline.sh");
|
|
247
|
+
const innerCmd =
|
|
248
|
+
alreadyWired
|
|
249
|
+
? (existing?._meko?.statusLine?.previousCommand ?? "")
|
|
250
|
+
: existingCmd;
|
|
251
|
+
|
|
252
|
+
const wrapperCmd = innerCmd
|
|
253
|
+
? `${shellQuote(scriptDest)} --inner-cmd ${shellQuote(innerCmd)}`
|
|
254
|
+
: shellQuote(scriptDest);
|
|
255
|
+
|
|
256
|
+
if (dryRun) {
|
|
257
|
+
log.dim(` Would copy ${scriptSource} → ${scriptDest}`);
|
|
258
|
+
log.dim(` Would set ${settingsPath} statusLine.command = ${wrapperCmd}`);
|
|
259
|
+
if (innerCmd) log.dim(` (preserving prior inner command: ${innerCmd})`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
mkdirSync(join(homedir(), ".claude"), { recursive: true });
|
|
264
|
+
copyFileSync(scriptSource, scriptDest);
|
|
265
|
+
chmodSync(scriptDest, 0o755);
|
|
266
|
+
|
|
267
|
+
const patch = {
|
|
268
|
+
statusLine: { type: "command", command: wrapperCmd },
|
|
269
|
+
};
|
|
270
|
+
// Stash the inner command so uninstall can restore it. Only write this
|
|
271
|
+
// namespace on the first install (when alreadyWired is false), so a
|
|
272
|
+
// re-install never overwrites the genuine original with our wrapper.
|
|
273
|
+
if (!alreadyWired) {
|
|
274
|
+
patch._meko = { statusLine: { previousCommand: innerCmd || null } };
|
|
275
|
+
}
|
|
276
|
+
mergeSettings(settingsPath, patch);
|
|
277
|
+
|
|
278
|
+
if (verbose) {
|
|
279
|
+
log.dim(` Statusline script: ${scriptDest}`);
|
|
280
|
+
log.dim(` Wrapper command: ${wrapperCmd}`);
|
|
281
|
+
if (innerCmd) log.dim(` Inner command preserved: ${innerCmd}`);
|
|
282
|
+
if (alreadyWired) log.dim(` (refreshed existing wrapper, no settings change beyond script copy)`);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
210
285
|
installSkills({ scope, dryRun, verbose, log }) {
|
|
211
286
|
// NB: we deliberately do NOT pass --sparse. Claude CLI's sparse
|
|
212
287
|
// path filter silently produces an empty clone for our layout,
|
|
213
288
|
// leaving the marketplace.json missing. Full clone is small
|
|
214
289
|
// (~few MB, mostly markdown) and is the only way that reliably
|
|
215
290
|
// succeeds today.
|
|
291
|
+
// Marketplace source is normally the public generated skills repo. Use
|
|
292
|
+
// an explicit HTTPS URL: GitHub's owner/repo shorthand makes Claude Code
|
|
293
|
+
// clone over SSH, which fails for users without GitHub SSH credentials.
|
|
294
|
+
// A dev/e2e
|
|
295
|
+
// override lets a test install the plugin from a LOCAL working-tree
|
|
296
|
+
// checkout (a directory path) so unreleased skill changes can be
|
|
297
|
+
// exercised end-to-end before they are published to `yugabyte/meko-skills`.
|
|
298
|
+
// Only set MEKO_PLUGIN_MARKETPLACE_SOURCE in tests.
|
|
299
|
+
const marketplaceSource =
|
|
300
|
+
process.env.MEKO_PLUGIN_MARKETPLACE_SOURCE
|
|
301
|
+
|| "https://github.com/yugabyte/meko-skills.git";
|
|
216
302
|
const marketplaceCmd =
|
|
217
|
-
`claude plugin marketplace add
|
|
303
|
+
`claude plugin marketplace add ${shellQuote(marketplaceSource)} --scope ${shellQuote(scope)}`;
|
|
218
304
|
// `claude plugin list` dropped --scope in CLI 2.1.x; it prints all
|
|
219
305
|
// scopes. The match-by-token regex used below is scope-agnostic by
|
|
220
306
|
// design — `plugin update` failing with "not found" on a
|
|
@@ -393,6 +479,44 @@ export function createClaudeCodeAdapter(options = {}) {
|
|
|
393
479
|
results.push({ key: "hooks", ok: false, message: `Could not prune Meko hook entries: ${err.message}` });
|
|
394
480
|
}
|
|
395
481
|
|
|
482
|
+
try {
|
|
483
|
+
// Restore the user's prior statusLine.command (if any) and remove our
|
|
484
|
+
// bookkeeping. If no prior command was preserved, remove the whole
|
|
485
|
+
// statusLine block — installStatusline() only added it on first
|
|
486
|
+
// install, so we own it cleanly.
|
|
487
|
+
const cfg = readJsonFile(settingsPath) ?? {};
|
|
488
|
+
const wrapperWired =
|
|
489
|
+
cfg?.statusLine?.type === "command" &&
|
|
490
|
+
typeof cfg?.statusLine?.command === "string" &&
|
|
491
|
+
cfg.statusLine.command.includes("meko-statusline.sh");
|
|
492
|
+
if (wrapperWired) {
|
|
493
|
+
const prev = cfg?._meko?.statusLine?.previousCommand;
|
|
494
|
+
if (typeof prev === "string" && prev.length > 0) {
|
|
495
|
+
cfg.statusLine = { type: "command", command: prev };
|
|
496
|
+
results.push({ key: "statusline", ok: true, message: "Restored prior statusLine.command." });
|
|
497
|
+
} else {
|
|
498
|
+
delete cfg.statusLine;
|
|
499
|
+
results.push({ key: "statusline", ok: true, message: "Removed Meko statusLine wrapper." });
|
|
500
|
+
}
|
|
501
|
+
if (cfg?._meko?.statusLine) {
|
|
502
|
+
delete cfg._meko.statusLine;
|
|
503
|
+
if (cfg._meko && Object.keys(cfg._meko).length === 0) delete cfg._meko;
|
|
504
|
+
}
|
|
505
|
+
writeJsonFile(settingsPath, cfg);
|
|
506
|
+
// Remove the copied script too — installStatusline() owns it, so
|
|
507
|
+
// leaving it behind would be a stale orphan after uninstall.
|
|
508
|
+
try {
|
|
509
|
+
unlinkSync(join(homedir(), ".claude", "meko-statusline.sh"));
|
|
510
|
+
} catch {
|
|
511
|
+
// Best-effort: absent file (ENOENT) or unreadable dir is fine.
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
results.push({ key: "statusline", ok: false, message: "Statusline wrapper was not installed (nothing to remove)." });
|
|
515
|
+
}
|
|
516
|
+
} catch (err) {
|
|
517
|
+
results.push({ key: "statusline", ok: false, message: `Could not restore statusLine: ${err.message}` });
|
|
518
|
+
}
|
|
519
|
+
|
|
396
520
|
try {
|
|
397
521
|
// Qualified plugin@marketplace form — CLI 2.1.x rejects the bare
|
|
398
522
|
// name (issue #63). Before: `catch` swallowed every failure as
|
|
@@ -460,6 +584,13 @@ export function createClaudeCodeAdapter(options = {}) {
|
|
|
460
584
|
}
|
|
461
585
|
|
|
462
586
|
const authHeader = getHeader(entry.headers, "Authorization");
|
|
587
|
+
const userAgent = getHeader(entry.headers, "User-Agent");
|
|
588
|
+
if (!userAgent) {
|
|
589
|
+
return {
|
|
590
|
+
ok: false,
|
|
591
|
+
message: `Server "${serverName}" in ${configPath} is missing the User-Agent header required by the Cloud WAF.`,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
463
594
|
if (apiKey) {
|
|
464
595
|
const expected = `Bearer ${apiKey}`;
|
|
465
596
|
if (authHeader !== expected) {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
removeKeys,
|
|
10
10
|
} from "../config.mjs";
|
|
11
11
|
import {
|
|
12
|
+
MEKO_MCP_USER_AGENT,
|
|
12
13
|
getAssetRoot,
|
|
13
14
|
getInstallerVersion,
|
|
14
15
|
markerIsStale,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
* "meko": {
|
|
32
33
|
* "command": "npx",
|
|
33
34
|
* "args": ["-y", "mcp-remote", "<url>", "--transport", "http-only",
|
|
35
|
+
* "--header", "User-Agent:meko-mcp-installer/1.0 (...)",
|
|
34
36
|
* "--header", "Authorization:Bearer ${MEKO_API_KEY}"],
|
|
35
37
|
* "env": { "MEKO_API_KEY": "<key>" }
|
|
36
38
|
* }
|
|
@@ -97,9 +99,11 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
97
99
|
const fromMcpb =
|
|
98
100
|
/\bClaude\b[^ ]*\bExtensions\b/i.test(commandJoined) ||
|
|
99
101
|
/mcp-remote-launcher\.js/.test(commandJoined);
|
|
102
|
+
const url = getMcpRemoteEntryUrl(entry);
|
|
100
103
|
return {
|
|
101
104
|
found: true,
|
|
102
105
|
status: fromMcpb ? "Configured (via .mcpb extension)" : "Configured",
|
|
106
|
+
...(url ? { url } : {}),
|
|
103
107
|
};
|
|
104
108
|
},
|
|
105
109
|
removeRegistration(name) {
|
|
@@ -122,8 +126,10 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
122
126
|
mergeSettings(path, { mcpServers: { [name]: server } });
|
|
123
127
|
},
|
|
124
128
|
setHookEnvironment({ dryRun, log }) {
|
|
125
|
-
// Claude Desktop has no hook API
|
|
126
|
-
//
|
|
129
|
+
// Claude Desktop has no hook API, so the skill mandates per-turn
|
|
130
|
+
// conversation_add_message posting (the server extracts memories from
|
|
131
|
+
// those posts); proactive memory_add is no longer the model. Nothing to
|
|
132
|
+
// write here.
|
|
127
133
|
if (dryRun) {
|
|
128
134
|
log.dim(" No hook environment required for Claude Desktop (skill-driven).");
|
|
129
135
|
}
|
|
@@ -132,6 +138,8 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
132
138
|
const assetRoot = assetRootResolver();
|
|
133
139
|
const script = join(assetRoot, "scripts", "package-desktop-skill.sh");
|
|
134
140
|
const output = join(assetRoot, "meko-mcp-tools-desktop.skill");
|
|
141
|
+
const selectOutput = join(assetRoot, "meko-select-datapack-desktop.skill");
|
|
142
|
+
const outputs = [output, selectOutput];
|
|
135
143
|
// The .skill output is a ZIP file, so we can't drop a marker inside it.
|
|
136
144
|
// Stamp the marker in the parent dir instead — it signals which
|
|
137
145
|
// installer version produced the bundle sitting alongside it, so a
|
|
@@ -141,9 +149,11 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
141
149
|
|
|
142
150
|
if (dryRun) {
|
|
143
151
|
log.dim(` Would run: bash ${script}`);
|
|
144
|
-
|
|
152
|
+
for (const skillOutput of outputs) {
|
|
153
|
+
log.dim(` Would produce: ${skillOutput}`);
|
|
154
|
+
}
|
|
145
155
|
log.dim(` Would write .meko-installer marker (version=${currentVersion}) in ${markerDir}`);
|
|
146
|
-
return { marketplace: "skipped", skills: "dry-run", output };
|
|
156
|
+
return { marketplace: "skipped", skills: "dry-run", output, outputs };
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
if (!existsSync(script)) {
|
|
@@ -158,7 +168,7 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
158
168
|
// re-drag the .skill into Claude Desktop's Extensions panel; we can't do
|
|
159
169
|
// that step for them. Keep rebuilding either way.
|
|
160
170
|
const existingMarker = readInstallerMarker(markerDir);
|
|
161
|
-
if (existsSync(
|
|
171
|
+
if (outputs.some((skillOutput) => existsSync(skillOutput)) && existingMarker && markerIsStale(markerDir, currentVersion)) {
|
|
162
172
|
log.warn(
|
|
163
173
|
`Claude Desktop .skill bundle was produced by installer ${existingMarker.version}. ` +
|
|
164
174
|
`Rebuilding — you will need to re-drag it into Claude Desktop > Settings > Extensions.`,
|
|
@@ -172,8 +182,9 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
172
182
|
return { marketplace: "skipped", skills: "error", error: err.message };
|
|
173
183
|
}
|
|
174
184
|
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
const missingOutputs = outputs.filter((skillOutput) => !existsSync(skillOutput));
|
|
186
|
+
if (missingOutputs.length) {
|
|
187
|
+
log.warn(`Expected skill bundle(s) were not produced: ${missingOutputs.join(", ")}`);
|
|
177
188
|
return { marketplace: "skipped", skills: "error" };
|
|
178
189
|
}
|
|
179
190
|
|
|
@@ -186,12 +197,14 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
186
197
|
log.warn(`Could not write .skill installer marker: ${err.message}`);
|
|
187
198
|
}
|
|
188
199
|
|
|
189
|
-
|
|
200
|
+
for (const skillOutput of outputs) {
|
|
201
|
+
log.info(`Built Meko skill bundle: ${skillOutput}`);
|
|
202
|
+
}
|
|
190
203
|
log.info(
|
|
191
|
-
"Install
|
|
192
|
-
"or drag
|
|
204
|
+
"Install both in Claude Desktop: Settings > Extensions > Import skill, " +
|
|
205
|
+
"or drag each .skill file onto the Claude Desktop window.",
|
|
193
206
|
);
|
|
194
|
-
return { marketplace: "skipped", skills: "ok", output };
|
|
207
|
+
return { marketplace: "skipped", skills: "ok", output, outputs };
|
|
195
208
|
},
|
|
196
209
|
uninstall({ name }) {
|
|
197
210
|
const results = [];
|
|
@@ -246,6 +259,12 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
246
259
|
`Server "${serverName}" in ${path} is missing required "command" and "args" fields.`,
|
|
247
260
|
};
|
|
248
261
|
}
|
|
262
|
+
if (!entry.args.includes(`User-Agent:${MEKO_MCP_USER_AGENT}`)) {
|
|
263
|
+
return {
|
|
264
|
+
ok: false,
|
|
265
|
+
message: `Server "${serverName}" in ${path} is missing the User-Agent header required by the Cloud WAF.`,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
249
268
|
|
|
250
269
|
return { ok: true, message: `Server "${serverName}" is registered in ${path}.` };
|
|
251
270
|
},
|
|
@@ -262,7 +281,15 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
262
281
|
* @returns {{command: string, args: string[], env?: Record<string,string>}}
|
|
263
282
|
*/
|
|
264
283
|
export function buildMcpRemoteEntry({ url, apiKey }) {
|
|
265
|
-
const args = [
|
|
284
|
+
const args = [
|
|
285
|
+
"-y",
|
|
286
|
+
"mcp-remote",
|
|
287
|
+
url,
|
|
288
|
+
"--transport",
|
|
289
|
+
"http-only",
|
|
290
|
+
"--header",
|
|
291
|
+
`User-Agent:${MEKO_MCP_USER_AGENT}`,
|
|
292
|
+
];
|
|
266
293
|
const entry = { command: "npx", args };
|
|
267
294
|
if (apiKey) {
|
|
268
295
|
// `Authorization:Bearer ${MEKO_API_KEY}` with no space around the colon
|
|
@@ -275,6 +302,18 @@ export function buildMcpRemoteEntry({ url, apiKey }) {
|
|
|
275
302
|
return entry;
|
|
276
303
|
}
|
|
277
304
|
|
|
305
|
+
/** Read the remote URL from installer-managed or MCPB Claude Desktop entries. */
|
|
306
|
+
export function getMcpRemoteEntryUrl(entry) {
|
|
307
|
+
if (typeof entry?.env?.MEKO_MCP_URL === "string") {
|
|
308
|
+
return entry.env.MEKO_MCP_URL;
|
|
309
|
+
}
|
|
310
|
+
if (typeof entry?.url === "string") return entry.url;
|
|
311
|
+
if (!Array.isArray(entry?.args)) return undefined;
|
|
312
|
+
const remoteIndex = entry.args.indexOf("mcp-remote");
|
|
313
|
+
const candidate = remoteIndex >= 0 ? entry.args[remoteIndex + 1] : undefined;
|
|
314
|
+
return typeof candidate === "string" ? candidate : undefined;
|
|
315
|
+
}
|
|
316
|
+
|
|
278
317
|
function defaultRunPackageScript(scriptPath) {
|
|
279
318
|
if (!statSync(scriptPath).isFile()) {
|
|
280
319
|
throw new Error(`Not a file: ${scriptPath}`);
|