lensmcp 1.18.1 → 1.18.2
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/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +233 -0
- package/lib/plugin-version.d.ts +121 -0
- package/lib/plugin-version.d.ts.map +1 -0
- package/lib/plugin-version.js +277 -0
- package/lib/status-client.d.ts +59 -0
- package/lib/status-client.d.ts.map +1 -0
- package/lib/status-client.js +241 -0
- package/lib/status-policy.d.ts +234 -0
- package/lib/status-policy.d.ts.map +1 -0
- package/lib/status-policy.js +303 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +15 -0
- package/plugin/hooks/hooks.json +31 -0
- package/plugin/hooks/status-hook.sh +34 -0
- package/plugin/hooks/version-hook.sh +31 -0
- package/plugin/skills/status/SKILL.md +18 -0
package/lib/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,UAAU;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,gEAAgE;IAChE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAgID,wBAAsB,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAgDhE;AAypBD,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEnE;;;;;;;;iFAQiF;AACjF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,mBAAmB,CAItB"}
|
package/lib/cli.js
CHANGED
|
@@ -8,6 +8,9 @@ import { ensureLensConfig } from './workspace-scope.js';
|
|
|
8
8
|
import { isMcpMode } from './mcp-mode.js';
|
|
9
9
|
import { effectiveMcpMode, LENS_CONFIG, probeSharedMcp, probeSharedMcpSync, readMcpWiring, wireMcpMode, } from './mcp-wiring.js';
|
|
10
10
|
import { sweepLensRuntimeFiles } from './sweep.js';
|
|
11
|
+
import { candidateSignals, findingLine, formatContext, hookPayload, isHookEvent, select, } from './status-policy.js';
|
|
12
|
+
import { DEFAULT_BUDGET_MS, readStatus } from './status-client.js';
|
|
13
|
+
import { detectSkew, formatSkewContext, readVersions, spawnRunner, syncPlugin, updateCommands, } from './plugin-version.js';
|
|
11
14
|
const HELP = `lensmcp — CLI for LensMCP (FrontMCP-based observability for coding agents)
|
|
12
15
|
|
|
13
16
|
Usage:
|
|
@@ -83,6 +86,24 @@ Commands:
|
|
|
83
86
|
cluster host (+ lensmcp.local), flush DNS. Wraps the
|
|
84
87
|
auto-discovered \`@lensmcp/cluster:trust\` target.
|
|
85
88
|
Idempotent; sudo prompts inline — run in a terminal.
|
|
89
|
+
status [--cwd <dir>] [--json] [--quiet] [--hook <event>] [--timeout <ms>]
|
|
90
|
+
Workspace health in one line — the hook-callable read of
|
|
91
|
+
\`agent://current-status\` from this workspace's SHARED MCP
|
|
92
|
+
server (embedded mode has none). Reports ONLY what it can
|
|
93
|
+
vouch for: a signal with no producer is unmonitored, not
|
|
94
|
+
healthy, and a verdict with no evidence of when it last
|
|
95
|
+
ran is unknown, not clean. \`--quiet\` prints NOTHING unless
|
|
96
|
+
something actionable is trustworthy — it is meant to run
|
|
97
|
+
every turn. \`--hook <Stop|SessionStart|SubagentStop>\`
|
|
98
|
+
emits the Claude Code \`additionalContext\` envelope
|
|
99
|
+
(advisory only — it never blocks). FAILS OPEN: no server,
|
|
100
|
+
no setup, a timeout → silent, exit 0, always.
|
|
101
|
+
version-check [--cwd <dir>] [--json] [--quiet] [--hook <event>] [--plugin-root <dir>]
|
|
102
|
+
Compare the installed Claude Code PLUGIN's version against
|
|
103
|
+
the lensmcp this project uses. On a proven mismatch it
|
|
104
|
+
names both versions and the exact update command; when
|
|
105
|
+
they match, or either version cannot be read, it is
|
|
106
|
+
silent. Same fail-open contract as \`status\`.
|
|
86
107
|
doctor [--cwd <dir>] [--json]
|
|
87
108
|
Diagnose the install: Node, workspace + plugin,
|
|
88
109
|
.mcp.json server entry, MCP bundle, per-project
|
|
@@ -142,6 +163,10 @@ export async function runCli(ctx) {
|
|
|
142
163
|
return runInstall(ctx, rest, out, err);
|
|
143
164
|
case 'trust':
|
|
144
165
|
return runTrust(ctx, rest, out, err);
|
|
166
|
+
case 'status':
|
|
167
|
+
return runStatus(ctx, rest, out);
|
|
168
|
+
case 'version-check':
|
|
169
|
+
return runVersionCheck(ctx, rest, out);
|
|
145
170
|
case 'doctor':
|
|
146
171
|
return runDoctor(ctx, rest, out);
|
|
147
172
|
case 'rollout':
|
|
@@ -1069,6 +1094,37 @@ async function applyMcpModeStep(cwd, flag, env, out) {
|
|
|
1069
1094
|
? ` \u2713 already up \u2014 ${health.server ?? 'MCP'} (protocol ${health.protocolVersion ?? '?'})`
|
|
1070
1095
|
: ` \u00b7 not up yet (${health.error ?? 'unknown'}) \u2014 the next session starts it on demand`);
|
|
1071
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Bring the installed Claude Code plugin in step with this lensmcp.
|
|
1099
|
+
*
|
|
1100
|
+
* `setup` already reconciles `.mcp.json`, the nx config, the vite/nest wiring
|
|
1101
|
+
* and the pods; the plugin was the one component it walked past, and it drifted
|
|
1102
|
+
* nine minors on the machine this was written for. It is reconciled LAST and
|
|
1103
|
+
* NON-FATALLY: `setup` is the recovery command, so a marketplace/cache/network
|
|
1104
|
+
* problem must degrade to one warning line rather than abort the steps that
|
|
1105
|
+
* actually fix a broken workspace.
|
|
1106
|
+
*
|
|
1107
|
+
* Nothing is written into `~/.claude/plugins` — see `plugin-version.ts` for why
|
|
1108
|
+
* the sanctioned `claude plugin …` commands are the only path taken.
|
|
1109
|
+
*/
|
|
1110
|
+
function applyPluginSyncStep(cwd, env, out) {
|
|
1111
|
+
const facts = readVersions({ cwd, cliVersion: readCliVersion() });
|
|
1112
|
+
const result = syncPlugin({ run: spawnRunner(env), facts });
|
|
1113
|
+
// Quiet when already current — `setup` is run constantly and must not churn.
|
|
1114
|
+
if (result.outcome === 'current' || result.outcome === 'unknown')
|
|
1115
|
+
return;
|
|
1116
|
+
out('');
|
|
1117
|
+
if (result.outcome === 'updated') {
|
|
1118
|
+
out(`[plugin] ${result.from} → ${result.to}`);
|
|
1119
|
+
out(' restart Claude Code for the new plugin to load.');
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
out(`[plugin] the Claude Code plugin is ${result.from}, this lensmcp is ${result.to}.`);
|
|
1123
|
+
out(` could not update it automatically (${result.detail ?? 'unknown reason'}). Run:`);
|
|
1124
|
+
for (const c of result.commands ?? [])
|
|
1125
|
+
out(` ${c}`);
|
|
1126
|
+
out(' …then restart Claude Code.');
|
|
1127
|
+
}
|
|
1072
1128
|
async function runSetup(ctx, args, out, err) {
|
|
1073
1129
|
const opts = parseFlags(args, { string: ['--cwd', '--mcp'] });
|
|
1074
1130
|
const cwd = resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd);
|
|
@@ -1111,6 +1167,14 @@ async function runSetup(ctx, args, out, err) {
|
|
|
1111
1167
|
// shared mode, and re-affirms stdio otherwise. Runs AFTER install so it is the
|
|
1112
1168
|
// last writer of that entry.
|
|
1113
1169
|
await applyMcpModeStep(cwd, mcpFlag, ctx.env ?? process.env, out);
|
|
1170
|
+
// The plugin ships inside this npm package but updates on Claude Code's
|
|
1171
|
+
// schedule, not ours — so reconcile it here, last, and never fatally.
|
|
1172
|
+
try {
|
|
1173
|
+
applyPluginSyncStep(cwd, { ...process.env, ...(ctx.env ?? {}) }, out);
|
|
1174
|
+
}
|
|
1175
|
+
catch {
|
|
1176
|
+
/* the plugin step can never be the reason `setup` failed */
|
|
1177
|
+
}
|
|
1114
1178
|
// All steps succeeded → mark the workspace set up for this version (opens the MCP gate).
|
|
1115
1179
|
writeSetupState(cwd);
|
|
1116
1180
|
out('');
|
|
@@ -1124,6 +1188,175 @@ async function runSetup(ctx, args, out, err) {
|
|
|
1124
1188
|
out(' verify: lensmcp doctor');
|
|
1125
1189
|
return { exitCode: 0 };
|
|
1126
1190
|
}
|
|
1191
|
+
/**
|
|
1192
|
+
* `lensmcp status` — the hook-callable health read.
|
|
1193
|
+
*
|
|
1194
|
+
* THE CONTRACT, and the reason this function looks paranoid: it runs at the end
|
|
1195
|
+
* of every turn, so it must never block one, never error, and never hang. Hence
|
|
1196
|
+
* `exitCode: 0` on every path including the catch, a whole-call budget, and
|
|
1197
|
+
* silence as the default output. A non-zero exit or a stray line here would show
|
|
1198
|
+
* up in the transcript as a hook error on turns where nothing is wrong, which is
|
|
1199
|
+
* the exact noise the feature exists to avoid.
|
|
1200
|
+
*/
|
|
1201
|
+
async function runStatus(ctx, args, out) {
|
|
1202
|
+
const opts = parseFlags(args, {
|
|
1203
|
+
string: ['--cwd', '--hook', '--timeout'],
|
|
1204
|
+
boolean: ['--json', '--quiet'],
|
|
1205
|
+
});
|
|
1206
|
+
const cwd = resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd);
|
|
1207
|
+
const hookRaw = stringFlag(opts.flags['--hook']);
|
|
1208
|
+
const hook = isHookEvent(hookRaw) ? hookRaw : undefined;
|
|
1209
|
+
// A caller that asked for hook output and named an event we do not emit gets
|
|
1210
|
+
// NOTHING, rather than the human text on a channel expecting JSON. Silence is
|
|
1211
|
+
// the honest answer to "I cannot produce what you asked for".
|
|
1212
|
+
if (hookRaw !== undefined && hook === undefined)
|
|
1213
|
+
return { exitCode: 0 };
|
|
1214
|
+
const asJson = opts.flags['--json'] === true;
|
|
1215
|
+
// A hook is a quiet caller by definition: it speaks only when it has news.
|
|
1216
|
+
const quiet = opts.flags['--quiet'] === true || hookRaw !== undefined;
|
|
1217
|
+
const budget = Number(stringFlag(opts.flags['--timeout']) ?? '');
|
|
1218
|
+
const budgetMs = Number.isFinite(budget) && budget > 0 ? budget : DEFAULT_BUDGET_MS;
|
|
1219
|
+
const env = { ...process.env, ...(ctx.env ?? {}) };
|
|
1220
|
+
try {
|
|
1221
|
+
const read = await readStatus({
|
|
1222
|
+
cwd,
|
|
1223
|
+
env,
|
|
1224
|
+
budgetMs,
|
|
1225
|
+
// Only signals we are about to REPORT get a verification read, so the
|
|
1226
|
+
// clean path costs exactly one request.
|
|
1227
|
+
verify: (status) => candidateSignals(status).map((s) => s.resource),
|
|
1228
|
+
});
|
|
1229
|
+
if (!read.available) {
|
|
1230
|
+
if (asJson && !quiet) {
|
|
1231
|
+
out(JSON.stringify({ available: false, workspace: read.source.workspace, mode: read.source.mode, reason: read.reason, elapsedMs: read.elapsedMs }));
|
|
1232
|
+
}
|
|
1233
|
+
else if (!quiet) {
|
|
1234
|
+
out(`lensmcp status — ${read.source.workspace}: unavailable (${read.reason})`);
|
|
1235
|
+
}
|
|
1236
|
+
return { exitCode: 0 };
|
|
1237
|
+
}
|
|
1238
|
+
const selection = select(read.status, read.evidence, Date.now());
|
|
1239
|
+
const context = formatContext(selection, {
|
|
1240
|
+
workspace: read.source.workspace,
|
|
1241
|
+
includeUnmonitored: hook === 'SessionStart' || !quiet,
|
|
1242
|
+
});
|
|
1243
|
+
if (asJson) {
|
|
1244
|
+
if (quiet && context === undefined)
|
|
1245
|
+
return { exitCode: 0 };
|
|
1246
|
+
out(JSON.stringify({
|
|
1247
|
+
available: true,
|
|
1248
|
+
workspace: read.source.workspace,
|
|
1249
|
+
mode: read.source.mode,
|
|
1250
|
+
url: read.source.url,
|
|
1251
|
+
aggregate: read.status.status ?? 'unknown',
|
|
1252
|
+
elapsedMs: read.elapsedMs,
|
|
1253
|
+
findings: selection.findings,
|
|
1254
|
+
suppressed: selection.suppressed,
|
|
1255
|
+
unmonitored: selection.unmonitored,
|
|
1256
|
+
}));
|
|
1257
|
+
return { exitCode: 0 };
|
|
1258
|
+
}
|
|
1259
|
+
if (hook) {
|
|
1260
|
+
if (context !== undefined)
|
|
1261
|
+
out(hookPayload(hook, context));
|
|
1262
|
+
return { exitCode: 0 };
|
|
1263
|
+
}
|
|
1264
|
+
if (quiet) {
|
|
1265
|
+
if (context !== undefined)
|
|
1266
|
+
out(context);
|
|
1267
|
+
return { exitCode: 0 };
|
|
1268
|
+
}
|
|
1269
|
+
// The human view. Unlike the hook it always says something, and it shows the
|
|
1270
|
+
// SUPPRESSED signals too — "why did it stay quiet about the build?" has to be
|
|
1271
|
+
// answerable, or the silence looks like a bug.
|
|
1272
|
+
out(`lensmcp status — ${read.source.workspace} (${read.source.mode}, ${read.source.url})`);
|
|
1273
|
+
out(` aggregate says: ${read.status.status ?? 'unknown'} (read in ${read.elapsedMs}ms)`);
|
|
1274
|
+
if (selection.findings.length === 0) {
|
|
1275
|
+
out(' nothing actionable that this command can vouch for.');
|
|
1276
|
+
}
|
|
1277
|
+
else {
|
|
1278
|
+
for (const f of selection.findings)
|
|
1279
|
+
out(` • ${findingLine(f)}`);
|
|
1280
|
+
}
|
|
1281
|
+
for (const s of selection.suppressed)
|
|
1282
|
+
out(` · not reported — ${s.kind}: ${s.reason}`);
|
|
1283
|
+
if (selection.unmonitored.length > 0) {
|
|
1284
|
+
out(` · unmonitored (unknown, NOT clean): ${selection.unmonitored.join(', ')}`);
|
|
1285
|
+
}
|
|
1286
|
+
return { exitCode: 0 };
|
|
1287
|
+
}
|
|
1288
|
+
catch (e) {
|
|
1289
|
+
// Fail open. A status read is never worth a failed turn.
|
|
1290
|
+
if (!quiet)
|
|
1291
|
+
out(`lensmcp status: unavailable (${e instanceof Error ? e.message : String(e)})`);
|
|
1292
|
+
return { exitCode: 0 };
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* `lensmcp version-check` — is the installed plugin the one this lensmcp ships?
|
|
1297
|
+
*
|
|
1298
|
+
* A separate command from `status` because it is a separate question with a
|
|
1299
|
+
* separate cadence: the answer changes only when someone upgrades, so it belongs
|
|
1300
|
+
* at SessionStart, whereas health changes every turn. Same fail-open contract,
|
|
1301
|
+
* and the same advisory-only rule — it names the command, it does not block.
|
|
1302
|
+
*/
|
|
1303
|
+
function runVersionCheck(ctx, args, out) {
|
|
1304
|
+
const opts = parseFlags(args, {
|
|
1305
|
+
string: ['--cwd', '--hook', '--plugin-root'],
|
|
1306
|
+
boolean: ['--json', '--quiet'],
|
|
1307
|
+
});
|
|
1308
|
+
const cwd = resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd);
|
|
1309
|
+
const hookRaw = stringFlag(opts.flags['--hook']);
|
|
1310
|
+
const hook = isHookEvent(hookRaw) ? hookRaw : undefined;
|
|
1311
|
+
if (hookRaw !== undefined && hook === undefined)
|
|
1312
|
+
return { exitCode: 0 };
|
|
1313
|
+
const asJson = opts.flags['--json'] === true;
|
|
1314
|
+
const quiet = opts.flags['--quiet'] === true || hookRaw !== undefined;
|
|
1315
|
+
const pluginRoot = stringFlag(opts.flags['--plugin-root']);
|
|
1316
|
+
try {
|
|
1317
|
+
const facts = readVersions({
|
|
1318
|
+
cwd,
|
|
1319
|
+
cliVersion: readCliVersion(),
|
|
1320
|
+
...(pluginRoot ? { pluginRoot } : {}),
|
|
1321
|
+
});
|
|
1322
|
+
const skew = detectSkew(facts);
|
|
1323
|
+
if (asJson) {
|
|
1324
|
+
if (quiet && !skew)
|
|
1325
|
+
return { exitCode: 0 };
|
|
1326
|
+
out(JSON.stringify({ ...facts, skew: skew ?? null }));
|
|
1327
|
+
return { exitCode: 0 };
|
|
1328
|
+
}
|
|
1329
|
+
if (hook) {
|
|
1330
|
+
if (skew)
|
|
1331
|
+
out(hookPayload(hook, formatSkewContext(skew)));
|
|
1332
|
+
return { exitCode: 0 };
|
|
1333
|
+
}
|
|
1334
|
+
if (quiet) {
|
|
1335
|
+
if (skew)
|
|
1336
|
+
out(formatSkewContext(skew));
|
|
1337
|
+
return { exitCode: 0 };
|
|
1338
|
+
}
|
|
1339
|
+
out(`lensmcp version-check — ${facts.pluginId ?? 'lensmcp plugin'}`);
|
|
1340
|
+
out(` plugin ${facts.plugin ?? 'unknown'}${facts.pluginSource ? ` (${facts.pluginSource})` : ''}`);
|
|
1341
|
+
out(` project ${facts.project ?? 'unknown'} (node_modules/lensmcp)`);
|
|
1342
|
+
out(` cli ${facts.cli ?? 'unknown'} (the binary running now)`);
|
|
1343
|
+
if (!skew) {
|
|
1344
|
+
out(facts.plugin && (facts.project ?? facts.cli)
|
|
1345
|
+
? ' ✓ in step.'
|
|
1346
|
+
: ' ? cannot compare — one of the versions is unreadable (staying silent).');
|
|
1347
|
+
return { exitCode: 0 };
|
|
1348
|
+
}
|
|
1349
|
+
out(` ✗ the plugin is ${skew.direction} — update it, then restart Claude Code:`);
|
|
1350
|
+
for (const c of updateCommands(skew))
|
|
1351
|
+
out(` ${c}`);
|
|
1352
|
+
return { exitCode: 0 };
|
|
1353
|
+
}
|
|
1354
|
+
catch (e) {
|
|
1355
|
+
if (!quiet)
|
|
1356
|
+
out(`lensmcp version-check: unavailable (${e instanceof Error ? e.message : String(e)})`);
|
|
1357
|
+
return { exitCode: 0 };
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1127
1360
|
async function runDoctor(ctx, args, out) {
|
|
1128
1361
|
const opts = parseFlags(args, { string: ['--cwd'], boolean: ['--json'] });
|
|
1129
1362
|
const cwd = resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** The plugin's id as Claude Code knows it: `<plugin>@<marketplace>`. */
|
|
2
|
+
export declare const PLUGIN_NAME = "lensmcp";
|
|
3
|
+
export interface InstalledPlugin {
|
|
4
|
+
/** `lensmcp@lensmcp-marketplace`. */
|
|
5
|
+
id: string;
|
|
6
|
+
marketplace: string;
|
|
7
|
+
version: string;
|
|
8
|
+
scope?: string;
|
|
9
|
+
projectPath?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* What Claude Code records about the installed plugin.
|
|
13
|
+
*
|
|
14
|
+
* Read from `installed_plugins.json` rather than by shelling out to
|
|
15
|
+
* `claude plugin list --json`: it is the same data, costs no subprocess (~0.26s
|
|
16
|
+
* measured), and cannot prompt or hang — all three matter on a hook path. The
|
|
17
|
+
* file is only ever READ.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readInstalledPlugin(cwd: string, home?: string): InstalledPlugin | undefined;
|
|
20
|
+
export interface VersionFacts {
|
|
21
|
+
/**
|
|
22
|
+
* The plugin actually loaded, read from `${CLAUDE_PLUGIN_ROOT}` when a hook
|
|
23
|
+
* passed it — the only fully authoritative source, since it is the manifest
|
|
24
|
+
* Claude Code parsed. Falls back to the install index.
|
|
25
|
+
*/
|
|
26
|
+
plugin?: string;
|
|
27
|
+
pluginSource?: 'plugin-root' | 'install-index';
|
|
28
|
+
pluginId?: string;
|
|
29
|
+
pluginMarketplace?: string;
|
|
30
|
+
/** The `lensmcp` this project depends on — what the plugin should match. */
|
|
31
|
+
project?: string;
|
|
32
|
+
/** The CLI binary currently executing (may be a global install). */
|
|
33
|
+
cli?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ReadVersionsOptions {
|
|
36
|
+
cwd: string;
|
|
37
|
+
/** `${CLAUDE_PLUGIN_ROOT}`, when invoked from a plugin hook. */
|
|
38
|
+
pluginRoot?: string;
|
|
39
|
+
/** The running CLI's own version. */
|
|
40
|
+
cliVersion?: string;
|
|
41
|
+
home?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function readVersions(options: ReadVersionsOptions): VersionFacts;
|
|
44
|
+
/** -1 / 0 / 1, or `undefined` when either side is not a comparable version. */
|
|
45
|
+
export declare function compareVersions(a: string, b: string): number | undefined;
|
|
46
|
+
export interface Skew {
|
|
47
|
+
/** PROVEN: both versions were read and they differ. */
|
|
48
|
+
mismatch: boolean;
|
|
49
|
+
plugin: string;
|
|
50
|
+
expected: string;
|
|
51
|
+
/** Which version the `expected` side came from. */
|
|
52
|
+
expectedSource: 'project' | 'cli';
|
|
53
|
+
direction: 'behind' | 'ahead' | 'differs';
|
|
54
|
+
/** `<plugin>@<marketplace>`, when the install index named one. */
|
|
55
|
+
pluginId?: string;
|
|
56
|
+
marketplace?: string;
|
|
57
|
+
/** A global CLI that disagrees with the project's dependency, if any. */
|
|
58
|
+
cliSkew?: {
|
|
59
|
+
cli: string;
|
|
60
|
+
project: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Decide whether there is a skew worth telling anyone about.
|
|
65
|
+
*
|
|
66
|
+
* Returns `undefined` for every uncertain case — no plugin found, no reference
|
|
67
|
+
* version, an unparseable version string. Trust rule: warn only on a PROVEN
|
|
68
|
+
* mismatch, never a suspected one. A hook that guesses is a hook that gets
|
|
69
|
+
* muted.
|
|
70
|
+
*/
|
|
71
|
+
export declare function detectSkew(facts: VersionFacts): Skew | undefined;
|
|
72
|
+
/** The exact commands that reconcile the plugin, in order. */
|
|
73
|
+
export declare function updateCommands(skew: Pick<Skew, 'pluginId' | 'marketplace'>): string[];
|
|
74
|
+
/**
|
|
75
|
+
* The SessionStart context for a proven skew.
|
|
76
|
+
*
|
|
77
|
+
* States both versions and names the command, because "something is out of
|
|
78
|
+
* date" without the fix is a nag. Advisory: it asks the agent to update first,
|
|
79
|
+
* it does not prevent anything.
|
|
80
|
+
*/
|
|
81
|
+
export declare function formatSkewContext(skew: Skew): string;
|
|
82
|
+
export type SyncOutcome =
|
|
83
|
+
/** Already at the expected version — nothing done, nothing printed. */
|
|
84
|
+
'current'
|
|
85
|
+
/** Skew found and the update commands succeeded. */
|
|
86
|
+
| 'updated'
|
|
87
|
+
/** Skew found; the update could not be run or failed. Actionable, non-fatal. */
|
|
88
|
+
| 'manual'
|
|
89
|
+
/** Nothing installed, or not enough information to say. Silent. */
|
|
90
|
+
| 'unknown';
|
|
91
|
+
export interface SyncResult {
|
|
92
|
+
outcome: SyncOutcome;
|
|
93
|
+
from?: string;
|
|
94
|
+
to?: string;
|
|
95
|
+
/** What the user should run when we could not. */
|
|
96
|
+
commands?: string[];
|
|
97
|
+
detail?: string;
|
|
98
|
+
}
|
|
99
|
+
export interface SyncDeps {
|
|
100
|
+
/** Run a command; `undefined` means the binary was not found. */
|
|
101
|
+
run: (cmd: string, args: string[]) => {
|
|
102
|
+
code: number;
|
|
103
|
+
stderr?: string;
|
|
104
|
+
} | undefined;
|
|
105
|
+
facts: VersionFacts;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Reconcile the installed plugin with the lensmcp this workspace uses.
|
|
109
|
+
*
|
|
110
|
+
* Quiet when current (the common case — `setup` is run constantly), and NEVER
|
|
111
|
+
* fatal: every failure path returns `manual` with the command to run, so a
|
|
112
|
+
* marketplace, cache or network problem cannot abort the rest of setup.
|
|
113
|
+
*/
|
|
114
|
+
export declare function syncPlugin(deps: SyncDeps): SyncResult;
|
|
115
|
+
/** The real runner. Bounded, non-interactive, and never throws. */
|
|
116
|
+
export declare function spawnRunner(env: NodeJS.ProcessEnv, timeoutMs?: number): SyncDeps['run'];
|
|
117
|
+
/** Where a plugin's manifest lives, for callers that already have its root. */
|
|
118
|
+
export declare const pluginManifestPath: (root: string) => string;
|
|
119
|
+
/** True when `root` looks like an unpacked Claude Code plugin. */
|
|
120
|
+
export declare const isPluginRoot: (root: string) => boolean;
|
|
121
|
+
//# sourceMappingURL=plugin-version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-version.d.ts","sourceRoot":"","sources":["../../src/lib/plugin-version.ts"],"names":[],"mappings":"AA2CA,yEAAyE;AACzE,eAAO,MAAM,WAAW,YAAY,CAAC;AAmBrC,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,MAAkB,GACvB,eAAe,GAAG,SAAS,CAgC7B;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,aAAa,GAAG,eAAe,CAAC;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAyBvE;AAMD,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiBxE;AAED,MAAM,WAAW,IAAI;IACnB,uDAAuD;IACvD,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,cAAc,EAAE,SAAS,GAAG,KAAK,CAAC;IAClC,SAAS,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAC1C,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,OAAO,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,CA2BhE;AAED,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,aAAa,CAAC,GAAG,MAAM,EAAE,CAMrF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAcpD;AAMD,MAAM,MAAM,WAAW;AACrB,uEAAuE;AACrE,SAAS;AACX,oDAAoD;GAClD,SAAS;AACX,gFAAgF;GAC9E,QAAQ;AACV,mEAAmE;GACjE,SAAS,CAAC;AAEd,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,iEAAiE;IACjE,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACpF,KAAK,EAAE,YAAY,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAiCrD;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAkBvF;AAED,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MACL,CAAC;AAE9C,kEAAkE;AAClE,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,OAA+C,CAAC"}
|