@sift-wiki/cli 0.1.3 → 0.1.4
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 +31 -22
- package/dist/bin/sift.js +1152 -199
- package/package.json +1 -1
package/dist/bin/sift.js
CHANGED
|
@@ -137,6 +137,8 @@ function parseSearchQuery(input) {
|
|
|
137
137
|
function parseContextQuery(input) {
|
|
138
138
|
return {
|
|
139
139
|
query: requireString(input, "query"),
|
|
140
|
+
queryIssuedAt: optionalString(input, "queryIssuedAt"),
|
|
141
|
+
timezone: optionalString(input, "timezone"),
|
|
140
142
|
maxChars: requireInteger(input, "maxChars", 4e3)
|
|
141
143
|
};
|
|
142
144
|
}
|
|
@@ -375,6 +377,19 @@ function writeTool(name, summary, properties, cliExample, options) {
|
|
|
375
377
|
hostedAgent: options?.hostedAgent
|
|
376
378
|
});
|
|
377
379
|
}
|
|
380
|
+
function hostedAgentOnlyReadTool(name, summary, properties, options) {
|
|
381
|
+
return defineTool({
|
|
382
|
+
name,
|
|
383
|
+
summary,
|
|
384
|
+
properties,
|
|
385
|
+
required: options.required,
|
|
386
|
+
capability: "record:read",
|
|
387
|
+
mutability: "read",
|
|
388
|
+
transports: [],
|
|
389
|
+
cliExample: "",
|
|
390
|
+
hostedAgent: { available: true, ...options.hostedAgent }
|
|
391
|
+
});
|
|
392
|
+
}
|
|
378
393
|
function sourceWriteTool(name, summary, properties, cliExample) {
|
|
379
394
|
return defineTool({
|
|
380
395
|
name,
|
|
@@ -438,31 +453,8 @@ function defaultRiskClass(mutability) {
|
|
|
438
453
|
return "low";
|
|
439
454
|
}
|
|
440
455
|
function defaultToolsets(name) {
|
|
441
|
-
const [prefix] = name.split(".");
|
|
442
|
-
|
|
443
|
-
case "decision":
|
|
444
|
-
case "task":
|
|
445
|
-
return ["work"];
|
|
446
|
-
case "skill":
|
|
447
|
-
return ["brain", "work"];
|
|
448
|
-
case "record":
|
|
449
|
-
case "source":
|
|
450
|
-
case "capture":
|
|
451
|
-
case "ingestion":
|
|
452
|
-
return ["brain", "ingestion"];
|
|
453
|
-
case "search":
|
|
454
|
-
case "context":
|
|
455
|
-
case "evidence":
|
|
456
|
-
case "graph":
|
|
457
|
-
return ["brain", "retrieval"];
|
|
458
|
-
case "tools":
|
|
459
|
-
return ["registry"];
|
|
460
|
-
case "audit":
|
|
461
|
-
case "event":
|
|
462
|
-
return ["audit"];
|
|
463
|
-
default:
|
|
464
|
-
return ["brain"];
|
|
465
|
-
}
|
|
456
|
+
const [prefix = ""] = name.split(".");
|
|
457
|
+
return defaultToolsetsByPrefix[prefix] ?? ["brain"];
|
|
466
458
|
}
|
|
467
459
|
function defaultSearchTerms(name, summary) {
|
|
468
460
|
return [.../* @__PURE__ */ new Set([...tokenize(name), ...tokenize(summary)])];
|
|
@@ -473,7 +465,7 @@ function tokenize(text) {
|
|
|
473
465
|
function stringProps(names) {
|
|
474
466
|
return Object.fromEntries(names.map((name) => [name, { type: "string" }]));
|
|
475
467
|
}
|
|
476
|
-
var readTransports, writeTransports, NO_CAPABILITY, toolDefinitions;
|
|
468
|
+
var readTransports, writeTransports, NO_CAPABILITY, defaultToolsetsByPrefix, toolDefinitions;
|
|
477
469
|
var init_registry = __esm({
|
|
478
470
|
"../tools/dist/registry.js"() {
|
|
479
471
|
"use strict";
|
|
@@ -483,6 +475,23 @@ var init_registry = __esm({
|
|
|
483
475
|
readTransports = ["cli", "hosted_mcp", "local_mcp"];
|
|
484
476
|
writeTransports = ["cli", "hosted_mcp", "local_mcp"];
|
|
485
477
|
NO_CAPABILITY = "none";
|
|
478
|
+
defaultToolsetsByPrefix = {
|
|
479
|
+
audit: ["audit"],
|
|
480
|
+
capture: ["brain", "ingestion"],
|
|
481
|
+
context: ["brain", "retrieval"],
|
|
482
|
+
decision: ["work"],
|
|
483
|
+
event: ["audit"],
|
|
484
|
+
evidence: ["brain", "retrieval"],
|
|
485
|
+
graph: ["brain", "retrieval"],
|
|
486
|
+
ingestion: ["brain", "ingestion"],
|
|
487
|
+
record: ["brain", "ingestion"],
|
|
488
|
+
search: ["brain", "retrieval"],
|
|
489
|
+
skill: ["brain", "work"],
|
|
490
|
+
source: ["brain", "ingestion"],
|
|
491
|
+
task: ["work"],
|
|
492
|
+
tools: ["registry"],
|
|
493
|
+
web: ["web"]
|
|
494
|
+
};
|
|
486
495
|
toolDefinitions = [
|
|
487
496
|
readTool("contract.get", "Fetch the Sift agent contract (kernel + workspace overlay) and the contractVersion to echo on every gated tool call. Call this before any other Sift work.", {}, "sift contract get"),
|
|
488
497
|
readTool("whoami", "Return principal, actor, scope, and capabilities.", {}, "sift whoami"),
|
|
@@ -601,16 +610,68 @@ var init_registry = __esm({
|
|
|
601
610
|
severity: { type: "string" },
|
|
602
611
|
visibility: { type: "array", items: { type: "string" } }
|
|
603
612
|
}, "sift skill teach <skill-id> --lesson 'when X, do Y'", { required: ["skillId", "lesson", "visibility"] }),
|
|
604
|
-
readTool("search.query", "Search authorized brain context and return cited results.", {
|
|
613
|
+
readTool("search.query", "Search authorized brain context and return raw cited candidate results for exploration.", {
|
|
605
614
|
query: { type: "string" },
|
|
606
615
|
limit: { type: "integer", minimum: 1, maximum: 20 }
|
|
607
616
|
}, "sift search query 'launch risks'"),
|
|
608
|
-
readTool("context.assemble", "Assemble
|
|
617
|
+
readTool("context.assemble", "Assemble grounded answer-preparation context with request time, caller identity, task guidance from visible Sift skills when available, safe source metadata, gaps, and raw cited fallback.", {
|
|
618
|
+
query: { type: "string" },
|
|
619
|
+
queryIssuedAt: { type: "string" },
|
|
620
|
+
timezone: { type: "string" },
|
|
621
|
+
maxChars: { type: "integer", minimum: 1 }
|
|
622
|
+
}, "sift context assemble 'launch risks'", {
|
|
623
|
+
required: ["query"],
|
|
609
624
|
hostedAgent: {
|
|
610
625
|
toolsets: ["brain", "retrieval"],
|
|
611
626
|
searchTerms: ["context", "cite", "answer", "evidence"]
|
|
612
627
|
}
|
|
613
628
|
}),
|
|
629
|
+
hostedAgentOnlyReadTool("web.search", "Search public web sources for current or public facts.", {
|
|
630
|
+
query: {
|
|
631
|
+
type: "string",
|
|
632
|
+
description: "Public web search query. Do not include private Sift brain context unless the user explicitly provided it for public lookup."
|
|
633
|
+
},
|
|
634
|
+
limit: { type: "integer", minimum: 1, maximum: 10 },
|
|
635
|
+
recencyDays: { type: "integer", minimum: 1, maximum: 3650 },
|
|
636
|
+
allowedDomains: { type: "array", items: { type: "string" } },
|
|
637
|
+
blockedDomains: { type: "array", items: { type: "string" } }
|
|
638
|
+
}, {
|
|
639
|
+
required: ["query"],
|
|
640
|
+
hostedAgent: {
|
|
641
|
+
toolsets: ["web"],
|
|
642
|
+
searchTerms: [
|
|
643
|
+
"web",
|
|
644
|
+
"search",
|
|
645
|
+
"current",
|
|
646
|
+
"public",
|
|
647
|
+
"company",
|
|
648
|
+
"product",
|
|
649
|
+
"docs",
|
|
650
|
+
"news",
|
|
651
|
+
"pricing",
|
|
652
|
+
"people",
|
|
653
|
+
"law",
|
|
654
|
+
"rules"
|
|
655
|
+
],
|
|
656
|
+
inputHints: ["query", "limit", "recencyDays", "allowedDomains", "blockedDomains"],
|
|
657
|
+
riskClass: "medium"
|
|
658
|
+
}
|
|
659
|
+
}),
|
|
660
|
+
hostedAgentOnlyReadTool("web.fetch", "Read one selected public URL through guarded bounded extraction.", {
|
|
661
|
+
url: {
|
|
662
|
+
type: "string",
|
|
663
|
+
description: "Public http(s) URL to fetch. Local, private, and metadata URLs are refused."
|
|
664
|
+
},
|
|
665
|
+
maxChars: { type: "integer", minimum: 1, maximum: 12e3 }
|
|
666
|
+
}, {
|
|
667
|
+
required: ["url"],
|
|
668
|
+
hostedAgent: {
|
|
669
|
+
toolsets: ["web"],
|
|
670
|
+
searchTerms: ["web", "fetch", "read", "url", "page", "extract", "public"],
|
|
671
|
+
inputHints: ["url", "maxChars"],
|
|
672
|
+
riskClass: "medium"
|
|
673
|
+
}
|
|
674
|
+
}),
|
|
614
675
|
readTool("context.profile", "Read a permission-filtered profile context model.", {}, "sift context profile"),
|
|
615
676
|
readTool("evidence.list", "List authorized evidence links for a record.", stringProps(["recordId"]), "sift evidence list <record-id>"),
|
|
616
677
|
readTool("evidence.get", "Read an authorized evidence item.", stringProps(["evidenceId"]), "sift evidence get <evidence-id>"),
|
|
@@ -713,6 +774,7 @@ var init_discovery = __esm({
|
|
|
713
774
|
"use strict";
|
|
714
775
|
init_registry();
|
|
715
776
|
IMPLEMENTED_TOOL_NAMES = [
|
|
777
|
+
"contract.get",
|
|
716
778
|
"whoami",
|
|
717
779
|
"brain.list",
|
|
718
780
|
"brain.use",
|
|
@@ -1034,7 +1096,9 @@ function runtimeAvailableToolNames(service) {
|
|
|
1034
1096
|
[service.listGraphNeighbors !== void 0, ["graph.neighbors"]],
|
|
1035
1097
|
[service.listEvents !== void 0, ["event.list"]],
|
|
1036
1098
|
[service.getContextProfile !== void 0, ["context.profile"]],
|
|
1037
|
-
[service.listAuditEvents !== void 0, ["audit.events"]]
|
|
1099
|
+
[service.listAuditEvents !== void 0, ["audit.events"]],
|
|
1100
|
+
[service.webSearch !== void 0, ["web.search"]],
|
|
1101
|
+
[service.webFetch !== void 0, ["web.fetch"]]
|
|
1038
1102
|
];
|
|
1039
1103
|
return [...baseNames, ...optionalNames.flatMap(([enabled, names]) => enabled ? names : [])];
|
|
1040
1104
|
}
|
|
@@ -1112,6 +1176,73 @@ var init_toolLog = __esm({
|
|
|
1112
1176
|
}
|
|
1113
1177
|
});
|
|
1114
1178
|
|
|
1179
|
+
// ../tools/dist/webToolRuntime.js
|
|
1180
|
+
function webToolHandlers(input, toolInput) {
|
|
1181
|
+
return {
|
|
1182
|
+
"web.search": () => executeWebSearch(input, toolInput),
|
|
1183
|
+
"web.fetch": () => executeWebFetch(input, toolInput)
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
function executeWebSearch(input, toolInput) {
|
|
1187
|
+
if (input.service.webSearch === void 0) {
|
|
1188
|
+
throw new Error("Tool 'web.search' is unavailable without a web search service contract.");
|
|
1189
|
+
}
|
|
1190
|
+
return input.service.webSearch({ auth: input.auth, ...parseWebSearch(toolInput) });
|
|
1191
|
+
}
|
|
1192
|
+
function executeWebFetch(input, toolInput) {
|
|
1193
|
+
if (input.service.webFetch === void 0) {
|
|
1194
|
+
throw new Error("Tool 'web.fetch' is unavailable without a web fetch service contract.");
|
|
1195
|
+
}
|
|
1196
|
+
return input.service.webFetch({ auth: input.auth, ...parseWebFetch(toolInput) });
|
|
1197
|
+
}
|
|
1198
|
+
function parseWebSearch(input) {
|
|
1199
|
+
const parsed = {
|
|
1200
|
+
query: requireString(input, "query"),
|
|
1201
|
+
limit: requireBoundedInteger(input, "limit", 5, 1, 10)
|
|
1202
|
+
};
|
|
1203
|
+
if (input.recencyDays !== void 0) {
|
|
1204
|
+
parsed.recencyDays = requireBoundedInteger(input, "recencyDays", 30, 1, 3650);
|
|
1205
|
+
}
|
|
1206
|
+
if (input.allowedDomains !== void 0) {
|
|
1207
|
+
parsed.allowedDomains = requireBoundedStringArray(input, "allowedDomains", 10);
|
|
1208
|
+
}
|
|
1209
|
+
if (input.blockedDomains !== void 0) {
|
|
1210
|
+
parsed.blockedDomains = requireBoundedStringArray(input, "blockedDomains", 10);
|
|
1211
|
+
}
|
|
1212
|
+
return parsed;
|
|
1213
|
+
}
|
|
1214
|
+
function parseWebFetch(input) {
|
|
1215
|
+
return {
|
|
1216
|
+
url: requireString(input, "url"),
|
|
1217
|
+
maxChars: requireBoundedInteger(input, "maxChars", 8e3, 1, 12e3)
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function requireBoundedStringArray(input, key, maxItems) {
|
|
1221
|
+
const value = input[key];
|
|
1222
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
1223
|
+
throw new Error(`${key} must be a string array.`);
|
|
1224
|
+
}
|
|
1225
|
+
const values = value.map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1226
|
+
if (values.length > maxItems) {
|
|
1227
|
+
throw new Error(`${key} must contain no more than ${maxItems} items.`);
|
|
1228
|
+
}
|
|
1229
|
+
return values;
|
|
1230
|
+
}
|
|
1231
|
+
function requireBoundedInteger(input, key, fallback, minimum, maximum) {
|
|
1232
|
+
const value = input[key];
|
|
1233
|
+
const integer = value === void 0 ? fallback : value;
|
|
1234
|
+
if (!Number.isInteger(integer) || Number(integer) < minimum || Number(integer) > maximum) {
|
|
1235
|
+
throw new Error(`${key} must be an integer between ${minimum} and ${maximum}.`);
|
|
1236
|
+
}
|
|
1237
|
+
return Number(integer);
|
|
1238
|
+
}
|
|
1239
|
+
var init_webToolRuntime = __esm({
|
|
1240
|
+
"../tools/dist/webToolRuntime.js"() {
|
|
1241
|
+
"use strict";
|
|
1242
|
+
init_inputParsers();
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1115
1246
|
// ../tools/dist/executor.js
|
|
1116
1247
|
function createRuntimeToolExecutor(input) {
|
|
1117
1248
|
const availableToolNames = runtimeAvailableToolNames(input.service);
|
|
@@ -1179,6 +1310,7 @@ function createToolHandlers(input, toolInput) {
|
|
|
1179
1310
|
"context.profile": () => executeContextProfile(input, toolInput),
|
|
1180
1311
|
"decision.create": () => executeDecisionCreate(input, toolInput),
|
|
1181
1312
|
"task.create": () => executeTaskCreate(input, toolInput),
|
|
1313
|
+
...webToolHandlers(input, toolInput),
|
|
1182
1314
|
...skillToolHandlers(input, toolInput),
|
|
1183
1315
|
...agentIdentityToolHandlers(input, toolInput),
|
|
1184
1316
|
...contractToolHandlers(input),
|
|
@@ -1238,6 +1370,21 @@ function executeSearchQuery(input, toolInput) {
|
|
|
1238
1370
|
}
|
|
1239
1371
|
function executeContextAssemble(input, toolInput) {
|
|
1240
1372
|
const query = parseContextQuery(toolInput);
|
|
1373
|
+
if (input.service.assembleGroundedContext !== void 0) {
|
|
1374
|
+
return input.service.assembleGroundedContext({
|
|
1375
|
+
auth: input.auth,
|
|
1376
|
+
query: query.query,
|
|
1377
|
+
queryIssuedAt: query.queryIssuedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1378
|
+
timezone: query.timezone ?? "UTC",
|
|
1379
|
+
requester: {
|
|
1380
|
+
principalId: input.auth.principalId,
|
|
1381
|
+
actorId: input.auth.actorId
|
|
1382
|
+
},
|
|
1383
|
+
surface: taskGuidanceSurface(input.transport),
|
|
1384
|
+
limit: 8,
|
|
1385
|
+
maxChars: query.maxChars
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1241
1388
|
return input.service.retrieveCitedContext({
|
|
1242
1389
|
auth: input.auth,
|
|
1243
1390
|
query: query.query,
|
|
@@ -1245,6 +1392,13 @@ function executeContextAssemble(input, toolInput) {
|
|
|
1245
1392
|
maxChars: query.maxChars
|
|
1246
1393
|
});
|
|
1247
1394
|
}
|
|
1395
|
+
function taskGuidanceSurface(transport) {
|
|
1396
|
+
if (transport === "cli")
|
|
1397
|
+
return "cli";
|
|
1398
|
+
if (transport === "hosted_mcp" || transport === "local_mcp")
|
|
1399
|
+
return "mcp";
|
|
1400
|
+
return "app";
|
|
1401
|
+
}
|
|
1248
1402
|
function executeContextProfile(input, toolInput) {
|
|
1249
1403
|
if (input.service.getContextProfile === void 0) {
|
|
1250
1404
|
throw new Error("Tool 'context.profile' requires a profile read service contract.");
|
|
@@ -1356,6 +1510,7 @@ var init_executor = __esm({
|
|
|
1356
1510
|
init_toolAvailability();
|
|
1357
1511
|
init_results();
|
|
1358
1512
|
init_toolLog();
|
|
1513
|
+
init_webToolRuntime();
|
|
1359
1514
|
}
|
|
1360
1515
|
});
|
|
1361
1516
|
|
|
@@ -1394,7 +1549,7 @@ function createMcpAdapter(input) {
|
|
|
1394
1549
|
...IMPLEMENTED_TOOL_NAMES
|
|
1395
1550
|
];
|
|
1396
1551
|
const availableNameSet = new Set(availableToolNames);
|
|
1397
|
-
const available = listToolDefinitions().filter((tool) => availableNameSet.has(tool.name) && tool.transports.includes(input.transport) && input.capabilities
|
|
1552
|
+
const available = listToolDefinitions().filter((tool) => availableNameSet.has(tool.name) && tool.transports.includes(input.transport) && isToolAuthorized(input.capabilities, tool));
|
|
1398
1553
|
return {
|
|
1399
1554
|
listTools() {
|
|
1400
1555
|
return createMcpToolSchemas({
|
|
@@ -1546,89 +1701,41 @@ var init_hostedMcpEntrypoint = __esm({
|
|
|
1546
1701
|
}
|
|
1547
1702
|
});
|
|
1548
1703
|
|
|
1549
|
-
// ../tools/dist/
|
|
1550
|
-
function
|
|
1704
|
+
// ../tools/dist/mcpJsonRpcCore.js
|
|
1705
|
+
function createMcpJsonRpcCore(input) {
|
|
1706
|
+
const { adapter, config: config2 } = input;
|
|
1551
1707
|
return {
|
|
1552
|
-
async
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
capabilities: serverInput.capabilities,
|
|
1556
|
-
executor: serverInput.executor
|
|
1557
|
-
});
|
|
1558
|
-
let buffer = "";
|
|
1559
|
-
input.input.setEncoding("utf8");
|
|
1560
|
-
for await (const chunk of input.input) {
|
|
1561
|
-
buffer += chunk;
|
|
1562
|
-
let newline = buffer.indexOf("\n");
|
|
1563
|
-
while (newline >= 0) {
|
|
1564
|
-
const line = buffer.slice(0, newline).trim();
|
|
1565
|
-
buffer = buffer.slice(newline + 1);
|
|
1566
|
-
if (line.length > 0) {
|
|
1567
|
-
await handleLine(line, adapter, input.output, input.error);
|
|
1568
|
-
}
|
|
1569
|
-
newline = buffer.indexOf("\n");
|
|
1570
|
-
}
|
|
1708
|
+
async handleMessage(message) {
|
|
1709
|
+
if (message.id === void 0) {
|
|
1710
|
+
return null;
|
|
1571
1711
|
}
|
|
1572
|
-
const
|
|
1573
|
-
if (
|
|
1574
|
-
|
|
1712
|
+
const id = normalizeId(message.id);
|
|
1713
|
+
if (message.jsonrpc !== "2.0" || typeof message.method !== "string") {
|
|
1714
|
+
return errorResponse(id, -32600, "Invalid Request");
|
|
1715
|
+
}
|
|
1716
|
+
try {
|
|
1717
|
+
return {
|
|
1718
|
+
jsonrpc: "2.0",
|
|
1719
|
+
id,
|
|
1720
|
+
result: await dispatchRequest(message.method, message.params, adapter, config2)
|
|
1721
|
+
};
|
|
1722
|
+
} catch (err) {
|
|
1723
|
+
return errorResponse(id, -32601, err instanceof Error ? err.message : "Method not found");
|
|
1575
1724
|
}
|
|
1576
1725
|
}
|
|
1577
1726
|
};
|
|
1578
1727
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
try {
|
|
1582
|
-
message = JSON.parse(line);
|
|
1583
|
-
} catch {
|
|
1584
|
-
writeResponse(output, {
|
|
1585
|
-
jsonrpc: "2.0",
|
|
1586
|
-
id: null,
|
|
1587
|
-
error: { code: -32700, message: "Parse error" }
|
|
1588
|
-
});
|
|
1589
|
-
return;
|
|
1590
|
-
}
|
|
1591
|
-
if (message.id === void 0) {
|
|
1592
|
-
if (message.method === "notifications/initialized")
|
|
1593
|
-
return;
|
|
1594
|
-
error?.write(`Ignoring MCP notification '${String(message.method)}'.
|
|
1595
|
-
`);
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
|
-
const id = normalizeId(message.id);
|
|
1599
|
-
if (message.jsonrpc !== "2.0" || typeof message.method !== "string") {
|
|
1600
|
-
writeResponse(output, {
|
|
1601
|
-
jsonrpc: "2.0",
|
|
1602
|
-
id,
|
|
1603
|
-
error: { code: -32600, message: "Invalid Request" }
|
|
1604
|
-
});
|
|
1605
|
-
return;
|
|
1606
|
-
}
|
|
1607
|
-
try {
|
|
1608
|
-
writeResponse(output, {
|
|
1609
|
-
jsonrpc: "2.0",
|
|
1610
|
-
id,
|
|
1611
|
-
result: await dispatchRequest(message.method, message.params, adapter)
|
|
1612
|
-
});
|
|
1613
|
-
} catch (err) {
|
|
1614
|
-
writeResponse(output, {
|
|
1615
|
-
jsonrpc: "2.0",
|
|
1616
|
-
id,
|
|
1617
|
-
error: {
|
|
1618
|
-
code: -32601,
|
|
1619
|
-
message: err instanceof Error ? err.message : "Method not found"
|
|
1620
|
-
}
|
|
1621
|
-
});
|
|
1622
|
-
}
|
|
1728
|
+
function parseErrorResponse() {
|
|
1729
|
+
return errorResponse(null, -32700, "Parse error");
|
|
1623
1730
|
}
|
|
1624
|
-
async function dispatchRequest(method, params, adapter) {
|
|
1731
|
+
async function dispatchRequest(method, params, adapter, config2) {
|
|
1625
1732
|
if (method === "initialize") {
|
|
1626
1733
|
const requested = readProtocolVersion(params);
|
|
1627
1734
|
return {
|
|
1628
1735
|
protocolVersion: requested ?? MCP_PROTOCOL_VERSION,
|
|
1629
1736
|
capabilities: { tools: { listChanged: false } },
|
|
1630
|
-
serverInfo: { name:
|
|
1631
|
-
instructions:
|
|
1737
|
+
serverInfo: { name: config2.serverName, version: config2.version },
|
|
1738
|
+
instructions: config2.instructions
|
|
1632
1739
|
};
|
|
1633
1740
|
}
|
|
1634
1741
|
if (method === "ping")
|
|
@@ -1639,7 +1746,7 @@ async function dispatchRequest(method, params, adapter) {
|
|
|
1639
1746
|
const call = parseToolCall(params);
|
|
1640
1747
|
return adapter.callTool(call);
|
|
1641
1748
|
}
|
|
1642
|
-
throw new Error(`Method '${method}' is not supported by Sift
|
|
1749
|
+
throw new Error(`Method '${method}' is not supported by Sift MCP.`);
|
|
1643
1750
|
}
|
|
1644
1751
|
function readProtocolVersion(params) {
|
|
1645
1752
|
if (!isRecord(params))
|
|
@@ -1655,6 +1762,9 @@ function parseToolCall(params) {
|
|
|
1655
1762
|
arguments: isRecord(params.arguments) ? params.arguments : {}
|
|
1656
1763
|
};
|
|
1657
1764
|
}
|
|
1765
|
+
function errorResponse(id, code, message) {
|
|
1766
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
1767
|
+
}
|
|
1658
1768
|
function isRecord(value) {
|
|
1659
1769
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1660
1770
|
}
|
|
@@ -1663,16 +1773,80 @@ function normalizeId(value) {
|
|
|
1663
1773
|
return value;
|
|
1664
1774
|
return null;
|
|
1665
1775
|
}
|
|
1776
|
+
var MCP_PROTOCOL_VERSION;
|
|
1777
|
+
var init_mcpJsonRpcCore = __esm({
|
|
1778
|
+
"../tools/dist/mcpJsonRpcCore.js"() {
|
|
1779
|
+
"use strict";
|
|
1780
|
+
MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
// ../tools/dist/localMcpStdioServer.js
|
|
1785
|
+
function createLocalMcpStdioServer(input) {
|
|
1786
|
+
return {
|
|
1787
|
+
async serve(serverInput) {
|
|
1788
|
+
const adapter = createMcpAdapter({
|
|
1789
|
+
transport: "local_mcp",
|
|
1790
|
+
capabilities: serverInput.capabilities,
|
|
1791
|
+
executor: serverInput.executor
|
|
1792
|
+
});
|
|
1793
|
+
const core = createMcpJsonRpcCore({
|
|
1794
|
+
adapter,
|
|
1795
|
+
config: {
|
|
1796
|
+
serverName: "sift-local-mcp",
|
|
1797
|
+
version: "0.1.0",
|
|
1798
|
+
instructions: LOCAL_INSTRUCTIONS
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
let buffer = "";
|
|
1802
|
+
input.input.setEncoding("utf8");
|
|
1803
|
+
for await (const chunk of input.input) {
|
|
1804
|
+
buffer += chunk;
|
|
1805
|
+
let newline = buffer.indexOf("\n");
|
|
1806
|
+
while (newline >= 0) {
|
|
1807
|
+
const line = buffer.slice(0, newline).trim();
|
|
1808
|
+
buffer = buffer.slice(newline + 1);
|
|
1809
|
+
if (line.length > 0) {
|
|
1810
|
+
await handleLine(line, core, input.output, input.error);
|
|
1811
|
+
}
|
|
1812
|
+
newline = buffer.indexOf("\n");
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
const trailing = buffer.trim();
|
|
1816
|
+
if (trailing.length > 0) {
|
|
1817
|
+
await handleLine(trailing, core, input.output, input.error);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
async function handleLine(line, core, output, error) {
|
|
1823
|
+
let message;
|
|
1824
|
+
try {
|
|
1825
|
+
message = JSON.parse(line);
|
|
1826
|
+
} catch {
|
|
1827
|
+
writeResponse(output, parseErrorResponse());
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
if (message.id === void 0 && message.method !== "notifications/initialized") {
|
|
1831
|
+
error?.write(`Ignoring MCP notification '${String(message.method)}'.
|
|
1832
|
+
`);
|
|
1833
|
+
}
|
|
1834
|
+
const response = await core.handleMessage(message);
|
|
1835
|
+
if (response !== null) {
|
|
1836
|
+
writeResponse(output, response);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1666
1839
|
function writeResponse(output, response) {
|
|
1667
1840
|
output.write(`${JSON.stringify(response)}
|
|
1668
1841
|
`);
|
|
1669
1842
|
}
|
|
1670
|
-
var
|
|
1843
|
+
var LOCAL_INSTRUCTIONS;
|
|
1671
1844
|
var init_localMcpStdioServer = __esm({
|
|
1672
1845
|
"../tools/dist/localMcpStdioServer.js"() {
|
|
1673
1846
|
"use strict";
|
|
1674
1847
|
init_mcpAdapter();
|
|
1675
|
-
|
|
1848
|
+
init_mcpJsonRpcCore();
|
|
1849
|
+
LOCAL_INSTRUCTIONS = "Call contract.get first and echo its contractVersion on every other Sift tool call. Use Sift tools to read and write the hosted canonical brain.";
|
|
1676
1850
|
}
|
|
1677
1851
|
});
|
|
1678
1852
|
|
|
@@ -1686,11 +1860,13 @@ __export(dist_exports, {
|
|
|
1686
1860
|
createHostedMcpEntrypoint: () => createHostedMcpEntrypoint,
|
|
1687
1861
|
createLocalMcpStdioServer: () => createLocalMcpStdioServer,
|
|
1688
1862
|
createMcpAdapter: () => createMcpAdapter,
|
|
1863
|
+
createMcpJsonRpcCore: () => createMcpJsonRpcCore,
|
|
1689
1864
|
createMcpToolSchemas: () => createMcpToolSchemas,
|
|
1690
1865
|
createRuntimeToolExecutor: () => createRuntimeToolExecutor,
|
|
1691
1866
|
isGatedTool: () => isGatedTool,
|
|
1692
1867
|
isToolAuthorized: () => isToolAuthorized,
|
|
1693
|
-
listToolDefinitions: () => listToolDefinitions
|
|
1868
|
+
listToolDefinitions: () => listToolDefinitions,
|
|
1869
|
+
parseErrorResponse: () => parseErrorResponse
|
|
1694
1870
|
});
|
|
1695
1871
|
var init_dist = __esm({
|
|
1696
1872
|
"../tools/dist/index.js"() {
|
|
@@ -1700,13 +1876,14 @@ var init_dist = __esm({
|
|
|
1700
1876
|
init_hostedMcpEntrypoint();
|
|
1701
1877
|
init_localMcpStdioServer();
|
|
1702
1878
|
init_mcpAdapter();
|
|
1879
|
+
init_mcpJsonRpcCore();
|
|
1703
1880
|
init_gating();
|
|
1704
1881
|
init_registry();
|
|
1705
1882
|
}
|
|
1706
1883
|
});
|
|
1707
1884
|
|
|
1708
1885
|
// src/index.ts
|
|
1709
|
-
import { readFile } from "fs/promises";
|
|
1886
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1710
1887
|
|
|
1711
1888
|
// src/support.ts
|
|
1712
1889
|
import { createHash } from "crypto";
|
|
@@ -2123,6 +2300,9 @@ function withContractVersion(executor, contractVersion) {
|
|
|
2123
2300
|
};
|
|
2124
2301
|
}
|
|
2125
2302
|
|
|
2303
|
+
// src/specialCommands.ts
|
|
2304
|
+
import { readFile } from "fs/promises";
|
|
2305
|
+
|
|
2126
2306
|
// src/doctor.ts
|
|
2127
2307
|
async function doctor(input) {
|
|
2128
2308
|
const checks = [
|
|
@@ -2287,6 +2467,7 @@ function toolNamesFromResult(result2) {
|
|
|
2287
2467
|
// src/simpleCommands.ts
|
|
2288
2468
|
var knownTopLevelCommands = /* @__PURE__ */ new Set([
|
|
2289
2469
|
"add",
|
|
2470
|
+
"agent",
|
|
2290
2471
|
"ask",
|
|
2291
2472
|
"audit",
|
|
2292
2473
|
"auth",
|
|
@@ -2596,6 +2777,201 @@ function argsWithoutOptions(args) {
|
|
|
2596
2777
|
return positionals;
|
|
2597
2778
|
}
|
|
2598
2779
|
|
|
2780
|
+
// src/skill/skillCommands.ts
|
|
2781
|
+
import { access, mkdir as nodeMkdir, writeFile as nodeWriteFile } from "fs/promises";
|
|
2782
|
+
import { homedir } from "os";
|
|
2783
|
+
import { isAbsolute, join, resolve } from "path";
|
|
2784
|
+
|
|
2785
|
+
// src/skill/skillContent.ts
|
|
2786
|
+
var SIFT_SETUP_SKILL_MARKDOWN = '---\nname: sift-setup\ndescription: Connect this agent to Sift, the team\'s shared cited brain, by setting up the Sift CLI: install it, have the human sign in, register this agent on the workspace, and confirm it works. Use this skill whenever the user pastes a Sift onboarding/setup prompt or says anything like "set up Sift", "connect me to Sift", "install the sift CLI", "sign me in to Sift", or "give you access to our brain" \u2014 even if they never say "CLI". This is first-run setup; once `sift doctor` is green you have full access and can use the brain. Hand off to the sift-cli skill for the full read/write playbook.\n---\n\n# Sift Setup\n\nSift is your team\'s shared, cited brain: context that people and agents both read\nand write. The `sift` CLI is how you reach it. Once the human signs in and you\nregister yourself, you are an agent on the workspace with full access \u2014 you read\nthe brain before answering and write back to it like a teammate.\n\nWork top to bottom. Each step says how to confirm it. If a step is genuinely\nblocked, say so and ask the human for the one thing you need \u2014 do not fake\nprogress. Never print token values, `.env` contents, or keychain secrets.\n\n## 1. Install the CLI\n\nThe package is `@sift-wiki/cli` (Node.js 20+); it installs a `sift` command.\n\n- If it will be used repeatedly, install it globally:\n\n ```bash\n npm install -g @sift-wiki/cli\n ```\n\n- For a one-off or a sandbox, run it with no install: `npx -y\n @sift-wiki/cli@latest <command>`.\n\nIf `sift` is missing right after a global install, npm\'s global bin directory is\nnot on `PATH` \u2014 find it with `npm config get prefix` and ensure `<prefix>/bin` is\non `PATH`. If the install fails, confirm Node is 20+ with `node --version`.\n\nConfirm: `sift auth status --json` runs (it will say `{"auth":"none"}` until\nsign-in \u2014 expected).\n\n## 2. The human signs in\n\n`sift login` opens the browser sign-in and stores the profile on this machine.\n\n- Run `sift login` (or ask the human to), then have them finish the approval in\n the browser tab that opened. Wait for it to complete. One sign-in covers this\n machine.\n- Headless box with no browser (CI, a remote runner): use env-token auth instead\n with credentials the human provides \u2014 `SIFT_API_BASE_URL`, `SIFT_API_TOKEN`,\n `SIFT_WORKSPACE_ID`, `SIFT_BRAIN_ID`, `SIFT_PRINCIPAL_ID`,\n `SIFT_TOKEN_CAPABILITIES`. Never invent these.\n\nConfirm: `sift auth status --json` reports `"auth":"stored"` (sign-in) or\n`"auth":"env"` (token).\n\n## 3. Register yourself as an agent\n\nThis puts you on the workspace as an agent and signs your writes as you.\n\n- Set `SIFT_AGENT="<your product name>"` on every `sift` call (for example\n `SIFT_AGENT="Claude Code"`), or pass `--as-agent "<name>"`. Use your own\n product identity \u2014 never the human\'s name, never the device label.\n- Register once:\n\n ```bash\n sift agent register --name "<your product name>" --description "<one line>"\n ```\n\n Registration is idempotent and makes you visible to the workspace on the People\n page. If your name is refused as "removed from this workspace," a human removed\n it deliberately \u2014 stop and tell the user; do not assert a different name.\n\nConfirm: `sift agent status --json` shows your registration.\n\n## 4. Confirm it works\n\n```bash\nsift doctor --json\n```\n\nWhen the checks pass, you are connected with full access \u2014 say so. If a check\nfails, fix that one thing (missing command \u2192 install; missing auth \u2192 `sift\nlogin`; API unreachable \u2192 report the configured base URL and ask the human).\nDon\'t fall back to local files; local files are not the brain.\n\n## 5. Now use it \u2014 write like a teammate\n\nYou have full access. Treat Sift as your source of truth.\n\n- Read before answering: `sift ask "<question>"` or `sift search "<query>"`.\n- Before substantive work, run `sift context assemble "<task>"` or the equivalent\n `sift ask` path and inspect any returned `## Task guidance` section. That\n section may contain relevant workspace skills/custom prompts. Follow it before\n producing output, and if you use a matched skill and your token can write,\n record `skill.exercise` after the output.\n- **Write freely** for routine, non-destructive things \u2014 capturing notes,\n context, decisions, and tasks (`sift remember`, `sift add`, `sift decide`,\n `sift todo`). You don\'t need to ask permission to record what\'s worth keeping;\n that\'s the job.\n- **Ask the human first only for important or destructive changes** \u2014 deleting or\n overwriting existing records, editing someone else\'s work, or recording a\n consequential decision. The test is simple: if it\'s hard to undo or could\n mislead the team, confirm first; otherwise just do it.\n\nFor the full read/write playbook (context assembly, capture-before-derived,\npatching records, citations), load the companion skill:\n\n```bash\nsift skill print sift-cli\n```\n\n## Report back\n\nWhen setup is done, tell the human, without exposing secrets: which CLI path you\nused, the auth source (`stored`, `env`, or `none`), the agent name you\nregistered, and that the brain is reachable \u2014 or the one step that\'s blocked and\nwhat you need from them.\n';
|
|
2787
|
+
var SIFT_CLI_SKILL_MARKDOWN = '---\nname: sift-cli\ndescription: Use this skill whenever an agent needs to use the Sift CLI to read from or write to the Sift brain, including searching, assembling context, capturing text or files, patching records, creating decisions or tasks, debugging auth/scope, handling local API or sandbox failures, or falling back from missing `sift` on PATH. Prefer this skill for Sift CLI work even if the user only says "use Sift", "query the brain", "capture this", "remember this", "record a decision", or "what is latest in Sift".\n---\n\n# Sift CLI\n\nUse the Sift CLI as a thin client to the hosted Sift brain. The hosted brain is\ncanonical. Local files, terminal output, and chat text are not canonical until\ncaptured into Sift.\n\nThe CLI package is `@sift-wiki/cli` and it installs a `sift` bin. The package is\nlive on npm, npm-first, and Node.js 20+. Install or upgrade with `@latest`. It is\na command package, not a public SDK. Do not import it as a library, publish\ninternal Sift packages, or make local files a source of truth.\n\nInstall the live CLI with:\n\n```bash\nnpm install -g @sift-wiki/cli\n```\n\nThe CLI bundles its own agent skills, versioned with the package. The first-run\nsetup skill is `sift-setup`; `sift skill install` installs it by default (writes\n`.claude/skills/sift-setup/SKILL.md`; add `--global` for `~/.claude/skills` or\n`--dir <path>`). Install this usage skill on disk with\n`sift skill install sift-cli`, print any skill with `sift skill print <name>`,\nor list bundled skills with `sift skill list`. The zero-install setup entry point\nis `npx -y @sift-wiki/cli@latest skill install`, which works before any global\ninstall. These skill commands are local and need no auth.\n\nFor one-off or headless use without a global install, run the live package\ndirectly from npm:\n\n```bash\nnpx -y @sift-wiki/cli@latest auth status --json\nnpm exec --yes --package @sift-wiki/cli@latest -- sift auth status --json\n```\n\nFor CLI distribution changes inside the repo, build, pack, and verify the\ninstalled tarball with `pnpm --filter @sift-wiki/cli pack:verify` before a\nrelease. This private monorepo owns the CLI source and verifier; npm publishes\nare cut from the public `goodnight000/sift-cli` release mirror so npm provenance\ncan point at public GitHub release source.\n\n## Preflight\n\nBefore reading or writing, establish the command path, auth, and scope.\n\n1. Prefer installed `sift` when it exists on `PATH`.\n2. If `sift` is missing outside the repo and the user needs repeated\n interactive use, install the live package with\n `npm install -g @sift-wiki/cli`.\n3. If `sift` is missing outside the repo and the user needs one-off, CI, or\n headless execution, use\n `npm exec --yes --package @sift-wiki/cli@latest -- sift ...` or\n `npx -y @sift-wiki/cli@latest ...`.\n4. For normal human setup, use `sift login`; it opens the existing browser login\n flow and stores the CLI profile.\n5. For CI/headless agents only, use env-token auth when the user or environment\n provides it: `SIFT_API_BASE_URL`, `SIFT_API_TOKEN`, `SIFT_WORKSPACE_ID`,\n `SIFT_BRAIN_ID`, `SIFT_PRINCIPAL_ID`, and `SIFT_TOKEN_CAPABILITIES`.\n6. Do not print `.env` files, token values, keychain output, bearer secrets, or\n full credential-store output.\n7. Check auth and scope with `sift auth status --json`, then\n `sift scope current --json` when authenticated.\n8. Declare your agent identity on every invocation: set\n `SIFT_AGENT="<your product name>"` (for example `SIFT_AGENT="Claude Code"`)\n in the environment of each `sift` call, or pass `--as-agent "<name>"`. Use\n your own product identity \u2014 never the device label and never the human\'s\n name. This keeps authorship correct when several agents share one CLI\n profile and token; your writes are stamped as you, acting for the human who\n approved the token. A human running `sift` directly sets nothing and stays\n plainly themselves.\n9. Once authenticated, check `sift agent status --json`; if it reports no\n agent identity registration for your name, run `sift agent register` with\n your product name and a one-line description. Registration is idempotent\n (re-running converges on the same identity and refreshes the description),\n requires only your usable token, and makes you visible to the workspace on\n the People page. First use of a `SIFT_AGENT` name also auto-registers it;\n explicit register is how you add a self-description.\n10. Run `sift doctor --json` when setup, auth, API reachability, or tool\n availability is unclear.\n11. In the `sift-v3` repo only, if `sift` is missing and you need the\n development CLI, build first and run the built JS entrypoint:\n\n ```bash\n pnpm --filter @sift-wiki/cli build\n node packages/cli/dist/bin/sift.js auth status --json\n ```\n\n12. Do not run TypeScript source as the CLI bin. The package bin points to\n `dist/bin/sift.js`.\n13. If a local development API is required and down, report that directly. Do\n not silently switch to local files.\n\nUse package verification only when the task is about distribution or installed\nartifact proof:\n\n```bash\npnpm --filter @sift-wiki/cli pack:verify\n```\n\nThat verifier packs `@sift-wiki/cli`, installs the tarball into an isolated npm\nprefix, runs installed unauthenticated `sift auth status --json`, then uses\nenv-token auth against a local fake hosted API to prove `auth status`,\n`whoami --json`, and `ask "package smoke" --json` with bearer, workspace, and\nbrain headers.\n\nStop and ask for the minimum missing permission or setup action when:\n\n- no runnable CLI path exists;\n- auth is missing or expired;\n- workspace or brain scope is missing;\n- the hosted/local API cannot be reached from the runtime;\n- sandbox/network restrictions block the command;\n- a required write is requested with read-only capabilities;\n- tool discovery says the required runtime contract is unavailable.\n- the user asks to publish a new CLI version that has not passed post-publish\n install smoke.\n\n## Fast Read Path\n\nUse one focused context command before broad search loops.\n\nPreferred simple commands, when available:\n\n```bash\nsift "what is latest with the company?"\nsift ask "what changed since the last meeting?"\nsift search "Slack ingestion launch"\nsift status --json\nsift whoami --json\n```\n\nCurrent power-command fallback:\n\n```bash\nsift context assemble "what is latest with the company?" --max-chars 12000 --json\nsift search query "Slack ingestion launch" --json\nsift context profile "reviewer evidence" --limit 6 --json\n```\n\nRules:\n\n- Use `context assemble` for grounded answers, summaries, handoffs, write\n preparation, and "latest/current" questions.\n- Before substantive work, inspect any returned `## Task guidance` section. It\n may contain relevant workspace skills/custom prompts; follow the matched\n skill/custom prompt before producing output.\n- If `## Task guidance` names a matched skill and you produce output informed by\n it, call `skill exercise` / `skill.exercise` after the output when your token\n can write. Use the skill id, pinned version id, surface, outputRef, and a\n stable idempotency key. If the token is read-only, do not claim exercise\n attribution was recorded.\n- Use `search query` for raw retrieval or to find candidate records.\n- Do not call `record get` until `tools list` or `doctor` proves it is backed by\n the runtime; older slices may advertise it while returning `tool_unavailable`.\n- Keep broad context calls capped with `--max-chars`; increase only when the\n returned context is too thin.\n- Include Sift citations, record IDs, version IDs, source IDs, headings, or\n chunk locators when the CLI returns them.\n\n## Fast Write Path\n\nWrites must go through Sift tools, not local notes.\n\nPreferred simple commands, when available:\n\n```bash\nsift remember "Follow up with Caleb about the Underscore intro."\nsift remember --stdin\nsift add ./meeting-notes.md\nsift decide "Ship the retrieval-only slice first."\nsift todo "Collect three evidence examples for onboarding."\nsift edit <record-id> --section Risks --replace "..."\n```\n\nCurrent power-command fallbacks:\n\n```bash\nsift capture text \\\n --source "CLI Capture" \\\n --external-id "cli-text:<stable-id>" \\\n --title "Follow up with Caleb" \\\n --visibility team \\\n --markdown "Follow up with Caleb about the Underscore intro."\n\nsift capture file ./meeting-notes.md \\\n --source "CLI Capture" \\\n --external-id "cli-file:<stable-id>" \\\n --title "Meeting notes" \\\n --visibility team\n\nsift decision create \\\n --statement "Ship the retrieval-only slice first." \\\n --state accepted \\\n --visibility team\n\nsift task create \\\n --title "Collect three evidence examples for onboarding." \\\n --status open \\\n --visibility team\n\nsift record patch-section <record-id> risks \\\n --replacement-markdown "..." \\\n --expected-markdown "..."\n```\n\nRules:\n\n- Verify the token has `record:write` before writes.\n- Verify the token has `source:write` before `remember`, `add`, `capture text`,\n `capture file`, or `capture batch`.\n- Prefer capture before derived records when the user supplies raw source.\n- Use stable external IDs for capture retries.\n- Read a record before patching it, and include expected content when available.\n- If a conflict is returned, surface current version metadata and do not\n overwrite.\n- Print or summarize write receipts with record, version, source item, and job\n IDs. Do not claim a write happened without a receipt.\n\n## Troubleshooting\n\nClassify failures before retrying.\n\n- **Command missing:** for repeated interactive use, install the live package\n with `npm install -g @sift-wiki/cli`. For one-off or headless use, run\n `npm exec --yes --package @sift-wiki/cli@latest -- sift ...` or\n `npx -y @sift-wiki/cli@latest ...`. In `sift-v3`, build and use\n `node packages/cli/dist/bin/sift.js` as the development fallback when working\n on unpublished local CLI changes.\n- **Auth missing:** run or request `sift login`; use env-token auth only when it\n is explicitly provided for CI/headless use.\n- **Read-only token:** reads may proceed, writes must stop.\n- **API down:** report the configured API base URL and failure class.\n- **Sandbox network block:** rerun with approved escalation only when the user\n asked for live CLI execution and policy allows it.\n- **Local listener blocked:** `pack:verify` uses a fake API on `127.0.0.1`; if\n the sandbox returns `listen EPERM`, rerun with approved escalation instead of\n weakening the installed-artifact test.\n- **Tool unavailable:** use `tools list`, `tools help`, or `doctor`; do not\n retry the same unsupported command repeatedly.\n- **Agent identity refused:** a `SIFT_AGENT` name that returns "has been\n removed from this workspace" was deliberately removed by a human. Stop and\n tell the user; do not work around it by asserting a different name.\n- **`agent` commands unavailable:** the installed CLI or the API predates\n agent workers; proceed without identity assertion and note the limitation.\n- **GitHub Actions publish blocked:** package publishing runs from the public\n `goodnight000/sift-cli` release mirror. The private `sift-v3` workflow is\n verify-only; do not publish `@sift-wiki/cli` from the private repo.\n- **Large output:** reduce `--max-chars`, search more narrowly, then assemble\n context for the narrowed query.\n\n## Expected Response\n\nWhen answering the user after CLI work:\n\n- State which CLI path was used: installed `sift`, zero-install `npx`/`npm exec`,\n built repo JS, or verified packed tarball.\n- State the auth source only at a safe level: `stored`, `env`, or `none`.\n- State the agent identity asserted (the `SIFT_AGENT` name), or that none was\n set.\n- State the API scope used without exposing secrets.\n- Summarize the answer or write result in normal prose.\n- Include Sift citations or write receipt IDs.\n- Call out limitations such as read-only auth, local API dependency, missing\n runtime contract, sandbox escalation, unpublished package version, or\n stale/unverified context.\n';
|
|
2788
|
+
var SIFT_AGENT_SKILL_MARKDOWN = '---\nname: sift-agent\ndescription: Use this skill whenever an external coding or research agent needs to read from, capture into, patch, or create work objects in a Sift brain through Sift CLI or MCP tools. Prefer this skill when the user mentions Sift, the hosted brain, brain records, captured source, cited context, decisions, tasks, or choosing between CLI and MCP access.\n---\n\n# Sift Agent\n\nUse Sift as the canonical shared brain for accumulated human and agent context.\nThe hosted Sift brain is canonical. Local files are not canonical brain state\nunless they have been captured into Sift; local files are not canonical by\nthemselves.\n\n## The Contract Comes First\n\nBefore any other Sift work, fetch the Sift agent contract and read it:\n\n- CLI: run `sift contract get --json`.\n- MCP: call `contract.get`.\n\nThe contract is the authoritative protocol (reading, writing, the learning\nloop, restraint) plus this workspace\'s own rules. Echo the returned\n`contractVersion` in the input of every other tool call (CLI: pass\n`--contract <version>`). A call refused with `contract_required` returns the\ncurrent contract in the error message \u2014 read it and retry with the new\nversion. The full rules live in the served contract, not in this file.\n\n## Transport Choice\n\nAgents should prefer CLI when it is already installed, authenticated, and\nscoped; otherwise use MCP.\n\n1. Prefer CLI when `sift` is installed, authenticated, and already scoped to the\n correct workspace and brain.\n2. Use MCP when CLI is unavailable, unauthenticated, unscoped, or blocked by the\n runtime.\n3. Do not ask the user to install CLI during the task if MCP tools are already\n available.\n4. Treat both CLI and MCP as access transports. Do not treat local markdown,\n local manifests, or chat transcript text as canonical storage.\n\n## First Checks\n\nAfter fetching the contract, establish identity and scope:\n\n- CLI: run `sift scope current --json` or `sift whoami --json`.\n- MCP: call `scope.current` or `whoami`.\n\nIf the tool reports missing scope, expired auth, or insufficient capability,\nreturn the compact tool error and ask for the minimum permission or reconnect\naction needed. Do not guess a workspace, brain, source, or principal.\n\n## Agent Identity\n\nDeclare who you are so your writes are authored as you, acting for the human\nwho approved the token:\n\n1. Set `SIFT_AGENT="<your product name>"` (for example "Claude Code") in the\n environment of every CLI invocation, or pass `--as-agent "<name>"`. Use\n your own product identity \u2014 never the device label and never the human\'s\n name. First use auto-registers you as a workspace agent worker, visible on\n the People page.\n2. Check `sift agent status --json` (CLI) or `agent.status` (MCP); register a\n one-line self-description with `sift agent register --name "<name>"\n --description "<one line>"` or `agent.register`. Registration is idempotent\n and requires only your usable token.\n3. Identity is authorship, not authority: asserting a name never changes what\n the token may read or write.\n4. If your asserted name is refused as removed from the workspace, stop and\n tell the user; do not assert a different name to work around it.\n\n## Search And Context\n\nSearch and assemble context before answering from memory:\n\n1. Use `search.query` for targeted lookup.\n2. Use `context.assemble` when the user needs a grounded answer, handoff, patch,\n decision, or task.\n3. Before substantive work, inspect any returned `## Task guidance` section.\n It may contain the relevant workspace skills/custom prompts for this task;\n follow the matched skill/custom prompt before producing output.\n4. Use `context.profile` only for durable profile or workspace context.\n5. Keep responses grounded in returned Sift citations. Do not invent facts\n outside the brain.\n\nCite record IDs, version IDs, source IDs, source item IDs, heading anchors, and\nchunk locators when the tool returns them. Prefer compact cited summaries over\ndumping large content.\n\nIf `## Task guidance` names a matched skill and you produce output informed by\nit, call `skill.exercise` after the output when your token can write. Use the\nskill id, pinned version id, surface (`cli`, `mcp`, or `api`), an outputRef, and\na stable idempotency key. If your token is read-only, do not claim exercise\nattribution was recorded.\n\n## Capture And Derived Writes\n\nCapture raw source before creating derived knowledge when possible:\n\n1. Use `capture.text` for copied text or markdown.\n2. Use `capture.file` for local files; the file path is only an input, not\n canonical brain state.\n3. Use `capture.batch` for bounded repeatable imports.\n4. Reuse stable external IDs or idempotency keys when retrying capture.\n\nOnly create a derived markdown record after the relevant raw source is captured\nor after the user explicitly asks for an authored record without source capture.\n\n## Records And Patches\n\nRead the current record version before editing. Patch bounded sections instead\nof rewriting whole records:\n\n- Use `record.get` to inspect the current record or requested section.\n- Patch bounded sections with `record.patch_section`.\n- Include expected content when available so conflicts are detected.\n- If a patch conflict is returned, show the current version metadata and ask for\n the next edit boundary. Do not silently overwrite.\n\n## Decisions And Tasks\n\nCreate decisions and tasks only from explicit user intent or grounded context:\n\n- Use `decision.create` when the user asks to record a decision or the context\n clearly contains a chosen course of action.\n- Use `task.create` when the user asks to track follow-up work or the context\n clearly assigns a next action.\n- Include rationale or explicit authorship.\n- Link evidence when available.\n- Do not create thread-like records; `thread.create` is unavailable until the\n Collaboration Threads spec owns that behavior.\n\n## Safety Boundaries\n\n- Do not use destructive forget, delete, broad admin, connector OAuth install,\n or hosted-agent run-control tools in this first tool slice.\n- Do not expose token values, secrets, raw private snippets, or full provider\n payloads in logs or user-facing messages.\n- Preserve principal, actor, request, workspace, brain, and source scope across\n tool calls.\n- If a permission denial hides a private object, do not reveal private\n existence, title, count, or snippet.\n';
|
|
2789
|
+
|
|
2790
|
+
// src/skill/skillCommands.ts
|
|
2791
|
+
var BUNDLED_SKILLS = [
|
|
2792
|
+
{
|
|
2793
|
+
name: "sift-setup",
|
|
2794
|
+
summary: "Set up the Sift CLI and connect this agent to the brain (first run).",
|
|
2795
|
+
markdown: SIFT_SETUP_SKILL_MARKDOWN
|
|
2796
|
+
},
|
|
2797
|
+
{
|
|
2798
|
+
name: "sift-cli",
|
|
2799
|
+
summary: "Set up and use the Sift CLI as a thin client to the hosted brain.",
|
|
2800
|
+
markdown: SIFT_CLI_SKILL_MARKDOWN
|
|
2801
|
+
},
|
|
2802
|
+
{
|
|
2803
|
+
name: "sift-agent",
|
|
2804
|
+
summary: "Read, capture, and patch the Sift brain over CLI or MCP.",
|
|
2805
|
+
markdown: SIFT_AGENT_SKILL_MARKDOWN
|
|
2806
|
+
}
|
|
2807
|
+
];
|
|
2808
|
+
var DEFAULT_SKILL = "sift-setup";
|
|
2809
|
+
function defaultSkillIo(input) {
|
|
2810
|
+
return {
|
|
2811
|
+
writeFile: (path, data) => nodeWriteFile(path, data, "utf8"),
|
|
2812
|
+
mkdir: async (path) => {
|
|
2813
|
+
await nodeMkdir(path, { recursive: true });
|
|
2814
|
+
},
|
|
2815
|
+
pathExists: async (path) => {
|
|
2816
|
+
try {
|
|
2817
|
+
await access(path);
|
|
2818
|
+
return true;
|
|
2819
|
+
} catch {
|
|
2820
|
+
return false;
|
|
2821
|
+
}
|
|
2822
|
+
},
|
|
2823
|
+
homeDir: input?.homeDir ?? homedir(),
|
|
2824
|
+
cwd: input?.cwd ?? process.cwd()
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
async function runSkillCommand(input) {
|
|
2828
|
+
const [subcommand, ...rest] = input.rest;
|
|
2829
|
+
if (subcommand === void 0 || subcommand === "list") {
|
|
2830
|
+
return listSkills(input.json);
|
|
2831
|
+
}
|
|
2832
|
+
if (subcommand === "print" || subcommand === "show") {
|
|
2833
|
+
return printSkill(rest, input.json);
|
|
2834
|
+
}
|
|
2835
|
+
if (subcommand === "install") {
|
|
2836
|
+
return installSkill(rest, input.json, input.io);
|
|
2837
|
+
}
|
|
2838
|
+
return errorResultWithCode(
|
|
2839
|
+
"tool_unavailable",
|
|
2840
|
+
`Unknown skill subcommand '${subcommand}'. Use 'list', 'print [name]', or 'install [name]'.`,
|
|
2841
|
+
input.json
|
|
2842
|
+
);
|
|
2843
|
+
}
|
|
2844
|
+
function listSkills(json) {
|
|
2845
|
+
if (json) {
|
|
2846
|
+
return ok(
|
|
2847
|
+
`${JSON.stringify({
|
|
2848
|
+
skills: BUNDLED_SKILLS.map(({ name, summary }) => ({ name, summary }))
|
|
2849
|
+
})}
|
|
2850
|
+
`
|
|
2851
|
+
);
|
|
2852
|
+
}
|
|
2853
|
+
const lines = BUNDLED_SKILLS.map((skill) => `${skill.name}: ${skill.summary}`);
|
|
2854
|
+
return ok(`${lines.join("\n")}
|
|
2855
|
+
`);
|
|
2856
|
+
}
|
|
2857
|
+
function printSkill(rest, json) {
|
|
2858
|
+
const requested = positionalArgs(rest)[0];
|
|
2859
|
+
const skill = findSkill(requested);
|
|
2860
|
+
if (skill === void 0) {
|
|
2861
|
+
return unknownSkill(requested, json);
|
|
2862
|
+
}
|
|
2863
|
+
if (json) {
|
|
2864
|
+
return ok(`${JSON.stringify({ skill: skill.name, markdown: skill.markdown })}
|
|
2865
|
+
`);
|
|
2866
|
+
}
|
|
2867
|
+
return ok(withTrailingNewline(skill.markdown));
|
|
2868
|
+
}
|
|
2869
|
+
async function installSkill(rest, json, io) {
|
|
2870
|
+
const requested = positionalArgs(rest)[0];
|
|
2871
|
+
const skill = findSkill(requested);
|
|
2872
|
+
if (skill === void 0) {
|
|
2873
|
+
return unknownSkill(requested, json);
|
|
2874
|
+
}
|
|
2875
|
+
const parsed = parseOptions(rest);
|
|
2876
|
+
const baseDir = resolveBaseDir({ rest, parsed, io });
|
|
2877
|
+
const targetDir = join(baseDir, skill.name);
|
|
2878
|
+
const targetPath = join(targetDir, "SKILL.md");
|
|
2879
|
+
const existed = await io.pathExists(targetPath);
|
|
2880
|
+
await io.mkdir(targetDir);
|
|
2881
|
+
const markdown = withTrailingNewline(skill.markdown);
|
|
2882
|
+
await io.writeFile(targetPath, markdown);
|
|
2883
|
+
const status2 = existed ? "updated" : "created";
|
|
2884
|
+
if (json) {
|
|
2885
|
+
return ok(
|
|
2886
|
+
`${JSON.stringify({
|
|
2887
|
+
installed: true,
|
|
2888
|
+
skill: skill.name,
|
|
2889
|
+
path: targetPath,
|
|
2890
|
+
status: status2,
|
|
2891
|
+
bytes: Buffer.byteLength(markdown, "utf8")
|
|
2892
|
+
})}
|
|
2893
|
+
`
|
|
2894
|
+
);
|
|
2895
|
+
}
|
|
2896
|
+
return ok(renderInstall(skill.name, targetPath, status2));
|
|
2897
|
+
}
|
|
2898
|
+
function resolveBaseDir(input) {
|
|
2899
|
+
const explicit = optionalOption(input.parsed, "dir");
|
|
2900
|
+
if (explicit !== void 0) {
|
|
2901
|
+
return isAbsolute(explicit) ? explicit : resolve(input.io.cwd, explicit);
|
|
2902
|
+
}
|
|
2903
|
+
if (input.rest.includes("--global")) {
|
|
2904
|
+
return join(input.io.homeDir, ".claude", "skills");
|
|
2905
|
+
}
|
|
2906
|
+
return resolve(input.io.cwd, ".claude", "skills");
|
|
2907
|
+
}
|
|
2908
|
+
function findSkill(name) {
|
|
2909
|
+
const target = name ?? DEFAULT_SKILL;
|
|
2910
|
+
return BUNDLED_SKILLS.find((skill) => skill.name === target);
|
|
2911
|
+
}
|
|
2912
|
+
function unknownSkill(name, json) {
|
|
2913
|
+
const available = BUNDLED_SKILLS.map((skill) => skill.name).join(", ");
|
|
2914
|
+
return errorResultWithCode(
|
|
2915
|
+
"validation_failure",
|
|
2916
|
+
`Unknown skill '${name}'. Available skills: ${available}.`,
|
|
2917
|
+
json
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
2920
|
+
function renderInstall(name, path, status2) {
|
|
2921
|
+
const verb = status2 === "created" ? "Installed" : "Updated";
|
|
2922
|
+
return [
|
|
2923
|
+
`${verb} the Sift skill: ${name}`,
|
|
2924
|
+
`Path: ${path}`,
|
|
2925
|
+
"",
|
|
2926
|
+
"Next, open that SKILL.md and follow it to finish setup:",
|
|
2927
|
+
" 1. Put the CLI on PATH: npm install -g @sift-wiki/cli",
|
|
2928
|
+
" 2. Authenticate: sift login",
|
|
2929
|
+
' 3. Identify yourself: set SIFT_AGENT to your product name (e.g. "Claude Code"), then sift agent register',
|
|
2930
|
+
" 4. Confirm: sift doctor",
|
|
2931
|
+
"",
|
|
2932
|
+
"Then use Sift as your source of truth: search and assemble context before",
|
|
2933
|
+
"answering, and capture decisions and notes back into the brain.",
|
|
2934
|
+
""
|
|
2935
|
+
].join("\n");
|
|
2936
|
+
}
|
|
2937
|
+
function withTrailingNewline(markdown) {
|
|
2938
|
+
return markdown.endsWith("\n") ? markdown : `${markdown}
|
|
2939
|
+
`;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
// src/specialCommands.ts
|
|
2943
|
+
async function runSpecialCommand(input, args, json, group, command) {
|
|
2944
|
+
if (group === "doctor" && command === void 0) {
|
|
2945
|
+
return doctor({
|
|
2946
|
+
config: input.config,
|
|
2947
|
+
executor: input.executor,
|
|
2948
|
+
json,
|
|
2949
|
+
now: input.now ?? /* @__PURE__ */ new Date()
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
if (group === "skill") {
|
|
2953
|
+
const io = input.skillIo ?? defaultSkillIo({ cwd: input.cwd, homeDir: input.homeDir });
|
|
2954
|
+
return runSkillCommand({ rest: args.slice(1), json, io });
|
|
2955
|
+
}
|
|
2956
|
+
const simpleCommand = resolveSimpleCommand({
|
|
2957
|
+
args,
|
|
2958
|
+
json,
|
|
2959
|
+
config: input.config,
|
|
2960
|
+
executor: input.executor,
|
|
2961
|
+
readFile: input.readFile ?? readFile,
|
|
2962
|
+
readStdin: input.readStdin,
|
|
2963
|
+
now: input.now ?? /* @__PURE__ */ new Date()
|
|
2964
|
+
});
|
|
2965
|
+
if (simpleCommand === void 0) return void 0;
|
|
2966
|
+
try {
|
|
2967
|
+
validateAuthenticatedScope(input.config, input.now ?? /* @__PURE__ */ new Date());
|
|
2968
|
+
validateCommandCapability({ commandKey: simpleCommand.commandKey, config: input.config });
|
|
2969
|
+
return await simpleCommand.run();
|
|
2970
|
+
} catch (error) {
|
|
2971
|
+
return errorResult(error, json);
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2599
2975
|
// src/toolDiscovery.ts
|
|
2600
2976
|
function toolsList(input) {
|
|
2601
2977
|
if (input.executor !== void 0) {
|
|
@@ -2730,8 +3106,8 @@ async function runSiftCli(rawInput) {
|
|
|
2730
3106
|
json
|
|
2731
3107
|
}),
|
|
2732
3108
|
"capture:text": () => captureText(input.executor, rest, json),
|
|
2733
|
-
"capture:file": () => captureFile(input.executor, input.readFile ??
|
|
2734
|
-
"capture:batch": () => captureBatch(input.executor, input.readFile ??
|
|
3109
|
+
"capture:file": () => captureFile(input.executor, input.readFile ?? readFile2, rest, json),
|
|
3110
|
+
"capture:batch": () => captureBatch(input.executor, input.readFile ?? readFile2, rest, json),
|
|
2735
3111
|
"source:list": () => sourceList(input.executor, json),
|
|
2736
3112
|
"source:create": () => sourceCreate(input.executor, rest, json),
|
|
2737
3113
|
"source:get": () => sourceRead(input.executor, "get", rest, json),
|
|
@@ -2742,9 +3118,30 @@ async function runSiftCli(rawInput) {
|
|
|
2742
3118
|
"record:create-markdown": () => createMarkdownRecord(input.executor, rest, json),
|
|
2743
3119
|
"record:patch-section": () => patchRecordSection(input.executor, rest, json),
|
|
2744
3120
|
"record:versions": () => recordRead(input.executor, "record.versions", rest, json),
|
|
2745
|
-
"evidence:list": () => idTool({
|
|
2746
|
-
|
|
2747
|
-
|
|
3121
|
+
"evidence:list": () => idTool({
|
|
3122
|
+
executor: input.executor,
|
|
3123
|
+
toolName: "evidence.list",
|
|
3124
|
+
inputKey: "recordId",
|
|
3125
|
+
idLabel: "record ID",
|
|
3126
|
+
rest,
|
|
3127
|
+
json
|
|
3128
|
+
}),
|
|
3129
|
+
"evidence:get": () => idTool({
|
|
3130
|
+
executor: input.executor,
|
|
3131
|
+
toolName: "evidence.get",
|
|
3132
|
+
inputKey: "evidenceId",
|
|
3133
|
+
idLabel: "evidence ID",
|
|
3134
|
+
rest,
|
|
3135
|
+
json
|
|
3136
|
+
}),
|
|
3137
|
+
"graph:neighbors": () => idTool({
|
|
3138
|
+
executor: input.executor,
|
|
3139
|
+
toolName: "graph.neighbors",
|
|
3140
|
+
inputKey: "recordId",
|
|
3141
|
+
idLabel: "record ID",
|
|
3142
|
+
rest,
|
|
3143
|
+
json
|
|
3144
|
+
}),
|
|
2748
3145
|
"event:list": () => executeSimple2(input.executor, "event.list", {}, json),
|
|
2749
3146
|
"audit:events": () => auditEvents(input.executor, rest, json),
|
|
2750
3147
|
"decision:create": () => createDecision(input.executor, rest, json),
|
|
@@ -2765,7 +3162,7 @@ async function runSiftCli(rawInput) {
|
|
|
2765
3162
|
);
|
|
2766
3163
|
}
|
|
2767
3164
|
try {
|
|
2768
|
-
if (isAuthCommand(commandKey)) {
|
|
3165
|
+
if (isAuthCommand(commandKey) || commandKey === "mcp:serve") {
|
|
2769
3166
|
return await handler();
|
|
2770
3167
|
}
|
|
2771
3168
|
validateAuthenticatedScope(input.config, input.now ?? /* @__PURE__ */ new Date());
|
|
@@ -2775,34 +3172,12 @@ async function runSiftCli(rawInput) {
|
|
|
2775
3172
|
return errorResult(error, json);
|
|
2776
3173
|
}
|
|
2777
3174
|
}
|
|
2778
|
-
async function
|
|
2779
|
-
if (
|
|
2780
|
-
return
|
|
2781
|
-
}
|
|
2782
|
-
const simpleCommand = resolveSimpleCommand({
|
|
2783
|
-
args,
|
|
2784
|
-
json,
|
|
2785
|
-
config: input.config,
|
|
2786
|
-
executor: input.executor,
|
|
2787
|
-
readFile: input.readFile ?? readFile,
|
|
2788
|
-
readStdin: input.readStdin,
|
|
2789
|
-
now: input.now ?? /* @__PURE__ */ new Date()
|
|
2790
|
-
});
|
|
2791
|
-
if (simpleCommand === void 0) return void 0;
|
|
2792
|
-
try {
|
|
2793
|
-
validateAuthenticatedScope(input.config, input.now ?? /* @__PURE__ */ new Date());
|
|
2794
|
-
validateCommandCapability({ commandKey: simpleCommand.commandKey, config: input.config });
|
|
2795
|
-
return await simpleCommand.run();
|
|
2796
|
-
} catch (error) {
|
|
2797
|
-
return errorResult(error, json);
|
|
2798
|
-
}
|
|
2799
|
-
}
|
|
2800
|
-
async function mcpServe(mcpServer, config2, executor, json) {
|
|
2801
|
-
if (mcpServer === void 0) {
|
|
2802
|
-
return fail("No local MCP server is configured for mcp.serve.");
|
|
3175
|
+
async function mcpServe(mcpServer, config2, executor, _json) {
|
|
3176
|
+
if (mcpServer === void 0) {
|
|
3177
|
+
return fail("No local MCP server is configured for mcp.serve.");
|
|
2803
3178
|
}
|
|
2804
3179
|
if (executor === void 0) {
|
|
2805
|
-
return fail("
|
|
3180
|
+
return fail("Not signed in. Run 'sift login', then 'sift mcp serve'.");
|
|
2806
3181
|
}
|
|
2807
3182
|
const result2 = await mcpServer.serve({ config: config2, executor, transport: "local_mcp" });
|
|
2808
3183
|
if (result2 === void 0) return ok("");
|
|
@@ -3127,15 +3502,18 @@ async function idTool(input) {
|
|
|
3127
3502
|
}
|
|
3128
3503
|
|
|
3129
3504
|
// src/auth/configStore.ts
|
|
3130
|
-
import { mkdir, readFile as
|
|
3131
|
-
import { dirname, join } from "path";
|
|
3505
|
+
import { mkdir, readFile as readFile3, rm, writeFile, chmod } from "fs/promises";
|
|
3506
|
+
import { dirname, join as join2 } from "path";
|
|
3507
|
+
function refreshSlotTokenId(tokenId) {
|
|
3508
|
+
return `refresh:${tokenId}`;
|
|
3509
|
+
}
|
|
3132
3510
|
function resolveSiftConfigPath(input) {
|
|
3133
|
-
return
|
|
3511
|
+
return join2(input.homeDir, ".sift", "config.json");
|
|
3134
3512
|
}
|
|
3135
3513
|
async function readStoredSiftConfig(input) {
|
|
3136
3514
|
let raw;
|
|
3137
3515
|
try {
|
|
3138
|
-
raw = await
|
|
3516
|
+
raw = await readFile3(resolveSiftConfigPath(input), "utf8");
|
|
3139
3517
|
} catch (error) {
|
|
3140
3518
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
3141
3519
|
return void 0;
|
|
@@ -3168,18 +3546,19 @@ async function loadCliAuthConfig(input) {
|
|
|
3168
3546
|
if (profile === void 0) {
|
|
3169
3547
|
throw new Error(`Stored Sift profile '${stored.currentProfile}' was not found.`);
|
|
3170
3548
|
}
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3549
|
+
const tokenKind = profile.tokenKind ?? "legacy";
|
|
3550
|
+
const expired = Date.parse(profile.tokenExpiresAt) <= input.now.getTime();
|
|
3551
|
+
const token = await resolveStoredToken({
|
|
3552
|
+
homeDir: input.homeDir,
|
|
3553
|
+
credentialStore: input.credentialStore,
|
|
3554
|
+
profile,
|
|
3555
|
+
tokenKind,
|
|
3556
|
+
expired,
|
|
3557
|
+
oauthRefresher: input.oauthRefresher
|
|
3177
3558
|
});
|
|
3178
|
-
if (token === void 0) {
|
|
3179
|
-
throw new Error("Stored Sift credential store secret is missing; run `sift login` again.");
|
|
3180
|
-
}
|
|
3181
3559
|
return {
|
|
3182
3560
|
source: "stored",
|
|
3561
|
+
tokenKind,
|
|
3183
3562
|
token,
|
|
3184
3563
|
config: {
|
|
3185
3564
|
apiBaseUrl: profile.apiBaseUrl,
|
|
@@ -3192,6 +3571,63 @@ async function loadCliAuthConfig(input) {
|
|
|
3192
3571
|
}
|
|
3193
3572
|
};
|
|
3194
3573
|
}
|
|
3574
|
+
async function resolveStoredToken(input) {
|
|
3575
|
+
const { profile } = input;
|
|
3576
|
+
if (input.expired && input.tokenKind === "oauth" && profile.refreshable === true && input.oauthRefresher !== void 0) {
|
|
3577
|
+
return refreshOAuthToken({
|
|
3578
|
+
homeDir: input.homeDir,
|
|
3579
|
+
credentialStore: input.credentialStore,
|
|
3580
|
+
profile,
|
|
3581
|
+
oauthRefresher: input.oauthRefresher
|
|
3582
|
+
});
|
|
3583
|
+
}
|
|
3584
|
+
if (input.expired) {
|
|
3585
|
+
throw new Error("Stored Sift CLI auth has expired; run `sift login` again.");
|
|
3586
|
+
}
|
|
3587
|
+
const token = await input.credentialStore.read({
|
|
3588
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3589
|
+
tokenId: profile.tokenId
|
|
3590
|
+
});
|
|
3591
|
+
if (token === void 0) {
|
|
3592
|
+
throw new Error("Stored Sift credential store secret is missing; run `sift login` again.");
|
|
3593
|
+
}
|
|
3594
|
+
return token;
|
|
3595
|
+
}
|
|
3596
|
+
async function refreshOAuthToken(input) {
|
|
3597
|
+
const { profile } = input;
|
|
3598
|
+
const refreshToken = await input.credentialStore.read({
|
|
3599
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3600
|
+
tokenId: refreshSlotTokenId(profile.tokenId)
|
|
3601
|
+
});
|
|
3602
|
+
if (refreshToken === void 0) {
|
|
3603
|
+
throw new Error("Stored Sift OAuth refresh token is missing; run `sift login` again.");
|
|
3604
|
+
}
|
|
3605
|
+
const refreshed = await input.oauthRefresher({
|
|
3606
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3607
|
+
refreshToken
|
|
3608
|
+
});
|
|
3609
|
+
await input.credentialStore.write({
|
|
3610
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3611
|
+
tokenId: profile.tokenId,
|
|
3612
|
+
secret: refreshed.accessToken
|
|
3613
|
+
});
|
|
3614
|
+
if (refreshed.refreshToken !== void 0) {
|
|
3615
|
+
await input.credentialStore.write({
|
|
3616
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3617
|
+
tokenId: refreshSlotTokenId(profile.tokenId),
|
|
3618
|
+
secret: refreshed.refreshToken
|
|
3619
|
+
});
|
|
3620
|
+
}
|
|
3621
|
+
const updated = {
|
|
3622
|
+
...profile,
|
|
3623
|
+
tokenExpiresAt: refreshed.expiresAt ?? profile.tokenExpiresAt
|
|
3624
|
+
};
|
|
3625
|
+
await writeStoredSiftConfig({
|
|
3626
|
+
homeDir: input.homeDir,
|
|
3627
|
+
config: { currentProfile: "default", profiles: { default: updated } }
|
|
3628
|
+
});
|
|
3629
|
+
return refreshed.accessToken;
|
|
3630
|
+
}
|
|
3195
3631
|
function loadEnvAuth(env, token) {
|
|
3196
3632
|
return {
|
|
3197
3633
|
source: "env",
|
|
@@ -3222,10 +3658,10 @@ function parseStoredSiftConfig(value) {
|
|
|
3222
3658
|
}
|
|
3223
3659
|
function parseStoredSiftProfile(value) {
|
|
3224
3660
|
const record = objectValue(value, "profile");
|
|
3225
|
-
if ("token" in record || "secret" in record || "tokenSecret" in record) {
|
|
3661
|
+
if ("token" in record || "secret" in record || "tokenSecret" in record || "accessToken" in record || "refreshToken" in record) {
|
|
3226
3662
|
throw new Error("Stored Sift config must not contain token secrets.");
|
|
3227
3663
|
}
|
|
3228
|
-
|
|
3664
|
+
const profile = {
|
|
3229
3665
|
apiBaseUrl: stringValue(record.apiBaseUrl, "apiBaseUrl").replace(/\/+$/u, ""),
|
|
3230
3666
|
appBaseUrl: stringValue(record.appBaseUrl, "appBaseUrl").replace(/\/+$/u, ""),
|
|
3231
3667
|
workspaceId: stringValue(record.workspaceId, "workspaceId"),
|
|
@@ -3236,6 +3672,21 @@ function parseStoredSiftProfile(value) {
|
|
|
3236
3672
|
tokenExpiresAt: stringValue(record.tokenExpiresAt, "tokenExpiresAt"),
|
|
3237
3673
|
capabilities: stringArray(record.capabilities, "capabilities")
|
|
3238
3674
|
};
|
|
3675
|
+
const tokenKind = tokenKindValue(record.tokenKind);
|
|
3676
|
+
if (tokenKind !== void 0) {
|
|
3677
|
+
profile.tokenKind = tokenKind;
|
|
3678
|
+
}
|
|
3679
|
+
if (record.refreshable === true) {
|
|
3680
|
+
profile.refreshable = true;
|
|
3681
|
+
}
|
|
3682
|
+
return profile;
|
|
3683
|
+
}
|
|
3684
|
+
function tokenKindValue(value) {
|
|
3685
|
+
if (value === void 0) return void 0;
|
|
3686
|
+
if (value === "legacy" || value === "oauth" || value === "service") {
|
|
3687
|
+
return value;
|
|
3688
|
+
}
|
|
3689
|
+
throw new Error("tokenKind must be one of legacy, oauth, service.");
|
|
3239
3690
|
}
|
|
3240
3691
|
function requiredEnv(env, name) {
|
|
3241
3692
|
const value = clean(env[name]);
|
|
@@ -3369,21 +3820,115 @@ function isExecError(error) {
|
|
|
3369
3820
|
|
|
3370
3821
|
// src/auth/loginFlow.ts
|
|
3371
3822
|
import { execFile as execFile2 } from "child_process";
|
|
3372
|
-
import { hostname } from "os";
|
|
3823
|
+
import { hostname as hostname3 } from "os";
|
|
3373
3824
|
import { promisify as promisify2 } from "util";
|
|
3374
3825
|
|
|
3826
|
+
// src/auth/loginHelpers.ts
|
|
3827
|
+
async function resolveLoginApiBaseUrl(input) {
|
|
3828
|
+
const options = parseOptions(input.argv);
|
|
3829
|
+
const fromFlag = clean2(options.get("api-base-url"));
|
|
3830
|
+
if (fromFlag !== void 0) return normalizeUrl(fromFlag);
|
|
3831
|
+
const fromEnv = clean2(input.env.SIFT_API_BASE_URL);
|
|
3832
|
+
if (fromEnv !== void 0) return normalizeUrl(fromEnv);
|
|
3833
|
+
const stored = await readStoredSiftConfig({ homeDir: input.homeDir });
|
|
3834
|
+
const profile = stored?.profiles[stored.currentProfile];
|
|
3835
|
+
if (profile !== void 0) return normalizeUrl(profile.apiBaseUrl);
|
|
3836
|
+
return "https://api.sift.com";
|
|
3837
|
+
}
|
|
3838
|
+
function requestedCapabilities(rest) {
|
|
3839
|
+
const option = parseOptions(rest).get("capability");
|
|
3840
|
+
return option === void 0 ? ["record:read"] : option.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
3841
|
+
}
|
|
3842
|
+
function errorMessage2(parsed, status2) {
|
|
3843
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
|
|
3844
|
+
const error = parsed.error;
|
|
3845
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
3846
|
+
const message = error.message;
|
|
3847
|
+
if (typeof message === "string") return message;
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
return `CLI auth request failed with status ${status2}.`;
|
|
3851
|
+
}
|
|
3852
|
+
function normalizeUrl(value) {
|
|
3853
|
+
return value.replace(/\/+$/u, "");
|
|
3854
|
+
}
|
|
3855
|
+
function clean2(value) {
|
|
3856
|
+
const trimmed = value?.trim();
|
|
3857
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
// src/auth/oauthConfig.ts
|
|
3861
|
+
var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
3862
|
+
function oauthLoginSelected(input) {
|
|
3863
|
+
if (input.argv.includes("--no-oauth")) return false;
|
|
3864
|
+
if (input.argv.includes("--oauth")) return true;
|
|
3865
|
+
const fromEnv = input.env.SIFT_OAUTH?.trim().toLowerCase();
|
|
3866
|
+
return fromEnv !== void 0 && TRUE_VALUES.has(fromEnv);
|
|
3867
|
+
}
|
|
3868
|
+
function resolveCliOAuthConfig(input) {
|
|
3869
|
+
const options = parseOptions(input.argv);
|
|
3870
|
+
const authorizeUrl = clean3(options.get("oauth-authorize-url")) ?? clean3(input.env.SIFT_OAUTH_AUTHORIZE_URL);
|
|
3871
|
+
const tokenUrl = clean3(options.get("oauth-token-url")) ?? clean3(input.env.SIFT_OAUTH_TOKEN_URL);
|
|
3872
|
+
const clientId = clean3(options.get("oauth-client-id")) ?? clean3(input.env.SIFT_OAUTH_CLIENT_ID);
|
|
3873
|
+
if (authorizeUrl === void 0 || tokenUrl === void 0 || clientId === void 0) {
|
|
3874
|
+
return void 0;
|
|
3875
|
+
}
|
|
3876
|
+
const registrationUrl = clean3(options.get("oauth-registration-url")) ?? clean3(input.env.SIFT_OAUTH_REGISTRATION_URL);
|
|
3877
|
+
const config2 = { authorizeUrl, tokenUrl, clientId };
|
|
3878
|
+
if (registrationUrl !== void 0) {
|
|
3879
|
+
config2.registrationUrl = registrationUrl;
|
|
3880
|
+
}
|
|
3881
|
+
const scopes = parseScopeList(clean3(options.get("oauth-scopes")) ?? clean3(input.env.SIFT_OAUTH_SCOPES));
|
|
3882
|
+
if (scopes.length > 0) {
|
|
3883
|
+
config2.defaultScopes = scopes;
|
|
3884
|
+
}
|
|
3885
|
+
return config2;
|
|
3886
|
+
}
|
|
3887
|
+
function scopesForCapabilities(capabilities) {
|
|
3888
|
+
const scopes = /* @__PURE__ */ new Set(["read"]);
|
|
3889
|
+
for (const capability of capabilities) {
|
|
3890
|
+
if (capability.endsWith(":write")) {
|
|
3891
|
+
scopes.add("write");
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
return [...scopes];
|
|
3895
|
+
}
|
|
3896
|
+
function parseScopeList(value) {
|
|
3897
|
+
if (value === void 0) return [];
|
|
3898
|
+
return value.split(/[\s,]+/u).map((item) => item.trim()).filter((item) => item.length > 0);
|
|
3899
|
+
}
|
|
3900
|
+
function clean3(value) {
|
|
3901
|
+
const trimmed = value?.trim();
|
|
3902
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
// src/auth/oauthLoginFlow.ts
|
|
3906
|
+
import { hostname } from "os";
|
|
3907
|
+
|
|
3375
3908
|
// src/auth/localCallback.ts
|
|
3376
3909
|
import { createServer } from "http";
|
|
3377
3910
|
async function createLocalCallbackServer() {
|
|
3378
3911
|
let resolveCallback;
|
|
3379
3912
|
let rejectCallback;
|
|
3380
|
-
const callbackPromise = new Promise((
|
|
3381
|
-
resolveCallback =
|
|
3913
|
+
const callbackPromise = new Promise((resolve2, reject) => {
|
|
3914
|
+
resolveCallback = resolve2;
|
|
3382
3915
|
rejectCallback = reject;
|
|
3383
3916
|
});
|
|
3384
3917
|
const server = createServer((request, response) => {
|
|
3385
3918
|
try {
|
|
3386
3919
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
3920
|
+
const error = url.searchParams.get("error");
|
|
3921
|
+
if (error !== null) {
|
|
3922
|
+
const description = url.searchParams.get("error_description");
|
|
3923
|
+
response.writeHead(400, { "Content-Type": "text/plain" });
|
|
3924
|
+
response.end("Sift CLI authorization failed. You can return to the terminal.");
|
|
3925
|
+
rejectCallback?.(
|
|
3926
|
+
new Error(
|
|
3927
|
+
description === null || description.trim().length === 0 ? `Authorization failed: ${error}.` : `Authorization failed: ${error}: ${description}`
|
|
3928
|
+
)
|
|
3929
|
+
);
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3387
3932
|
const code = url.searchParams.get("code");
|
|
3388
3933
|
const state = url.searchParams.get("state");
|
|
3389
3934
|
if (code === null || state === null) {
|
|
@@ -3411,21 +3956,21 @@ async function createLocalCallbackServer() {
|
|
|
3411
3956
|
};
|
|
3412
3957
|
}
|
|
3413
3958
|
function listen(server) {
|
|
3414
|
-
return new Promise((
|
|
3959
|
+
return new Promise((resolve2, reject) => {
|
|
3415
3960
|
server.once("error", reject);
|
|
3416
3961
|
server.listen(0, "127.0.0.1", () => {
|
|
3417
3962
|
server.off("error", reject);
|
|
3418
|
-
|
|
3963
|
+
resolve2();
|
|
3419
3964
|
});
|
|
3420
3965
|
});
|
|
3421
3966
|
}
|
|
3422
3967
|
function closeServer(server) {
|
|
3423
|
-
return new Promise((
|
|
3968
|
+
return new Promise((resolve2, reject) => {
|
|
3424
3969
|
server.close((error) => {
|
|
3425
3970
|
if (error) {
|
|
3426
3971
|
reject(error);
|
|
3427
3972
|
} else {
|
|
3428
|
-
|
|
3973
|
+
resolve2();
|
|
3429
3974
|
}
|
|
3430
3975
|
});
|
|
3431
3976
|
});
|
|
@@ -3451,15 +3996,445 @@ function sha256Base64Url(value) {
|
|
|
3451
3996
|
return createHash2("sha256").update(value).digest("base64url");
|
|
3452
3997
|
}
|
|
3453
3998
|
|
|
3999
|
+
// src/auth/oauthLoginFlow.ts
|
|
4000
|
+
async function oauthBrowserLogin(input) {
|
|
4001
|
+
await input.credentialStore.assertAvailable();
|
|
4002
|
+
const callbackServer = await (input.createCallbackServer ?? createLocalCallbackServer)();
|
|
4003
|
+
try {
|
|
4004
|
+
const pkce = createPkceState({ nextSecret: input.nextSecret });
|
|
4005
|
+
const scopes = mergeScopes(scopesForCapabilities(input.capabilities), input.oauth.defaultScopes);
|
|
4006
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
4007
|
+
oauth: input.oauth,
|
|
4008
|
+
redirectUri: callbackServer.redirectUri,
|
|
4009
|
+
codeChallenge: pkce.codeChallenge,
|
|
4010
|
+
state: pkce.state,
|
|
4011
|
+
scopes
|
|
4012
|
+
});
|
|
4013
|
+
await tryOpenBrowser(input.openBrowser, authorizeUrl);
|
|
4014
|
+
const callback = await callbackServer.waitForCallback();
|
|
4015
|
+
if (callback.state !== pkce.state) {
|
|
4016
|
+
throw new Error("OAuth callback state mismatch.");
|
|
4017
|
+
}
|
|
4018
|
+
const tokens = await exchangeAuthorizationCode({
|
|
4019
|
+
oauth: input.oauth,
|
|
4020
|
+
fetch: input.fetch,
|
|
4021
|
+
code: callback.code,
|
|
4022
|
+
codeVerifier: pkce.codeVerifier,
|
|
4023
|
+
redirectUri: callbackServer.redirectUri
|
|
4024
|
+
});
|
|
4025
|
+
return finalizeOAuthLogin(input, tokens);
|
|
4026
|
+
} finally {
|
|
4027
|
+
await callbackServer.close();
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
async function oauthRefresh(input) {
|
|
4031
|
+
const body = new URLSearchParams({
|
|
4032
|
+
grant_type: "refresh_token",
|
|
4033
|
+
refresh_token: input.refreshToken,
|
|
4034
|
+
client_id: input.oauth.clientId
|
|
4035
|
+
});
|
|
4036
|
+
const tokens = await postForm(input.fetch, input.oauth.tokenUrl, body);
|
|
4037
|
+
return toTokenSet(tokens);
|
|
4038
|
+
}
|
|
4039
|
+
async function finalizeOAuthLogin(input, tokens) {
|
|
4040
|
+
const scope = await input.resolveScope({
|
|
4041
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
4042
|
+
token: tokens.accessToken,
|
|
4043
|
+
fetch: input.fetch
|
|
4044
|
+
});
|
|
4045
|
+
const profile = {
|
|
4046
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
4047
|
+
appBaseUrl: input.appBaseUrl,
|
|
4048
|
+
workspaceId: scope.workspaceId,
|
|
4049
|
+
brainId: scope.brainId,
|
|
4050
|
+
principalId: scope.principalId,
|
|
4051
|
+
// Synthetic, non-secret slot id so the converged token reuses the same
|
|
4052
|
+
// keychain account scheme (apiBaseUrl|tokenId) as the legacy flow.
|
|
4053
|
+
tokenId: "oauth",
|
|
4054
|
+
tokenLabel: tokens.tokenLabel,
|
|
4055
|
+
tokenExpiresAt: tokens.expiresAt ?? farFuture(),
|
|
4056
|
+
capabilities: scope.capabilities,
|
|
4057
|
+
tokenKind: "oauth",
|
|
4058
|
+
refreshable: tokens.refreshToken !== void 0
|
|
4059
|
+
};
|
|
4060
|
+
const result2 = { profile, accessToken: tokens.accessToken };
|
|
4061
|
+
if (tokens.refreshToken !== void 0) {
|
|
4062
|
+
result2.refreshToken = tokens.refreshToken;
|
|
4063
|
+
}
|
|
4064
|
+
return result2;
|
|
4065
|
+
}
|
|
4066
|
+
function buildAuthorizeUrl(input) {
|
|
4067
|
+
const url = new URL(input.oauth.authorizeUrl);
|
|
4068
|
+
url.searchParams.set("response_type", "code");
|
|
4069
|
+
url.searchParams.set("client_id", input.oauth.clientId);
|
|
4070
|
+
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
4071
|
+
url.searchParams.set("code_challenge", input.codeChallenge);
|
|
4072
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
4073
|
+
url.searchParams.set("state", input.state);
|
|
4074
|
+
if (input.scopes.length > 0) {
|
|
4075
|
+
url.searchParams.set("scope", input.scopes.join(" "));
|
|
4076
|
+
}
|
|
4077
|
+
return url.toString();
|
|
4078
|
+
}
|
|
4079
|
+
async function exchangeAuthorizationCode(input) {
|
|
4080
|
+
const body = new URLSearchParams({
|
|
4081
|
+
grant_type: "authorization_code",
|
|
4082
|
+
code: input.code,
|
|
4083
|
+
redirect_uri: input.redirectUri,
|
|
4084
|
+
client_id: input.oauth.clientId,
|
|
4085
|
+
code_verifier: input.codeVerifier
|
|
4086
|
+
});
|
|
4087
|
+
const tokens = await postForm(input.fetch, input.oauth.tokenUrl, body);
|
|
4088
|
+
return toTokenSet(tokens);
|
|
4089
|
+
}
|
|
4090
|
+
async function postForm(fetchImpl, url, body) {
|
|
4091
|
+
const response = await fetchImpl(url, {
|
|
4092
|
+
method: "POST",
|
|
4093
|
+
headers: {
|
|
4094
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
4095
|
+
Accept: "application/json"
|
|
4096
|
+
},
|
|
4097
|
+
body: body.toString()
|
|
4098
|
+
});
|
|
4099
|
+
const text = await response.text();
|
|
4100
|
+
const parsed = text.length === 0 ? {} : JSON.parse(text);
|
|
4101
|
+
if (!response.ok) {
|
|
4102
|
+
throw new Error(oauthTokenError(parsed, response.status));
|
|
4103
|
+
}
|
|
4104
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
4105
|
+
throw new Error("OAuth token endpoint returned a non-object response.");
|
|
4106
|
+
}
|
|
4107
|
+
return parsed;
|
|
4108
|
+
}
|
|
4109
|
+
function toTokenSet(tokens) {
|
|
4110
|
+
const accessToken = tokens.access_token;
|
|
4111
|
+
if (typeof accessToken !== "string" || accessToken.trim().length === 0) {
|
|
4112
|
+
throw new Error("OAuth token endpoint did not return an access token.");
|
|
4113
|
+
}
|
|
4114
|
+
const set = { accessToken, tokenLabel: oauthTokenLabel() };
|
|
4115
|
+
const refreshToken = tokens.refresh_token;
|
|
4116
|
+
if (typeof refreshToken === "string" && refreshToken.trim().length > 0) {
|
|
4117
|
+
set.refreshToken = refreshToken;
|
|
4118
|
+
}
|
|
4119
|
+
const expiresAt = expiresAtFrom(tokens.expires_in);
|
|
4120
|
+
if (expiresAt !== void 0) {
|
|
4121
|
+
set.expiresAt = expiresAt;
|
|
4122
|
+
}
|
|
4123
|
+
return set;
|
|
4124
|
+
}
|
|
4125
|
+
function expiresAtFrom(expiresIn) {
|
|
4126
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
4127
|
+
return void 0;
|
|
4128
|
+
}
|
|
4129
|
+
return new Date(Date.now() + expiresIn * 1e3).toISOString();
|
|
4130
|
+
}
|
|
4131
|
+
function mergeScopes(derived, defaults) {
|
|
4132
|
+
const merged = new Set(derived);
|
|
4133
|
+
for (const scope of defaults ?? []) {
|
|
4134
|
+
merged.add(scope);
|
|
4135
|
+
}
|
|
4136
|
+
return [...merged];
|
|
4137
|
+
}
|
|
4138
|
+
function oauthTokenLabel() {
|
|
4139
|
+
const name = hostname().trim();
|
|
4140
|
+
return name.length === 0 ? "oauth" : `oauth-${name}`;
|
|
4141
|
+
}
|
|
4142
|
+
function farFuture() {
|
|
4143
|
+
return new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toISOString();
|
|
4144
|
+
}
|
|
4145
|
+
async function tryOpenBrowser(openBrowser, url) {
|
|
4146
|
+
if (openBrowser === void 0) return;
|
|
4147
|
+
await openBrowser(url).catch(() => void 0);
|
|
4148
|
+
}
|
|
4149
|
+
function oauthTokenError(parsed, status2) {
|
|
4150
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
4151
|
+
const record = parsed;
|
|
4152
|
+
const error = typeof record.error === "string" ? record.error : void 0;
|
|
4153
|
+
const description = typeof record.error_description === "string" ? record.error_description : void 0;
|
|
4154
|
+
if (error !== void 0) {
|
|
4155
|
+
return description === void 0 ? `OAuth token request failed: ${error}.` : `OAuth token request failed: ${error}: ${description}`;
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
return `OAuth token request failed with status ${status2}.`;
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
// src/auth/serviceTokenLogin.ts
|
|
4162
|
+
import { hostname as hostname2 } from "os";
|
|
4163
|
+
async function serviceTokenLogin(input) {
|
|
4164
|
+
await input.credentialStore.assertAvailable();
|
|
4165
|
+
const callerBearer = await input.resolveCallerBearer();
|
|
4166
|
+
if (callerBearer === void 0) {
|
|
4167
|
+
throw new Error(
|
|
4168
|
+
"Headless login needs an authenticated caller. Set SIFT_API_TOKEN or run 'sift login' once interactively, then retry 'sift login --no-browser'."
|
|
4169
|
+
);
|
|
4170
|
+
}
|
|
4171
|
+
const options = parseOptions(input.rest);
|
|
4172
|
+
const requestBody = buildServiceTokenRequest({
|
|
4173
|
+
rest: input.rest,
|
|
4174
|
+
capabilities: input.capabilities,
|
|
4175
|
+
label: options.get("label"),
|
|
4176
|
+
workspaceId: options.get("workspace-id"),
|
|
4177
|
+
ttlDays: options.get("ttl-days")
|
|
4178
|
+
});
|
|
4179
|
+
const minted = await postServiceTokenMint(
|
|
4180
|
+
input.fetch,
|
|
4181
|
+
`${input.apiBaseUrl}/cli-auth/service-token`,
|
|
4182
|
+
callerBearer,
|
|
4183
|
+
requestBody
|
|
4184
|
+
);
|
|
4185
|
+
const profile = {
|
|
4186
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
4187
|
+
appBaseUrl: input.appBaseUrl,
|
|
4188
|
+
workspaceId: minted.workspaceId,
|
|
4189
|
+
brainId: minted.brainId,
|
|
4190
|
+
principalId: minted.principalId,
|
|
4191
|
+
tokenId: minted.tokenId,
|
|
4192
|
+
tokenLabel: minted.tokenLabel,
|
|
4193
|
+
tokenExpiresAt: minted.tokenExpiresAt,
|
|
4194
|
+
capabilities: minted.capabilities,
|
|
4195
|
+
tokenKind: "service"
|
|
4196
|
+
};
|
|
4197
|
+
return { profile, token: minted.token };
|
|
4198
|
+
}
|
|
4199
|
+
function buildServiceTokenRequest(input) {
|
|
4200
|
+
const body = {
|
|
4201
|
+
label: clean4(input.label) ?? defaultLabel()
|
|
4202
|
+
};
|
|
4203
|
+
const workspaceId = clean4(input.workspaceId);
|
|
4204
|
+
if (workspaceId !== void 0) {
|
|
4205
|
+
body.workspaceId = workspaceId;
|
|
4206
|
+
}
|
|
4207
|
+
if (capabilityFlagPresent(input.rest)) {
|
|
4208
|
+
body.capabilities = input.capabilities;
|
|
4209
|
+
}
|
|
4210
|
+
const ttlDays = clean4(input.ttlDays);
|
|
4211
|
+
if (ttlDays !== void 0) {
|
|
4212
|
+
const parsed = Number(ttlDays);
|
|
4213
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
4214
|
+
throw new Error("Option --ttl-days must be a positive integer.");
|
|
4215
|
+
}
|
|
4216
|
+
body.ttlDays = parsed;
|
|
4217
|
+
}
|
|
4218
|
+
return body;
|
|
4219
|
+
}
|
|
4220
|
+
function capabilityFlagPresent(rest) {
|
|
4221
|
+
return rest.includes("--capability");
|
|
4222
|
+
}
|
|
4223
|
+
async function postServiceTokenMint(fetchImpl, url, callerBearer, body) {
|
|
4224
|
+
const response = await fetchImpl(url, {
|
|
4225
|
+
method: "POST",
|
|
4226
|
+
headers: {
|
|
4227
|
+
"Content-Type": "application/json",
|
|
4228
|
+
Authorization: `Bearer ${callerBearer}`
|
|
4229
|
+
},
|
|
4230
|
+
body: JSON.stringify(body)
|
|
4231
|
+
});
|
|
4232
|
+
const text = await response.text();
|
|
4233
|
+
const parsed = text.length === 0 ? {} : JSON.parse(text);
|
|
4234
|
+
if (!response.ok) {
|
|
4235
|
+
throw new Error(serviceTokenError(parsed, response.status));
|
|
4236
|
+
}
|
|
4237
|
+
return assertServiceTokenResponse(parsed);
|
|
4238
|
+
}
|
|
4239
|
+
function assertServiceTokenResponse(parsed) {
|
|
4240
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
4241
|
+
throw new Error("Service-token mint returned a non-object response.");
|
|
4242
|
+
}
|
|
4243
|
+
const record = parsed;
|
|
4244
|
+
return {
|
|
4245
|
+
token: requiredString(record.token, "token"),
|
|
4246
|
+
tokenId: requiredString(record.tokenId, "tokenId"),
|
|
4247
|
+
tokenLabel: requiredString(record.tokenLabel, "tokenLabel"),
|
|
4248
|
+
tokenExpiresAt: requiredString(record.tokenExpiresAt, "tokenExpiresAt"),
|
|
4249
|
+
workspaceId: requiredString(record.workspaceId, "workspaceId"),
|
|
4250
|
+
brainId: requiredString(record.brainId, "brainId"),
|
|
4251
|
+
principalId: requiredString(record.principalId, "principalId"),
|
|
4252
|
+
capabilities: stringArray2(record.capabilities, "capabilities")
|
|
4253
|
+
};
|
|
4254
|
+
}
|
|
4255
|
+
function requiredString(value, name) {
|
|
4256
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4257
|
+
throw new Error(`Service-token mint response missing ${name}.`);
|
|
4258
|
+
}
|
|
4259
|
+
return value;
|
|
4260
|
+
}
|
|
4261
|
+
function stringArray2(value, name) {
|
|
4262
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
4263
|
+
throw new Error(`Service-token mint response field ${name} must be a string array.`);
|
|
4264
|
+
}
|
|
4265
|
+
return [...value];
|
|
4266
|
+
}
|
|
4267
|
+
function serviceTokenError(parsed, status2) {
|
|
4268
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
|
|
4269
|
+
const error = parsed.error;
|
|
4270
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
4271
|
+
const message = error.message;
|
|
4272
|
+
if (typeof message === "string" && message.trim().length > 0) {
|
|
4273
|
+
return message;
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
return `Service-token mint failed with status ${status2}.`;
|
|
4278
|
+
}
|
|
4279
|
+
function defaultLabel() {
|
|
4280
|
+
const name = hostname2().trim();
|
|
4281
|
+
return name.length === 0 ? "sift-cli-service" : `sift-cli-service-${name}`;
|
|
4282
|
+
}
|
|
4283
|
+
function clean4(value) {
|
|
4284
|
+
const trimmed = value?.trim();
|
|
4285
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
// src/auth/convergedLogin.ts
|
|
4289
|
+
function oauthRefresherFor(input, rest) {
|
|
4290
|
+
const oauth = input.oauthConfig ?? resolveCliOAuthConfig({ argv: rest, env: input.env });
|
|
4291
|
+
if (oauth === void 0) return void 0;
|
|
4292
|
+
return ({ refreshToken }) => oauthRefresh({ oauth, fetch: input.fetch, refreshToken });
|
|
4293
|
+
}
|
|
4294
|
+
async function oauthBrowserLoginFlow(input, rest, json) {
|
|
4295
|
+
const apiBaseUrl = await resolveLoginApiBaseUrl({ argv: rest, env: input.env, homeDir: input.homeDir });
|
|
4296
|
+
const oauth = resolveOAuthConfigOrThrow(input, rest);
|
|
4297
|
+
const result2 = await oauthBrowserLogin({
|
|
4298
|
+
apiBaseUrl,
|
|
4299
|
+
appBaseUrl: resolveAppBaseUrl(input.env, apiBaseUrl),
|
|
4300
|
+
oauth,
|
|
4301
|
+
capabilities: requestedCapabilities(rest),
|
|
4302
|
+
fetch: input.fetch,
|
|
4303
|
+
credentialStore: input.credentialStore,
|
|
4304
|
+
...input.openBrowser === void 0 ? {} : { openBrowser: input.openBrowser },
|
|
4305
|
+
...input.createCallbackServer === void 0 ? {} : { createCallbackServer: input.createCallbackServer },
|
|
4306
|
+
resolveScope: input.resolveScope ?? whoamiResolveScope,
|
|
4307
|
+
...input.nextSecret === void 0 ? {} : { nextSecret: input.nextSecret }
|
|
4308
|
+
});
|
|
4309
|
+
return persistConvergedLogin(
|
|
4310
|
+
input,
|
|
4311
|
+
{
|
|
4312
|
+
profile: result2.profile,
|
|
4313
|
+
accessToken: result2.accessToken,
|
|
4314
|
+
...result2.refreshToken === void 0 ? {} : { refreshToken: result2.refreshToken }
|
|
4315
|
+
},
|
|
4316
|
+
json
|
|
4317
|
+
);
|
|
4318
|
+
}
|
|
4319
|
+
async function serviceTokenLoginFlow(input, rest, json) {
|
|
4320
|
+
const apiBaseUrl = await resolveLoginApiBaseUrl({ argv: rest, env: input.env, homeDir: input.homeDir });
|
|
4321
|
+
const result2 = await serviceTokenLogin({
|
|
4322
|
+
apiBaseUrl,
|
|
4323
|
+
appBaseUrl: resolveAppBaseUrl(input.env, apiBaseUrl),
|
|
4324
|
+
rest,
|
|
4325
|
+
capabilities: requestedCapabilities(rest),
|
|
4326
|
+
fetch: input.fetch,
|
|
4327
|
+
credentialStore: input.credentialStore,
|
|
4328
|
+
resolveCallerBearer: defaultCallerBearerResolver(input)
|
|
4329
|
+
});
|
|
4330
|
+
return persistConvergedLogin(input, { profile: result2.profile, accessToken: result2.token }, json);
|
|
4331
|
+
}
|
|
4332
|
+
function resolveOAuthConfigOrThrow(input, rest) {
|
|
4333
|
+
const oauth = input.oauthConfig ?? resolveCliOAuthConfig({ argv: rest, env: input.env });
|
|
4334
|
+
if (oauth === void 0) {
|
|
4335
|
+
throw new Error(
|
|
4336
|
+
"OAuth login is not yet enabled. Set SIFT_OAUTH_AUTHORIZE_URL, SIFT_OAUTH_TOKEN_URL, and SIFT_OAUTH_CLIENT_ID, or omit --oauth to use the default sign-in."
|
|
4337
|
+
);
|
|
4338
|
+
}
|
|
4339
|
+
return oauth;
|
|
4340
|
+
}
|
|
4341
|
+
function defaultCallerBearerResolver(input) {
|
|
4342
|
+
return async () => {
|
|
4343
|
+
const envToken = clean2(input.env.SIFT_API_TOKEN);
|
|
4344
|
+
if (envToken !== void 0) return envToken;
|
|
4345
|
+
const stored = await readStoredSiftConfig({ homeDir: input.homeDir });
|
|
4346
|
+
const profile = stored?.profiles[stored.currentProfile];
|
|
4347
|
+
if (profile === void 0) return void 0;
|
|
4348
|
+
return input.credentialStore.read({
|
|
4349
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
4350
|
+
tokenId: profile.tokenId
|
|
4351
|
+
});
|
|
4352
|
+
};
|
|
4353
|
+
}
|
|
4354
|
+
var whoamiResolveScope = async ({ apiBaseUrl, token, fetch: fetchImpl }) => {
|
|
4355
|
+
const response = await fetchImpl(`${apiBaseUrl}/agent-tools/whoami`, {
|
|
4356
|
+
method: "POST",
|
|
4357
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
4358
|
+
body: JSON.stringify({ input: {} })
|
|
4359
|
+
});
|
|
4360
|
+
const text = await response.text();
|
|
4361
|
+
const parsed = text.length === 0 ? {} : JSON.parse(text);
|
|
4362
|
+
if (!response.ok) {
|
|
4363
|
+
throw new Error(errorMessage2(parsed, response.status));
|
|
4364
|
+
}
|
|
4365
|
+
return whoamiScopeFrom(parsed);
|
|
4366
|
+
};
|
|
4367
|
+
function whoamiScopeFrom(parsed) {
|
|
4368
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
4369
|
+
throw new Error("whoami returned a non-object response.");
|
|
4370
|
+
}
|
|
4371
|
+
const record = parsed;
|
|
4372
|
+
const principalId = nestedString(record.principal, "id");
|
|
4373
|
+
const workspaceId = nestedString(record.scope, "workspaceId");
|
|
4374
|
+
const brainId = nestedString(record.scope, "brainId");
|
|
4375
|
+
if (principalId === void 0 || workspaceId === void 0 || brainId === void 0) {
|
|
4376
|
+
throw new Error("whoami response is missing principal or scope fields.");
|
|
4377
|
+
}
|
|
4378
|
+
const capabilities = Array.isArray(record.capabilities) ? record.capabilities.filter((item) => typeof item === "string") : [];
|
|
4379
|
+
return { principalId, workspaceId, brainId, capabilities };
|
|
4380
|
+
}
|
|
4381
|
+
function nestedString(parent, key) {
|
|
4382
|
+
if (typeof parent !== "object" || parent === null) return void 0;
|
|
4383
|
+
const value = parent[key];
|
|
4384
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4385
|
+
}
|
|
4386
|
+
function resolveAppBaseUrl(env, apiBaseUrl) {
|
|
4387
|
+
const fromEnv = clean2(env.SIFT_APP_BASE_URL);
|
|
4388
|
+
if (fromEnv !== void 0) return normalizeUrl(fromEnv);
|
|
4389
|
+
return apiBaseUrl.replace(/\/\/api\./u, "//");
|
|
4390
|
+
}
|
|
4391
|
+
async function persistConvergedLogin(input, result2, json) {
|
|
4392
|
+
const { profile } = result2;
|
|
4393
|
+
try {
|
|
4394
|
+
await input.credentialStore.write({
|
|
4395
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
4396
|
+
tokenId: profile.tokenId,
|
|
4397
|
+
secret: result2.accessToken
|
|
4398
|
+
});
|
|
4399
|
+
if (result2.refreshToken !== void 0) {
|
|
4400
|
+
await input.credentialStore.write({
|
|
4401
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
4402
|
+
tokenId: refreshSlotTokenId(profile.tokenId),
|
|
4403
|
+
secret: result2.refreshToken
|
|
4404
|
+
});
|
|
4405
|
+
}
|
|
4406
|
+
} catch (error) {
|
|
4407
|
+
return fail(
|
|
4408
|
+
`Sift CLI login storage failure: ${error instanceof Error ? error.message : "credential store write failed"}`
|
|
4409
|
+
);
|
|
4410
|
+
}
|
|
4411
|
+
await writeStoredSiftConfig({
|
|
4412
|
+
homeDir: input.homeDir,
|
|
4413
|
+
config: { currentProfile: "default", profiles: { default: profile } }
|
|
4414
|
+
});
|
|
4415
|
+
const scope = {
|
|
4416
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
4417
|
+
tokenLabel: profile.tokenLabel,
|
|
4418
|
+
tokenExpiresAt: profile.tokenExpiresAt,
|
|
4419
|
+
principalId: profile.principalId,
|
|
4420
|
+
workspaceId: profile.workspaceId,
|
|
4421
|
+
brainId: profile.brainId,
|
|
4422
|
+
capabilities: profile.capabilities
|
|
4423
|
+
};
|
|
4424
|
+
return ok(json ? `${JSON.stringify(scope)}
|
|
4425
|
+
` : `Authenticated Sift CLI
|
|
4426
|
+
${renderScope(scope)}`);
|
|
4427
|
+
}
|
|
4428
|
+
|
|
3454
4429
|
// src/auth/loginFlow.ts
|
|
3455
4430
|
var execFileAsync2 = promisify2(execFile2);
|
|
3456
4431
|
function createSiftCliAuthCommands(input) {
|
|
3457
4432
|
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
3458
|
-
const sleep = input.sleep ?? ((ms) => new Promise((
|
|
4433
|
+
const sleep = input.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
3459
4434
|
return {
|
|
3460
4435
|
async login({ rest, json }) {
|
|
3461
4436
|
try {
|
|
3462
|
-
return
|
|
4437
|
+
return await routeLogin(input, rest, sleep, json);
|
|
3463
4438
|
} catch (error) {
|
|
3464
4439
|
return json ? failJson(error instanceof Error ? error.message : "Login failed.") : fail(error instanceof Error ? error.message : "Login failed.");
|
|
3465
4440
|
}
|
|
@@ -3475,21 +4450,18 @@ function createSiftCliAuthCommands(input) {
|
|
|
3475
4450
|
env: input.env,
|
|
3476
4451
|
homeDir: input.homeDir,
|
|
3477
4452
|
credentialStore: input.credentialStore,
|
|
3478
|
-
now: now()
|
|
4453
|
+
now: now(),
|
|
4454
|
+
oauthRefresher: oauthRefresherFor(input, [])
|
|
3479
4455
|
});
|
|
3480
4456
|
}
|
|
3481
4457
|
};
|
|
3482
4458
|
}
|
|
3483
|
-
async function
|
|
3484
|
-
const
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
const stored = await readStoredSiftConfig({ homeDir: input.homeDir });
|
|
3490
|
-
const profile = stored?.profiles[stored.currentProfile];
|
|
3491
|
-
if (profile !== void 0) return normalizeUrl(profile.apiBaseUrl);
|
|
3492
|
-
return "https://api.sift.com";
|
|
4459
|
+
async function routeLogin(input, rest, sleep, json) {
|
|
4460
|
+
const noBrowser = rest.includes("--no-browser");
|
|
4461
|
+
if (oauthLoginSelected({ argv: rest, env: input.env })) {
|
|
4462
|
+
return noBrowser ? serviceTokenLoginFlow(input, rest, json) : oauthBrowserLoginFlow(input, rest, json);
|
|
4463
|
+
}
|
|
4464
|
+
return noBrowser ? deviceLogin(input, rest, sleep, json) : browserLogin(input, rest, json);
|
|
3493
4465
|
}
|
|
3494
4466
|
async function browserLogin(input, rest, json) {
|
|
3495
4467
|
await input.credentialStore.assertAvailable();
|
|
@@ -3503,10 +4475,10 @@ async function browserLogin(input, rest, json) {
|
|
|
3503
4475
|
codeChallenge: pkce.codeChallenge,
|
|
3504
4476
|
codeChallengeMethod: "S256",
|
|
3505
4477
|
stateHash: pkce.stateHash,
|
|
3506
|
-
deviceLabel: input.deviceLabel ??
|
|
4478
|
+
deviceLabel: input.deviceLabel ?? hostname3(),
|
|
3507
4479
|
requestedCapabilities: requestedCapabilities(rest)
|
|
3508
4480
|
});
|
|
3509
|
-
await
|
|
4481
|
+
await tryOpenBrowser2(input.openBrowser, request.authorizeUrl);
|
|
3510
4482
|
const callback = await callbackServer.waitForCallback();
|
|
3511
4483
|
if (callback.state !== pkce.stateHash) {
|
|
3512
4484
|
throw new Error("CLI auth callback state mismatch.");
|
|
@@ -3526,7 +4498,7 @@ async function deviceLogin(input, rest, sleep, json) {
|
|
|
3526
4498
|
await input.credentialStore.assertAvailable();
|
|
3527
4499
|
const apiBaseUrl = await resolveLoginApiBaseUrl({ argv: rest, env: input.env, homeDir: input.homeDir });
|
|
3528
4500
|
const request = await postJson(input.fetch, `${apiBaseUrl}/cli-auth/device`, {
|
|
3529
|
-
deviceLabel: input.deviceLabel ??
|
|
4501
|
+
deviceLabel: input.deviceLabel ?? hostname3(),
|
|
3530
4502
|
requestedCapabilities: requestedCapabilities(rest)
|
|
3531
4503
|
});
|
|
3532
4504
|
let intervalSeconds = request.intervalSeconds;
|
|
@@ -3652,10 +4624,6 @@ function okStatus(source, loaded, json) {
|
|
|
3652
4624
|
${renderScope(loaded.config)}`
|
|
3653
4625
|
);
|
|
3654
4626
|
}
|
|
3655
|
-
function requestedCapabilities(rest) {
|
|
3656
|
-
const option = parseOptions(rest).get("capability");
|
|
3657
|
-
return option === void 0 ? ["record:read"] : option.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
3658
|
-
}
|
|
3659
4627
|
function configFromToken(token) {
|
|
3660
4628
|
return {
|
|
3661
4629
|
currentProfile: "default",
|
|
@@ -3674,7 +4642,7 @@ function configFromToken(token) {
|
|
|
3674
4642
|
}
|
|
3675
4643
|
};
|
|
3676
4644
|
}
|
|
3677
|
-
async function
|
|
4645
|
+
async function tryOpenBrowser2(openBrowser, url) {
|
|
3678
4646
|
await (openBrowser ?? openBrowserUrl)(url).catch(() => void 0);
|
|
3679
4647
|
}
|
|
3680
4648
|
async function openBrowserUrl(url) {
|
|
@@ -3704,16 +4672,6 @@ async function postJson(fetchImpl, url, body, headers = {}) {
|
|
|
3704
4672
|
}
|
|
3705
4673
|
return parsed;
|
|
3706
4674
|
}
|
|
3707
|
-
function errorMessage2(parsed, status2) {
|
|
3708
|
-
if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
|
|
3709
|
-
const error = parsed.error;
|
|
3710
|
-
if (typeof error === "object" && error !== null && "message" in error) {
|
|
3711
|
-
const message = error.message;
|
|
3712
|
-
if (typeof message === "string") return message;
|
|
3713
|
-
}
|
|
3714
|
-
}
|
|
3715
|
-
return `CLI auth request failed with status ${status2}.`;
|
|
3716
|
-
}
|
|
3717
4675
|
function failJson(message) {
|
|
3718
4676
|
return {
|
|
3719
4677
|
exitCode: 1,
|
|
@@ -3722,13 +4680,6 @@ function failJson(message) {
|
|
|
3722
4680
|
stderr: ""
|
|
3723
4681
|
};
|
|
3724
4682
|
}
|
|
3725
|
-
function normalizeUrl(value) {
|
|
3726
|
-
return value.replace(/\/+$/u, "");
|
|
3727
|
-
}
|
|
3728
|
-
function clean2(value) {
|
|
3729
|
-
const trimmed = value?.trim();
|
|
3730
|
-
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
3731
|
-
}
|
|
3732
4683
|
|
|
3733
4684
|
// src/bin/sift.ts
|
|
3734
4685
|
var credentialStore = createMacOSKeychainStore();
|
|
@@ -3764,7 +4715,9 @@ var result = await runSiftCli({
|
|
|
3764
4715
|
mcpServer: {
|
|
3765
4716
|
serve: async ({ config: config2, executor }) => {
|
|
3766
4717
|
if (executor === void 0) {
|
|
3767
|
-
throw new Error(
|
|
4718
|
+
throw new Error(
|
|
4719
|
+
"Not signed in. Run 'sift login' to authenticate, then 'sift mcp serve' to start the local MCP server."
|
|
4720
|
+
);
|
|
3768
4721
|
}
|
|
3769
4722
|
const { createLocalMcpStdioServer: createLocalMcpStdioServer2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
3770
4723
|
return createLocalMcpStdioServer2({
|