@zleap-ai/sag-cli 0.2.1 → 0.4.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.en.md +28 -7
- package/README.md +28 -8
- package/dist/cli.js +436 -192
- package/dist/cli.js.map +1 -1
- package/package.json +3 -2
- package/skill/SKILL.md +69 -0
- package/skill/references/citation-rules.md +85 -0
- package/skill/references/cli-reference.md +223 -0
- package/skill/references/search-strategies.md +73 -0
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { confirm,
|
|
4
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
5
5
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@zleap-ai/sag-cli",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.4.0",
|
|
10
10
|
description: "Command-line client and diagnostics for SAG knowledge bases",
|
|
11
11
|
type: "module",
|
|
12
12
|
bin: {
|
|
@@ -16,7 +16,8 @@ var package_default = {
|
|
|
16
16
|
"dist",
|
|
17
17
|
"README.md",
|
|
18
18
|
"README.en.md",
|
|
19
|
-
"LICENSE"
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"skill"
|
|
20
21
|
],
|
|
21
22
|
scripts: {
|
|
22
23
|
build: "tsup",
|
|
@@ -176,7 +177,7 @@ var claudeEntrySchema = z.object({
|
|
|
176
177
|
function invalidSpec() {
|
|
177
178
|
return new CliError(
|
|
178
179
|
"INVALID_ARGUMENT",
|
|
179
|
-
"Claude Code
|
|
180
|
+
"The Claude Code adapter supports Docker stdio MCP connections only",
|
|
180
181
|
{
|
|
181
182
|
exitCode: exitCodes.invalidArgument
|
|
182
183
|
}
|
|
@@ -205,6 +206,12 @@ var ClaudeCodeAdapter = class {
|
|
|
205
206
|
}
|
|
206
207
|
runner;
|
|
207
208
|
id = "claude-code";
|
|
209
|
+
capabilities = {
|
|
210
|
+
transports: ["stdio"],
|
|
211
|
+
scopes: ["user"],
|
|
212
|
+
structuredRead: true,
|
|
213
|
+
projectApproval: false
|
|
214
|
+
};
|
|
208
215
|
readConfig;
|
|
209
216
|
async detect() {
|
|
210
217
|
try {
|
|
@@ -293,7 +300,7 @@ var CODEX_STATUS_FIELDS = /* @__PURE__ */ new Set(["name", "disabled_reason", "a
|
|
|
293
300
|
function invalidSpec2() {
|
|
294
301
|
return new CliError(
|
|
295
302
|
"INVALID_ARGUMENT",
|
|
296
|
-
"Codex
|
|
303
|
+
"The Codex adapter supports Docker stdio MCP connections only",
|
|
297
304
|
{
|
|
298
305
|
exitCode: exitCodes.invalidArgument
|
|
299
306
|
}
|
|
@@ -317,6 +324,12 @@ var CodexAdapter = class {
|
|
|
317
324
|
}
|
|
318
325
|
runner;
|
|
319
326
|
id = "codex";
|
|
327
|
+
capabilities = {
|
|
328
|
+
transports: ["stdio"],
|
|
329
|
+
scopes: ["user"],
|
|
330
|
+
structuredRead: true,
|
|
331
|
+
projectApproval: false
|
|
332
|
+
};
|
|
320
333
|
async detect() {
|
|
321
334
|
try {
|
|
322
335
|
const result = await this.runner.run({
|
|
@@ -533,9 +546,9 @@ var ManagedConnectionStore = class {
|
|
|
533
546
|
await this.#saveUnlocked(state);
|
|
534
547
|
});
|
|
535
548
|
}
|
|
536
|
-
async list(
|
|
549
|
+
async list(input2) {
|
|
537
550
|
return Object.values((await this.load()).connections).filter(
|
|
538
|
-
(connection) => (!
|
|
551
|
+
(connection) => (!input2?.agent || connection.agent === input2.agent) && (!input2?.scope || connection.scope === input2.scope)
|
|
539
552
|
);
|
|
540
553
|
}
|
|
541
554
|
};
|
|
@@ -557,12 +570,12 @@ function validateProfileName(name) {
|
|
|
557
570
|
}
|
|
558
571
|
return name;
|
|
559
572
|
}
|
|
560
|
-
function normalizeOrigin(
|
|
573
|
+
function normalizeOrigin(input2) {
|
|
561
574
|
let url;
|
|
562
575
|
try {
|
|
563
|
-
url = new URL(
|
|
576
|
+
url = new URL(input2);
|
|
564
577
|
} catch (cause) {
|
|
565
|
-
throw new CliError("INVALID_ARGUMENT", `Invalid SAG URL: ${
|
|
578
|
+
throw new CliError("INVALID_ARGUMENT", `Invalid SAG URL: ${input2}`, {
|
|
566
579
|
exitCode: exitCodes.invalidArgument,
|
|
567
580
|
cause,
|
|
568
581
|
hint: "Use an absolute HTTP or HTTPS URL such as http://localhost:8000."
|
|
@@ -1089,16 +1102,6 @@ var McpVerifier = class {
|
|
|
1089
1102
|
}
|
|
1090
1103
|
cliVersion;
|
|
1091
1104
|
async verify(spec, options) {
|
|
1092
|
-
if (spec.transport !== "stdio") {
|
|
1093
|
-
throw new CliError(
|
|
1094
|
-
"INVALID_ARGUMENT",
|
|
1095
|
-
"HTTP MCP is not supported in SAG CLI v0.2",
|
|
1096
|
-
{
|
|
1097
|
-
exitCode: exitCodes.invalidArgument,
|
|
1098
|
-
hint: "Use a local Docker stdio connection."
|
|
1099
|
-
}
|
|
1100
|
-
);
|
|
1101
|
-
}
|
|
1102
1105
|
const client = new Client(
|
|
1103
1106
|
{
|
|
1104
1107
|
name: "@zleap-ai/sag-cli",
|
|
@@ -1190,12 +1193,12 @@ var McpVerifier = class {
|
|
|
1190
1193
|
// src/process/runner.ts
|
|
1191
1194
|
import { spawn } from "child_process";
|
|
1192
1195
|
var NodeProcessRunner = class {
|
|
1193
|
-
async run(
|
|
1196
|
+
async run(input2) {
|
|
1194
1197
|
return new Promise((resolve, reject) => {
|
|
1195
|
-
const child = spawn(
|
|
1198
|
+
const child = spawn(input2.command, input2.args, {
|
|
1196
1199
|
shell: false,
|
|
1197
|
-
env:
|
|
1198
|
-
stdio: [
|
|
1200
|
+
env: input2.env ?? process.env,
|
|
1201
|
+
stdio: [input2.stdin === "pipe" ? "pipe" : "ignore", "pipe", "pipe"]
|
|
1199
1202
|
});
|
|
1200
1203
|
const childStdout = child.stdout;
|
|
1201
1204
|
const childStderr = child.stderr;
|
|
@@ -1209,7 +1212,7 @@ var NodeProcessRunner = class {
|
|
|
1209
1212
|
forceKill = setTimeout(() => {
|
|
1210
1213
|
child.kill("SIGKILL");
|
|
1211
1214
|
}, 250);
|
|
1212
|
-
},
|
|
1215
|
+
}, input2.timeoutMs);
|
|
1213
1216
|
childStdout?.setEncoding("utf8");
|
|
1214
1217
|
childStderr?.setEncoding("utf8");
|
|
1215
1218
|
childStdout?.on("data", (chunk) => {
|
|
@@ -1222,10 +1225,10 @@ var NodeProcessRunner = class {
|
|
|
1222
1225
|
clearTimeout(timeout);
|
|
1223
1226
|
if (forceKill) clearTimeout(forceKill);
|
|
1224
1227
|
reject(
|
|
1225
|
-
new CliError("DEPENDENCY_MISSING", `Unable to start ${
|
|
1228
|
+
new CliError("DEPENDENCY_MISSING", `Unable to start ${input2.command}`, {
|
|
1226
1229
|
exitCode: exitCodes.dependencyMissing,
|
|
1227
1230
|
cause,
|
|
1228
|
-
hint: `Install ${
|
|
1231
|
+
hint: `Install ${input2.command} and ensure it is available on PATH.`
|
|
1229
1232
|
})
|
|
1230
1233
|
);
|
|
1231
1234
|
});
|
|
@@ -1234,9 +1237,9 @@ var NodeProcessRunner = class {
|
|
|
1234
1237
|
if (forceKill) clearTimeout(forceKill);
|
|
1235
1238
|
if (timedOut) {
|
|
1236
1239
|
reject(
|
|
1237
|
-
new CliError("DEPENDENCY_MISSING", `${
|
|
1240
|
+
new CliError("DEPENDENCY_MISSING", `${input2.command} timed out`, {
|
|
1238
1241
|
exitCode: exitCodes.dependencyMissing,
|
|
1239
|
-
hint: `Check that ${
|
|
1242
|
+
hint: `Check that ${input2.command} is responsive and try again.`
|
|
1240
1243
|
})
|
|
1241
1244
|
);
|
|
1242
1245
|
return;
|
|
@@ -1273,6 +1276,10 @@ var userSchema = z6.object({
|
|
|
1273
1276
|
name: z6.string(),
|
|
1274
1277
|
created_at: dateTime.optional()
|
|
1275
1278
|
}).passthrough();
|
|
1279
|
+
var loginResponseSchema = z6.object({
|
|
1280
|
+
access_token: z6.string().min(1),
|
|
1281
|
+
user: userSchema
|
|
1282
|
+
}).passthrough();
|
|
1276
1283
|
var sourceSchema = z6.object({
|
|
1277
1284
|
id: z6.string().min(1),
|
|
1278
1285
|
name: z6.string(),
|
|
@@ -1354,6 +1361,43 @@ var mcpDescriptorSchema = z6.object({
|
|
|
1354
1361
|
headers: z6.record(z6.string(), z6.string())
|
|
1355
1362
|
}).passthrough()
|
|
1356
1363
|
}).passthrough();
|
|
1364
|
+
var outlineItemSchema = z6.object({
|
|
1365
|
+
rank: z6.number().int(),
|
|
1366
|
+
heading: z6.string(),
|
|
1367
|
+
chunk_id: z6.string()
|
|
1368
|
+
}).passthrough();
|
|
1369
|
+
var outlineSchema = z6.object({
|
|
1370
|
+
document_id: z6.string(),
|
|
1371
|
+
filename: z6.string(),
|
|
1372
|
+
outline: z6.array(outlineItemSchema)
|
|
1373
|
+
}).passthrough();
|
|
1374
|
+
var grepMatchSchema = z6.object({
|
|
1375
|
+
chunk_id: z6.string(),
|
|
1376
|
+
heading: z6.string(),
|
|
1377
|
+
snippet: z6.string()
|
|
1378
|
+
}).passthrough();
|
|
1379
|
+
var grepResponseSchema = z6.object({
|
|
1380
|
+
pattern: z6.string(),
|
|
1381
|
+
matches: z6.array(grepMatchSchema),
|
|
1382
|
+
count: z6.number().int().nonnegative()
|
|
1383
|
+
}).passthrough();
|
|
1384
|
+
var readResponseSchema = z6.object({
|
|
1385
|
+
document_id: z6.string(),
|
|
1386
|
+
filename: z6.string(),
|
|
1387
|
+
total_lines: z6.number().int().nonnegative(),
|
|
1388
|
+
offset: z6.number().int().positive(),
|
|
1389
|
+
limit: z6.number().int().positive(),
|
|
1390
|
+
lines: z6.array(z6.string())
|
|
1391
|
+
}).passthrough();
|
|
1392
|
+
var entityContextSchema = z6.object({
|
|
1393
|
+
entity_id: z6.string(),
|
|
1394
|
+
name: z6.string(),
|
|
1395
|
+
type: z6.string(),
|
|
1396
|
+
description: z6.string(),
|
|
1397
|
+
context: z6.string(),
|
|
1398
|
+
source_id: z6.string(),
|
|
1399
|
+
source_name: z6.string()
|
|
1400
|
+
}).passthrough();
|
|
1357
1401
|
|
|
1358
1402
|
// src/api/client.ts
|
|
1359
1403
|
var SagClient = class {
|
|
@@ -1385,6 +1429,14 @@ var SagClient = class {
|
|
|
1385
1429
|
me() {
|
|
1386
1430
|
return this.#request("/api/v1/auth/me", userSchema);
|
|
1387
1431
|
}
|
|
1432
|
+
async login(name) {
|
|
1433
|
+
const response = await this.#request("/api/v1/auth/login", loginResponseSchema, {
|
|
1434
|
+
authenticated: false,
|
|
1435
|
+
method: "POST",
|
|
1436
|
+
body: { name }
|
|
1437
|
+
});
|
|
1438
|
+
return { accessToken: response.access_token, user: response.user };
|
|
1439
|
+
}
|
|
1388
1440
|
mcpDescriptor() {
|
|
1389
1441
|
return this.#request("/api/v1/system/mcp", mcpDescriptorSchema);
|
|
1390
1442
|
}
|
|
@@ -1409,12 +1461,48 @@ var SagClient = class {
|
|
|
1409
1461
|
documentSchema
|
|
1410
1462
|
);
|
|
1411
1463
|
}
|
|
1412
|
-
search(
|
|
1464
|
+
search(input2) {
|
|
1413
1465
|
return this.#request("/api/v1/search", searchResponseSchema, {
|
|
1414
1466
|
method: "POST",
|
|
1415
|
-
body:
|
|
1467
|
+
body: input2
|
|
1416
1468
|
});
|
|
1417
1469
|
}
|
|
1470
|
+
outline(sourceId, documentId) {
|
|
1471
|
+
const params = new URLSearchParams({ document_id: documentId });
|
|
1472
|
+
return this.#request(
|
|
1473
|
+
`/api/v1/sources/${encodeURIComponent(sourceId)}/outline?${params}`,
|
|
1474
|
+
outlineSchema
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
grep(sourceId, pattern, limit) {
|
|
1478
|
+
const params = new URLSearchParams({ pattern });
|
|
1479
|
+
if (limit !== void 0) {
|
|
1480
|
+
params.set("limit", String(limit));
|
|
1481
|
+
}
|
|
1482
|
+
return this.#request(
|
|
1483
|
+
`/api/v1/sources/${encodeURIComponent(sourceId)}/grep?${params}`,
|
|
1484
|
+
grepResponseSchema
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
readDocument(sourceId, documentId, offset, limit) {
|
|
1488
|
+
const params = new URLSearchParams();
|
|
1489
|
+
if (offset !== void 0) {
|
|
1490
|
+
params.set("offset", String(offset));
|
|
1491
|
+
}
|
|
1492
|
+
if (limit !== void 0) {
|
|
1493
|
+
params.set("limit", String(limit));
|
|
1494
|
+
}
|
|
1495
|
+
return this.#request(
|
|
1496
|
+
`/api/v1/sources/${encodeURIComponent(sourceId)}/documents/${encodeURIComponent(documentId)}/read?${params}`,
|
|
1497
|
+
readResponseSchema
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
getEntityContext(sourceId, name) {
|
|
1501
|
+
return this.#request(
|
|
1502
|
+
`/api/v1/sources/${encodeURIComponent(sourceId)}/entities/${encodeURIComponent(name)}/context`,
|
|
1503
|
+
entityContextSchema
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1418
1506
|
async #request(path5, schema, options = {}) {
|
|
1419
1507
|
const authenticated = options.authenticated ?? true;
|
|
1420
1508
|
if (authenticated && !this.#token) {
|
|
@@ -1529,81 +1617,98 @@ function hostFailure3(message, cause, hint) {
|
|
|
1529
1617
|
hint: hint ?? "Inspect the Agent MCP configuration and run the command again."
|
|
1530
1618
|
});
|
|
1531
1619
|
}
|
|
1532
|
-
function determinePlan(
|
|
1620
|
+
function determinePlan(input2) {
|
|
1533
1621
|
let action;
|
|
1534
|
-
if (!
|
|
1535
|
-
if (
|
|
1622
|
+
if (!input2.hostFingerprint || !input2.hostSpecFingerprint) {
|
|
1623
|
+
if (input2.managed) {
|
|
1536
1624
|
throw conflict("Managed state exists but the Agent MCP entry is missing");
|
|
1537
1625
|
}
|
|
1538
1626
|
action = "create";
|
|
1539
|
-
} else if (!
|
|
1627
|
+
} else if (!input2.managed) {
|
|
1540
1628
|
throw conflict("The Agent MCP name is already owned by the user");
|
|
1541
|
-
} else if (
|
|
1629
|
+
} else if (input2.managed.fingerprint !== input2.hostFingerprint) {
|
|
1542
1630
|
throw conflict("The managed Agent MCP entry was changed outside SAG CLI");
|
|
1543
|
-
} else if (
|
|
1631
|
+
} else if (input2.hostSpecFingerprint === input2.desiredFingerprint) {
|
|
1544
1632
|
action = "unchanged";
|
|
1545
1633
|
} else {
|
|
1546
1634
|
action = "update";
|
|
1547
1635
|
}
|
|
1548
1636
|
return {
|
|
1549
1637
|
action,
|
|
1550
|
-
agent:
|
|
1551
|
-
serverName:
|
|
1552
|
-
scope:
|
|
1638
|
+
agent: input2.agent,
|
|
1639
|
+
serverName: input2.serverName,
|
|
1640
|
+
scope: input2.scope,
|
|
1553
1641
|
provider: "docker-stdio",
|
|
1554
|
-
target:
|
|
1555
|
-
sourceId:
|
|
1556
|
-
...
|
|
1557
|
-
desiredFingerprint:
|
|
1642
|
+
target: input2.target.name,
|
|
1643
|
+
sourceId: input2.sourceId ?? null,
|
|
1644
|
+
...input2.hostSpecFingerprint ? { currentFingerprint: input2.hostSpecFingerprint } : {},
|
|
1645
|
+
desiredFingerprint: input2.desiredFingerprint
|
|
1558
1646
|
};
|
|
1559
1647
|
}
|
|
1560
|
-
function managedConnection(
|
|
1648
|
+
function managedConnection(input2, resolved, fingerprint, existing) {
|
|
1561
1649
|
return {
|
|
1562
|
-
agent:
|
|
1650
|
+
agent: input2.agent,
|
|
1563
1651
|
provider: "docker-stdio",
|
|
1564
|
-
profile:
|
|
1565
|
-
serverName:
|
|
1652
|
+
profile: input2.profile,
|
|
1653
|
+
serverName: input2.serverName,
|
|
1566
1654
|
docker: {
|
|
1567
1655
|
...resolved.target.composeProject ? { composeProject: resolved.target.composeProject } : {},
|
|
1568
1656
|
...resolved.target.composeService ? { composeService: resolved.target.composeService } : {},
|
|
1569
1657
|
containerName: resolved.target.name
|
|
1570
1658
|
},
|
|
1571
|
-
sourceId:
|
|
1572
|
-
scope:
|
|
1659
|
+
sourceId: input2.sourceId ?? null,
|
|
1660
|
+
scope: input2.scope,
|
|
1573
1661
|
fingerprint,
|
|
1574
|
-
createdAt: existing?.createdAt ??
|
|
1662
|
+
createdAt: existing?.createdAt ?? input2.now().toISOString()
|
|
1575
1663
|
};
|
|
1576
1664
|
}
|
|
1577
|
-
async function rollbackHost(
|
|
1578
|
-
if (
|
|
1665
|
+
async function rollbackHost(input2) {
|
|
1666
|
+
if (input2.previousSpec) {
|
|
1579
1667
|
try {
|
|
1580
|
-
await
|
|
1668
|
+
await input2.adapter.remove(input2.serverName, input2.scope);
|
|
1581
1669
|
} catch {
|
|
1582
1670
|
}
|
|
1583
|
-
await
|
|
1584
|
-
const restored = await
|
|
1585
|
-
if (!restored || restored.fingerprint !== (
|
|
1671
|
+
await input2.adapter.add(input2.serverName, input2.scope, input2.previousSpec);
|
|
1672
|
+
const restored = await input2.adapter.read(input2.serverName, input2.scope);
|
|
1673
|
+
if (!restored || restored.fingerprint !== (input2.previousFingerprint ?? fingerprintConnection(input2.previousSpec))) {
|
|
1586
1674
|
throw new Error("Previous Agent MCP entry was not restored");
|
|
1587
1675
|
}
|
|
1588
1676
|
return;
|
|
1589
1677
|
}
|
|
1590
|
-
await
|
|
1678
|
+
await input2.adapter.remove(input2.serverName, input2.scope);
|
|
1591
1679
|
}
|
|
1592
|
-
async function connectAgent(
|
|
1593
|
-
validateServerName(
|
|
1594
|
-
const resolved = await
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1680
|
+
async function connectAgent(input2) {
|
|
1681
|
+
validateServerName(input2.serverName);
|
|
1682
|
+
const resolved = await input2.resolveConnection();
|
|
1683
|
+
if (!input2.adapter.capabilities.transports.includes(resolved.spec.transport)) {
|
|
1684
|
+
throw new CliError(
|
|
1685
|
+
"INVALID_ARGUMENT",
|
|
1686
|
+
`${input2.agent} adapter does not support ${resolved.spec.transport} MCP connections`,
|
|
1687
|
+
{
|
|
1688
|
+
exitCode: exitCodes.invalidArgument,
|
|
1689
|
+
hint: "Choose a provider supported by the installed Agent adapter."
|
|
1690
|
+
}
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
if (!input2.adapter.capabilities.scopes.includes(input2.scope)) {
|
|
1694
|
+
throw new CliError(
|
|
1695
|
+
"INVALID_ARGUMENT",
|
|
1696
|
+
`${input2.agent} adapter does not support ${input2.scope} scope`,
|
|
1697
|
+
{ exitCode: exitCodes.invalidArgument }
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
await input2.verifier.verify(resolved.spec, { timeoutMs: input2.timeoutMs });
|
|
1701
|
+
await input2.adapter.detect();
|
|
1702
|
+
const host = await input2.adapter.read(input2.serverName, input2.scope);
|
|
1703
|
+
const key = connectionKey(input2.agent, input2.scope, input2.serverName);
|
|
1704
|
+
const managed = await input2.state.get(key);
|
|
1600
1705
|
const desiredFingerprint = fingerprintConnection(resolved.spec);
|
|
1601
1706
|
const plan = determinePlan({
|
|
1602
|
-
agent:
|
|
1603
|
-
serverName:
|
|
1604
|
-
scope:
|
|
1707
|
+
agent: input2.agent,
|
|
1708
|
+
serverName: input2.serverName,
|
|
1709
|
+
scope: input2.scope,
|
|
1605
1710
|
target: resolved.target,
|
|
1606
|
-
...
|
|
1711
|
+
...input2.sourceId ? { sourceId: input2.sourceId } : {},
|
|
1607
1712
|
desiredFingerprint,
|
|
1608
1713
|
...host ? { hostFingerprint: host.fingerprint } : {},
|
|
1609
1714
|
...host ? { hostSpecFingerprint: fingerprintConnection(host.spec) } : {},
|
|
@@ -1611,9 +1716,9 @@ async function connectAgent(input) {
|
|
|
1611
1716
|
});
|
|
1612
1717
|
if (plan.action === "unchanged") {
|
|
1613
1718
|
return {
|
|
1614
|
-
agent:
|
|
1615
|
-
serverName:
|
|
1616
|
-
scope:
|
|
1719
|
+
agent: input2.agent,
|
|
1720
|
+
serverName: input2.serverName,
|
|
1721
|
+
scope: input2.scope,
|
|
1617
1722
|
action: "unchanged",
|
|
1618
1723
|
changed: false,
|
|
1619
1724
|
verified: true,
|
|
@@ -1621,11 +1726,11 @@ async function connectAgent(input) {
|
|
|
1621
1726
|
plan
|
|
1622
1727
|
};
|
|
1623
1728
|
}
|
|
1624
|
-
if (
|
|
1729
|
+
if (input2.dryRun) {
|
|
1625
1730
|
return {
|
|
1626
|
-
agent:
|
|
1627
|
-
serverName:
|
|
1628
|
-
scope:
|
|
1731
|
+
agent: input2.agent,
|
|
1732
|
+
serverName: input2.serverName,
|
|
1733
|
+
scope: input2.scope,
|
|
1629
1734
|
action: plan.action,
|
|
1630
1735
|
changed: false,
|
|
1631
1736
|
verified: true,
|
|
@@ -1633,13 +1738,13 @@ async function connectAgent(input) {
|
|
|
1633
1738
|
plan
|
|
1634
1739
|
};
|
|
1635
1740
|
}
|
|
1636
|
-
if (!
|
|
1637
|
-
`${plan.action === "create" ? "Add" : "Update"} ${
|
|
1741
|
+
if (!input2.yes && !await input2.confirm(
|
|
1742
|
+
`${plan.action === "create" ? "Add" : "Update"} ${input2.serverName} in ${input2.agent}?`
|
|
1638
1743
|
)) {
|
|
1639
1744
|
return {
|
|
1640
|
-
agent:
|
|
1641
|
-
serverName:
|
|
1642
|
-
scope:
|
|
1745
|
+
agent: input2.agent,
|
|
1746
|
+
serverName: input2.serverName,
|
|
1747
|
+
scope: input2.scope,
|
|
1643
1748
|
action: "cancelled",
|
|
1644
1749
|
changed: false,
|
|
1645
1750
|
verified: true,
|
|
@@ -1649,23 +1754,23 @@ async function connectAgent(input) {
|
|
|
1649
1754
|
}
|
|
1650
1755
|
try {
|
|
1651
1756
|
if (plan.action === "update") {
|
|
1652
|
-
await
|
|
1757
|
+
await input2.adapter.remove(input2.serverName, input2.scope);
|
|
1653
1758
|
}
|
|
1654
|
-
await
|
|
1655
|
-
const readBack = await
|
|
1759
|
+
await input2.adapter.add(input2.serverName, input2.scope, resolved.spec);
|
|
1760
|
+
const readBack = await input2.adapter.read(input2.serverName, input2.scope);
|
|
1656
1761
|
if (!readBack || fingerprintConnection(readBack.spec) !== desiredFingerprint) {
|
|
1657
1762
|
throw hostFailure3("Agent MCP read-back did not match the requested connection");
|
|
1658
1763
|
}
|
|
1659
|
-
await
|
|
1660
|
-
await
|
|
1661
|
-
managedConnection(
|
|
1764
|
+
await input2.verifier.verify(readBack.spec, { timeoutMs: input2.timeoutMs });
|
|
1765
|
+
await input2.state.set(
|
|
1766
|
+
managedConnection(input2, resolved, readBack.fingerprint, managed)
|
|
1662
1767
|
);
|
|
1663
1768
|
} catch (cause) {
|
|
1664
1769
|
try {
|
|
1665
1770
|
await rollbackHost({
|
|
1666
|
-
adapter:
|
|
1667
|
-
serverName:
|
|
1668
|
-
scope:
|
|
1771
|
+
adapter: input2.adapter,
|
|
1772
|
+
serverName: input2.serverName,
|
|
1773
|
+
scope: input2.scope,
|
|
1669
1774
|
...host ? { previousSpec: host.spec } : {},
|
|
1670
1775
|
...host ? { previousFingerprint: host.fingerprint } : {}
|
|
1671
1776
|
});
|
|
@@ -1673,7 +1778,7 @@ async function connectAgent(input) {
|
|
|
1673
1778
|
throw hostFailure3(
|
|
1674
1779
|
"Agent MCP configuration failed and rollback also failed",
|
|
1675
1780
|
cause,
|
|
1676
|
-
`Inspect the ${
|
|
1781
|
+
`Inspect the ${input2.agent} MCP entry \`${input2.serverName}\` before retrying.`
|
|
1677
1782
|
);
|
|
1678
1783
|
}
|
|
1679
1784
|
throw hostFailure3(
|
|
@@ -1682,9 +1787,9 @@ async function connectAgent(input) {
|
|
|
1682
1787
|
);
|
|
1683
1788
|
}
|
|
1684
1789
|
return {
|
|
1685
|
-
agent:
|
|
1686
|
-
serverName:
|
|
1687
|
-
scope:
|
|
1790
|
+
agent: input2.agent,
|
|
1791
|
+
serverName: input2.serverName,
|
|
1792
|
+
scope: input2.scope,
|
|
1688
1793
|
action: plan.action,
|
|
1689
1794
|
changed: true,
|
|
1690
1795
|
verified: true,
|
|
@@ -1692,15 +1797,15 @@ async function connectAgent(input) {
|
|
|
1692
1797
|
plan
|
|
1693
1798
|
};
|
|
1694
1799
|
}
|
|
1695
|
-
async function disconnectAgent(
|
|
1696
|
-
validateServerName(
|
|
1697
|
-
await
|
|
1698
|
-
const key = connectionKey(
|
|
1699
|
-
const managed = await
|
|
1800
|
+
async function disconnectAgent(input2) {
|
|
1801
|
+
validateServerName(input2.serverName);
|
|
1802
|
+
await input2.adapter.detect();
|
|
1803
|
+
const key = connectionKey(input2.agent, input2.scope, input2.serverName);
|
|
1804
|
+
const managed = await input2.state.get(key);
|
|
1700
1805
|
if (!managed) {
|
|
1701
1806
|
throw conflict("The Agent MCP entry is not managed by SAG CLI");
|
|
1702
1807
|
}
|
|
1703
|
-
const host = await
|
|
1808
|
+
const host = await input2.adapter.read(input2.serverName, input2.scope);
|
|
1704
1809
|
if (!host) {
|
|
1705
1810
|
throw conflict("Managed state exists but the Agent MCP entry is missing");
|
|
1706
1811
|
}
|
|
@@ -1709,20 +1814,20 @@ async function disconnectAgent(input) {
|
|
|
1709
1814
|
}
|
|
1710
1815
|
const plan = {
|
|
1711
1816
|
action: "unchanged",
|
|
1712
|
-
agent:
|
|
1713
|
-
serverName:
|
|
1714
|
-
scope:
|
|
1817
|
+
agent: input2.agent,
|
|
1818
|
+
serverName: input2.serverName,
|
|
1819
|
+
scope: input2.scope,
|
|
1715
1820
|
provider: "docker-stdio",
|
|
1716
1821
|
target: managed.docker.containerName,
|
|
1717
1822
|
sourceId: managed.sourceId,
|
|
1718
1823
|
currentFingerprint: host.fingerprint,
|
|
1719
1824
|
desiredFingerprint: host.fingerprint
|
|
1720
1825
|
};
|
|
1721
|
-
if (!
|
|
1826
|
+
if (!input2.yes && !await input2.confirm(`Remove ${input2.serverName} from ${input2.agent}?`)) {
|
|
1722
1827
|
return {
|
|
1723
|
-
agent:
|
|
1724
|
-
serverName:
|
|
1725
|
-
scope:
|
|
1828
|
+
agent: input2.agent,
|
|
1829
|
+
serverName: input2.serverName,
|
|
1830
|
+
scope: input2.scope,
|
|
1726
1831
|
action: "cancelled",
|
|
1727
1832
|
changed: false,
|
|
1728
1833
|
verified: true,
|
|
@@ -1730,12 +1835,12 @@ async function disconnectAgent(input) {
|
|
|
1730
1835
|
plan
|
|
1731
1836
|
};
|
|
1732
1837
|
}
|
|
1733
|
-
await
|
|
1838
|
+
await input2.adapter.remove(input2.serverName, input2.scope);
|
|
1734
1839
|
try {
|
|
1735
|
-
await
|
|
1840
|
+
await input2.state.delete(key);
|
|
1736
1841
|
} catch (cause) {
|
|
1737
1842
|
try {
|
|
1738
|
-
await
|
|
1843
|
+
await input2.adapter.add(input2.serverName, input2.scope, host.spec);
|
|
1739
1844
|
} catch {
|
|
1740
1845
|
throw hostFailure3(
|
|
1741
1846
|
"Managed state cleanup failed and the Agent entry could not be restored",
|
|
@@ -1748,9 +1853,9 @@ async function disconnectAgent(input) {
|
|
|
1748
1853
|
);
|
|
1749
1854
|
}
|
|
1750
1855
|
return {
|
|
1751
|
-
agent:
|
|
1752
|
-
serverName:
|
|
1753
|
-
scope:
|
|
1856
|
+
agent: input2.agent,
|
|
1857
|
+
serverName: input2.serverName,
|
|
1858
|
+
scope: input2.scope,
|
|
1754
1859
|
action: "disconnect",
|
|
1755
1860
|
changed: true,
|
|
1756
1861
|
verified: true,
|
|
@@ -1763,21 +1868,21 @@ async function disconnectAgent(input) {
|
|
|
1763
1868
|
var SOURCE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
|
|
1764
1869
|
var DockerStdioProvider = class {
|
|
1765
1870
|
id = "docker-stdio";
|
|
1766
|
-
async inspect(
|
|
1871
|
+
async inspect(input2) {
|
|
1767
1872
|
return {
|
|
1768
1873
|
provider: this.id,
|
|
1769
1874
|
ready: true,
|
|
1770
|
-
target:
|
|
1875
|
+
target: input2.target.name
|
|
1771
1876
|
};
|
|
1772
1877
|
}
|
|
1773
|
-
async createSpec(
|
|
1774
|
-
if (
|
|
1878
|
+
async createSpec(input2) {
|
|
1879
|
+
if (input2.sourceId && !SOURCE_ID_PATTERN.test(input2.sourceId)) {
|
|
1775
1880
|
throw new CliError("INVALID_ARGUMENT", "Invalid SAG source id", {
|
|
1776
1881
|
exitCode: exitCodes.invalidArgument,
|
|
1777
1882
|
hint: "Use 1-128 letters, numbers, underscores, or dashes."
|
|
1778
1883
|
});
|
|
1779
1884
|
}
|
|
1780
|
-
const sourceArguments =
|
|
1885
|
+
const sourceArguments = input2.sourceId ? ["-e", `SAG_MCP_SOURCE_ID=${input2.sourceId}`] : [];
|
|
1781
1886
|
return {
|
|
1782
1887
|
transport: "stdio",
|
|
1783
1888
|
command: "docker",
|
|
@@ -1785,7 +1890,7 @@ var DockerStdioProvider = class {
|
|
|
1785
1890
|
"exec",
|
|
1786
1891
|
"-i",
|
|
1787
1892
|
...sourceArguments,
|
|
1788
|
-
|
|
1893
|
+
input2.target.name,
|
|
1789
1894
|
"python",
|
|
1790
1895
|
"-m",
|
|
1791
1896
|
"sag_api.mcp.server"
|
|
@@ -1835,13 +1940,13 @@ async function validateTarget(docker, container) {
|
|
|
1835
1940
|
}
|
|
1836
1941
|
return toTarget(inspection);
|
|
1837
1942
|
}
|
|
1838
|
-
async function discoverSagContainer(
|
|
1839
|
-
await
|
|
1840
|
-
if (
|
|
1841
|
-
validateContainer(
|
|
1842
|
-
return validateTarget(
|
|
1943
|
+
async function discoverSagContainer(input2) {
|
|
1944
|
+
await input2.docker.version();
|
|
1945
|
+
if (input2.container) {
|
|
1946
|
+
validateContainer(input2.container);
|
|
1947
|
+
return validateTarget(input2.docker, input2.container);
|
|
1843
1948
|
}
|
|
1844
|
-
const containers = await
|
|
1949
|
+
const containers = await input2.docker.listRunningContainers();
|
|
1845
1950
|
const apiCandidates = containers.filter(
|
|
1846
1951
|
(container) => container.labels["com.docker.compose.service"] === "api"
|
|
1847
1952
|
);
|
|
@@ -1856,7 +1961,7 @@ async function discoverSagContainer(input) {
|
|
|
1856
1961
|
let preferredFailure;
|
|
1857
1962
|
for (const candidate of apiCandidates) {
|
|
1858
1963
|
try {
|
|
1859
|
-
targets.push(await validateTarget(
|
|
1964
|
+
targets.push(await validateTarget(input2.docker, candidate.id));
|
|
1860
1965
|
} catch (error) {
|
|
1861
1966
|
firstFailure ??= error;
|
|
1862
1967
|
if (candidate.labels["com.docker.compose.project"] === "sag") {
|
|
@@ -1872,13 +1977,13 @@ async function discoverSagContainer(input) {
|
|
|
1872
1977
|
if (rankedTargets.length === 1) {
|
|
1873
1978
|
return rankedTargets[0];
|
|
1874
1979
|
}
|
|
1875
|
-
if (!
|
|
1980
|
+
if (!input2.interactive || !input2.select) {
|
|
1876
1981
|
throw new CliError("CONFIG_CONFLICT", "Multiple SAG API containers were found", {
|
|
1877
1982
|
exitCode: exitCodes.configConflict,
|
|
1878
1983
|
hint: "Pass `--container <name-or-id>` to select one."
|
|
1879
1984
|
});
|
|
1880
1985
|
}
|
|
1881
|
-
const selectedId = await
|
|
1986
|
+
const selectedId = await input2.select(rankedTargets);
|
|
1882
1987
|
const selected = rankedTargets.find(
|
|
1883
1988
|
(target) => target.id === selectedId || target.name === selectedId
|
|
1884
1989
|
);
|
|
@@ -1891,34 +1996,34 @@ async function discoverSagContainer(input) {
|
|
|
1891
1996
|
}
|
|
1892
1997
|
|
|
1893
1998
|
// src/commands/agent.ts
|
|
1894
|
-
async function connectLocalAgent(runtime2,
|
|
1999
|
+
async function connectLocalAgent(runtime2, input2) {
|
|
1895
2000
|
return connectAgent({
|
|
1896
|
-
agent:
|
|
1897
|
-
serverName:
|
|
2001
|
+
agent: input2.agent,
|
|
2002
|
+
serverName: input2.serverName,
|
|
1898
2003
|
scope: "user",
|
|
1899
|
-
profile:
|
|
1900
|
-
...
|
|
1901
|
-
timeoutMs:
|
|
1902
|
-
dryRun:
|
|
1903
|
-
yes:
|
|
2004
|
+
profile: input2.profile,
|
|
2005
|
+
...input2.sourceId ? { sourceId: input2.sourceId } : {},
|
|
2006
|
+
timeoutMs: input2.timeoutMs,
|
|
2007
|
+
dryRun: input2.dryRun,
|
|
2008
|
+
yes: input2.yes,
|
|
1904
2009
|
resolveConnection: async () => {
|
|
1905
2010
|
const target = await discoverSagContainer({
|
|
1906
2011
|
docker: runtime2.docker,
|
|
1907
|
-
...
|
|
2012
|
+
...input2.container ? { container: input2.container } : {},
|
|
1908
2013
|
interactive: runtime2.interactive,
|
|
1909
2014
|
select: runtime2.selectContainer
|
|
1910
2015
|
});
|
|
1911
2016
|
const spec = await new DockerStdioProvider().createSpec({
|
|
1912
2017
|
target,
|
|
1913
|
-
...
|
|
2018
|
+
...input2.sourceId ? { sourceId: input2.sourceId } : {}
|
|
1914
2019
|
});
|
|
1915
2020
|
return { target, spec };
|
|
1916
2021
|
},
|
|
1917
2022
|
verifier: runtime2.verifier,
|
|
1918
|
-
adapter: runtime2.adapters[
|
|
2023
|
+
adapter: runtime2.adapters[input2.agent],
|
|
1919
2024
|
state: runtime2.state,
|
|
1920
|
-
confirm:
|
|
1921
|
-
now:
|
|
2025
|
+
confirm: input2.confirm,
|
|
2026
|
+
now: input2.now
|
|
1922
2027
|
});
|
|
1923
2028
|
}
|
|
1924
2029
|
async function agentStatus(runtime2, agent, serverName) {
|
|
@@ -1997,10 +2102,10 @@ async function agentStatus(runtime2, agent, serverName) {
|
|
|
1997
2102
|
};
|
|
1998
2103
|
}
|
|
1999
2104
|
}
|
|
2000
|
-
async function statusLocalAgents(runtime2,
|
|
2001
|
-
const agents =
|
|
2105
|
+
async function statusLocalAgents(runtime2, input2) {
|
|
2106
|
+
const agents = input2.agent ? [input2.agent] : ["codex", "claude-code"];
|
|
2002
2107
|
return Promise.all(
|
|
2003
|
-
agents.map((agent) => agentStatus(runtime2, agent,
|
|
2108
|
+
agents.map((agent) => agentStatus(runtime2, agent, input2.serverName))
|
|
2004
2109
|
);
|
|
2005
2110
|
}
|
|
2006
2111
|
async function listAgentHosts(runtime2) {
|
|
@@ -2021,58 +2126,69 @@ async function listAgentHosts(runtime2) {
|
|
|
2021
2126
|
})
|
|
2022
2127
|
);
|
|
2023
2128
|
}
|
|
2024
|
-
async function disconnectLocalAgent(runtime2,
|
|
2129
|
+
async function disconnectLocalAgent(runtime2, input2) {
|
|
2025
2130
|
return disconnectAgent({
|
|
2026
|
-
agent:
|
|
2027
|
-
serverName:
|
|
2131
|
+
agent: input2.agent,
|
|
2132
|
+
serverName: input2.serverName,
|
|
2028
2133
|
scope: "user",
|
|
2029
|
-
adapter: runtime2.adapters[
|
|
2134
|
+
adapter: runtime2.adapters[input2.agent],
|
|
2030
2135
|
state: runtime2.state,
|
|
2031
|
-
yes:
|
|
2032
|
-
confirm:
|
|
2136
|
+
yes: input2.yes,
|
|
2137
|
+
confirm: input2.confirm
|
|
2033
2138
|
});
|
|
2034
2139
|
}
|
|
2035
2140
|
|
|
2036
2141
|
// src/commands/auth.ts
|
|
2037
|
-
async function login(
|
|
2038
|
-
const
|
|
2039
|
-
if (
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2142
|
+
async function login(input2) {
|
|
2143
|
+
const environmentToken = input2.environmentToken?.trim();
|
|
2144
|
+
if (environmentToken) {
|
|
2145
|
+
const user2 = await input2.validate(environmentToken);
|
|
2146
|
+
await input2.store.set(input2.credentialRef, environmentToken);
|
|
2147
|
+
return {
|
|
2148
|
+
authenticated: true,
|
|
2149
|
+
credentialRef: input2.credentialRef,
|
|
2150
|
+
credentialStore: input2.store.kind,
|
|
2151
|
+
user: user2
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
const name = (input2.name ?? await input2.promptName()).trim();
|
|
2155
|
+
if (!name) {
|
|
2156
|
+
throw new CliError("INVALID_ARGUMENT", "A SAG user name is required", {
|
|
2157
|
+
exitCode: exitCodes.invalidArgument,
|
|
2158
|
+
hint: "Pass `--name <name>` or enter a name when prompted."
|
|
2043
2159
|
});
|
|
2044
2160
|
}
|
|
2045
|
-
const user = await
|
|
2046
|
-
await
|
|
2161
|
+
const { accessToken, user } = await input2.authenticate(name);
|
|
2162
|
+
await input2.store.set(input2.credentialRef, accessToken);
|
|
2047
2163
|
return {
|
|
2048
2164
|
authenticated: true,
|
|
2049
|
-
credentialRef:
|
|
2050
|
-
credentialStore:
|
|
2165
|
+
credentialRef: input2.credentialRef,
|
|
2166
|
+
credentialStore: input2.store.kind,
|
|
2051
2167
|
user
|
|
2052
2168
|
};
|
|
2053
2169
|
}
|
|
2054
|
-
async function status(
|
|
2055
|
-
const token =
|
|
2170
|
+
async function status(input2) {
|
|
2171
|
+
const token = input2.environmentToken?.trim() || await input2.store.get(input2.credentialRef);
|
|
2056
2172
|
if (!token) {
|
|
2057
2173
|
return {
|
|
2058
2174
|
authenticated: false,
|
|
2059
|
-
credentialRef:
|
|
2060
|
-
credentialStore:
|
|
2175
|
+
credentialRef: input2.credentialRef,
|
|
2176
|
+
credentialStore: input2.store.kind
|
|
2061
2177
|
};
|
|
2062
2178
|
}
|
|
2063
|
-
const user = await
|
|
2179
|
+
const user = await input2.validate(token);
|
|
2064
2180
|
return {
|
|
2065
2181
|
authenticated: true,
|
|
2066
|
-
credentialRef:
|
|
2067
|
-
credentialStore:
|
|
2182
|
+
credentialRef: input2.credentialRef,
|
|
2183
|
+
credentialStore: input2.store.kind,
|
|
2068
2184
|
user
|
|
2069
2185
|
};
|
|
2070
2186
|
}
|
|
2071
|
-
async function logout(
|
|
2072
|
-
await
|
|
2187
|
+
async function logout(input2) {
|
|
2188
|
+
await input2.store.delete(input2.credentialRef);
|
|
2073
2189
|
return {
|
|
2074
2190
|
authenticated: false,
|
|
2075
|
-
credentialRef:
|
|
2191
|
+
credentialRef: input2.credentialRef
|
|
2076
2192
|
};
|
|
2077
2193
|
}
|
|
2078
2194
|
|
|
@@ -2133,25 +2249,63 @@ async function documentStatus(client, sourceId, documentId) {
|
|
|
2133
2249
|
};
|
|
2134
2250
|
}
|
|
2135
2251
|
|
|
2252
|
+
// src/commands/knowledge.ts
|
|
2253
|
+
function requiredIdentifier2(value, label) {
|
|
2254
|
+
const normalized = value.trim();
|
|
2255
|
+
if (!normalized) {
|
|
2256
|
+
throw new CliError("INVALID_ARGUMENT", `${label} is required`, {
|
|
2257
|
+
exitCode: exitCodes.invalidArgument
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
return normalized;
|
|
2261
|
+
}
|
|
2262
|
+
async function outline(client, sourceId, documentId) {
|
|
2263
|
+
return client.outline(
|
|
2264
|
+
requiredIdentifier2(sourceId, "Source ID"),
|
|
2265
|
+
requiredIdentifier2(documentId, "Document ID")
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
async function grep(client, sourceId, pattern, limit) {
|
|
2269
|
+
return client.grep(
|
|
2270
|
+
requiredIdentifier2(sourceId, "Source ID"),
|
|
2271
|
+
requiredIdentifier2(pattern, "Pattern"),
|
|
2272
|
+
limit
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
async function read(client, sourceId, documentId, offset, limit) {
|
|
2276
|
+
return client.readDocument(
|
|
2277
|
+
requiredIdentifier2(sourceId, "Source ID"),
|
|
2278
|
+
requiredIdentifier2(documentId, "Document ID"),
|
|
2279
|
+
offset,
|
|
2280
|
+
limit
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
async function getEntityContext(client, sourceId, name) {
|
|
2284
|
+
return client.getEntityContext(
|
|
2285
|
+
requiredIdentifier2(sourceId, "Source ID"),
|
|
2286
|
+
requiredIdentifier2(name, "Entity name")
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2136
2290
|
// src/commands/mcp.ts
|
|
2137
|
-
async function testLocalMcp(runtime2,
|
|
2291
|
+
async function testLocalMcp(runtime2, input2) {
|
|
2138
2292
|
const container = await discoverSagContainer({
|
|
2139
2293
|
docker: runtime2.docker,
|
|
2140
|
-
...
|
|
2294
|
+
...input2.container ? { container: input2.container } : {},
|
|
2141
2295
|
interactive: runtime2.interactive,
|
|
2142
2296
|
select: runtime2.selectContainer
|
|
2143
2297
|
});
|
|
2144
2298
|
const spec = await new DockerStdioProvider().createSpec({
|
|
2145
2299
|
target: container,
|
|
2146
|
-
...
|
|
2300
|
+
...input2.sourceId ? { sourceId: input2.sourceId } : {}
|
|
2147
2301
|
});
|
|
2148
2302
|
const verification = await runtime2.verifier.verify(spec, {
|
|
2149
|
-
timeoutMs:
|
|
2303
|
+
timeoutMs: input2.timeoutMs
|
|
2150
2304
|
});
|
|
2151
2305
|
return {
|
|
2152
2306
|
provider: "docker-stdio",
|
|
2153
2307
|
container,
|
|
2154
|
-
sourceId:
|
|
2308
|
+
sourceId: input2.sourceId ?? null,
|
|
2155
2309
|
...verification
|
|
2156
2310
|
};
|
|
2157
2311
|
}
|
|
@@ -2163,9 +2317,9 @@ function skipped(name, detail) {
|
|
|
2163
2317
|
detail
|
|
2164
2318
|
};
|
|
2165
2319
|
}
|
|
2166
|
-
async function localMcpDoctorChecks(runtime2,
|
|
2320
|
+
async function localMcpDoctorChecks(runtime2, input2) {
|
|
2167
2321
|
try {
|
|
2168
|
-
const report = await testLocalMcp(runtime2,
|
|
2322
|
+
const report = await testLocalMcp(runtime2, input2);
|
|
2169
2323
|
return [
|
|
2170
2324
|
{
|
|
2171
2325
|
name: "docker",
|
|
@@ -2362,16 +2516,16 @@ function directCredentialRef(url) {
|
|
|
2362
2516
|
const fingerprint = createHash2("sha256").update(url).digest("hex").slice(0, 16);
|
|
2363
2517
|
return `sag-cli/direct-${fingerprint}`;
|
|
2364
2518
|
}
|
|
2365
|
-
async function createRuntimeContext(
|
|
2366
|
-
const config = await
|
|
2367
|
-
const connection = resolveConnection(
|
|
2519
|
+
async function createRuntimeContext(input2) {
|
|
2520
|
+
const config = await input2.configStore.load();
|
|
2521
|
+
const connection = resolveConnection(input2.options, input2.environment, config);
|
|
2368
2522
|
const credentialRef = connection.credentialRef ?? directCredentialRef(connection.url);
|
|
2369
|
-
const token = connection.environmentToken ?? await
|
|
2523
|
+
const token = connection.environmentToken ?? await input2.credentialStore.get(credentialRef) ?? void 0;
|
|
2370
2524
|
return {
|
|
2371
2525
|
connection,
|
|
2372
2526
|
credentialRef,
|
|
2373
2527
|
...token ? { token } : {},
|
|
2374
|
-
client:
|
|
2528
|
+
client: input2.createClient({
|
|
2375
2529
|
origin: connection.url,
|
|
2376
2530
|
...token ? { token } : {}
|
|
2377
2531
|
})
|
|
@@ -2475,7 +2629,7 @@ function documentDetail(total, statuses) {
|
|
|
2475
2629
|
const parts = displayOrder.filter((status2) => statuses[status2] > 0).map((status2) => `${statuses[status2]} ${status2}`);
|
|
2476
2630
|
return `${total} ${total === 1 ? "document" : "documents"}${parts.length ? `: ${parts.join(", ")}` : ""}`;
|
|
2477
2631
|
}
|
|
2478
|
-
async function runDoctor(client,
|
|
2632
|
+
async function runDoctor(client, input2, localMcpChecks) {
|
|
2479
2633
|
const checks = [];
|
|
2480
2634
|
let sagVersion;
|
|
2481
2635
|
let authenticated = false;
|
|
@@ -2590,8 +2744,8 @@ async function runDoctor(client, input, localMcpChecks) {
|
|
|
2590
2744
|
}
|
|
2591
2745
|
return {
|
|
2592
2746
|
status: overallStatus(checks),
|
|
2593
|
-
url:
|
|
2594
|
-
...
|
|
2747
|
+
url: input2.url,
|
|
2748
|
+
...input2.profileName ? { profileName: input2.profileName } : {},
|
|
2595
2749
|
...sagVersion ? { sagVersion } : {},
|
|
2596
2750
|
checks,
|
|
2597
2751
|
summary: {
|
|
@@ -2681,6 +2835,45 @@ function renderLocalMcp(value) {
|
|
|
2681
2835
|
].join("\n")}
|
|
2682
2836
|
`;
|
|
2683
2837
|
}
|
|
2838
|
+
function renderOutline(value) {
|
|
2839
|
+
const filename = typeof value.filename === "string" ? value.filename : "Unknown";
|
|
2840
|
+
const items = Array.isArray(value.outline) ? value.outline.filter(isRecord) : [];
|
|
2841
|
+
const lines = items.map(
|
|
2842
|
+
(item) => `${String(item.rank ?? 0).padStart(3)}. ${item.heading || "(untitled)"} (chunk_id=${item.chunk_id})`
|
|
2843
|
+
);
|
|
2844
|
+
return `${filename}
|
|
2845
|
+
${lines.join("\n")}
|
|
2846
|
+
`;
|
|
2847
|
+
}
|
|
2848
|
+
function renderGrep(value) {
|
|
2849
|
+
const matches = Array.isArray(value.matches) ? value.matches.filter(isRecord) : [];
|
|
2850
|
+
const lines = matches.map(
|
|
2851
|
+
(match, index) => `[${index + 1}] ${match.heading || "Match"}
|
|
2852
|
+
${match.snippet}`
|
|
2853
|
+
);
|
|
2854
|
+
return lines.length ? `${lines.join("\n\n")}
|
|
2855
|
+
` : "No matches.\n";
|
|
2856
|
+
}
|
|
2857
|
+
function renderRead(value) {
|
|
2858
|
+
const filename = typeof value.filename === "string" ? value.filename : "Unknown";
|
|
2859
|
+
const total = typeof value.total_lines === "number" ? value.total_lines : 0;
|
|
2860
|
+
const offset = typeof value.offset === "number" ? value.offset : 1;
|
|
2861
|
+
const lines = Array.isArray(value.lines) ? value.lines.map(String) : [];
|
|
2862
|
+
const header = `${filename} \xB7 lines ${offset}-${offset + lines.length - 1} / ${total}`;
|
|
2863
|
+
return `${header}
|
|
2864
|
+
${lines.join("")}`;
|
|
2865
|
+
}
|
|
2866
|
+
function renderEntityContext(value) {
|
|
2867
|
+
const name = typeof value.name === "string" ? value.name : "Unknown";
|
|
2868
|
+
const type = typeof value.type === "string" ? value.type : "";
|
|
2869
|
+
const source = typeof value.source_name === "string" ? value.source_name : "";
|
|
2870
|
+
const context = typeof value.context === "string" ? value.context : "";
|
|
2871
|
+
return `Entity: ${name}${type ? ` (${type})` : ""}
|
|
2872
|
+
Source: ${source}
|
|
2873
|
+
|
|
2874
|
+
${context}
|
|
2875
|
+
`;
|
|
2876
|
+
}
|
|
2684
2877
|
function renderHuman(value, quiet = false) {
|
|
2685
2878
|
if (quiet) {
|
|
2686
2879
|
if (Array.isArray(value)) {
|
|
@@ -2703,6 +2896,18 @@ function renderHuman(value, quiet = false) {
|
|
|
2703
2896
|
if (isRecord(value) && Array.isArray(value.sections) && "query" in value) {
|
|
2704
2897
|
return renderSearch(value);
|
|
2705
2898
|
}
|
|
2899
|
+
if (isRecord(value) && Array.isArray(value.outline) && "filename" in value) {
|
|
2900
|
+
return renderOutline(value);
|
|
2901
|
+
}
|
|
2902
|
+
if (isRecord(value) && Array.isArray(value.matches) && "pattern" in value) {
|
|
2903
|
+
return renderGrep(value);
|
|
2904
|
+
}
|
|
2905
|
+
if (isRecord(value) && Array.isArray(value.lines) && "total_lines" in value) {
|
|
2906
|
+
return renderRead(value);
|
|
2907
|
+
}
|
|
2908
|
+
if (isRecord(value) && "entity_id" in value && "context" in value) {
|
|
2909
|
+
return renderEntityContext(value);
|
|
2910
|
+
}
|
|
2706
2911
|
if (isRecord(value) && value.provider === "docker-stdio" && "protocolVersion" in value && "container" in value) {
|
|
2707
2912
|
return renderLocalMcp(value);
|
|
2708
2913
|
}
|
|
@@ -2847,12 +3052,14 @@ function addProfileCommands(program, dependencies2) {
|
|
|
2847
3052
|
}
|
|
2848
3053
|
function addAuthCommands(program, dependencies2) {
|
|
2849
3054
|
const auth = program.command("auth").description("Manage SAG credentials");
|
|
2850
|
-
auth.command("login").action(async () => {
|
|
3055
|
+
auth.command("login").option("--name <name>", "SAG user name for the existing local login").action(async (options) => {
|
|
2851
3056
|
const context = await runtime(program, dependencies2);
|
|
2852
3057
|
const result = await login({
|
|
2853
3058
|
credentialRef: context.credentialRef,
|
|
2854
3059
|
...context.connection.environmentToken ? { environmentToken: context.connection.environmentToken } : {},
|
|
2855
|
-
|
|
3060
|
+
...options.name ? { name: options.name } : {},
|
|
3061
|
+
promptName: dependencies2.promptName,
|
|
3062
|
+
authenticate: (name) => dependencies2.createClient({ origin: context.connection.url }).login(name),
|
|
2856
3063
|
validate: async (token) => dependencies2.createClient({ origin: context.connection.url, token }).me(),
|
|
2857
3064
|
store: dependencies2.credentialStore
|
|
2858
3065
|
});
|
|
@@ -2946,6 +3153,46 @@ function addKnowledgeCommands(program, dependencies2) {
|
|
|
2946
3153
|
);
|
|
2947
3154
|
}
|
|
2948
3155
|
);
|
|
3156
|
+
program.command("outline").argument("<document-id>").requiredOption("--source <source-id>").action(async (documentId, options) => {
|
|
3157
|
+
const context = await runtime(program, dependencies2);
|
|
3158
|
+
emit(
|
|
3159
|
+
program,
|
|
3160
|
+
dependencies2,
|
|
3161
|
+
await outline(context.client, options.source, documentId)
|
|
3162
|
+
);
|
|
3163
|
+
});
|
|
3164
|
+
program.command("grep").argument("<pattern>").requiredOption("--source <source-id>").option("--limit <number>", "Maximum result count", parseInteger).action(async (pattern, options) => {
|
|
3165
|
+
const context = await runtime(program, dependencies2);
|
|
3166
|
+
emit(
|
|
3167
|
+
program,
|
|
3168
|
+
dependencies2,
|
|
3169
|
+
await grep(context.client, options.source, pattern, options.limit)
|
|
3170
|
+
);
|
|
3171
|
+
});
|
|
3172
|
+
program.command("read").argument("<document-id>").requiredOption("--source <source-id>").option("--offset <number>", "Starting line (1-based)", parseInteger).option("--limit <number>", "Lines to read", parseInteger).action(
|
|
3173
|
+
async (documentId, options) => {
|
|
3174
|
+
const context = await runtime(program, dependencies2);
|
|
3175
|
+
emit(
|
|
3176
|
+
program,
|
|
3177
|
+
dependencies2,
|
|
3178
|
+
await read(
|
|
3179
|
+
context.client,
|
|
3180
|
+
options.source,
|
|
3181
|
+
documentId,
|
|
3182
|
+
options.offset,
|
|
3183
|
+
options.limit
|
|
3184
|
+
)
|
|
3185
|
+
);
|
|
3186
|
+
}
|
|
3187
|
+
);
|
|
3188
|
+
program.command("get-entity").argument("<name>").requiredOption("--source <source-id>").action(async (name, options) => {
|
|
3189
|
+
const context = await runtime(program, dependencies2);
|
|
3190
|
+
emit(
|
|
3191
|
+
program,
|
|
3192
|
+
dependencies2,
|
|
3193
|
+
await getEntityContext(context.client, options.source, name)
|
|
3194
|
+
);
|
|
3195
|
+
});
|
|
2949
3196
|
}
|
|
2950
3197
|
function addLocalMcpCommands(program, dependencies2) {
|
|
2951
3198
|
const mcp = program.command("mcp").description("Verify local SAG MCP");
|
|
@@ -3081,10 +3328,7 @@ var dependencies = {
|
|
|
3081
3328
|
configStore: new ConfigStore(defaultConfigPath()),
|
|
3082
3329
|
credentialStore: credentialSelection.store,
|
|
3083
3330
|
createClient: defaultClientFactory,
|
|
3084
|
-
|
|
3085
|
-
message: "SAG Token",
|
|
3086
|
-
mask: "*"
|
|
3087
|
-
}),
|
|
3331
|
+
promptName: () => input({ message: "SAG user name" }),
|
|
3088
3332
|
confirm: (message) => confirm({ message, default: false }),
|
|
3089
3333
|
writeStdout: (value) => process.stdout.write(value),
|
|
3090
3334
|
writeStderr: (value) => process.stderr.write(value),
|