@sift-wiki/cli 0.1.1 → 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 +25 -4
- package/dist/bin/sift.js +1508 -250
- package/package.json +11 -9
package/dist/bin/sift.js
CHANGED
|
@@ -9,6 +9,75 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// ../tools/dist/gating.js
|
|
13
|
+
function isGatedTool(name) {
|
|
14
|
+
return !ungatedToolNames.has(name);
|
|
15
|
+
}
|
|
16
|
+
var UNGATED_TOOL_NAMES, ungatedToolNames;
|
|
17
|
+
var init_gating = __esm({
|
|
18
|
+
"../tools/dist/gating.js"() {
|
|
19
|
+
"use strict";
|
|
20
|
+
UNGATED_TOOL_NAMES = [
|
|
21
|
+
"contract.get",
|
|
22
|
+
"whoami",
|
|
23
|
+
"tools.list",
|
|
24
|
+
"tools.schema",
|
|
25
|
+
"tools.help",
|
|
26
|
+
"tools.search",
|
|
27
|
+
"brain.list",
|
|
28
|
+
"brain.use",
|
|
29
|
+
"scope.current"
|
|
30
|
+
];
|
|
31
|
+
ungatedToolNames = new Set(UNGATED_TOOL_NAMES);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// ../tools/dist/canvasTools.js
|
|
36
|
+
function canvasToolDefinitions(writeTool2) {
|
|
37
|
+
return [
|
|
38
|
+
writeTool2("canvas.create", "Create a canvas (a spatial board of live views and notes). Returns the canvas id; compose it with canvas.add_node.", {
|
|
39
|
+
title: { type: "string" },
|
|
40
|
+
intent: { type: "string" }
|
|
41
|
+
}, "sift canvas create --title 'This week'", { required: ["title"] }),
|
|
42
|
+
writeTool2("canvas.add_node", [
|
|
43
|
+
"Add one node to a canvas. Pick the most structured nodeType that fits; prose is the",
|
|
44
|
+
"last resort. nodeType: plan (title + tracks of {name, owners?, goal?, items:[{text,",
|
|
45
|
+
"who?, when?, done?}]} \u2014 roadmaps, weekly plans, agendas), metric (label + value,",
|
|
46
|
+
"optional delta {direction,magnitude,sentiment} \u2014 one number), chart (rows of",
|
|
47
|
+
"{label, value:number}, optional title \u2014 comparing numbers), kanban or checklist",
|
|
48
|
+
"(queryRef binding to a live workspace query), prose (markdown, optional title),",
|
|
49
|
+
"note (text \u2014 a short plain annotation, never primary content).",
|
|
50
|
+
"Content must come from brain data you actually read, never invented."
|
|
51
|
+
].join(" "), {
|
|
52
|
+
canvasId: { type: "string" },
|
|
53
|
+
nodeType: {
|
|
54
|
+
type: "string",
|
|
55
|
+
enum: ["plan", "metric", "chart", "kanban", "checklist", "prose", "note"]
|
|
56
|
+
},
|
|
57
|
+
text: { type: "string" },
|
|
58
|
+
markdown: { type: "string" },
|
|
59
|
+
title: { type: "string" },
|
|
60
|
+
timeframe: { type: "string" },
|
|
61
|
+
tracks: { type: "array", items: { type: "object" } },
|
|
62
|
+
label: { type: "string" },
|
|
63
|
+
value: { type: "string" },
|
|
64
|
+
delta: { type: "object" },
|
|
65
|
+
rows: { type: "array", items: { type: "object" } },
|
|
66
|
+
queryRef: { type: "string" },
|
|
67
|
+
position: { type: "object" }
|
|
68
|
+
}, "sift canvas add-node <canvas-id> --type metric", { required: ["canvasId", "nodeType"] }),
|
|
69
|
+
writeTool2("canvas.remove_node", "Remove a node or note from a canvas by id.", {
|
|
70
|
+
canvasId: { type: "string" },
|
|
71
|
+
nodeId: { type: "string" }
|
|
72
|
+
}, "sift canvas remove-node <canvas-id> <node-id>", { required: ["canvasId", "nodeId"] })
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
var init_canvasTools = __esm({
|
|
76
|
+
"../tools/dist/canvasTools.js"() {
|
|
77
|
+
"use strict";
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
12
81
|
// ../tools/dist/inputParsers.js
|
|
13
82
|
function parseCaptureFile(input) {
|
|
14
83
|
return {
|
|
@@ -68,6 +137,8 @@ function parseSearchQuery(input) {
|
|
|
68
137
|
function parseContextQuery(input) {
|
|
69
138
|
return {
|
|
70
139
|
query: requireString(input, "query"),
|
|
140
|
+
queryIssuedAt: optionalString(input, "queryIssuedAt"),
|
|
141
|
+
timezone: optionalString(input, "timezone"),
|
|
71
142
|
maxChars: requireInteger(input, "maxChars", 4e3)
|
|
72
143
|
};
|
|
73
144
|
}
|
|
@@ -247,7 +318,38 @@ var init_inputParsers = __esm({
|
|
|
247
318
|
|
|
248
319
|
// ../tools/dist/registry.js
|
|
249
320
|
function listToolDefinitions() {
|
|
250
|
-
return [...toolDefinitions];
|
|
321
|
+
return [...toolDefinitions, ...canvasToolDefinitions(writeTool)].map((tool) => isGatedTool(tool.name) ? withContractVersionProperty(tool) : tool);
|
|
322
|
+
}
|
|
323
|
+
function withContractVersionProperty(tool) {
|
|
324
|
+
return {
|
|
325
|
+
...tool,
|
|
326
|
+
inputSchema: {
|
|
327
|
+
...tool.inputSchema,
|
|
328
|
+
properties: {
|
|
329
|
+
...tool.inputSchema.properties,
|
|
330
|
+
contractVersion: {
|
|
331
|
+
type: "string",
|
|
332
|
+
description: "Echo the current Sift contract version from contract.get."
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function isToolAuthorized(capabilities, tool) {
|
|
339
|
+
return tool.capability === NO_CAPABILITY || capabilities.includes(tool.capability);
|
|
340
|
+
}
|
|
341
|
+
function identityTool(name, summary, properties, cliExample, options) {
|
|
342
|
+
return defineTool({
|
|
343
|
+
name,
|
|
344
|
+
summary,
|
|
345
|
+
properties,
|
|
346
|
+
required: options.required ?? [],
|
|
347
|
+
capability: NO_CAPABILITY,
|
|
348
|
+
mutability: options.mutability,
|
|
349
|
+
transports: writeTransports,
|
|
350
|
+
cliExample,
|
|
351
|
+
hostedAgent: { available: false, ...options.hostedAgent }
|
|
352
|
+
});
|
|
251
353
|
}
|
|
252
354
|
function readTool(name, summary, properties, cliExample, options) {
|
|
253
355
|
return defineTool({
|
|
@@ -275,6 +377,19 @@ function writeTool(name, summary, properties, cliExample, options) {
|
|
|
275
377
|
hostedAgent: options?.hostedAgent
|
|
276
378
|
});
|
|
277
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
|
+
}
|
|
278
393
|
function sourceWriteTool(name, summary, properties, cliExample) {
|
|
279
394
|
return defineTool({
|
|
280
395
|
name,
|
|
@@ -338,31 +453,8 @@ function defaultRiskClass(mutability) {
|
|
|
338
453
|
return "low";
|
|
339
454
|
}
|
|
340
455
|
function defaultToolsets(name) {
|
|
341
|
-
const [prefix] = name.split(".");
|
|
342
|
-
|
|
343
|
-
case "decision":
|
|
344
|
-
case "task":
|
|
345
|
-
return ["work"];
|
|
346
|
-
case "skill":
|
|
347
|
-
return ["brain", "work"];
|
|
348
|
-
case "record":
|
|
349
|
-
case "source":
|
|
350
|
-
case "capture":
|
|
351
|
-
case "ingestion":
|
|
352
|
-
return ["brain", "ingestion"];
|
|
353
|
-
case "search":
|
|
354
|
-
case "context":
|
|
355
|
-
case "evidence":
|
|
356
|
-
case "graph":
|
|
357
|
-
return ["brain", "retrieval"];
|
|
358
|
-
case "tools":
|
|
359
|
-
return ["registry"];
|
|
360
|
-
case "audit":
|
|
361
|
-
case "event":
|
|
362
|
-
return ["audit"];
|
|
363
|
-
default:
|
|
364
|
-
return ["brain"];
|
|
365
|
-
}
|
|
456
|
+
const [prefix = ""] = name.split(".");
|
|
457
|
+
return defaultToolsetsByPrefix[prefix] ?? ["brain"];
|
|
366
458
|
}
|
|
367
459
|
function defaultSearchTerms(name, summary) {
|
|
368
460
|
return [.../* @__PURE__ */ new Set([...tokenize(name), ...tokenize(summary)])];
|
|
@@ -373,14 +465,35 @@ function tokenize(text) {
|
|
|
373
465
|
function stringProps(names) {
|
|
374
466
|
return Object.fromEntries(names.map((name) => [name, { type: "string" }]));
|
|
375
467
|
}
|
|
376
|
-
var readTransports, writeTransports, toolDefinitions;
|
|
468
|
+
var readTransports, writeTransports, NO_CAPABILITY, defaultToolsetsByPrefix, toolDefinitions;
|
|
377
469
|
var init_registry = __esm({
|
|
378
470
|
"../tools/dist/registry.js"() {
|
|
379
471
|
"use strict";
|
|
472
|
+
init_gating();
|
|
473
|
+
init_canvasTools();
|
|
380
474
|
init_inputParsers();
|
|
381
475
|
readTransports = ["cli", "hosted_mcp", "local_mcp"];
|
|
382
476
|
writeTransports = ["cli", "hosted_mcp", "local_mcp"];
|
|
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
|
+
};
|
|
383
495
|
toolDefinitions = [
|
|
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"),
|
|
384
497
|
readTool("whoami", "Return principal, actor, scope, and capabilities.", {}, "sift whoami"),
|
|
385
498
|
readTool("brain.list", "List brains available to the current scope.", {}, "sift brain list"),
|
|
386
499
|
readTool("brain.use", "Select the current brain scope for subsequent operations.", stringProps(["brainId"]), "sift brain use <brain-id>"),
|
|
@@ -465,8 +578,15 @@ var init_registry = __esm({
|
|
|
465
578
|
evidenceIds: { type: "array", items: { type: "string" } },
|
|
466
579
|
visibility: { type: "array", items: { type: "string" } }
|
|
467
580
|
}, "sift task create", { required: ["title", "visibility"] }),
|
|
581
|
+
identityTool("agent.register", "Register the calling agent as a workspace agent worker acting for the token's owner; idempotent on (workspace, owner, normalized name) and never mutates token state.", {
|
|
582
|
+
name: { type: "string", maxLength: 80 },
|
|
583
|
+
description: { type: "string", maxLength: 280 },
|
|
584
|
+
kind: { type: "string" }
|
|
585
|
+
}, 'sift agent register --name "Claude Code" --description "Coding agent"', { required: ["name"], mutability: "write" }),
|
|
586
|
+
identityTool("agent.status", "Report the request's resolved agent identity (from the asserted agent name) or none.", {}, "sift agent status --json", { mutability: "read" }),
|
|
468
587
|
readTool("skill.resolve", "Resolve at most three advisory skill candidates for a task description, each with the skill record id, pinned active version id, title, and applicability summary, or an empty list.", { query: { type: "string" } }, "sift skill resolve 'draft the monthly investor update'", { required: ["query"] }),
|
|
469
|
-
readTool("skill.get", "Read a skill's pinned active version markdown body
|
|
588
|
+
readTool("skill.get", "Read a skill's pinned active version markdown body, version id, and bundle file paths by skill record id.", { skillId: { type: "string" } }, "sift skill get <skill-id>", { required: ["skillId"] }),
|
|
589
|
+
readTool("skill.file", "Read one of a skill's bundle files by path; fetch a file only when the skill body references it and the task needs that detail, using a path listed by skill.get.", { skillId: { type: "string" }, path: { type: "string" } }, "sift skill file <skill-id> examples/2026-05-29-good.md", { required: ["skillId", "path"] }),
|
|
470
590
|
writeTool("skill.exercise", "Report that a skill version informed an output on a surface; the report is an attribution claim only.", {
|
|
471
591
|
skillId: { type: "string" },
|
|
472
592
|
versionId: { type: "string" },
|
|
@@ -490,16 +610,68 @@ var init_registry = __esm({
|
|
|
490
610
|
severity: { type: "string" },
|
|
491
611
|
visibility: { type: "array", items: { type: "string" } }
|
|
492
612
|
}, "sift skill teach <skill-id> --lesson 'when X, do Y'", { required: ["skillId", "lesson", "visibility"] }),
|
|
493
|
-
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.", {
|
|
494
614
|
query: { type: "string" },
|
|
495
615
|
limit: { type: "integer", minimum: 1, maximum: 20 }
|
|
496
616
|
}, "sift search query 'launch risks'"),
|
|
497
|
-
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"],
|
|
498
624
|
hostedAgent: {
|
|
499
625
|
toolsets: ["brain", "retrieval"],
|
|
500
626
|
searchTerms: ["context", "cite", "answer", "evidence"]
|
|
501
627
|
}
|
|
502
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
|
+
}),
|
|
503
675
|
readTool("context.profile", "Read a permission-filtered profile context model.", {}, "sift context profile"),
|
|
504
676
|
readTool("evidence.list", "List authorized evidence links for a record.", stringProps(["recordId"]), "sift evidence list <record-id>"),
|
|
505
677
|
readTool("evidence.get", "Read an authorized evidence item.", stringProps(["evidenceId"]), "sift evidence get <evidence-id>"),
|
|
@@ -515,6 +687,7 @@ function executeWhoami(auth) {
|
|
|
515
687
|
return {
|
|
516
688
|
principal: { id: auth.principalId, type: auth.principalType },
|
|
517
689
|
actor: { id: auth.actorId, type: auth.actorType },
|
|
690
|
+
...auth.agent === void 0 ? {} : { agent: auth.agent },
|
|
518
691
|
scope: { workspaceId: auth.workspaceId, brainId: auth.brainId },
|
|
519
692
|
requestId: auth.requestId,
|
|
520
693
|
authPath: auth.authPath,
|
|
@@ -581,7 +754,7 @@ function executeToolsSearch(input, searchInput) {
|
|
|
581
754
|
function availableTools(input) {
|
|
582
755
|
const transport = input.transport ?? "local_mcp";
|
|
583
756
|
const availableNames = new Set(input.availableToolNames ?? IMPLEMENTED_TOOL_NAMES);
|
|
584
|
-
return listToolDefinitions().filter((tool) => availableNames.has(tool.name) && tool.transports.includes(transport) && input.auth.capabilities
|
|
757
|
+
return listToolDefinitions().filter((tool) => availableNames.has(tool.name) && tool.transports.includes(transport) && isToolAuthorized(input.auth.capabilities, tool));
|
|
585
758
|
}
|
|
586
759
|
function scoreTool(tool, intentTokens) {
|
|
587
760
|
const haystack = /* @__PURE__ */ new Set([
|
|
@@ -601,6 +774,7 @@ var init_discovery = __esm({
|
|
|
601
774
|
"use strict";
|
|
602
775
|
init_registry();
|
|
603
776
|
IMPLEMENTED_TOOL_NAMES = [
|
|
777
|
+
"contract.get",
|
|
604
778
|
"whoami",
|
|
605
779
|
"brain.list",
|
|
606
780
|
"brain.use",
|
|
@@ -631,7 +805,9 @@ var init_discovery = __esm({
|
|
|
631
805
|
"evidence.get",
|
|
632
806
|
"graph.neighbors",
|
|
633
807
|
"event.list",
|
|
634
|
-
"audit.events"
|
|
808
|
+
"audit.events",
|
|
809
|
+
"agent.register",
|
|
810
|
+
"agent.status"
|
|
635
811
|
];
|
|
636
812
|
}
|
|
637
813
|
});
|
|
@@ -704,6 +880,26 @@ var init_captureTools = __esm({
|
|
|
704
880
|
}
|
|
705
881
|
});
|
|
706
882
|
|
|
883
|
+
// ../tools/dist/contractTools.js
|
|
884
|
+
function contractToolHandlers(input) {
|
|
885
|
+
return {
|
|
886
|
+
"contract.get": () => {
|
|
887
|
+
if (input.service.getContract === void 0) {
|
|
888
|
+
throw new Error("Tool 'contract.get' requires a runtime service contract.");
|
|
889
|
+
}
|
|
890
|
+
return input.service.getContract({ auth: input.auth });
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
function contractToolAvailability(service) {
|
|
895
|
+
return [[service.getContract !== void 0, ["contract.get"]]];
|
|
896
|
+
}
|
|
897
|
+
var init_contractTools = __esm({
|
|
898
|
+
"../tools/dist/contractTools.js"() {
|
|
899
|
+
"use strict";
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
707
903
|
// ../tools/dist/skillTools.js
|
|
708
904
|
function skillToolHandlers(input, toolInput) {
|
|
709
905
|
return {
|
|
@@ -723,6 +919,15 @@ function skillToolHandlers(input, toolInput) {
|
|
|
723
919
|
skillId: requireString(toolInput, "skillId")
|
|
724
920
|
});
|
|
725
921
|
},
|
|
922
|
+
"skill.file": () => {
|
|
923
|
+
if (input.service.getSkillFile === void 0)
|
|
924
|
+
throw missingSkillService("skill.file");
|
|
925
|
+
return input.service.getSkillFile({
|
|
926
|
+
auth: input.auth,
|
|
927
|
+
skillId: requireString(toolInput, "skillId"),
|
|
928
|
+
path: requireString(toolInput, "path")
|
|
929
|
+
});
|
|
930
|
+
},
|
|
726
931
|
"skill.exercise": () => {
|
|
727
932
|
if (input.service.recordSkillExercise === void 0) {
|
|
728
933
|
throw missingSkillService("skill.exercise");
|
|
@@ -743,6 +948,7 @@ function skillToolAvailability(service) {
|
|
|
743
948
|
return [
|
|
744
949
|
[service.resolveSkills !== void 0, ["skill.resolve"]],
|
|
745
950
|
[service.getSkill !== void 0, ["skill.get"]],
|
|
951
|
+
[service.getSkillFile !== void 0, ["skill.file"]],
|
|
746
952
|
[service.recordSkillExercise !== void 0, ["skill.exercise"]],
|
|
747
953
|
[service.teachSkill !== void 0, ["skill.teach"]]
|
|
748
954
|
];
|
|
@@ -805,6 +1011,106 @@ var init_skillTools = __esm({
|
|
|
805
1011
|
}
|
|
806
1012
|
});
|
|
807
1013
|
|
|
1014
|
+
// ../tools/dist/agentIdentityTools.js
|
|
1015
|
+
function agentIdentityToolHandlers(input, toolInput) {
|
|
1016
|
+
return {
|
|
1017
|
+
"agent.register": () => {
|
|
1018
|
+
if (input.service.registerAgent === void 0) {
|
|
1019
|
+
throw missingAgentIdentityService("agent.register");
|
|
1020
|
+
}
|
|
1021
|
+
return input.service.registerAgent({
|
|
1022
|
+
auth: input.auth,
|
|
1023
|
+
name: requireString(toolInput, "name"),
|
|
1024
|
+
description: optionalString(toolInput, "description"),
|
|
1025
|
+
kind: parseAgentKind(toolInput)
|
|
1026
|
+
});
|
|
1027
|
+
},
|
|
1028
|
+
"agent.status": () => {
|
|
1029
|
+
if (input.service.getAgentStatus === void 0) {
|
|
1030
|
+
throw missingAgentIdentityService("agent.status");
|
|
1031
|
+
}
|
|
1032
|
+
return input.service.getAgentStatus({ auth: input.auth });
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
function agentIdentityToolAvailability(service) {
|
|
1037
|
+
return [
|
|
1038
|
+
[service.registerAgent !== void 0, ["agent.register"]],
|
|
1039
|
+
[service.getAgentStatus !== void 0, ["agent.status"]]
|
|
1040
|
+
];
|
|
1041
|
+
}
|
|
1042
|
+
function parseAgentKind(toolInput) {
|
|
1043
|
+
const kind = optionalString(toolInput, "kind");
|
|
1044
|
+
if (kind === void 0) {
|
|
1045
|
+
return void 0;
|
|
1046
|
+
}
|
|
1047
|
+
if (kind !== "agent" && kind !== "service") {
|
|
1048
|
+
throw new Error("kind must be 'agent' or 'service'.");
|
|
1049
|
+
}
|
|
1050
|
+
return kind;
|
|
1051
|
+
}
|
|
1052
|
+
function missingAgentIdentityService(toolName) {
|
|
1053
|
+
return new Error(`Tool '${toolName}' requires a runtime service contract.`);
|
|
1054
|
+
}
|
|
1055
|
+
var init_agentIdentityTools = __esm({
|
|
1056
|
+
"../tools/dist/agentIdentityTools.js"() {
|
|
1057
|
+
"use strict";
|
|
1058
|
+
init_inputParsers();
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
// ../tools/dist/toolAvailability.js
|
|
1063
|
+
function runtimeAvailableToolNames(service) {
|
|
1064
|
+
const baseNames = [
|
|
1065
|
+
"whoami",
|
|
1066
|
+
"tools.list",
|
|
1067
|
+
"tools.schema",
|
|
1068
|
+
"tools.help",
|
|
1069
|
+
"tools.search",
|
|
1070
|
+
"brain.list",
|
|
1071
|
+
"brain.use",
|
|
1072
|
+
"scope.current",
|
|
1073
|
+
"capture.text",
|
|
1074
|
+
"capture.batch",
|
|
1075
|
+
"search.query",
|
|
1076
|
+
"context.assemble"
|
|
1077
|
+
];
|
|
1078
|
+
const optionalNames = [
|
|
1079
|
+
[service.ingestFile !== void 0, ["capture.file"]],
|
|
1080
|
+
[service.createDecision !== void 0, ["decision.create"]],
|
|
1081
|
+
[service.createTask !== void 0, ["task.create"]],
|
|
1082
|
+
...skillToolAvailability(service),
|
|
1083
|
+
...agentIdentityToolAvailability(service),
|
|
1084
|
+
...contractToolAvailability(service),
|
|
1085
|
+
[service.listSources !== void 0, ["source.list"]],
|
|
1086
|
+
[service.createSource !== void 0, ["source.create"]],
|
|
1087
|
+
[service.getSource !== void 0, ["source.get", "source.status"]],
|
|
1088
|
+
[service.getIngestionStatus !== void 0, ["ingestion.status"]],
|
|
1089
|
+
[service.listRecords !== void 0, ["record.list"]],
|
|
1090
|
+
[service.getRecord !== void 0, ["record.get"]],
|
|
1091
|
+
[service.createMarkdownRecord !== void 0, ["record.create_markdown"]],
|
|
1092
|
+
[service.patchRecordSection !== void 0, ["record.patch_section"]],
|
|
1093
|
+
[service.listRecordVersions !== void 0, ["record.versions"]],
|
|
1094
|
+
[service.listEvidence !== void 0, ["evidence.list"]],
|
|
1095
|
+
[service.getEvidence !== void 0, ["evidence.get"]],
|
|
1096
|
+
[service.listGraphNeighbors !== void 0, ["graph.neighbors"]],
|
|
1097
|
+
[service.listEvents !== void 0, ["event.list"]],
|
|
1098
|
+
[service.getContextProfile !== void 0, ["context.profile"]],
|
|
1099
|
+
[service.listAuditEvents !== void 0, ["audit.events"]],
|
|
1100
|
+
[service.webSearch !== void 0, ["web.search"]],
|
|
1101
|
+
[service.webFetch !== void 0, ["web.fetch"]]
|
|
1102
|
+
];
|
|
1103
|
+
return [...baseNames, ...optionalNames.flatMap(([enabled, names]) => enabled ? names : [])];
|
|
1104
|
+
}
|
|
1105
|
+
var init_toolAvailability = __esm({
|
|
1106
|
+
"../tools/dist/toolAvailability.js"() {
|
|
1107
|
+
"use strict";
|
|
1108
|
+
init_agentIdentityTools();
|
|
1109
|
+
init_contractTools();
|
|
1110
|
+
init_skillTools();
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
|
|
808
1114
|
// ../tools/dist/toolLog.js
|
|
809
1115
|
function logToolCall(input) {
|
|
810
1116
|
input.onToolLog?.({
|
|
@@ -870,6 +1176,73 @@ var init_toolLog = __esm({
|
|
|
870
1176
|
}
|
|
871
1177
|
});
|
|
872
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
|
+
|
|
873
1246
|
// ../tools/dist/executor.js
|
|
874
1247
|
function createRuntimeToolExecutor(input) {
|
|
875
1248
|
const availableToolNames = runtimeAvailableToolNames(input.service);
|
|
@@ -937,7 +1310,10 @@ function createToolHandlers(input, toolInput) {
|
|
|
937
1310
|
"context.profile": () => executeContextProfile(input, toolInput),
|
|
938
1311
|
"decision.create": () => executeDecisionCreate(input, toolInput),
|
|
939
1312
|
"task.create": () => executeTaskCreate(input, toolInput),
|
|
1313
|
+
...webToolHandlers(input, toolInput),
|
|
940
1314
|
...skillToolHandlers(input, toolInput),
|
|
1315
|
+
...agentIdentityToolHandlers(input, toolInput),
|
|
1316
|
+
...contractToolHandlers(input),
|
|
941
1317
|
"source.list": () => executeSourceList(input),
|
|
942
1318
|
"source.create": () => executeSourceCreate(input, toolInput),
|
|
943
1319
|
"source.get": () => executeSourceRead(input, toolInput, "source.get"),
|
|
@@ -980,44 +1356,6 @@ function createToolHandlers(input, toolInput) {
|
|
|
980
1356
|
"audit.events": () => executeAuditEvents(input, toolInput)
|
|
981
1357
|
};
|
|
982
1358
|
}
|
|
983
|
-
function runtimeAvailableToolNames(service) {
|
|
984
|
-
const baseNames = [
|
|
985
|
-
"whoami",
|
|
986
|
-
"tools.list",
|
|
987
|
-
"tools.schema",
|
|
988
|
-
"tools.help",
|
|
989
|
-
"tools.search",
|
|
990
|
-
"brain.list",
|
|
991
|
-
"brain.use",
|
|
992
|
-
"scope.current",
|
|
993
|
-
"capture.text",
|
|
994
|
-
"capture.batch",
|
|
995
|
-
"search.query",
|
|
996
|
-
"context.assemble"
|
|
997
|
-
];
|
|
998
|
-
const optionalNames = [
|
|
999
|
-
[service.ingestFile !== void 0, ["capture.file"]],
|
|
1000
|
-
[service.createDecision !== void 0, ["decision.create"]],
|
|
1001
|
-
[service.createTask !== void 0, ["task.create"]],
|
|
1002
|
-
...skillToolAvailability(service),
|
|
1003
|
-
[service.listSources !== void 0, ["source.list"]],
|
|
1004
|
-
[service.createSource !== void 0, ["source.create"]],
|
|
1005
|
-
[service.getSource !== void 0, ["source.get", "source.status"]],
|
|
1006
|
-
[service.getIngestionStatus !== void 0, ["ingestion.status"]],
|
|
1007
|
-
[service.listRecords !== void 0, ["record.list"]],
|
|
1008
|
-
[service.getRecord !== void 0, ["record.get"]],
|
|
1009
|
-
[service.createMarkdownRecord !== void 0, ["record.create_markdown"]],
|
|
1010
|
-
[service.patchRecordSection !== void 0, ["record.patch_section"]],
|
|
1011
|
-
[service.listRecordVersions !== void 0, ["record.versions"]],
|
|
1012
|
-
[service.listEvidence !== void 0, ["evidence.list"]],
|
|
1013
|
-
[service.getEvidence !== void 0, ["evidence.get"]],
|
|
1014
|
-
[service.listGraphNeighbors !== void 0, ["graph.neighbors"]],
|
|
1015
|
-
[service.listEvents !== void 0, ["event.list"]],
|
|
1016
|
-
[service.getContextProfile !== void 0, ["context.profile"]],
|
|
1017
|
-
[service.listAuditEvents !== void 0, ["audit.events"]]
|
|
1018
|
-
];
|
|
1019
|
-
return [...baseNames, ...optionalNames.flatMap(([enabled, names]) => enabled ? names : [])];
|
|
1020
|
-
}
|
|
1021
1359
|
function missingService(toolName) {
|
|
1022
1360
|
return new Error(`Tool '${toolName}' requires a runtime service contract.`);
|
|
1023
1361
|
}
|
|
@@ -1032,6 +1370,21 @@ function executeSearchQuery(input, toolInput) {
|
|
|
1032
1370
|
}
|
|
1033
1371
|
function executeContextAssemble(input, toolInput) {
|
|
1034
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
|
+
}
|
|
1035
1388
|
return input.service.retrieveCitedContext({
|
|
1036
1389
|
auth: input.auth,
|
|
1037
1390
|
query: query.query,
|
|
@@ -1039,6 +1392,13 @@ function executeContextAssemble(input, toolInput) {
|
|
|
1039
1392
|
maxChars: query.maxChars
|
|
1040
1393
|
});
|
|
1041
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
|
+
}
|
|
1042
1402
|
function executeContextProfile(input, toolInput) {
|
|
1043
1403
|
if (input.service.getContextProfile === void 0) {
|
|
1044
1404
|
throw new Error("Tool 'context.profile' requires a profile read service contract.");
|
|
@@ -1144,9 +1504,13 @@ var init_executor = __esm({
|
|
|
1144
1504
|
init_discovery();
|
|
1145
1505
|
init_captureTools();
|
|
1146
1506
|
init_inputParsers();
|
|
1507
|
+
init_contractTools();
|
|
1147
1508
|
init_skillTools();
|
|
1509
|
+
init_agentIdentityTools();
|
|
1510
|
+
init_toolAvailability();
|
|
1148
1511
|
init_results();
|
|
1149
1512
|
init_toolLog();
|
|
1513
|
+
init_webToolRuntime();
|
|
1150
1514
|
}
|
|
1151
1515
|
});
|
|
1152
1516
|
|
|
@@ -1169,7 +1533,7 @@ function createCliCommandMetadata(filter) {
|
|
|
1169
1533
|
}
|
|
1170
1534
|
function availableTools2(filter) {
|
|
1171
1535
|
const availableNames = new Set(filter.toolNames ?? IMPLEMENTED_TOOL_NAMES);
|
|
1172
|
-
return listToolDefinitions().filter((tool) => availableNames.has(tool.name) && tool.transports.includes(filter.transport) && filter.capabilities
|
|
1536
|
+
return listToolDefinitions().filter((tool) => availableNames.has(tool.name) && tool.transports.includes(filter.transport) && isToolAuthorized(filter.capabilities, tool));
|
|
1173
1537
|
}
|
|
1174
1538
|
var init_generated = __esm({
|
|
1175
1539
|
"../tools/dist/generated.js"() {
|
|
@@ -1185,7 +1549,7 @@ function createMcpAdapter(input) {
|
|
|
1185
1549
|
...IMPLEMENTED_TOOL_NAMES
|
|
1186
1550
|
];
|
|
1187
1551
|
const availableNameSet = new Set(availableToolNames);
|
|
1188
|
-
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));
|
|
1189
1553
|
return {
|
|
1190
1554
|
listTools() {
|
|
1191
1555
|
return createMcpToolSchemas({
|
|
@@ -1222,6 +1586,9 @@ function renderToolResult(result2) {
|
|
|
1222
1586
|
}
|
|
1223
1587
|
function classifyToolError(error) {
|
|
1224
1588
|
if (error instanceof Error) {
|
|
1589
|
+
if (error.message.startsWith("Sift contract required.")) {
|
|
1590
|
+
return "contract_required";
|
|
1591
|
+
}
|
|
1225
1592
|
if (isPermissionError2(error)) {
|
|
1226
1593
|
return "permission_denied";
|
|
1227
1594
|
}
|
|
@@ -1334,89 +1701,41 @@ var init_hostedMcpEntrypoint = __esm({
|
|
|
1334
1701
|
}
|
|
1335
1702
|
});
|
|
1336
1703
|
|
|
1337
|
-
// ../tools/dist/
|
|
1338
|
-
function
|
|
1704
|
+
// ../tools/dist/mcpJsonRpcCore.js
|
|
1705
|
+
function createMcpJsonRpcCore(input) {
|
|
1706
|
+
const { adapter, config: config2 } = input;
|
|
1339
1707
|
return {
|
|
1340
|
-
async
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
capabilities: serverInput.capabilities,
|
|
1344
|
-
executor: serverInput.executor
|
|
1345
|
-
});
|
|
1346
|
-
let buffer = "";
|
|
1347
|
-
input.input.setEncoding("utf8");
|
|
1348
|
-
for await (const chunk of input.input) {
|
|
1349
|
-
buffer += chunk;
|
|
1350
|
-
let newline = buffer.indexOf("\n");
|
|
1351
|
-
while (newline >= 0) {
|
|
1352
|
-
const line = buffer.slice(0, newline).trim();
|
|
1353
|
-
buffer = buffer.slice(newline + 1);
|
|
1354
|
-
if (line.length > 0) {
|
|
1355
|
-
await handleLine(line, adapter, input.output, input.error);
|
|
1356
|
-
}
|
|
1357
|
-
newline = buffer.indexOf("\n");
|
|
1358
|
-
}
|
|
1708
|
+
async handleMessage(message) {
|
|
1709
|
+
if (message.id === void 0) {
|
|
1710
|
+
return null;
|
|
1359
1711
|
}
|
|
1360
|
-
const
|
|
1361
|
-
if (
|
|
1362
|
-
|
|
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");
|
|
1363
1724
|
}
|
|
1364
1725
|
}
|
|
1365
1726
|
};
|
|
1366
1727
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
try {
|
|
1370
|
-
message = JSON.parse(line);
|
|
1371
|
-
} catch {
|
|
1372
|
-
writeResponse(output, {
|
|
1373
|
-
jsonrpc: "2.0",
|
|
1374
|
-
id: null,
|
|
1375
|
-
error: { code: -32700, message: "Parse error" }
|
|
1376
|
-
});
|
|
1377
|
-
return;
|
|
1378
|
-
}
|
|
1379
|
-
if (message.id === void 0) {
|
|
1380
|
-
if (message.method === "notifications/initialized")
|
|
1381
|
-
return;
|
|
1382
|
-
error?.write(`Ignoring MCP notification '${String(message.method)}'.
|
|
1383
|
-
`);
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
|
-
const id = normalizeId(message.id);
|
|
1387
|
-
if (message.jsonrpc !== "2.0" || typeof message.method !== "string") {
|
|
1388
|
-
writeResponse(output, {
|
|
1389
|
-
jsonrpc: "2.0",
|
|
1390
|
-
id,
|
|
1391
|
-
error: { code: -32600, message: "Invalid Request" }
|
|
1392
|
-
});
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1395
|
-
try {
|
|
1396
|
-
writeResponse(output, {
|
|
1397
|
-
jsonrpc: "2.0",
|
|
1398
|
-
id,
|
|
1399
|
-
result: await dispatchRequest(message.method, message.params, adapter)
|
|
1400
|
-
});
|
|
1401
|
-
} catch (err) {
|
|
1402
|
-
writeResponse(output, {
|
|
1403
|
-
jsonrpc: "2.0",
|
|
1404
|
-
id,
|
|
1405
|
-
error: {
|
|
1406
|
-
code: -32601,
|
|
1407
|
-
message: err instanceof Error ? err.message : "Method not found"
|
|
1408
|
-
}
|
|
1409
|
-
});
|
|
1410
|
-
}
|
|
1728
|
+
function parseErrorResponse() {
|
|
1729
|
+
return errorResponse(null, -32700, "Parse error");
|
|
1411
1730
|
}
|
|
1412
|
-
async function dispatchRequest(method, params, adapter) {
|
|
1731
|
+
async function dispatchRequest(method, params, adapter, config2) {
|
|
1413
1732
|
if (method === "initialize") {
|
|
1414
1733
|
const requested = readProtocolVersion(params);
|
|
1415
1734
|
return {
|
|
1416
1735
|
protocolVersion: requested ?? MCP_PROTOCOL_VERSION,
|
|
1417
1736
|
capabilities: { tools: { listChanged: false } },
|
|
1418
|
-
serverInfo: { name:
|
|
1419
|
-
instructions:
|
|
1737
|
+
serverInfo: { name: config2.serverName, version: config2.version },
|
|
1738
|
+
instructions: config2.instructions
|
|
1420
1739
|
};
|
|
1421
1740
|
}
|
|
1422
1741
|
if (method === "ping")
|
|
@@ -1427,7 +1746,7 @@ async function dispatchRequest(method, params, adapter) {
|
|
|
1427
1746
|
const call = parseToolCall(params);
|
|
1428
1747
|
return adapter.callTool(call);
|
|
1429
1748
|
}
|
|
1430
|
-
throw new Error(`Method '${method}' is not supported by Sift
|
|
1749
|
+
throw new Error(`Method '${method}' is not supported by Sift MCP.`);
|
|
1431
1750
|
}
|
|
1432
1751
|
function readProtocolVersion(params) {
|
|
1433
1752
|
if (!isRecord(params))
|
|
@@ -1443,6 +1762,9 @@ function parseToolCall(params) {
|
|
|
1443
1762
|
arguments: isRecord(params.arguments) ? params.arguments : {}
|
|
1444
1763
|
};
|
|
1445
1764
|
}
|
|
1765
|
+
function errorResponse(id, code, message) {
|
|
1766
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
1767
|
+
}
|
|
1446
1768
|
function isRecord(value) {
|
|
1447
1769
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1448
1770
|
}
|
|
@@ -1451,16 +1773,80 @@ function normalizeId(value) {
|
|
|
1451
1773
|
return value;
|
|
1452
1774
|
return null;
|
|
1453
1775
|
}
|
|
1454
|
-
function writeResponse(output, response) {
|
|
1455
|
-
output.write(`${JSON.stringify(response)}
|
|
1456
|
-
`);
|
|
1457
|
-
}
|
|
1458
1776
|
var MCP_PROTOCOL_VERSION;
|
|
1459
|
-
var
|
|
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
|
+
}
|
|
1839
|
+
function writeResponse(output, response) {
|
|
1840
|
+
output.write(`${JSON.stringify(response)}
|
|
1841
|
+
`);
|
|
1842
|
+
}
|
|
1843
|
+
var LOCAL_INSTRUCTIONS;
|
|
1844
|
+
var init_localMcpStdioServer = __esm({
|
|
1460
1845
|
"../tools/dist/localMcpStdioServer.js"() {
|
|
1461
1846
|
"use strict";
|
|
1462
1847
|
init_mcpAdapter();
|
|
1463
|
-
|
|
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.";
|
|
1464
1850
|
}
|
|
1465
1851
|
});
|
|
1466
1852
|
|
|
@@ -1468,13 +1854,19 @@ var init_localMcpStdioServer = __esm({
|
|
|
1468
1854
|
var dist_exports = {};
|
|
1469
1855
|
__export(dist_exports, {
|
|
1470
1856
|
MCP_PROTOCOL_VERSION: () => MCP_PROTOCOL_VERSION,
|
|
1857
|
+
NO_CAPABILITY: () => NO_CAPABILITY,
|
|
1858
|
+
UNGATED_TOOL_NAMES: () => UNGATED_TOOL_NAMES,
|
|
1471
1859
|
createCliCommandMetadata: () => createCliCommandMetadata,
|
|
1472
1860
|
createHostedMcpEntrypoint: () => createHostedMcpEntrypoint,
|
|
1473
1861
|
createLocalMcpStdioServer: () => createLocalMcpStdioServer,
|
|
1474
1862
|
createMcpAdapter: () => createMcpAdapter,
|
|
1863
|
+
createMcpJsonRpcCore: () => createMcpJsonRpcCore,
|
|
1475
1864
|
createMcpToolSchemas: () => createMcpToolSchemas,
|
|
1476
1865
|
createRuntimeToolExecutor: () => createRuntimeToolExecutor,
|
|
1477
|
-
|
|
1866
|
+
isGatedTool: () => isGatedTool,
|
|
1867
|
+
isToolAuthorized: () => isToolAuthorized,
|
|
1868
|
+
listToolDefinitions: () => listToolDefinitions,
|
|
1869
|
+
parseErrorResponse: () => parseErrorResponse
|
|
1478
1870
|
});
|
|
1479
1871
|
var init_dist = __esm({
|
|
1480
1872
|
"../tools/dist/index.js"() {
|
|
@@ -1484,12 +1876,14 @@ var init_dist = __esm({
|
|
|
1484
1876
|
init_hostedMcpEntrypoint();
|
|
1485
1877
|
init_localMcpStdioServer();
|
|
1486
1878
|
init_mcpAdapter();
|
|
1879
|
+
init_mcpJsonRpcCore();
|
|
1880
|
+
init_gating();
|
|
1487
1881
|
init_registry();
|
|
1488
1882
|
}
|
|
1489
1883
|
});
|
|
1490
1884
|
|
|
1491
1885
|
// src/index.ts
|
|
1492
|
-
import { readFile } from "fs/promises";
|
|
1886
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1493
1887
|
|
|
1494
1888
|
// src/support.ts
|
|
1495
1889
|
import { createHash } from "crypto";
|
|
@@ -1720,6 +2114,9 @@ function fail(message) {
|
|
|
1720
2114
|
}
|
|
1721
2115
|
function classifyError(error) {
|
|
1722
2116
|
if (error instanceof Error) {
|
|
2117
|
+
if (error.message.startsWith("Sift contract required.")) {
|
|
2118
|
+
return "contract_required";
|
|
2119
|
+
}
|
|
1723
2120
|
if (isPermissionError(error)) {
|
|
1724
2121
|
return "permission_denied";
|
|
1725
2122
|
}
|
|
@@ -1768,6 +2165,41 @@ function addReceiptLine(lines, label, value) {
|
|
|
1768
2165
|
}
|
|
1769
2166
|
}
|
|
1770
2167
|
|
|
2168
|
+
// src/agentCommands.ts
|
|
2169
|
+
async function agentRegister(executor, assertedAgentName, rest, json) {
|
|
2170
|
+
if (executor === void 0) {
|
|
2171
|
+
return fail("No Sift API executor is configured for agent.register.");
|
|
2172
|
+
}
|
|
2173
|
+
const parsed = parseOptions(rest);
|
|
2174
|
+
const name = optionalOption(parsed, "name") ?? assertedAgentName;
|
|
2175
|
+
if (name === void 0 || name.trim().length === 0) {
|
|
2176
|
+
return fail("Missing agent name: pass --name or set SIFT_AGENT.");
|
|
2177
|
+
}
|
|
2178
|
+
const input = { name };
|
|
2179
|
+
const description = optionalOption(parsed, "description");
|
|
2180
|
+
if (description !== void 0) {
|
|
2181
|
+
input.description = description;
|
|
2182
|
+
}
|
|
2183
|
+
const kind = optionalOption(parsed, "kind");
|
|
2184
|
+
if (kind !== void 0) {
|
|
2185
|
+
input.kind = kind;
|
|
2186
|
+
}
|
|
2187
|
+
const result2 = await executor.execute("agent.register", input);
|
|
2188
|
+
return ok(json ? `${JSON.stringify(result2)}
|
|
2189
|
+
` : renderAgentRegisterResult(result2));
|
|
2190
|
+
}
|
|
2191
|
+
function renderAgentRegisterResult(result2) {
|
|
2192
|
+
if (typeof result2 !== "object" || result2 === null || !("agent" in result2)) {
|
|
2193
|
+
return `${JSON.stringify(result2)}
|
|
2194
|
+
`;
|
|
2195
|
+
}
|
|
2196
|
+
const { agent, created, reactivated } = result2;
|
|
2197
|
+
const verb = created === true ? "Registered" : reactivated === true ? "Reactivated" : "Already registered";
|
|
2198
|
+
const actsFor = agent.actsForDisplayName === void 0 ? "" : ` (acting for ${agent.actsForDisplayName})`;
|
|
2199
|
+
return `${verb} agent worker '${agent.name ?? "unknown"}'${actsFor}.
|
|
2200
|
+
`;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
1771
2203
|
// src/auth/commandAdapter.ts
|
|
1772
2204
|
function isAuthCommand(commandKey) {
|
|
1773
2205
|
return commandKey === "login:" || commandKey === "auth:status" || commandKey === "logout:";
|
|
@@ -1791,6 +2223,7 @@ function authCommand(authCommands2, command, input) {
|
|
|
1791
2223
|
|
|
1792
2224
|
// src/capabilityGuard.ts
|
|
1793
2225
|
var commandCapabilities = {
|
|
2226
|
+
"contract:get": "record:read",
|
|
1794
2227
|
"whoami:": "record:read",
|
|
1795
2228
|
"brain:list": "record:read",
|
|
1796
2229
|
"brain:use": "record:read",
|
|
@@ -1838,6 +2271,38 @@ function validateCommandCapability(input) {
|
|
|
1838
2271
|
}
|
|
1839
2272
|
}
|
|
1840
2273
|
|
|
2274
|
+
// src/contractOption.ts
|
|
2275
|
+
function extractContractVersion(argv2) {
|
|
2276
|
+
const index = argv2.indexOf("--contract");
|
|
2277
|
+
if (index === -1) {
|
|
2278
|
+
return { argv: argv2 };
|
|
2279
|
+
}
|
|
2280
|
+
const value = argv2[index + 1];
|
|
2281
|
+
if (value === void 0 || value.trim().length === 0 || value.startsWith("--")) {
|
|
2282
|
+
return { argv: [...argv2.slice(0, index), ...argv2.slice(index + 1)] };
|
|
2283
|
+
}
|
|
2284
|
+
return {
|
|
2285
|
+
argv: [...argv2.slice(0, index), ...argv2.slice(index + 2)],
|
|
2286
|
+
contractVersion: value
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
function applyContractOption(input) {
|
|
2290
|
+
const { argv: argv2, contractVersion } = extractContractVersion(input.argv);
|
|
2291
|
+
if (contractVersion === void 0 || input.executor === void 0) {
|
|
2292
|
+
return { ...input, argv: argv2 };
|
|
2293
|
+
}
|
|
2294
|
+
return { ...input, argv: argv2, executor: withContractVersion(input.executor, contractVersion) };
|
|
2295
|
+
}
|
|
2296
|
+
function withContractVersion(executor, contractVersion) {
|
|
2297
|
+
return {
|
|
2298
|
+
...executor,
|
|
2299
|
+
execute: (name, toolInput) => executor.execute(name, { ...toolInput, contractVersion })
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
// src/specialCommands.ts
|
|
2304
|
+
import { readFile } from "fs/promises";
|
|
2305
|
+
|
|
1841
2306
|
// src/doctor.ts
|
|
1842
2307
|
async function doctor(input) {
|
|
1843
2308
|
const checks = [
|
|
@@ -2002,12 +2467,14 @@ function toolNamesFromResult(result2) {
|
|
|
2002
2467
|
// src/simpleCommands.ts
|
|
2003
2468
|
var knownTopLevelCommands = /* @__PURE__ */ new Set([
|
|
2004
2469
|
"add",
|
|
2470
|
+
"agent",
|
|
2005
2471
|
"ask",
|
|
2006
2472
|
"audit",
|
|
2007
2473
|
"auth",
|
|
2008
2474
|
"brain",
|
|
2009
2475
|
"capture",
|
|
2010
2476
|
"context",
|
|
2477
|
+
"contract",
|
|
2011
2478
|
"decision",
|
|
2012
2479
|
"decide",
|
|
2013
2480
|
"doctor",
|
|
@@ -2310,6 +2777,201 @@ function argsWithoutOptions(args) {
|
|
|
2310
2777
|
return positionals;
|
|
2311
2778
|
}
|
|
2312
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
|
+
|
|
2313
2975
|
// src/toolDiscovery.ts
|
|
2314
2976
|
function toolsList(input) {
|
|
2315
2977
|
if (input.executor !== void 0) {
|
|
@@ -2360,7 +3022,8 @@ function createHostedApiExecutor(input) {
|
|
|
2360
3022
|
authorization: `Bearer ${input.token}`,
|
|
2361
3023
|
"content-type": "application/json",
|
|
2362
3024
|
"x-sift-brain-id": input.brainId,
|
|
2363
|
-
"x-sift-workspace-id": input.workspaceId
|
|
3025
|
+
"x-sift-workspace-id": input.workspaceId,
|
|
3026
|
+
...input.agentName === void 0 || input.agentName.trim().length === 0 ? {} : { "x-sift-agent-name": input.agentName.trim() }
|
|
2364
3027
|
},
|
|
2365
3028
|
body: JSON.stringify({ input: toolInput }, serializeJsonValue)
|
|
2366
3029
|
});
|
|
@@ -2403,7 +3066,8 @@ function errorMessage(parsed, status2) {
|
|
|
2403
3066
|
}
|
|
2404
3067
|
|
|
2405
3068
|
// src/index.ts
|
|
2406
|
-
async function runSiftCli(
|
|
3069
|
+
async function runSiftCli(rawInput) {
|
|
3070
|
+
const input = applyContractOption(rawInput);
|
|
2407
3071
|
const json = input.argv.includes("--json");
|
|
2408
3072
|
const args = input.argv.filter((arg) => arg !== "--json");
|
|
2409
3073
|
const [group, command, ...rest] = args;
|
|
@@ -2412,6 +3076,7 @@ async function runSiftCli(input) {
|
|
|
2412
3076
|
const commandKey = group === "login" ? "login:" : `${group ?? ""}:${command ?? ""}`;
|
|
2413
3077
|
const commandRest = group === "login" ? args.slice(1) : rest;
|
|
2414
3078
|
const handlers = {
|
|
3079
|
+
"contract:get": () => executeSimple2(rawInput.executor, "contract.get", {}, json),
|
|
2415
3080
|
"whoami:": () => executeSimple2(input.executor, "whoami", {}, json),
|
|
2416
3081
|
"brain:list": () => executeSimple2(input.executor, "brain.list", {}, json),
|
|
2417
3082
|
"brain:use": () => idTool({
|
|
@@ -2441,8 +3106,8 @@ async function runSiftCli(input) {
|
|
|
2441
3106
|
json
|
|
2442
3107
|
}),
|
|
2443
3108
|
"capture:text": () => captureText(input.executor, rest, json),
|
|
2444
|
-
"capture:file": () => captureFile(input.executor, input.readFile ??
|
|
2445
|
-
"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),
|
|
2446
3111
|
"source:list": () => sourceList(input.executor, json),
|
|
2447
3112
|
"source:create": () => sourceCreate(input.executor, rest, json),
|
|
2448
3113
|
"source:get": () => sourceRead(input.executor, "get", rest, json),
|
|
@@ -2453,14 +3118,37 @@ async function runSiftCli(input) {
|
|
|
2453
3118
|
"record:create-markdown": () => createMarkdownRecord(input.executor, rest, json),
|
|
2454
3119
|
"record:patch-section": () => patchRecordSection(input.executor, rest, json),
|
|
2455
3120
|
"record:versions": () => recordRead(input.executor, "record.versions", rest, json),
|
|
2456
|
-
"evidence:list": () => idTool({
|
|
2457
|
-
|
|
2458
|
-
|
|
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
|
+
}),
|
|
2459
3145
|
"event:list": () => executeSimple2(input.executor, "event.list", {}, json),
|
|
2460
3146
|
"audit:events": () => auditEvents(input.executor, rest, json),
|
|
2461
3147
|
"decision:create": () => createDecision(input.executor, rest, json),
|
|
2462
3148
|
"task:create": () => createTask(input.executor, rest, json),
|
|
2463
|
-
"
|
|
3149
|
+
"agent:register": () => agentRegister(input.executor, input.agentName, rest, json),
|
|
3150
|
+
"agent:status": () => executeSimple2(input.executor, "agent.status", {}, json),
|
|
3151
|
+
"mcp:serve": () => mcpServe(input.mcpServer, input.config, rawInput.executor, json),
|
|
2464
3152
|
"login:": () => authCommand(input.authCommands, "login", { rest: commandRest, json }),
|
|
2465
3153
|
"auth:status": () => authCommand(input.authCommands, "status", { json }),
|
|
2466
3154
|
"logout:": () => authCommand(input.authCommands, "logout", { json })
|
|
@@ -2474,7 +3162,7 @@ async function runSiftCli(input) {
|
|
|
2474
3162
|
);
|
|
2475
3163
|
}
|
|
2476
3164
|
try {
|
|
2477
|
-
if (isAuthCommand(commandKey)) {
|
|
3165
|
+
if (isAuthCommand(commandKey) || commandKey === "mcp:serve") {
|
|
2478
3166
|
return await handler();
|
|
2479
3167
|
}
|
|
2480
3168
|
validateAuthenticatedScope(input.config, input.now ?? /* @__PURE__ */ new Date());
|
|
@@ -2484,39 +3172,16 @@ async function runSiftCli(input) {
|
|
|
2484
3172
|
return errorResult(error, json);
|
|
2485
3173
|
}
|
|
2486
3174
|
}
|
|
2487
|
-
async function
|
|
2488
|
-
if (group === "doctor" && command === void 0) {
|
|
2489
|
-
return doctor({ config: input.config, executor: input.executor, json, now: input.now ?? /* @__PURE__ */ new Date() });
|
|
2490
|
-
}
|
|
2491
|
-
const simpleCommand = resolveSimpleCommand({
|
|
2492
|
-
args,
|
|
2493
|
-
json,
|
|
2494
|
-
config: input.config,
|
|
2495
|
-
executor: input.executor,
|
|
2496
|
-
readFile: input.readFile ?? readFile,
|
|
2497
|
-
readStdin: input.readStdin,
|
|
2498
|
-
now: input.now ?? /* @__PURE__ */ new Date()
|
|
2499
|
-
});
|
|
2500
|
-
if (simpleCommand === void 0) return void 0;
|
|
2501
|
-
try {
|
|
2502
|
-
validateAuthenticatedScope(input.config, input.now ?? /* @__PURE__ */ new Date());
|
|
2503
|
-
validateCommandCapability({ commandKey: simpleCommand.commandKey, config: input.config });
|
|
2504
|
-
return await simpleCommand.run();
|
|
2505
|
-
} catch (error) {
|
|
2506
|
-
return errorResult(error, json);
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
async function mcpServe(mcpServer, config2, executor, json) {
|
|
3175
|
+
async function mcpServe(mcpServer, config2, executor, _json) {
|
|
2510
3176
|
if (mcpServer === void 0) {
|
|
2511
3177
|
return fail("No local MCP server is configured for mcp.serve.");
|
|
2512
3178
|
}
|
|
2513
3179
|
if (executor === void 0) {
|
|
2514
|
-
return fail("
|
|
3180
|
+
return fail("Not signed in. Run 'sift login', then 'sift mcp serve'.");
|
|
2515
3181
|
}
|
|
2516
3182
|
const result2 = await mcpServer.serve({ config: config2, executor, transport: "local_mcp" });
|
|
2517
3183
|
if (result2 === void 0) return ok("");
|
|
2518
|
-
return ok(
|
|
2519
|
-
` : `${JSON.stringify(result2)}
|
|
3184
|
+
return ok(`${JSON.stringify(result2)}
|
|
2520
3185
|
`);
|
|
2521
3186
|
}
|
|
2522
3187
|
function scopeCurrent(config2, json) {
|
|
@@ -2837,15 +3502,18 @@ async function idTool(input) {
|
|
|
2837
3502
|
}
|
|
2838
3503
|
|
|
2839
3504
|
// src/auth/configStore.ts
|
|
2840
|
-
import { mkdir, readFile as
|
|
2841
|
-
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
|
+
}
|
|
2842
3510
|
function resolveSiftConfigPath(input) {
|
|
2843
|
-
return
|
|
3511
|
+
return join2(input.homeDir, ".sift", "config.json");
|
|
2844
3512
|
}
|
|
2845
3513
|
async function readStoredSiftConfig(input) {
|
|
2846
3514
|
let raw;
|
|
2847
3515
|
try {
|
|
2848
|
-
raw = await
|
|
3516
|
+
raw = await readFile3(resolveSiftConfigPath(input), "utf8");
|
|
2849
3517
|
} catch (error) {
|
|
2850
3518
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
2851
3519
|
return void 0;
|
|
@@ -2878,18 +3546,19 @@ async function loadCliAuthConfig(input) {
|
|
|
2878
3546
|
if (profile === void 0) {
|
|
2879
3547
|
throw new Error(`Stored Sift profile '${stored.currentProfile}' was not found.`);
|
|
2880
3548
|
}
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
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
|
|
2887
3558
|
});
|
|
2888
|
-
if (token === void 0) {
|
|
2889
|
-
throw new Error("Stored Sift credential store secret is missing; run `sift login` again.");
|
|
2890
|
-
}
|
|
2891
3559
|
return {
|
|
2892
3560
|
source: "stored",
|
|
3561
|
+
tokenKind,
|
|
2893
3562
|
token,
|
|
2894
3563
|
config: {
|
|
2895
3564
|
apiBaseUrl: profile.apiBaseUrl,
|
|
@@ -2902,6 +3571,63 @@ async function loadCliAuthConfig(input) {
|
|
|
2902
3571
|
}
|
|
2903
3572
|
};
|
|
2904
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
|
+
}
|
|
2905
3631
|
function loadEnvAuth(env, token) {
|
|
2906
3632
|
return {
|
|
2907
3633
|
source: "env",
|
|
@@ -2932,10 +3658,10 @@ function parseStoredSiftConfig(value) {
|
|
|
2932
3658
|
}
|
|
2933
3659
|
function parseStoredSiftProfile(value) {
|
|
2934
3660
|
const record = objectValue(value, "profile");
|
|
2935
|
-
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) {
|
|
2936
3662
|
throw new Error("Stored Sift config must not contain token secrets.");
|
|
2937
3663
|
}
|
|
2938
|
-
|
|
3664
|
+
const profile = {
|
|
2939
3665
|
apiBaseUrl: stringValue(record.apiBaseUrl, "apiBaseUrl").replace(/\/+$/u, ""),
|
|
2940
3666
|
appBaseUrl: stringValue(record.appBaseUrl, "appBaseUrl").replace(/\/+$/u, ""),
|
|
2941
3667
|
workspaceId: stringValue(record.workspaceId, "workspaceId"),
|
|
@@ -2946,6 +3672,21 @@ function parseStoredSiftProfile(value) {
|
|
|
2946
3672
|
tokenExpiresAt: stringValue(record.tokenExpiresAt, "tokenExpiresAt"),
|
|
2947
3673
|
capabilities: stringArray(record.capabilities, "capabilities")
|
|
2948
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.");
|
|
2949
3690
|
}
|
|
2950
3691
|
function requiredEnv(env, name) {
|
|
2951
3692
|
const value = clean(env[name]);
|
|
@@ -3079,21 +3820,115 @@ function isExecError(error) {
|
|
|
3079
3820
|
|
|
3080
3821
|
// src/auth/loginFlow.ts
|
|
3081
3822
|
import { execFile as execFile2 } from "child_process";
|
|
3082
|
-
import { hostname } from "os";
|
|
3823
|
+
import { hostname as hostname3 } from "os";
|
|
3083
3824
|
import { promisify as promisify2 } from "util";
|
|
3084
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
|
+
|
|
3085
3908
|
// src/auth/localCallback.ts
|
|
3086
3909
|
import { createServer } from "http";
|
|
3087
3910
|
async function createLocalCallbackServer() {
|
|
3088
3911
|
let resolveCallback;
|
|
3089
3912
|
let rejectCallback;
|
|
3090
|
-
const callbackPromise = new Promise((
|
|
3091
|
-
resolveCallback =
|
|
3913
|
+
const callbackPromise = new Promise((resolve2, reject) => {
|
|
3914
|
+
resolveCallback = resolve2;
|
|
3092
3915
|
rejectCallback = reject;
|
|
3093
3916
|
});
|
|
3094
3917
|
const server = createServer((request, response) => {
|
|
3095
3918
|
try {
|
|
3096
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
|
+
}
|
|
3097
3932
|
const code = url.searchParams.get("code");
|
|
3098
3933
|
const state = url.searchParams.get("state");
|
|
3099
3934
|
if (code === null || state === null) {
|
|
@@ -3121,21 +3956,21 @@ async function createLocalCallbackServer() {
|
|
|
3121
3956
|
};
|
|
3122
3957
|
}
|
|
3123
3958
|
function listen(server) {
|
|
3124
|
-
return new Promise((
|
|
3959
|
+
return new Promise((resolve2, reject) => {
|
|
3125
3960
|
server.once("error", reject);
|
|
3126
3961
|
server.listen(0, "127.0.0.1", () => {
|
|
3127
3962
|
server.off("error", reject);
|
|
3128
|
-
|
|
3963
|
+
resolve2();
|
|
3129
3964
|
});
|
|
3130
3965
|
});
|
|
3131
3966
|
}
|
|
3132
3967
|
function closeServer(server) {
|
|
3133
|
-
return new Promise((
|
|
3968
|
+
return new Promise((resolve2, reject) => {
|
|
3134
3969
|
server.close((error) => {
|
|
3135
3970
|
if (error) {
|
|
3136
3971
|
reject(error);
|
|
3137
3972
|
} else {
|
|
3138
|
-
|
|
3973
|
+
resolve2();
|
|
3139
3974
|
}
|
|
3140
3975
|
});
|
|
3141
3976
|
});
|
|
@@ -3161,15 +3996,445 @@ function sha256Base64Url(value) {
|
|
|
3161
3996
|
return createHash2("sha256").update(value).digest("base64url");
|
|
3162
3997
|
}
|
|
3163
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
|
+
|
|
3164
4429
|
// src/auth/loginFlow.ts
|
|
3165
4430
|
var execFileAsync2 = promisify2(execFile2);
|
|
3166
4431
|
function createSiftCliAuthCommands(input) {
|
|
3167
4432
|
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
3168
|
-
const sleep = input.sleep ?? ((ms) => new Promise((
|
|
4433
|
+
const sleep = input.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
3169
4434
|
return {
|
|
3170
4435
|
async login({ rest, json }) {
|
|
3171
4436
|
try {
|
|
3172
|
-
return
|
|
4437
|
+
return await routeLogin(input, rest, sleep, json);
|
|
3173
4438
|
} catch (error) {
|
|
3174
4439
|
return json ? failJson(error instanceof Error ? error.message : "Login failed.") : fail(error instanceof Error ? error.message : "Login failed.");
|
|
3175
4440
|
}
|
|
@@ -3185,21 +4450,18 @@ function createSiftCliAuthCommands(input) {
|
|
|
3185
4450
|
env: input.env,
|
|
3186
4451
|
homeDir: input.homeDir,
|
|
3187
4452
|
credentialStore: input.credentialStore,
|
|
3188
|
-
now: now()
|
|
4453
|
+
now: now(),
|
|
4454
|
+
oauthRefresher: oauthRefresherFor(input, [])
|
|
3189
4455
|
});
|
|
3190
4456
|
}
|
|
3191
4457
|
};
|
|
3192
4458
|
}
|
|
3193
|
-
async function
|
|
3194
|
-
const
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
const stored = await readStoredSiftConfig({ homeDir: input.homeDir });
|
|
3200
|
-
const profile = stored?.profiles[stored.currentProfile];
|
|
3201
|
-
if (profile !== void 0) return normalizeUrl(profile.apiBaseUrl);
|
|
3202
|
-
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);
|
|
3203
4465
|
}
|
|
3204
4466
|
async function browserLogin(input, rest, json) {
|
|
3205
4467
|
await input.credentialStore.assertAvailable();
|
|
@@ -3213,10 +4475,10 @@ async function browserLogin(input, rest, json) {
|
|
|
3213
4475
|
codeChallenge: pkce.codeChallenge,
|
|
3214
4476
|
codeChallengeMethod: "S256",
|
|
3215
4477
|
stateHash: pkce.stateHash,
|
|
3216
|
-
deviceLabel: input.deviceLabel ??
|
|
4478
|
+
deviceLabel: input.deviceLabel ?? hostname3(),
|
|
3217
4479
|
requestedCapabilities: requestedCapabilities(rest)
|
|
3218
4480
|
});
|
|
3219
|
-
await
|
|
4481
|
+
await tryOpenBrowser2(input.openBrowser, request.authorizeUrl);
|
|
3220
4482
|
const callback = await callbackServer.waitForCallback();
|
|
3221
4483
|
if (callback.state !== pkce.stateHash) {
|
|
3222
4484
|
throw new Error("CLI auth callback state mismatch.");
|
|
@@ -3236,7 +4498,7 @@ async function deviceLogin(input, rest, sleep, json) {
|
|
|
3236
4498
|
await input.credentialStore.assertAvailable();
|
|
3237
4499
|
const apiBaseUrl = await resolveLoginApiBaseUrl({ argv: rest, env: input.env, homeDir: input.homeDir });
|
|
3238
4500
|
const request = await postJson(input.fetch, `${apiBaseUrl}/cli-auth/device`, {
|
|
3239
|
-
deviceLabel: input.deviceLabel ??
|
|
4501
|
+
deviceLabel: input.deviceLabel ?? hostname3(),
|
|
3240
4502
|
requestedCapabilities: requestedCapabilities(rest)
|
|
3241
4503
|
});
|
|
3242
4504
|
let intervalSeconds = request.intervalSeconds;
|
|
@@ -3362,10 +4624,6 @@ function okStatus(source, loaded, json) {
|
|
|
3362
4624
|
${renderScope(loaded.config)}`
|
|
3363
4625
|
);
|
|
3364
4626
|
}
|
|
3365
|
-
function requestedCapabilities(rest) {
|
|
3366
|
-
const option = parseOptions(rest).get("capability");
|
|
3367
|
-
return option === void 0 ? ["record:read"] : option.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
3368
|
-
}
|
|
3369
4627
|
function configFromToken(token) {
|
|
3370
4628
|
return {
|
|
3371
4629
|
currentProfile: "default",
|
|
@@ -3384,7 +4642,7 @@ function configFromToken(token) {
|
|
|
3384
4642
|
}
|
|
3385
4643
|
};
|
|
3386
4644
|
}
|
|
3387
|
-
async function
|
|
4645
|
+
async function tryOpenBrowser2(openBrowser, url) {
|
|
3388
4646
|
await (openBrowser ?? openBrowserUrl)(url).catch(() => void 0);
|
|
3389
4647
|
}
|
|
3390
4648
|
async function openBrowserUrl(url) {
|
|
@@ -3414,16 +4672,6 @@ async function postJson(fetchImpl, url, body, headers = {}) {
|
|
|
3414
4672
|
}
|
|
3415
4673
|
return parsed;
|
|
3416
4674
|
}
|
|
3417
|
-
function errorMessage2(parsed, status2) {
|
|
3418
|
-
if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
|
|
3419
|
-
const error = parsed.error;
|
|
3420
|
-
if (typeof error === "object" && error !== null && "message" in error) {
|
|
3421
|
-
const message = error.message;
|
|
3422
|
-
if (typeof message === "string") return message;
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
return `CLI auth request failed with status ${status2}.`;
|
|
3426
|
-
}
|
|
3427
4675
|
function failJson(message) {
|
|
3428
4676
|
return {
|
|
3429
4677
|
exitCode: 1,
|
|
@@ -3432,13 +4680,6 @@ function failJson(message) {
|
|
|
3432
4680
|
stderr: ""
|
|
3433
4681
|
};
|
|
3434
4682
|
}
|
|
3435
|
-
function normalizeUrl(value) {
|
|
3436
|
-
return value.replace(/\/+$/u, "");
|
|
3437
|
-
}
|
|
3438
|
-
function clean2(value) {
|
|
3439
|
-
const trimmed = value?.trim();
|
|
3440
|
-
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
3441
|
-
}
|
|
3442
4683
|
|
|
3443
4684
|
// src/bin/sift.ts
|
|
3444
4685
|
var credentialStore = createMacOSKeychainStore();
|
|
@@ -3457,21 +4698,26 @@ var config = loadedAuth?.config ?? {
|
|
|
3457
4698
|
principalId: "",
|
|
3458
4699
|
capabilities: []
|
|
3459
4700
|
};
|
|
4701
|
+
var { argv, agentName } = extractAgentName(process.argv.slice(2), process.env.SIFT_AGENT);
|
|
3460
4702
|
var result = await runSiftCli({
|
|
3461
|
-
argv
|
|
4703
|
+
argv,
|
|
3462
4704
|
config,
|
|
3463
4705
|
readStdin,
|
|
4706
|
+
agentName,
|
|
3464
4707
|
executor: loadedAuth === void 0 ? void 0 : createHostedApiExecutor({
|
|
3465
4708
|
apiBaseUrl: loadedAuth.config.apiBaseUrl,
|
|
3466
4709
|
token: loadedAuth.token,
|
|
3467
4710
|
workspaceId: loadedAuth.config.workspaceId,
|
|
3468
|
-
brainId: loadedAuth.config.brainId
|
|
4711
|
+
brainId: loadedAuth.config.brainId,
|
|
4712
|
+
agentName
|
|
3469
4713
|
}),
|
|
3470
4714
|
authCommands,
|
|
3471
4715
|
mcpServer: {
|
|
3472
4716
|
serve: async ({ config: config2, executor }) => {
|
|
3473
4717
|
if (executor === void 0) {
|
|
3474
|
-
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
|
+
);
|
|
3475
4721
|
}
|
|
3476
4722
|
const { createLocalMcpStdioServer: createLocalMcpStdioServer2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
3477
4723
|
return createLocalMcpStdioServer2({
|
|
@@ -3488,6 +4734,18 @@ var result = await runSiftCli({
|
|
|
3488
4734
|
process.stdout.write(result.stdout);
|
|
3489
4735
|
process.stderr.write(result.stderr);
|
|
3490
4736
|
process.exitCode = result.exitCode;
|
|
4737
|
+
function extractAgentName(args, envAgentName) {
|
|
4738
|
+
const flagIndex = args.indexOf("--as-agent");
|
|
4739
|
+
if (flagIndex !== -1 && args[flagIndex + 1] !== void 0) {
|
|
4740
|
+
const name = args[flagIndex + 1];
|
|
4741
|
+
return {
|
|
4742
|
+
argv: [...args.slice(0, flagIndex), ...args.slice(flagIndex + 2)],
|
|
4743
|
+
agentName: name
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
const env = envAgentName?.trim();
|
|
4747
|
+
return { argv: args, agentName: env === void 0 || env.length === 0 ? void 0 : env };
|
|
4748
|
+
}
|
|
3491
4749
|
async function readStdin() {
|
|
3492
4750
|
const chunks = [];
|
|
3493
4751
|
for await (const chunk of process.stdin) {
|