@gmickel/gno 1.12.4 → 1.14.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 +126 -59
- package/assets/skill/SKILL.md +8 -1
- package/assets/skill/cli-reference.md +18 -7
- package/assets/skill/mcp-reference.md +22 -3
- package/package.json +3 -1
- package/src/app/constants.ts +43 -10
- package/src/app/index-name.ts +127 -0
- package/src/cli/commands/doctor-activation.ts +151 -0
- package/src/cli/commands/doctor.ts +41 -16
- package/src/cli/commands/get.ts +18 -0
- package/src/cli/commands/mcp/atomic-config-write.ts +118 -0
- package/src/cli/commands/mcp/config-discovery.ts +42 -0
- package/src/cli/commands/mcp/config-editors.ts +432 -0
- package/src/cli/commands/mcp/config.ts +63 -160
- package/src/cli/commands/mcp/install.ts +75 -37
- package/src/cli/commands/mcp/paths.ts +141 -136
- package/src/cli/commands/mcp/server-entry.ts +66 -0
- package/src/cli/commands/mcp/status.ts +189 -57
- package/src/cli/commands/mcp/target-display.ts +30 -0
- package/src/cli/commands/mcp/uninstall.ts +29 -31
- package/src/cli/commands/mcp/yaml-config-editor.ts +257 -0
- package/src/cli/commands/mcp/yaml-layout-scanner.ts +447 -0
- package/src/cli/commands/multi-get.ts +31 -6
- package/src/cli/commands/status.ts +107 -11
- package/src/cli/program.ts +66 -20
- package/src/core/activation-connector-health.ts +19 -0
- package/src/core/activation-probe-plan.ts +321 -0
- package/src/core/activation-probe.ts +138 -0
- package/src/core/activation-receipt-store.ts +39 -0
- package/src/core/activation-status.ts +513 -0
- package/src/core/activation-verifier.ts +416 -0
- package/src/core/connector-environment.ts +68 -0
- package/src/core/connector-policy.ts +233 -0
- package/src/core/connector-verification-target.ts +150 -0
- package/src/core/connector-verifier.ts +497 -0
- package/src/core/indexed-reference.ts +33 -8
- package/src/core/runtime-entrypoint.ts +24 -0
- package/src/mcp/activation-verification-mode.ts +4 -0
- package/src/mcp/server.ts +9 -2
- package/src/sdk/client.ts +7 -0
- package/src/sdk/types.ts +1 -0
- package/src/serve/activation-health.ts +91 -0
- package/src/serve/background-runtime.ts +11 -1
- package/src/serve/connectors.ts +164 -19
- package/src/serve/public/components/BootstrapStatus.tsx +94 -1
- package/src/serve/public/components/FirstRunWizard.tsx +13 -51
- package/src/serve/public/components/HealthCenter.tsx +8 -2
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Connectors.tsx +216 -55
- package/src/serve/public/pages/Dashboard.tsx +1 -0
- package/src/serve/routes/api.ts +152 -8
- package/src/serve/server.ts +44 -9
- package/src/serve/status-model.ts +4 -0
- package/src/serve/status.ts +79 -35
- package/src/store/activation-receipts.ts +390 -0
- package/src/store/index.ts +8 -0
- package/src/store/migrations/012-activation-receipts.ts +38 -0
- package/src/store/migrations/013-fts-sync-marker.ts +39 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +313 -53
- package/src/store/types.ts +118 -0
|
@@ -11,6 +11,12 @@ import type { DocumentRow, StorePort, StoreResult } from "../../store/types";
|
|
|
11
11
|
import type { ParsedRef } from "./ref-parser";
|
|
12
12
|
|
|
13
13
|
import { decorateUriForIndex, parseUri } from "../../app/constants";
|
|
14
|
+
import {
|
|
15
|
+
canonicalizeIndexName,
|
|
16
|
+
INDEX_NAME_REQUIREMENTS,
|
|
17
|
+
indexNamesMatch,
|
|
18
|
+
isValidIndexName,
|
|
19
|
+
} from "../../app/index-name";
|
|
14
20
|
import { isGlobPattern, parseRef, splitRefs } from "./ref-parser";
|
|
15
21
|
import { initStore } from "./shared";
|
|
16
22
|
|
|
@@ -319,7 +325,13 @@ function resolveMultiGetIndex(
|
|
|
319
325
|
refs: string[],
|
|
320
326
|
globalIndexName?: string
|
|
321
327
|
): { ok: true; indexName?: string } | { ok: false; error: string } {
|
|
322
|
-
|
|
328
|
+
if (globalIndexName !== undefined && !isValidIndexName(globalIndexName)) {
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
error: `Invalid index name: ${INDEX_NAME_REQUIREMENTS}.`,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const explicitIndexes = new Map<string, string>();
|
|
323
335
|
let hasUnindexedRef = false;
|
|
324
336
|
|
|
325
337
|
for (const ref of refs) {
|
|
@@ -329,8 +341,17 @@ function resolveMultiGetIndex(
|
|
|
329
341
|
continue;
|
|
330
342
|
}
|
|
331
343
|
const indexName = parseUri(parsed.value)?.indexName;
|
|
332
|
-
if (indexName) {
|
|
333
|
-
|
|
344
|
+
if (indexName !== undefined) {
|
|
345
|
+
if (!isValidIndexName(indexName)) {
|
|
346
|
+
return {
|
|
347
|
+
ok: false,
|
|
348
|
+
error: `Invalid index name: ${INDEX_NAME_REQUIREMENTS}.`,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const identity = canonicalizeIndexName(indexName);
|
|
352
|
+
if (!explicitIndexes.has(identity)) {
|
|
353
|
+
explicitIndexes.set(identity, indexName);
|
|
354
|
+
}
|
|
334
355
|
} else {
|
|
335
356
|
hasUnindexedRef = true;
|
|
336
357
|
}
|
|
@@ -342,16 +363,20 @@ function resolveMultiGetIndex(
|
|
|
342
363
|
if (explicitIndexes.size > 1) {
|
|
343
364
|
return {
|
|
344
365
|
ok: false,
|
|
345
|
-
error: `multi-get cannot mix explicit indexes: ${[
|
|
366
|
+
error: `multi-get cannot mix explicit indexes: ${[
|
|
367
|
+
...explicitIndexes.values(),
|
|
368
|
+
]
|
|
369
|
+
.sort()
|
|
370
|
+
.join(", ")}`,
|
|
346
371
|
};
|
|
347
372
|
}
|
|
348
373
|
|
|
349
|
-
const explicitIndex = [...explicitIndexes][0];
|
|
374
|
+
const explicitIndex = [...explicitIndexes.values()][0];
|
|
350
375
|
if (
|
|
351
376
|
hasUnindexedRef &&
|
|
352
377
|
globalIndexName &&
|
|
353
378
|
explicitIndex &&
|
|
354
|
-
globalIndexName
|
|
379
|
+
!indexNamesMatch(globalIndexName, explicitIndex)
|
|
355
380
|
) {
|
|
356
381
|
return {
|
|
357
382
|
ok: false,
|
|
@@ -5,11 +5,16 @@
|
|
|
5
5
|
* @module src/cli/commands/status
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { ActivationStatus } from "../../core/activation-status";
|
|
8
9
|
import type { IndexStatus } from "../../store/types";
|
|
9
10
|
|
|
10
|
-
import { getIndexDbPath } from "../../app/constants";
|
|
11
|
+
import { getIndexDbPath, getModelsCachePath } from "../../app/constants";
|
|
11
12
|
import { getConfigPaths, isInitialized, loadConfig } from "../../config";
|
|
12
|
-
import {
|
|
13
|
+
import { isConnectorActivationComplete } from "../../core/activation-connector-health";
|
|
14
|
+
import { buildActivationStatus } from "../../core/activation-status";
|
|
15
|
+
import { ModelCache } from "../../llm/cache";
|
|
16
|
+
import { getActivePreset, resolveModelUri } from "../../llm/registry";
|
|
17
|
+
import { getConnectorVerificationTargets } from "../../serve/connectors";
|
|
13
18
|
import { SqliteAdapter } from "../../store/sqlite/adapter";
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -30,13 +35,35 @@ export interface StatusOptions {
|
|
|
30
35
|
* Result of status command.
|
|
31
36
|
*/
|
|
32
37
|
export type StatusResult =
|
|
33
|
-
| { success: true; status: IndexStatus }
|
|
38
|
+
| { success: true; status: IndexStatus; activation: ActivationStatus }
|
|
34
39
|
| { success: false; error: string };
|
|
35
40
|
|
|
41
|
+
function connectorProjectionLine(activation: ActivationStatus): string | null {
|
|
42
|
+
const { projected, total, truncated } = activation.connectorProjection;
|
|
43
|
+
if (!truncated) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return `Connector projection: ${projected}/${total} target/collection checks shown; ${total - projected} omitted`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isStatusHealthy(
|
|
50
|
+
indexStatus: IndexStatus,
|
|
51
|
+
activation: ActivationStatus
|
|
52
|
+
): boolean {
|
|
53
|
+
return (
|
|
54
|
+
indexStatus.healthy &&
|
|
55
|
+
activation.healthy &&
|
|
56
|
+
isConnectorActivationComplete(activation)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
36
60
|
/**
|
|
37
61
|
* Format status as terminal output.
|
|
38
62
|
*/
|
|
39
|
-
function formatTerminal(
|
|
63
|
+
function formatTerminal(
|
|
64
|
+
indexStatus: IndexStatus,
|
|
65
|
+
activation: ActivationStatus
|
|
66
|
+
): string {
|
|
40
67
|
const lines: string[] = [];
|
|
41
68
|
|
|
42
69
|
lines.push(`Index: ${indexStatus.indexName}`);
|
|
@@ -73,7 +100,34 @@ function formatTerminal(indexStatus: IndexStatus): string {
|
|
|
73
100
|
lines.push(`Last updated: ${indexStatus.lastUpdatedAt}`);
|
|
74
101
|
}
|
|
75
102
|
|
|
76
|
-
lines.push(
|
|
103
|
+
lines.push(
|
|
104
|
+
`Health: ${isStatusHealthy(indexStatus, activation) ? "OK" : "DEGRADED"}`
|
|
105
|
+
);
|
|
106
|
+
lines.push("");
|
|
107
|
+
lines.push(
|
|
108
|
+
`Lexical activation: ${activation.healthy ? "READY" : activation.usable ? "DEGRADED" : "BLOCKED"}`
|
|
109
|
+
);
|
|
110
|
+
for (const collection of activation.collections) {
|
|
111
|
+
const failedStage = collection.remediation?.stage;
|
|
112
|
+
const suffix = failedStage
|
|
113
|
+
? ` (${failedStage}: ${collection.remediation?.code}; ${collection.remediation?.command})`
|
|
114
|
+
: ` (semantic: ${collection.semanticAvailability.code})`;
|
|
115
|
+
lines.push(
|
|
116
|
+
` ${collection.collection}: ${collection.ready ? "lexical ready" : "not ready"}${suffix}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (activation.connectors.length > 0) {
|
|
120
|
+
lines.push("Connector proofs:");
|
|
121
|
+
for (const connector of activation.connectors) {
|
|
122
|
+
lines.push(
|
|
123
|
+
` ${connector.target}/${connector.collection}: ${connector.status}${connector.code ? ` (${connector.code})` : ""}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const projectionLine = connectorProjectionLine(activation);
|
|
128
|
+
if (projectionLine) {
|
|
129
|
+
lines.push(projectionLine);
|
|
130
|
+
}
|
|
77
131
|
|
|
78
132
|
return lines.join("\n");
|
|
79
133
|
}
|
|
@@ -81,14 +135,19 @@ function formatTerminal(indexStatus: IndexStatus): string {
|
|
|
81
135
|
/**
|
|
82
136
|
* Format status as Markdown.
|
|
83
137
|
*/
|
|
84
|
-
function formatMarkdown(
|
|
138
|
+
function formatMarkdown(
|
|
139
|
+
indexStatus: IndexStatus,
|
|
140
|
+
activation: ActivationStatus
|
|
141
|
+
): string {
|
|
85
142
|
const lines: string[] = [];
|
|
86
143
|
|
|
87
144
|
lines.push(`# Index Status: ${indexStatus.indexName}`);
|
|
88
145
|
lines.push("");
|
|
89
146
|
lines.push(`- **Config**: ${indexStatus.configPath}`);
|
|
90
147
|
lines.push(`- **Database**: ${indexStatus.dbPath}`);
|
|
91
|
-
lines.push(
|
|
148
|
+
lines.push(
|
|
149
|
+
`- **Health**: ${isStatusHealthy(indexStatus, activation) ? "✓ OK" : "⚠ DEGRADED"}`
|
|
150
|
+
);
|
|
92
151
|
lines.push("");
|
|
93
152
|
|
|
94
153
|
if (indexStatus.collections.length > 0) {
|
|
@@ -115,6 +174,26 @@ function formatMarkdown(indexStatus: IndexStatus): string {
|
|
|
115
174
|
lines.push(`- **Last updated**: ${indexStatus.lastUpdatedAt}`);
|
|
116
175
|
}
|
|
117
176
|
|
|
177
|
+
lines.push("");
|
|
178
|
+
lines.push("## Lexical activation");
|
|
179
|
+
lines.push("");
|
|
180
|
+
lines.push(`- **Usable**: ${activation.usable}`);
|
|
181
|
+
lines.push(`- **Lexically healthy**: ${activation.healthy}`);
|
|
182
|
+
for (const collection of activation.collections) {
|
|
183
|
+
lines.push(
|
|
184
|
+
`- **${collection.collection}**: ${collection.ready ? "lexical ready" : `${collection.remediation?.stage ?? "index"} ${collection.remediation?.code ?? "index_query_failed"}`}`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
for (const connector of activation.connectors) {
|
|
188
|
+
lines.push(
|
|
189
|
+
`- **${connector.target}/${connector.collection}**: ${connector.status}${connector.code ? ` (${connector.code})` : ""}`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const projectionLine = connectorProjectionLine(activation);
|
|
193
|
+
if (projectionLine) {
|
|
194
|
+
lines.push(`- **${projectionLine}**`);
|
|
195
|
+
}
|
|
196
|
+
|
|
118
197
|
return lines.join("\n");
|
|
119
198
|
}
|
|
120
199
|
|
|
@@ -158,7 +237,23 @@ export async function status(
|
|
|
158
237
|
return { success: false, error: statusResult.error.message };
|
|
159
238
|
}
|
|
160
239
|
|
|
161
|
-
|
|
240
|
+
const preset = getActivePreset(config);
|
|
241
|
+
const embedModelCached = await new ModelCache(
|
|
242
|
+
getModelsCachePath()
|
|
243
|
+
).isCached(preset.embed);
|
|
244
|
+
const activation = await buildActivationStatus(
|
|
245
|
+
store,
|
|
246
|
+
config.collections.map(({ name }) => name),
|
|
247
|
+
{
|
|
248
|
+
semantic: {
|
|
249
|
+
modelsCached: embedModelCached,
|
|
250
|
+
embeddingBacklog: statusResult.value.embeddingBacklog,
|
|
251
|
+
},
|
|
252
|
+
connectorTargets: await getConnectorVerificationTargets(),
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return { success: true, status: statusResult.value, activation };
|
|
162
257
|
} finally {
|
|
163
258
|
await store.close();
|
|
164
259
|
}
|
|
@@ -196,7 +291,8 @@ export function formatStatus(
|
|
|
196
291
|
totalChunks: s.totalChunks,
|
|
197
292
|
embeddingBacklog: s.embeddingBacklog,
|
|
198
293
|
lastUpdated: s.lastUpdatedAt,
|
|
199
|
-
healthy: s.
|
|
294
|
+
healthy: isStatusHealthy(s, result.activation),
|
|
295
|
+
activation: result.activation,
|
|
200
296
|
},
|
|
201
297
|
null,
|
|
202
298
|
2
|
|
@@ -204,8 +300,8 @@ export function formatStatus(
|
|
|
204
300
|
}
|
|
205
301
|
|
|
206
302
|
if (options.md) {
|
|
207
|
-
return formatMarkdown(result.status);
|
|
303
|
+
return formatMarkdown(result.status, result.activation);
|
|
208
304
|
}
|
|
209
305
|
|
|
210
|
-
return formatTerminal(result.status);
|
|
306
|
+
return formatTerminal(result.status, result.activation);
|
|
211
307
|
}
|
package/src/cli/program.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
PRODUCT_NAME,
|
|
19
19
|
VERSION,
|
|
20
20
|
} from "../app/constants";
|
|
21
|
+
import { INDEX_NAME_REQUIREMENTS, isValidIndexName } from "../app/index-name";
|
|
21
22
|
import { resolveDepthPolicy } from "../core/depth-policy";
|
|
22
23
|
import { parseAndValidateTagFilter } from "../core/tags";
|
|
23
24
|
import { setColorsEnabled } from "./colors";
|
|
@@ -240,6 +241,12 @@ export function createProgram(): Command {
|
|
|
240
241
|
program.hook("preAction", (thisCommand) => {
|
|
241
242
|
const rootOpts = thisCommand.optsWithGlobals();
|
|
242
243
|
const globals = parseGlobalOptions(rootOpts);
|
|
244
|
+
if (!isValidIndexName(globals.index)) {
|
|
245
|
+
throw new CliError(
|
|
246
|
+
"VALIDATION",
|
|
247
|
+
`Invalid index name: ${INDEX_NAME_REQUIREMENTS}.`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
243
250
|
applyGlobalOptions(globals);
|
|
244
251
|
globalState.current = globals;
|
|
245
252
|
});
|
|
@@ -1045,13 +1052,23 @@ function wireOnboardingCommands(program: Command): void {
|
|
|
1045
1052
|
.option("--json", "JSON output")
|
|
1046
1053
|
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
1047
1054
|
const format = getFormat(cmdOpts);
|
|
1048
|
-
const { doctor, formatDoctor } =
|
|
1049
|
-
|
|
1055
|
+
const { doctor, formatDoctor, hasCriticalDoctorErrors } =
|
|
1056
|
+
await import("./commands/doctor");
|
|
1057
|
+
const globals = getGlobals();
|
|
1058
|
+
const result = await doctor({
|
|
1059
|
+
configPath: globals.config,
|
|
1060
|
+
indexName: globals.index,
|
|
1061
|
+
json: format === "json",
|
|
1062
|
+
});
|
|
1050
1063
|
|
|
1051
|
-
// Doctor always succeeds but may report issues
|
|
1052
1064
|
process.stdout.write(
|
|
1053
1065
|
`${formatDoctor(result, { json: format === "json" })}\n`
|
|
1054
1066
|
);
|
|
1067
|
+
if (hasCriticalDoctorErrors(result.checks)) {
|
|
1068
|
+
throw new CliError("RUNTIME", "Critical health checks failed", {
|
|
1069
|
+
silent: true,
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1055
1072
|
});
|
|
1056
1073
|
}
|
|
1057
1074
|
|
|
@@ -1308,8 +1325,7 @@ function wireMcpCommand(program: Command): void {
|
|
|
1308
1325
|
)
|
|
1309
1326
|
.option(
|
|
1310
1327
|
"-s, --scope <scope>",
|
|
1311
|
-
"scope (user, project) - project
|
|
1312
|
-
"user"
|
|
1328
|
+
"scope (user, project) - defaults to project for LibreChat and user otherwise"
|
|
1313
1329
|
)
|
|
1314
1330
|
.option("-f, --force", "overwrite existing configuration")
|
|
1315
1331
|
.option("--dry-run", "show what would be done without making changes")
|
|
@@ -1320,10 +1336,11 @@ function wireMcpCommand(program: Command): void {
|
|
|
1320
1336
|
.option("--json", "JSON output")
|
|
1321
1337
|
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
1322
1338
|
const target = cmdOpts.target as string;
|
|
1323
|
-
const
|
|
1339
|
+
const requestedScope = cmdOpts.scope;
|
|
1324
1340
|
|
|
1325
1341
|
// Import MCP_TARGETS for validation
|
|
1326
|
-
const { MCP_TARGETS } =
|
|
1342
|
+
const { getDefaultTargetScope, MCP_TARGETS } =
|
|
1343
|
+
await import("./commands/mcp/paths.js");
|
|
1327
1344
|
|
|
1328
1345
|
// Validate target
|
|
1329
1346
|
if (!(MCP_TARGETS as string[]).includes(target)) {
|
|
@@ -1332,15 +1349,26 @@ function wireMcpCommand(program: Command): void {
|
|
|
1332
1349
|
`Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(", ")}.`
|
|
1333
1350
|
);
|
|
1334
1351
|
}
|
|
1335
|
-
// Validate scope
|
|
1336
|
-
if (
|
|
1352
|
+
// Validate an explicit scope, then let the target choose its default.
|
|
1353
|
+
if (
|
|
1354
|
+
requestedScope !== undefined &&
|
|
1355
|
+
(typeof requestedScope !== "string" ||
|
|
1356
|
+
!["user", "project"].includes(requestedScope))
|
|
1357
|
+
) {
|
|
1337
1358
|
throw new CliError(
|
|
1338
1359
|
"VALIDATION",
|
|
1339
|
-
`Invalid scope: ${
|
|
1360
|
+
`Invalid scope: ${JSON.stringify(requestedScope)}. Must be 'user' or 'project'.`
|
|
1340
1361
|
);
|
|
1341
1362
|
}
|
|
1363
|
+
const scope =
|
|
1364
|
+
typeof requestedScope === "string"
|
|
1365
|
+
? requestedScope
|
|
1366
|
+
: getDefaultTargetScope(
|
|
1367
|
+
target as Parameters<typeof getDefaultTargetScope>[0]
|
|
1368
|
+
);
|
|
1342
1369
|
|
|
1343
1370
|
const { installMcp } = await import("./commands/mcp/install.js");
|
|
1371
|
+
const globals = getGlobals();
|
|
1344
1372
|
await installMcp({
|
|
1345
1373
|
target: target as NonNullable<
|
|
1346
1374
|
Parameters<typeof installMcp>[0]
|
|
@@ -1349,6 +1377,8 @@ function wireMcpCommand(program: Command): void {
|
|
|
1349
1377
|
force: Boolean(cmdOpts.force),
|
|
1350
1378
|
dryRun: Boolean(cmdOpts.dryRun),
|
|
1351
1379
|
enableWrite: Boolean(cmdOpts.enableWrite),
|
|
1380
|
+
indexName: globals.index,
|
|
1381
|
+
configPath: globals.config,
|
|
1352
1382
|
// Pass undefined if not set, so global --json can take effect
|
|
1353
1383
|
json: cmdOpts.json === true ? true : undefined,
|
|
1354
1384
|
});
|
|
@@ -1363,14 +1393,18 @@ function wireMcpCommand(program: Command): void {
|
|
|
1363
1393
|
"target client (claude-desktop, cursor, zed, windsurf, opencode, amp, lmstudio, librechat, claude-code, codex)",
|
|
1364
1394
|
"claude-desktop"
|
|
1365
1395
|
)
|
|
1366
|
-
.option(
|
|
1396
|
+
.option(
|
|
1397
|
+
"-s, --scope <scope>",
|
|
1398
|
+
"scope (user, project) - defaults to project for LibreChat and user otherwise"
|
|
1399
|
+
)
|
|
1367
1400
|
.option("--json", "JSON output")
|
|
1368
1401
|
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
1369
1402
|
const target = cmdOpts.target as string;
|
|
1370
|
-
const
|
|
1403
|
+
const requestedScope = cmdOpts.scope;
|
|
1371
1404
|
|
|
1372
1405
|
// Import MCP_TARGETS for validation
|
|
1373
|
-
const { MCP_TARGETS } =
|
|
1406
|
+
const { getDefaultTargetScope, MCP_TARGETS } =
|
|
1407
|
+
await import("./commands/mcp/paths.js");
|
|
1374
1408
|
|
|
1375
1409
|
// Validate target
|
|
1376
1410
|
if (!(MCP_TARGETS as string[]).includes(target)) {
|
|
@@ -1379,13 +1413,23 @@ function wireMcpCommand(program: Command): void {
|
|
|
1379
1413
|
`Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(", ")}.`
|
|
1380
1414
|
);
|
|
1381
1415
|
}
|
|
1382
|
-
// Validate scope
|
|
1383
|
-
if (
|
|
1416
|
+
// Validate an explicit scope, then let the target choose its default.
|
|
1417
|
+
if (
|
|
1418
|
+
requestedScope !== undefined &&
|
|
1419
|
+
(typeof requestedScope !== "string" ||
|
|
1420
|
+
!["user", "project"].includes(requestedScope))
|
|
1421
|
+
) {
|
|
1384
1422
|
throw new CliError(
|
|
1385
1423
|
"VALIDATION",
|
|
1386
|
-
`Invalid scope: ${
|
|
1424
|
+
`Invalid scope: ${JSON.stringify(requestedScope)}. Must be 'user' or 'project'.`
|
|
1387
1425
|
);
|
|
1388
1426
|
}
|
|
1427
|
+
const scope =
|
|
1428
|
+
typeof requestedScope === "string"
|
|
1429
|
+
? requestedScope
|
|
1430
|
+
: getDefaultTargetScope(
|
|
1431
|
+
target as Parameters<typeof getDefaultTargetScope>[0]
|
|
1432
|
+
);
|
|
1389
1433
|
|
|
1390
1434
|
const { uninstallMcp } = await import("./commands/mcp/uninstall.js");
|
|
1391
1435
|
await uninstallMcp({
|
|
@@ -1418,7 +1462,7 @@ function wireMcpCommand(program: Command): void {
|
|
|
1418
1462
|
const scope = cmdOpts.scope as string;
|
|
1419
1463
|
|
|
1420
1464
|
// Import MCP_TARGETS for validation
|
|
1421
|
-
const {
|
|
1465
|
+
const { getTargetDisplayName, getTargetScopes, MCP_TARGETS } =
|
|
1422
1466
|
await import("./commands/mcp/paths.js");
|
|
1423
1467
|
|
|
1424
1468
|
// Validate target
|
|
@@ -1438,12 +1482,14 @@ function wireMcpCommand(program: Command): void {
|
|
|
1438
1482
|
// Validate target/scope combination
|
|
1439
1483
|
if (
|
|
1440
1484
|
target !== "all" &&
|
|
1441
|
-
scope
|
|
1442
|
-
!(
|
|
1485
|
+
scope !== "all" &&
|
|
1486
|
+
!getTargetScopes(
|
|
1487
|
+
target as Parameters<typeof getTargetScopes>[0]
|
|
1488
|
+
).includes(scope as "user" | "project")
|
|
1443
1489
|
) {
|
|
1444
1490
|
throw new CliError(
|
|
1445
1491
|
"VALIDATION",
|
|
1446
|
-
`${target} does not support
|
|
1492
|
+
`${getTargetDisplayName(target as Parameters<typeof getTargetDisplayName>[0])} does not support ${scope} scope.`
|
|
1447
1493
|
);
|
|
1448
1494
|
}
|
|
1449
1495
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ActivationStatus } from "./activation-status";
|
|
2
|
+
|
|
3
|
+
const NON_RUNTIME_CODES = new Set([
|
|
4
|
+
"connector_not_configured",
|
|
5
|
+
"target_runtime_unverifiable",
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
/** True when every observable proof passed and no target/collection pair was omitted. */
|
|
9
|
+
export function isConnectorActivationComplete(
|
|
10
|
+
activation: ActivationStatus
|
|
11
|
+
): boolean {
|
|
12
|
+
if (activation.connectorProjection.truncated) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return activation.connectors.every(
|
|
16
|
+
({ code, status }) =>
|
|
17
|
+
(code !== undefined && NON_RUNTIME_CODES.has(code)) || status === "passed"
|
|
18
|
+
);
|
|
19
|
+
}
|