@sift-wiki/cli 0.1.3 → 0.1.5
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 +52 -22
- package/dist/bin/sift.js +2069 -542
- package/package.json +1 -1
package/dist/bin/sift.js
CHANGED
|
@@ -137,6 +137,8 @@ function parseSearchQuery(input) {
|
|
|
137
137
|
function parseContextQuery(input) {
|
|
138
138
|
return {
|
|
139
139
|
query: requireString(input, "query"),
|
|
140
|
+
queryIssuedAt: optionalString(input, "queryIssuedAt"),
|
|
141
|
+
timezone: optionalString(input, "timezone"),
|
|
140
142
|
maxChars: requireInteger(input, "maxChars", 4e3)
|
|
141
143
|
};
|
|
142
144
|
}
|
|
@@ -147,20 +149,20 @@ function parseProfileQuery(input) {
|
|
|
147
149
|
};
|
|
148
150
|
}
|
|
149
151
|
function parseToolsSearch(input) {
|
|
150
|
-
const
|
|
152
|
+
const result = {
|
|
151
153
|
intent: requireString(input, "intent")
|
|
152
154
|
};
|
|
153
155
|
if (input.toolsetNames !== void 0) {
|
|
154
|
-
|
|
156
|
+
result.toolsetNames = requireStringArray(input, "toolsetNames");
|
|
155
157
|
}
|
|
156
158
|
if (input.limit !== void 0) {
|
|
157
159
|
const limit = optionalInteger(input, "limit");
|
|
158
160
|
if (limit === void 0 || limit < 1 || limit > 20) {
|
|
159
161
|
throw new Error("limit must be an integer between 1 and 20.");
|
|
160
162
|
}
|
|
161
|
-
|
|
163
|
+
result.limit = limit;
|
|
162
164
|
}
|
|
163
|
-
return
|
|
165
|
+
return result;
|
|
164
166
|
}
|
|
165
167
|
function parseDecision(input) {
|
|
166
168
|
return {
|
|
@@ -371,10 +373,24 @@ function writeTool(name, summary, properties, cliExample, options) {
|
|
|
371
373
|
capability: "record:write",
|
|
372
374
|
mutability: "write",
|
|
373
375
|
transports: writeTransports,
|
|
376
|
+
idempotency: options?.idempotency,
|
|
374
377
|
cliExample,
|
|
375
378
|
hostedAgent: options?.hostedAgent
|
|
376
379
|
});
|
|
377
380
|
}
|
|
381
|
+
function hostedAgentOnlyReadTool(name, summary, properties, options) {
|
|
382
|
+
return defineTool({
|
|
383
|
+
name,
|
|
384
|
+
summary,
|
|
385
|
+
properties,
|
|
386
|
+
required: options.required,
|
|
387
|
+
capability: "record:read",
|
|
388
|
+
mutability: "read",
|
|
389
|
+
transports: [],
|
|
390
|
+
cliExample: "",
|
|
391
|
+
hostedAgent: { available: true, ...options.hostedAgent }
|
|
392
|
+
});
|
|
393
|
+
}
|
|
378
394
|
function sourceWriteTool(name, summary, properties, cliExample) {
|
|
379
395
|
return defineTool({
|
|
380
396
|
name,
|
|
@@ -413,7 +429,7 @@ function defineTool(input) {
|
|
|
413
429
|
mutability: input.mutability,
|
|
414
430
|
auditCategory: input.mutability === "admin" ? "admin" : input.mutability,
|
|
415
431
|
transports: input.transports,
|
|
416
|
-
idempotency: input.mutability === "write" ? "recommended" : "none",
|
|
432
|
+
idempotency: input.idempotency ?? (input.mutability === "write" ? "recommended" : "none"),
|
|
417
433
|
resultSize: "compact",
|
|
418
434
|
cliExample: input.cliExample,
|
|
419
435
|
hostedAgent: hostedAgentMetadata(input, required)
|
|
@@ -438,31 +454,8 @@ function defaultRiskClass(mutability) {
|
|
|
438
454
|
return "low";
|
|
439
455
|
}
|
|
440
456
|
function defaultToolsets(name) {
|
|
441
|
-
const [prefix] = name.split(".");
|
|
442
|
-
|
|
443
|
-
case "decision":
|
|
444
|
-
case "task":
|
|
445
|
-
return ["work"];
|
|
446
|
-
case "skill":
|
|
447
|
-
return ["brain", "work"];
|
|
448
|
-
case "record":
|
|
449
|
-
case "source":
|
|
450
|
-
case "capture":
|
|
451
|
-
case "ingestion":
|
|
452
|
-
return ["brain", "ingestion"];
|
|
453
|
-
case "search":
|
|
454
|
-
case "context":
|
|
455
|
-
case "evidence":
|
|
456
|
-
case "graph":
|
|
457
|
-
return ["brain", "retrieval"];
|
|
458
|
-
case "tools":
|
|
459
|
-
return ["registry"];
|
|
460
|
-
case "audit":
|
|
461
|
-
case "event":
|
|
462
|
-
return ["audit"];
|
|
463
|
-
default:
|
|
464
|
-
return ["brain"];
|
|
465
|
-
}
|
|
457
|
+
const [prefix = ""] = name.split(".");
|
|
458
|
+
return defaultToolsetsByPrefix[prefix] ?? ["brain"];
|
|
466
459
|
}
|
|
467
460
|
function defaultSearchTerms(name, summary) {
|
|
468
461
|
return [.../* @__PURE__ */ new Set([...tokenize(name), ...tokenize(summary)])];
|
|
@@ -473,7 +466,7 @@ function tokenize(text) {
|
|
|
473
466
|
function stringProps(names) {
|
|
474
467
|
return Object.fromEntries(names.map((name) => [name, { type: "string" }]));
|
|
475
468
|
}
|
|
476
|
-
var readTransports, writeTransports, NO_CAPABILITY, toolDefinitions;
|
|
469
|
+
var readTransports, writeTransports, NO_CAPABILITY, defaultToolsetsByPrefix, toolDefinitions;
|
|
477
470
|
var init_registry = __esm({
|
|
478
471
|
"../tools/dist/registry.js"() {
|
|
479
472
|
"use strict";
|
|
@@ -483,6 +476,23 @@ var init_registry = __esm({
|
|
|
483
476
|
readTransports = ["cli", "hosted_mcp", "local_mcp"];
|
|
484
477
|
writeTransports = ["cli", "hosted_mcp", "local_mcp"];
|
|
485
478
|
NO_CAPABILITY = "none";
|
|
479
|
+
defaultToolsetsByPrefix = {
|
|
480
|
+
audit: ["audit"],
|
|
481
|
+
capture: ["brain", "ingestion"],
|
|
482
|
+
context: ["brain", "retrieval"],
|
|
483
|
+
decision: ["work"],
|
|
484
|
+
event: ["audit"],
|
|
485
|
+
evidence: ["brain", "retrieval"],
|
|
486
|
+
graph: ["brain", "retrieval"],
|
|
487
|
+
ingestion: ["brain", "ingestion"],
|
|
488
|
+
record: ["brain", "ingestion"],
|
|
489
|
+
search: ["brain", "retrieval"],
|
|
490
|
+
skill: ["brain", "work"],
|
|
491
|
+
source: ["brain", "ingestion"],
|
|
492
|
+
task: ["work"],
|
|
493
|
+
tools: ["registry"],
|
|
494
|
+
web: ["web"]
|
|
495
|
+
};
|
|
486
496
|
toolDefinitions = [
|
|
487
497
|
readTool("contract.get", "Fetch the Sift agent contract (kernel + workspace overlay) and the contractVersion to echo on every gated tool call. Call this before any other Sift work.", {}, "sift contract get"),
|
|
488
498
|
readTool("whoami", "Return principal, actor, scope, and capabilities.", {}, "sift whoami"),
|
|
@@ -601,16 +611,94 @@ var init_registry = __esm({
|
|
|
601
611
|
severity: { type: "string" },
|
|
602
612
|
visibility: { type: "array", items: { type: "string" } }
|
|
603
613
|
}, "sift skill teach <skill-id> --lesson 'when X, do Y'", { required: ["skillId", "lesson", "visibility"] }),
|
|
604
|
-
|
|
614
|
+
writeTool("skill.feedback", "Report structured feedback for a pinned skill version; external-agent reports remain reported until trusted Sift evidence verifies them.", {
|
|
615
|
+
skillId: { type: "string" },
|
|
616
|
+
skillVersionId: { type: "string" },
|
|
617
|
+
exerciseRef: {
|
|
618
|
+
type: "object",
|
|
619
|
+
properties: { exerciseId: { type: "string" } },
|
|
620
|
+
required: ["exerciseId"]
|
|
621
|
+
},
|
|
622
|
+
subjectRef: { type: "object", properties: {} },
|
|
623
|
+
signalKind: { type: "string" },
|
|
624
|
+
polarity: { type: "string", enum: ["positive", "negative", "mixed", "unknown"] },
|
|
625
|
+
strength: { type: "string", enum: ["weak", "medium", "strong"] },
|
|
626
|
+
payload: { type: "object", properties: {} },
|
|
627
|
+
idempotencyKey: { type: "string" }
|
|
628
|
+
}, "sift skill feedback <skill-id> <skill-version-id>", {
|
|
629
|
+
required: [
|
|
630
|
+
"skillId",
|
|
631
|
+
"skillVersionId",
|
|
632
|
+
"signalKind",
|
|
633
|
+
"polarity",
|
|
634
|
+
"strength",
|
|
635
|
+
"payload",
|
|
636
|
+
"idempotencyKey"
|
|
637
|
+
],
|
|
638
|
+
idempotency: "required"
|
|
639
|
+
}),
|
|
640
|
+
readTool("search.query", "Search authorized brain context and return raw cited candidate results for exploration.", {
|
|
605
641
|
query: { type: "string" },
|
|
606
642
|
limit: { type: "integer", minimum: 1, maximum: 20 }
|
|
607
643
|
}, "sift search query 'launch risks'"),
|
|
608
|
-
readTool("context.assemble", "Assemble
|
|
644
|
+
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.", {
|
|
645
|
+
query: { type: "string" },
|
|
646
|
+
queryIssuedAt: { type: "string" },
|
|
647
|
+
timezone: { type: "string" },
|
|
648
|
+
maxChars: { type: "integer", minimum: 1 }
|
|
649
|
+
}, "sift context assemble 'launch risks'", {
|
|
650
|
+
required: ["query"],
|
|
609
651
|
hostedAgent: {
|
|
610
652
|
toolsets: ["brain", "retrieval"],
|
|
611
653
|
searchTerms: ["context", "cite", "answer", "evidence"]
|
|
612
654
|
}
|
|
613
655
|
}),
|
|
656
|
+
hostedAgentOnlyReadTool("web.search", "Search public web sources for current or public facts.", {
|
|
657
|
+
query: {
|
|
658
|
+
type: "string",
|
|
659
|
+
description: "Public web search query. Do not include private Sift brain context unless the user explicitly provided it for public lookup."
|
|
660
|
+
},
|
|
661
|
+
limit: { type: "integer", minimum: 1, maximum: 10 },
|
|
662
|
+
recencyDays: { type: "integer", minimum: 1, maximum: 3650 },
|
|
663
|
+
allowedDomains: { type: "array", items: { type: "string" } },
|
|
664
|
+
blockedDomains: { type: "array", items: { type: "string" } }
|
|
665
|
+
}, {
|
|
666
|
+
required: ["query"],
|
|
667
|
+
hostedAgent: {
|
|
668
|
+
toolsets: ["web"],
|
|
669
|
+
searchTerms: [
|
|
670
|
+
"web",
|
|
671
|
+
"search",
|
|
672
|
+
"current",
|
|
673
|
+
"public",
|
|
674
|
+
"company",
|
|
675
|
+
"product",
|
|
676
|
+
"docs",
|
|
677
|
+
"news",
|
|
678
|
+
"pricing",
|
|
679
|
+
"people",
|
|
680
|
+
"law",
|
|
681
|
+
"rules"
|
|
682
|
+
],
|
|
683
|
+
inputHints: ["query", "limit", "recencyDays", "allowedDomains", "blockedDomains"],
|
|
684
|
+
riskClass: "medium"
|
|
685
|
+
}
|
|
686
|
+
}),
|
|
687
|
+
hostedAgentOnlyReadTool("web.fetch", "Read one selected public URL through guarded bounded extraction.", {
|
|
688
|
+
url: {
|
|
689
|
+
type: "string",
|
|
690
|
+
description: "Public http(s) URL to fetch. Local, private, and metadata URLs are refused."
|
|
691
|
+
},
|
|
692
|
+
maxChars: { type: "integer", minimum: 1, maximum: 12e3 }
|
|
693
|
+
}, {
|
|
694
|
+
required: ["url"],
|
|
695
|
+
hostedAgent: {
|
|
696
|
+
toolsets: ["web"],
|
|
697
|
+
searchTerms: ["web", "fetch", "read", "url", "page", "extract", "public"],
|
|
698
|
+
inputHints: ["url", "maxChars"],
|
|
699
|
+
riskClass: "medium"
|
|
700
|
+
}
|
|
701
|
+
}),
|
|
614
702
|
readTool("context.profile", "Read a permission-filtered profile context model.", {}, "sift context profile"),
|
|
615
703
|
readTool("evidence.list", "List authorized evidence links for a record.", stringProps(["recordId"]), "sift evidence list <record-id>"),
|
|
616
704
|
readTool("evidence.get", "Read an authorized evidence item.", stringProps(["evidenceId"]), "sift evidence get <evidence-id>"),
|
|
@@ -713,6 +801,7 @@ var init_discovery = __esm({
|
|
|
713
801
|
"use strict";
|
|
714
802
|
init_registry();
|
|
715
803
|
IMPLEMENTED_TOOL_NAMES = [
|
|
804
|
+
"contract.get",
|
|
716
805
|
"whoami",
|
|
717
806
|
"brain.list",
|
|
718
807
|
"brain.use",
|
|
@@ -751,24 +840,24 @@ var init_discovery = __esm({
|
|
|
751
840
|
});
|
|
752
841
|
|
|
753
842
|
// ../tools/dist/results.js
|
|
754
|
-
function captureResult(
|
|
843
|
+
function captureResult(result) {
|
|
755
844
|
return {
|
|
756
|
-
status:
|
|
757
|
-
jobId:
|
|
758
|
-
sourceId:
|
|
759
|
-
sourceItemId:
|
|
760
|
-
recordId:
|
|
761
|
-
versionId:
|
|
762
|
-
versionNumber:
|
|
845
|
+
status: result.job?.status ?? result.status ?? "captured",
|
|
846
|
+
jobId: result.job?.id,
|
|
847
|
+
sourceId: result.sourceId,
|
|
848
|
+
sourceItemId: result.sourceItemId,
|
|
849
|
+
recordId: result.recordId,
|
|
850
|
+
versionId: result.versionId,
|
|
851
|
+
versionNumber: result.versionNumber
|
|
763
852
|
};
|
|
764
853
|
}
|
|
765
|
-
function workRecordResult(
|
|
854
|
+
function workRecordResult(result) {
|
|
766
855
|
return {
|
|
767
|
-
status:
|
|
768
|
-
jobId:
|
|
769
|
-
recordId:
|
|
770
|
-
versionId:
|
|
771
|
-
versionNumber:
|
|
856
|
+
status: result.job.status,
|
|
857
|
+
jobId: result.job.id,
|
|
858
|
+
recordId: result.recordId,
|
|
859
|
+
versionId: result.versionId,
|
|
860
|
+
versionNumber: result.versionNumber
|
|
772
861
|
};
|
|
773
862
|
}
|
|
774
863
|
var init_results = __esm({
|
|
@@ -780,16 +869,16 @@ var init_results = __esm({
|
|
|
780
869
|
// ../tools/dist/captureTools.js
|
|
781
870
|
async function executeCaptureText(input, toolInput) {
|
|
782
871
|
const capture = parseCaptureText(toolInput);
|
|
783
|
-
const
|
|
784
|
-
return captureResult(
|
|
872
|
+
const result = await input.service.ingestText({ auth: input.auth, ...capture });
|
|
873
|
+
return captureResult(result);
|
|
785
874
|
}
|
|
786
875
|
async function executeCaptureFile(input, toolInput) {
|
|
787
876
|
if (input.service.ingestFile === void 0) {
|
|
788
877
|
throw new Error("Tool 'capture.file' requires a file ingestion service contract.");
|
|
789
878
|
}
|
|
790
879
|
const file = parseCaptureFile(toolInput);
|
|
791
|
-
const
|
|
792
|
-
return captureResult(
|
|
880
|
+
const result = await input.service.ingestFile({ auth: input.auth, ...file });
|
|
881
|
+
return captureResult(result);
|
|
793
882
|
}
|
|
794
883
|
async function executeCaptureBatch(input, toolInput) {
|
|
795
884
|
const batch = parseCaptureBatch(toolInput);
|
|
@@ -797,16 +886,16 @@ async function executeCaptureBatch(input, toolInput) {
|
|
|
797
886
|
for (const item of batch.items) {
|
|
798
887
|
if (item.kind === "text") {
|
|
799
888
|
const { kind: _kind2, ...capture } = item;
|
|
800
|
-
const
|
|
801
|
-
results.push(captureResult(
|
|
889
|
+
const result2 = await input.service.ingestText({ auth: input.auth, ...capture });
|
|
890
|
+
results.push(captureResult(result2));
|
|
802
891
|
continue;
|
|
803
892
|
}
|
|
804
893
|
if (input.service.ingestFile === void 0) {
|
|
805
894
|
throw new Error("Tool 'capture.batch' requires a file ingestion service contract.");
|
|
806
895
|
}
|
|
807
896
|
const { kind: _kind, ...file } = item;
|
|
808
|
-
const
|
|
809
|
-
results.push(captureResult(
|
|
897
|
+
const result = await input.service.ingestFile({ auth: input.auth, ...file });
|
|
898
|
+
results.push(captureResult(result));
|
|
810
899
|
}
|
|
811
900
|
return results;
|
|
812
901
|
}
|
|
@@ -879,6 +968,15 @@ function skillToolHandlers(input, toolInput) {
|
|
|
879
968
|
if (input.service.teachSkill === void 0)
|
|
880
969
|
throw missingSkillService("skill.teach");
|
|
881
970
|
return input.service.teachSkill({ auth: input.auth, ...parseSkillTeach(toolInput) });
|
|
971
|
+
},
|
|
972
|
+
"skill.feedback": () => {
|
|
973
|
+
if (input.service.reportFeedbackSignal === void 0) {
|
|
974
|
+
throw missingSkillService("skill.feedback");
|
|
975
|
+
}
|
|
976
|
+
return input.service.reportFeedbackSignal({
|
|
977
|
+
auth: input.auth,
|
|
978
|
+
...parseSkillFeedback(toolInput)
|
|
979
|
+
});
|
|
882
980
|
}
|
|
883
981
|
};
|
|
884
982
|
}
|
|
@@ -888,7 +986,8 @@ function skillToolAvailability(service) {
|
|
|
888
986
|
[service.getSkill !== void 0, ["skill.get"]],
|
|
889
987
|
[service.getSkillFile !== void 0, ["skill.file"]],
|
|
890
988
|
[service.recordSkillExercise !== void 0, ["skill.exercise"]],
|
|
891
|
-
[service.teachSkill !== void 0, ["skill.teach"]]
|
|
989
|
+
[service.teachSkill !== void 0, ["skill.teach"]],
|
|
990
|
+
[service.reportFeedbackSignal !== void 0, ["skill.feedback"]]
|
|
892
991
|
];
|
|
893
992
|
}
|
|
894
993
|
function parseSkillExercise(input) {
|
|
@@ -921,6 +1020,29 @@ function parseSkillTeach(input) {
|
|
|
921
1020
|
visibility
|
|
922
1021
|
};
|
|
923
1022
|
}
|
|
1023
|
+
function parseSkillFeedback(input) {
|
|
1024
|
+
const exerciseRef = optionalObject(input, "exerciseRef");
|
|
1025
|
+
const subjectRef = optionalObject(input, "subjectRef");
|
|
1026
|
+
const polarity = requireString(input, "polarity");
|
|
1027
|
+
const strength = requireString(input, "strength");
|
|
1028
|
+
if (!isSkillFeedbackPolarity(polarity)) {
|
|
1029
|
+
throw new Error("polarity must be positive, negative, mixed, or unknown.");
|
|
1030
|
+
}
|
|
1031
|
+
if (!isSkillFeedbackStrength(strength)) {
|
|
1032
|
+
throw new Error("strength must be weak, medium, or strong.");
|
|
1033
|
+
}
|
|
1034
|
+
return {
|
|
1035
|
+
skillId: requireString(input, "skillId"),
|
|
1036
|
+
skillVersionId: requireString(input, "skillVersionId"),
|
|
1037
|
+
exerciseRef: exerciseRef === void 0 ? void 0 : { exerciseId: requireString(exerciseRef, "exerciseId") },
|
|
1038
|
+
subjectRef,
|
|
1039
|
+
signalKind: requireString(input, "signalKind"),
|
|
1040
|
+
polarity,
|
|
1041
|
+
strength,
|
|
1042
|
+
payload: optionalObject(input, "payload") ?? {},
|
|
1043
|
+
idempotencyKey: requireString(input, "idempotencyKey")
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
924
1046
|
function requireSkillOutputRef(input, key) {
|
|
925
1047
|
const value = input[key];
|
|
926
1048
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
@@ -939,6 +1061,22 @@ function requireSkillOutputRef(input, key) {
|
|
|
939
1061
|
}
|
|
940
1062
|
throw new Error(`${key}.kind must be record or external.`);
|
|
941
1063
|
}
|
|
1064
|
+
function optionalObject(input, key) {
|
|
1065
|
+
const value = input[key];
|
|
1066
|
+
if (value === void 0) {
|
|
1067
|
+
return void 0;
|
|
1068
|
+
}
|
|
1069
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1070
|
+
throw new Error(`${key} must be an object.`);
|
|
1071
|
+
}
|
|
1072
|
+
return value;
|
|
1073
|
+
}
|
|
1074
|
+
function isSkillFeedbackPolarity(value) {
|
|
1075
|
+
return value === "positive" || value === "negative" || value === "mixed" || value === "unknown";
|
|
1076
|
+
}
|
|
1077
|
+
function isSkillFeedbackStrength(value) {
|
|
1078
|
+
return value === "weak" || value === "medium" || value === "strong";
|
|
1079
|
+
}
|
|
942
1080
|
function missingSkillService(toolName) {
|
|
943
1081
|
return new Error(`Tool '${toolName}' requires a runtime service contract.`);
|
|
944
1082
|
}
|
|
@@ -1034,7 +1172,9 @@ function runtimeAvailableToolNames(service) {
|
|
|
1034
1172
|
[service.listGraphNeighbors !== void 0, ["graph.neighbors"]],
|
|
1035
1173
|
[service.listEvents !== void 0, ["event.list"]],
|
|
1036
1174
|
[service.getContextProfile !== void 0, ["context.profile"]],
|
|
1037
|
-
[service.listAuditEvents !== void 0, ["audit.events"]]
|
|
1175
|
+
[service.listAuditEvents !== void 0, ["audit.events"]],
|
|
1176
|
+
[service.webSearch !== void 0, ["web.search"]],
|
|
1177
|
+
[service.webFetch !== void 0, ["web.fetch"]]
|
|
1038
1178
|
];
|
|
1039
1179
|
return [...baseNames, ...optionalNames.flatMap(([enabled, names]) => enabled ? names : [])];
|
|
1040
1180
|
}
|
|
@@ -1112,6 +1252,73 @@ var init_toolLog = __esm({
|
|
|
1112
1252
|
}
|
|
1113
1253
|
});
|
|
1114
1254
|
|
|
1255
|
+
// ../tools/dist/webToolRuntime.js
|
|
1256
|
+
function webToolHandlers(input, toolInput) {
|
|
1257
|
+
return {
|
|
1258
|
+
"web.search": () => executeWebSearch(input, toolInput),
|
|
1259
|
+
"web.fetch": () => executeWebFetch(input, toolInput)
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
function executeWebSearch(input, toolInput) {
|
|
1263
|
+
if (input.service.webSearch === void 0) {
|
|
1264
|
+
throw new Error("Tool 'web.search' is unavailable without a web search service contract.");
|
|
1265
|
+
}
|
|
1266
|
+
return input.service.webSearch({ auth: input.auth, ...parseWebSearch(toolInput) });
|
|
1267
|
+
}
|
|
1268
|
+
function executeWebFetch(input, toolInput) {
|
|
1269
|
+
if (input.service.webFetch === void 0) {
|
|
1270
|
+
throw new Error("Tool 'web.fetch' is unavailable without a web fetch service contract.");
|
|
1271
|
+
}
|
|
1272
|
+
return input.service.webFetch({ auth: input.auth, ...parseWebFetch(toolInput) });
|
|
1273
|
+
}
|
|
1274
|
+
function parseWebSearch(input) {
|
|
1275
|
+
const parsed = {
|
|
1276
|
+
query: requireString(input, "query"),
|
|
1277
|
+
limit: requireBoundedInteger(input, "limit", 5, 1, 10)
|
|
1278
|
+
};
|
|
1279
|
+
if (input.recencyDays !== void 0) {
|
|
1280
|
+
parsed.recencyDays = requireBoundedInteger(input, "recencyDays", 30, 1, 3650);
|
|
1281
|
+
}
|
|
1282
|
+
if (input.allowedDomains !== void 0) {
|
|
1283
|
+
parsed.allowedDomains = requireBoundedStringArray(input, "allowedDomains", 10);
|
|
1284
|
+
}
|
|
1285
|
+
if (input.blockedDomains !== void 0) {
|
|
1286
|
+
parsed.blockedDomains = requireBoundedStringArray(input, "blockedDomains", 10);
|
|
1287
|
+
}
|
|
1288
|
+
return parsed;
|
|
1289
|
+
}
|
|
1290
|
+
function parseWebFetch(input) {
|
|
1291
|
+
return {
|
|
1292
|
+
url: requireString(input, "url"),
|
|
1293
|
+
maxChars: requireBoundedInteger(input, "maxChars", 8e3, 1, 12e3)
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
function requireBoundedStringArray(input, key, maxItems) {
|
|
1297
|
+
const value = input[key];
|
|
1298
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
1299
|
+
throw new Error(`${key} must be a string array.`);
|
|
1300
|
+
}
|
|
1301
|
+
const values = value.map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1302
|
+
if (values.length > maxItems) {
|
|
1303
|
+
throw new Error(`${key} must contain no more than ${maxItems} items.`);
|
|
1304
|
+
}
|
|
1305
|
+
return values;
|
|
1306
|
+
}
|
|
1307
|
+
function requireBoundedInteger(input, key, fallback, minimum, maximum) {
|
|
1308
|
+
const value = input[key];
|
|
1309
|
+
const integer = value === void 0 ? fallback : value;
|
|
1310
|
+
if (!Number.isInteger(integer) || Number(integer) < minimum || Number(integer) > maximum) {
|
|
1311
|
+
throw new Error(`${key} must be an integer between ${minimum} and ${maximum}.`);
|
|
1312
|
+
}
|
|
1313
|
+
return Number(integer);
|
|
1314
|
+
}
|
|
1315
|
+
var init_webToolRuntime = __esm({
|
|
1316
|
+
"../tools/dist/webToolRuntime.js"() {
|
|
1317
|
+
"use strict";
|
|
1318
|
+
init_inputParsers();
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1115
1322
|
// ../tools/dist/executor.js
|
|
1116
1323
|
function createRuntimeToolExecutor(input) {
|
|
1117
1324
|
const availableToolNames = runtimeAvailableToolNames(input.service);
|
|
@@ -1136,7 +1343,7 @@ function createRuntimeToolExecutor(input) {
|
|
|
1136
1343
|
throw error;
|
|
1137
1344
|
}
|
|
1138
1345
|
try {
|
|
1139
|
-
const
|
|
1346
|
+
const result = await handler();
|
|
1140
1347
|
logToolCall({
|
|
1141
1348
|
auth: input.auth,
|
|
1142
1349
|
onToolLog: input.onToolLog,
|
|
@@ -1144,9 +1351,9 @@ function createRuntimeToolExecutor(input) {
|
|
|
1144
1351
|
toolInput,
|
|
1145
1352
|
status: "success",
|
|
1146
1353
|
startedAt,
|
|
1147
|
-
result
|
|
1354
|
+
result
|
|
1148
1355
|
});
|
|
1149
|
-
return
|
|
1356
|
+
return result;
|
|
1150
1357
|
} catch (error) {
|
|
1151
1358
|
logToolCall({
|
|
1152
1359
|
auth: input.auth,
|
|
@@ -1179,6 +1386,7 @@ function createToolHandlers(input, toolInput) {
|
|
|
1179
1386
|
"context.profile": () => executeContextProfile(input, toolInput),
|
|
1180
1387
|
"decision.create": () => executeDecisionCreate(input, toolInput),
|
|
1181
1388
|
"task.create": () => executeTaskCreate(input, toolInput),
|
|
1389
|
+
...webToolHandlers(input, toolInput),
|
|
1182
1390
|
...skillToolHandlers(input, toolInput),
|
|
1183
1391
|
...agentIdentityToolHandlers(input, toolInput),
|
|
1184
1392
|
...contractToolHandlers(input),
|
|
@@ -1238,6 +1446,21 @@ function executeSearchQuery(input, toolInput) {
|
|
|
1238
1446
|
}
|
|
1239
1447
|
function executeContextAssemble(input, toolInput) {
|
|
1240
1448
|
const query = parseContextQuery(toolInput);
|
|
1449
|
+
if (input.service.assembleGroundedContext !== void 0) {
|
|
1450
|
+
return input.service.assembleGroundedContext({
|
|
1451
|
+
auth: input.auth,
|
|
1452
|
+
query: query.query,
|
|
1453
|
+
queryIssuedAt: query.queryIssuedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1454
|
+
timezone: query.timezone ?? "UTC",
|
|
1455
|
+
requester: {
|
|
1456
|
+
principalId: input.auth.principalId,
|
|
1457
|
+
actorId: input.auth.actorId
|
|
1458
|
+
},
|
|
1459
|
+
surface: taskGuidanceSurface(input.transport),
|
|
1460
|
+
limit: 8,
|
|
1461
|
+
maxChars: query.maxChars
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1241
1464
|
return input.service.retrieveCitedContext({
|
|
1242
1465
|
auth: input.auth,
|
|
1243
1466
|
query: query.query,
|
|
@@ -1245,6 +1468,13 @@ function executeContextAssemble(input, toolInput) {
|
|
|
1245
1468
|
maxChars: query.maxChars
|
|
1246
1469
|
});
|
|
1247
1470
|
}
|
|
1471
|
+
function taskGuidanceSurface(transport) {
|
|
1472
|
+
if (transport === "cli")
|
|
1473
|
+
return "cli";
|
|
1474
|
+
if (transport === "hosted_mcp" || transport === "local_mcp")
|
|
1475
|
+
return "mcp";
|
|
1476
|
+
return "app";
|
|
1477
|
+
}
|
|
1248
1478
|
function executeContextProfile(input, toolInput) {
|
|
1249
1479
|
if (input.service.getContextProfile === void 0) {
|
|
1250
1480
|
throw new Error("Tool 'context.profile' requires a profile read service contract.");
|
|
@@ -1257,16 +1487,16 @@ async function executeDecisionCreate(input, toolInput) {
|
|
|
1257
1487
|
throw new Error("Tool 'decision.create' requires a work-record service contract.");
|
|
1258
1488
|
}
|
|
1259
1489
|
const decision = parseDecision(toolInput);
|
|
1260
|
-
const
|
|
1261
|
-
return workRecordResult(
|
|
1490
|
+
const result = await input.service.createDecision({ auth: input.auth, ...decision });
|
|
1491
|
+
return workRecordResult(result);
|
|
1262
1492
|
}
|
|
1263
1493
|
async function executeTaskCreate(input, toolInput) {
|
|
1264
1494
|
if (input.service.createTask === void 0) {
|
|
1265
1495
|
throw new Error("Tool 'task.create' requires a work-record service contract.");
|
|
1266
1496
|
}
|
|
1267
1497
|
const task = parseTask(toolInput);
|
|
1268
|
-
const
|
|
1269
|
-
return workRecordResult(
|
|
1498
|
+
const result = await input.service.createTask({ auth: input.auth, ...task });
|
|
1499
|
+
return workRecordResult(result);
|
|
1270
1500
|
}
|
|
1271
1501
|
function executeSourceList(input) {
|
|
1272
1502
|
if (input.service.listSources === void 0) {
|
|
@@ -1317,16 +1547,16 @@ async function executeRecordCreateMarkdown(input, toolInput) {
|
|
|
1317
1547
|
throw new Error("Tool 'record.create_markdown' requires a record write service contract.");
|
|
1318
1548
|
}
|
|
1319
1549
|
const record = parseMarkdownRecord(toolInput);
|
|
1320
|
-
const
|
|
1321
|
-
return workRecordResult(
|
|
1550
|
+
const result = await input.service.createMarkdownRecord({ auth: input.auth, ...record });
|
|
1551
|
+
return workRecordResult(result);
|
|
1322
1552
|
}
|
|
1323
1553
|
async function executeRecordPatchSection(input, toolInput) {
|
|
1324
1554
|
if (input.service.patchRecordSection === void 0) {
|
|
1325
1555
|
throw new Error("Tool 'record.patch_section' requires a record write service contract.");
|
|
1326
1556
|
}
|
|
1327
1557
|
const patch = parseRecordSectionPatch(toolInput);
|
|
1328
|
-
const
|
|
1329
|
-
return workRecordResult(
|
|
1558
|
+
const result = await input.service.patchRecordSection({ auth: input.auth, ...patch });
|
|
1559
|
+
return workRecordResult(result);
|
|
1330
1560
|
}
|
|
1331
1561
|
function executeRecordVersions(input, toolInput) {
|
|
1332
1562
|
if (input.service.listRecordVersions === void 0) {
|
|
@@ -1356,6 +1586,7 @@ var init_executor = __esm({
|
|
|
1356
1586
|
init_toolAvailability();
|
|
1357
1587
|
init_results();
|
|
1358
1588
|
init_toolLog();
|
|
1589
|
+
init_webToolRuntime();
|
|
1359
1590
|
}
|
|
1360
1591
|
});
|
|
1361
1592
|
|
|
@@ -1394,7 +1625,7 @@ function createMcpAdapter(input) {
|
|
|
1394
1625
|
...IMPLEMENTED_TOOL_NAMES
|
|
1395
1626
|
];
|
|
1396
1627
|
const availableNameSet = new Set(availableToolNames);
|
|
1397
|
-
const available = listToolDefinitions().filter((tool) => availableNameSet.has(tool.name) && tool.transports.includes(input.transport) && input.capabilities
|
|
1628
|
+
const available = listToolDefinitions().filter((tool) => availableNameSet.has(tool.name) && tool.transports.includes(input.transport) && isToolAuthorized(input.capabilities, tool));
|
|
1398
1629
|
return {
|
|
1399
1630
|
listTools() {
|
|
1400
1631
|
return createMcpToolSchemas({
|
|
@@ -1408,11 +1639,11 @@ function createMcpAdapter(input) {
|
|
|
1408
1639
|
return errorResult2("tool_unavailable", `Tool '${call.name}' is unavailable for this MCP transport or scope.`);
|
|
1409
1640
|
}
|
|
1410
1641
|
try {
|
|
1411
|
-
const
|
|
1642
|
+
const result = await input.executor.execute(call.name, call.arguments);
|
|
1412
1643
|
return {
|
|
1413
1644
|
isError: false,
|
|
1414
|
-
structuredContent:
|
|
1415
|
-
content: [{ type: "text", text: renderToolResult(
|
|
1645
|
+
structuredContent: result,
|
|
1646
|
+
content: [{ type: "text", text: renderToolResult(result) }]
|
|
1416
1647
|
};
|
|
1417
1648
|
} catch (error) {
|
|
1418
1649
|
return errorResult2(classifyToolError(error), messageForError(error));
|
|
@@ -1420,14 +1651,14 @@ function createMcpAdapter(input) {
|
|
|
1420
1651
|
}
|
|
1421
1652
|
};
|
|
1422
1653
|
}
|
|
1423
|
-
function renderToolResult(
|
|
1424
|
-
if (typeof
|
|
1425
|
-
const context =
|
|
1654
|
+
function renderToolResult(result) {
|
|
1655
|
+
if (typeof result === "object" && result !== null && "contextMarkdown" in result) {
|
|
1656
|
+
const context = result;
|
|
1426
1657
|
if (typeof context.contextMarkdown === "string") {
|
|
1427
1658
|
return context.contextMarkdown;
|
|
1428
1659
|
}
|
|
1429
1660
|
}
|
|
1430
|
-
return JSON.stringify(
|
|
1661
|
+
return JSON.stringify(result);
|
|
1431
1662
|
}
|
|
1432
1663
|
function classifyToolError(error) {
|
|
1433
1664
|
if (error instanceof Error) {
|
|
@@ -1546,89 +1777,41 @@ var init_hostedMcpEntrypoint = __esm({
|
|
|
1546
1777
|
}
|
|
1547
1778
|
});
|
|
1548
1779
|
|
|
1549
|
-
// ../tools/dist/
|
|
1550
|
-
function
|
|
1780
|
+
// ../tools/dist/mcpJsonRpcCore.js
|
|
1781
|
+
function createMcpJsonRpcCore(input) {
|
|
1782
|
+
const { adapter, config } = input;
|
|
1551
1783
|
return {
|
|
1552
|
-
async
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
capabilities: serverInput.capabilities,
|
|
1556
|
-
executor: serverInput.executor
|
|
1557
|
-
});
|
|
1558
|
-
let buffer = "";
|
|
1559
|
-
input.input.setEncoding("utf8");
|
|
1560
|
-
for await (const chunk of input.input) {
|
|
1561
|
-
buffer += chunk;
|
|
1562
|
-
let newline = buffer.indexOf("\n");
|
|
1563
|
-
while (newline >= 0) {
|
|
1564
|
-
const line = buffer.slice(0, newline).trim();
|
|
1565
|
-
buffer = buffer.slice(newline + 1);
|
|
1566
|
-
if (line.length > 0) {
|
|
1567
|
-
await handleLine(line, adapter, input.output, input.error);
|
|
1568
|
-
}
|
|
1569
|
-
newline = buffer.indexOf("\n");
|
|
1570
|
-
}
|
|
1784
|
+
async handleMessage(message) {
|
|
1785
|
+
if (message.id === void 0) {
|
|
1786
|
+
return null;
|
|
1571
1787
|
}
|
|
1572
|
-
const
|
|
1573
|
-
if (
|
|
1574
|
-
|
|
1788
|
+
const id = normalizeId(message.id);
|
|
1789
|
+
if (message.jsonrpc !== "2.0" || typeof message.method !== "string") {
|
|
1790
|
+
return errorResponse(id, -32600, "Invalid Request");
|
|
1791
|
+
}
|
|
1792
|
+
try {
|
|
1793
|
+
return {
|
|
1794
|
+
jsonrpc: "2.0",
|
|
1795
|
+
id,
|
|
1796
|
+
result: await dispatchRequest(message.method, message.params, adapter, config)
|
|
1797
|
+
};
|
|
1798
|
+
} catch (err) {
|
|
1799
|
+
return errorResponse(id, -32601, err instanceof Error ? err.message : "Method not found");
|
|
1575
1800
|
}
|
|
1576
1801
|
}
|
|
1577
1802
|
};
|
|
1578
1803
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
try {
|
|
1582
|
-
message = JSON.parse(line);
|
|
1583
|
-
} catch {
|
|
1584
|
-
writeResponse(output, {
|
|
1585
|
-
jsonrpc: "2.0",
|
|
1586
|
-
id: null,
|
|
1587
|
-
error: { code: -32700, message: "Parse error" }
|
|
1588
|
-
});
|
|
1589
|
-
return;
|
|
1590
|
-
}
|
|
1591
|
-
if (message.id === void 0) {
|
|
1592
|
-
if (message.method === "notifications/initialized")
|
|
1593
|
-
return;
|
|
1594
|
-
error?.write(`Ignoring MCP notification '${String(message.method)}'.
|
|
1595
|
-
`);
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
|
-
const id = normalizeId(message.id);
|
|
1599
|
-
if (message.jsonrpc !== "2.0" || typeof message.method !== "string") {
|
|
1600
|
-
writeResponse(output, {
|
|
1601
|
-
jsonrpc: "2.0",
|
|
1602
|
-
id,
|
|
1603
|
-
error: { code: -32600, message: "Invalid Request" }
|
|
1604
|
-
});
|
|
1605
|
-
return;
|
|
1606
|
-
}
|
|
1607
|
-
try {
|
|
1608
|
-
writeResponse(output, {
|
|
1609
|
-
jsonrpc: "2.0",
|
|
1610
|
-
id,
|
|
1611
|
-
result: await dispatchRequest(message.method, message.params, adapter)
|
|
1612
|
-
});
|
|
1613
|
-
} catch (err) {
|
|
1614
|
-
writeResponse(output, {
|
|
1615
|
-
jsonrpc: "2.0",
|
|
1616
|
-
id,
|
|
1617
|
-
error: {
|
|
1618
|
-
code: -32601,
|
|
1619
|
-
message: err instanceof Error ? err.message : "Method not found"
|
|
1620
|
-
}
|
|
1621
|
-
});
|
|
1622
|
-
}
|
|
1804
|
+
function parseErrorResponse() {
|
|
1805
|
+
return errorResponse(null, -32700, "Parse error");
|
|
1623
1806
|
}
|
|
1624
|
-
async function dispatchRequest(method, params, adapter) {
|
|
1807
|
+
async function dispatchRequest(method, params, adapter, config) {
|
|
1625
1808
|
if (method === "initialize") {
|
|
1626
1809
|
const requested = readProtocolVersion(params);
|
|
1627
1810
|
return {
|
|
1628
1811
|
protocolVersion: requested ?? MCP_PROTOCOL_VERSION,
|
|
1629
1812
|
capabilities: { tools: { listChanged: false } },
|
|
1630
|
-
serverInfo: { name:
|
|
1631
|
-
instructions:
|
|
1813
|
+
serverInfo: { name: config.serverName, version: config.version },
|
|
1814
|
+
instructions: config.instructions
|
|
1632
1815
|
};
|
|
1633
1816
|
}
|
|
1634
1817
|
if (method === "ping")
|
|
@@ -1639,23 +1822,26 @@ async function dispatchRequest(method, params, adapter) {
|
|
|
1639
1822
|
const call = parseToolCall(params);
|
|
1640
1823
|
return adapter.callTool(call);
|
|
1641
1824
|
}
|
|
1642
|
-
throw new Error(`Method '${method}' is not supported by Sift
|
|
1825
|
+
throw new Error(`Method '${method}' is not supported by Sift MCP.`);
|
|
1643
1826
|
}
|
|
1644
1827
|
function readProtocolVersion(params) {
|
|
1645
|
-
if (!
|
|
1828
|
+
if (!isRecord2(params))
|
|
1646
1829
|
return void 0;
|
|
1647
1830
|
return typeof params.protocolVersion === "string" ? params.protocolVersion : void 0;
|
|
1648
1831
|
}
|
|
1649
1832
|
function parseToolCall(params) {
|
|
1650
|
-
if (!
|
|
1833
|
+
if (!isRecord2(params) || typeof params.name !== "string") {
|
|
1651
1834
|
throw new Error("tools/call requires a tool name.");
|
|
1652
1835
|
}
|
|
1653
1836
|
return {
|
|
1654
1837
|
name: params.name,
|
|
1655
|
-
arguments:
|
|
1838
|
+
arguments: isRecord2(params.arguments) ? params.arguments : {}
|
|
1656
1839
|
};
|
|
1657
1840
|
}
|
|
1658
|
-
function
|
|
1841
|
+
function errorResponse(id, code, message) {
|
|
1842
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
1843
|
+
}
|
|
1844
|
+
function isRecord2(value) {
|
|
1659
1845
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1660
1846
|
}
|
|
1661
1847
|
function normalizeId(value) {
|
|
@@ -1663,16 +1849,80 @@ function normalizeId(value) {
|
|
|
1663
1849
|
return value;
|
|
1664
1850
|
return null;
|
|
1665
1851
|
}
|
|
1852
|
+
var MCP_PROTOCOL_VERSION;
|
|
1853
|
+
var init_mcpJsonRpcCore = __esm({
|
|
1854
|
+
"../tools/dist/mcpJsonRpcCore.js"() {
|
|
1855
|
+
"use strict";
|
|
1856
|
+
MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1860
|
+
// ../tools/dist/localMcpStdioServer.js
|
|
1861
|
+
function createLocalMcpStdioServer(input) {
|
|
1862
|
+
return {
|
|
1863
|
+
async serve(serverInput) {
|
|
1864
|
+
const adapter = createMcpAdapter({
|
|
1865
|
+
transport: "local_mcp",
|
|
1866
|
+
capabilities: serverInput.capabilities,
|
|
1867
|
+
executor: serverInput.executor
|
|
1868
|
+
});
|
|
1869
|
+
const core = createMcpJsonRpcCore({
|
|
1870
|
+
adapter,
|
|
1871
|
+
config: {
|
|
1872
|
+
serverName: "sift-local-mcp",
|
|
1873
|
+
version: "0.1.0",
|
|
1874
|
+
instructions: LOCAL_INSTRUCTIONS
|
|
1875
|
+
}
|
|
1876
|
+
});
|
|
1877
|
+
let buffer = "";
|
|
1878
|
+
input.input.setEncoding("utf8");
|
|
1879
|
+
for await (const chunk of input.input) {
|
|
1880
|
+
buffer += chunk;
|
|
1881
|
+
let newline = buffer.indexOf("\n");
|
|
1882
|
+
while (newline >= 0) {
|
|
1883
|
+
const line = buffer.slice(0, newline).trim();
|
|
1884
|
+
buffer = buffer.slice(newline + 1);
|
|
1885
|
+
if (line.length > 0) {
|
|
1886
|
+
await handleLine(line, core, input.output, input.error);
|
|
1887
|
+
}
|
|
1888
|
+
newline = buffer.indexOf("\n");
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
const trailing = buffer.trim();
|
|
1892
|
+
if (trailing.length > 0) {
|
|
1893
|
+
await handleLine(trailing, core, input.output, input.error);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
async function handleLine(line, core, output, error) {
|
|
1899
|
+
let message;
|
|
1900
|
+
try {
|
|
1901
|
+
message = JSON.parse(line);
|
|
1902
|
+
} catch {
|
|
1903
|
+
writeResponse(output, parseErrorResponse());
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
if (message.id === void 0 && message.method !== "notifications/initialized") {
|
|
1907
|
+
error?.write(`Ignoring MCP notification '${String(message.method)}'.
|
|
1908
|
+
`);
|
|
1909
|
+
}
|
|
1910
|
+
const response = await core.handleMessage(message);
|
|
1911
|
+
if (response !== null) {
|
|
1912
|
+
writeResponse(output, response);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1666
1915
|
function writeResponse(output, response) {
|
|
1667
1916
|
output.write(`${JSON.stringify(response)}
|
|
1668
1917
|
`);
|
|
1669
1918
|
}
|
|
1670
|
-
var
|
|
1919
|
+
var LOCAL_INSTRUCTIONS;
|
|
1671
1920
|
var init_localMcpStdioServer = __esm({
|
|
1672
1921
|
"../tools/dist/localMcpStdioServer.js"() {
|
|
1673
1922
|
"use strict";
|
|
1674
1923
|
init_mcpAdapter();
|
|
1675
|
-
|
|
1924
|
+
init_mcpJsonRpcCore();
|
|
1925
|
+
LOCAL_INSTRUCTIONS = "Call contract.get first and echo its contractVersion on every other Sift tool call. Use Sift tools to read and write the hosted canonical brain.";
|
|
1676
1926
|
}
|
|
1677
1927
|
});
|
|
1678
1928
|
|
|
@@ -1686,11 +1936,13 @@ __export(dist_exports, {
|
|
|
1686
1936
|
createHostedMcpEntrypoint: () => createHostedMcpEntrypoint,
|
|
1687
1937
|
createLocalMcpStdioServer: () => createLocalMcpStdioServer,
|
|
1688
1938
|
createMcpAdapter: () => createMcpAdapter,
|
|
1939
|
+
createMcpJsonRpcCore: () => createMcpJsonRpcCore,
|
|
1689
1940
|
createMcpToolSchemas: () => createMcpToolSchemas,
|
|
1690
1941
|
createRuntimeToolExecutor: () => createRuntimeToolExecutor,
|
|
1691
1942
|
isGatedTool: () => isGatedTool,
|
|
1692
1943
|
isToolAuthorized: () => isToolAuthorized,
|
|
1693
|
-
listToolDefinitions: () => listToolDefinitions
|
|
1944
|
+
listToolDefinitions: () => listToolDefinitions,
|
|
1945
|
+
parseErrorResponse: () => parseErrorResponse
|
|
1694
1946
|
});
|
|
1695
1947
|
var init_dist = __esm({
|
|
1696
1948
|
"../tools/dist/index.js"() {
|
|
@@ -1700,13 +1952,14 @@ var init_dist = __esm({
|
|
|
1700
1952
|
init_hostedMcpEntrypoint();
|
|
1701
1953
|
init_localMcpStdioServer();
|
|
1702
1954
|
init_mcpAdapter();
|
|
1955
|
+
init_mcpJsonRpcCore();
|
|
1703
1956
|
init_gating();
|
|
1704
1957
|
init_registry();
|
|
1705
1958
|
}
|
|
1706
1959
|
});
|
|
1707
1960
|
|
|
1708
1961
|
// src/index.ts
|
|
1709
|
-
import { readFile } from "fs/promises";
|
|
1962
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1710
1963
|
|
|
1711
1964
|
// src/support.ts
|
|
1712
1965
|
import { createHash } from "crypto";
|
|
@@ -1723,19 +1976,19 @@ function renderScope(scope) {
|
|
|
1723
1976
|
""
|
|
1724
1977
|
].join("\n");
|
|
1725
1978
|
}
|
|
1726
|
-
function validateAuthenticatedScope(
|
|
1979
|
+
function validateAuthenticatedScope(config, now) {
|
|
1727
1980
|
const requiredScope = [
|
|
1728
|
-
["apiBaseUrl",
|
|
1729
|
-
["workspaceId",
|
|
1730
|
-
["brainId",
|
|
1731
|
-
["principalId",
|
|
1981
|
+
["apiBaseUrl", config.apiBaseUrl],
|
|
1982
|
+
["workspaceId", config.workspaceId],
|
|
1983
|
+
["brainId", config.brainId],
|
|
1984
|
+
["principalId", config.principalId]
|
|
1732
1985
|
];
|
|
1733
1986
|
const missing = requiredScope.find(([, value]) => value.trim().length === 0);
|
|
1734
1987
|
if (missing !== void 0) {
|
|
1735
1988
|
throw new Error(`Missing authenticated CLI scope: ${missing[0]}.`);
|
|
1736
1989
|
}
|
|
1737
|
-
if (
|
|
1738
|
-
const expiresAt = Date.parse(
|
|
1990
|
+
if (config.tokenExpiresAt !== void 0) {
|
|
1991
|
+
const expiresAt = Date.parse(config.tokenExpiresAt);
|
|
1739
1992
|
if (!Number.isFinite(expiresAt)) {
|
|
1740
1993
|
throw new Error("Invalid CLI auth expiry timestamp.");
|
|
1741
1994
|
}
|
|
@@ -1744,49 +1997,49 @@ function validateAuthenticatedScope(config2, now) {
|
|
|
1744
1997
|
}
|
|
1745
1998
|
}
|
|
1746
1999
|
}
|
|
1747
|
-
function renderSearchResult(
|
|
1748
|
-
if (typeof
|
|
1749
|
-
const context =
|
|
2000
|
+
function renderSearchResult(result) {
|
|
2001
|
+
if (typeof result === "object" && result !== null && "contextMarkdown" in result) {
|
|
2002
|
+
const context = result;
|
|
1750
2003
|
return `${context.contextMarkdown}
|
|
1751
2004
|
`;
|
|
1752
2005
|
}
|
|
1753
|
-
return `${JSON.stringify(
|
|
2006
|
+
return `${JSON.stringify(result)}
|
|
1754
2007
|
`;
|
|
1755
2008
|
}
|
|
1756
|
-
function renderRecordResult(
|
|
1757
|
-
if (typeof
|
|
1758
|
-
const record =
|
|
2009
|
+
function renderRecordResult(result) {
|
|
2010
|
+
if (typeof result === "object" && result !== null && "markdown" in result) {
|
|
2011
|
+
const record = result;
|
|
1759
2012
|
return `${record.markdown}
|
|
1760
2013
|
`;
|
|
1761
2014
|
}
|
|
1762
|
-
return `${JSON.stringify(
|
|
2015
|
+
return `${JSON.stringify(result)}
|
|
1763
2016
|
`;
|
|
1764
2017
|
}
|
|
1765
|
-
function renderProfileResult(
|
|
1766
|
-
if (typeof
|
|
1767
|
-
const profile =
|
|
2018
|
+
function renderProfileResult(result) {
|
|
2019
|
+
if (typeof result === "object" && result !== null && "profileMarkdown" in result) {
|
|
2020
|
+
const profile = result;
|
|
1768
2021
|
return `${profile.profileMarkdown}
|
|
1769
2022
|
`;
|
|
1770
2023
|
}
|
|
1771
|
-
return `${JSON.stringify(
|
|
2024
|
+
return `${JSON.stringify(result)}
|
|
1772
2025
|
`;
|
|
1773
2026
|
}
|
|
1774
2027
|
function renderTools(tools) {
|
|
1775
2028
|
return `${tools.map((tool) => `${tool.name}: ${tool.summary}`).join("\n")}
|
|
1776
2029
|
`;
|
|
1777
2030
|
}
|
|
1778
|
-
function renderSiftFound(
|
|
2031
|
+
function renderSiftFound(result) {
|
|
1779
2032
|
return `Sift found:
|
|
1780
2033
|
|
|
1781
|
-
${renderSearchResult(
|
|
2034
|
+
${renderSearchResult(result)}`;
|
|
1782
2035
|
}
|
|
1783
|
-
function renderWriteReceipt(action,
|
|
1784
|
-
if (typeof
|
|
2036
|
+
function renderWriteReceipt(action, result) {
|
|
2037
|
+
if (typeof result !== "object" || result === null) {
|
|
1785
2038
|
return `${action} complete.
|
|
1786
|
-
${JSON.stringify(
|
|
2039
|
+
${JSON.stringify(result)}
|
|
1787
2040
|
`;
|
|
1788
2041
|
}
|
|
1789
|
-
const record =
|
|
2042
|
+
const record = result;
|
|
1790
2043
|
const lines = [`${action} complete.`];
|
|
1791
2044
|
addReceiptLine(lines, "Record", record.recordId);
|
|
1792
2045
|
addReceiptLine(lines, "Version", record.versionId);
|
|
@@ -1796,20 +2049,20 @@ ${JSON.stringify(result2)}
|
|
|
1796
2049
|
addReceiptLine(lines, "Job", record.job.id);
|
|
1797
2050
|
}
|
|
1798
2051
|
if (lines.length === 1) {
|
|
1799
|
-
lines.push(JSON.stringify(
|
|
2052
|
+
lines.push(JSON.stringify(result));
|
|
1800
2053
|
}
|
|
1801
2054
|
lines.push("");
|
|
1802
2055
|
return lines.join("\n");
|
|
1803
2056
|
}
|
|
1804
|
-
function renderDoctorResult(
|
|
1805
|
-
return `${
|
|
2057
|
+
function renderDoctorResult(result) {
|
|
2058
|
+
return `${result.checks.map((check) => {
|
|
1806
2059
|
const fix = check.fix === void 0 ? "" : ` Fix: ${check.fix}`;
|
|
1807
2060
|
return `[${check.status}] ${check.label}: ${check.detail}${fix}`;
|
|
1808
2061
|
}).join("\n")}
|
|
1809
2062
|
`;
|
|
1810
2063
|
}
|
|
1811
|
-
function aliasJson(command, tool,
|
|
1812
|
-
return `${JSON.stringify({ command, tool, result
|
|
2064
|
+
function aliasJson(command, tool, result) {
|
|
2065
|
+
return `${JSON.stringify({ command, tool, result })}
|
|
1813
2066
|
`;
|
|
1814
2067
|
}
|
|
1815
2068
|
function positionalArgs(args) {
|
|
@@ -2007,16 +2260,16 @@ async function agentRegister(executor, assertedAgentName, rest, json) {
|
|
|
2007
2260
|
if (kind !== void 0) {
|
|
2008
2261
|
input.kind = kind;
|
|
2009
2262
|
}
|
|
2010
|
-
const
|
|
2011
|
-
return ok(json ? `${JSON.stringify(
|
|
2012
|
-
` : renderAgentRegisterResult(
|
|
2263
|
+
const result = await executor.execute("agent.register", input);
|
|
2264
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
2265
|
+
` : renderAgentRegisterResult(result));
|
|
2013
2266
|
}
|
|
2014
|
-
function renderAgentRegisterResult(
|
|
2015
|
-
if (typeof
|
|
2016
|
-
return `${JSON.stringify(
|
|
2267
|
+
function renderAgentRegisterResult(result) {
|
|
2268
|
+
if (typeof result !== "object" || result === null || !("agent" in result)) {
|
|
2269
|
+
return `${JSON.stringify(result)}
|
|
2017
2270
|
`;
|
|
2018
2271
|
}
|
|
2019
|
-
const { agent, created, reactivated } =
|
|
2272
|
+
const { agent, created, reactivated } = result;
|
|
2020
2273
|
const verb = created === true ? "Registered" : reactivated === true ? "Reactivated" : "Already registered";
|
|
2021
2274
|
const actsFor = agent.actsForDisplayName === void 0 ? "" : ` (acting for ${agent.actsForDisplayName})`;
|
|
2022
2275
|
return `${verb} agent worker '${agent.name ?? "unknown"}'${actsFor}.
|
|
@@ -2085,7 +2338,8 @@ var commandCapabilities = {
|
|
|
2085
2338
|
"evidence:get": "record:read",
|
|
2086
2339
|
"graph:neighbors": "record:read",
|
|
2087
2340
|
"event:list": "record:read",
|
|
2088
|
-
"audit:events": "event:audit:read"
|
|
2341
|
+
"audit:events": "event:audit:read",
|
|
2342
|
+
"roam:import": "source:manage"
|
|
2089
2343
|
};
|
|
2090
2344
|
function validateCommandCapability(input) {
|
|
2091
2345
|
const capability = commandCapabilities[input.commandKey];
|
|
@@ -2123,86 +2377,273 @@ function withContractVersion(executor, contractVersion) {
|
|
|
2123
2377
|
};
|
|
2124
2378
|
}
|
|
2125
2379
|
|
|
2126
|
-
// src/
|
|
2127
|
-
async function
|
|
2128
|
-
const checks = [
|
|
2129
|
-
{ id: "bin", status: "ok", label: "Bin", detail: "sift command entrypoint is configured." },
|
|
2130
|
-
{ id: "package", status: "ok", label: "Package", detail: "@sift-wiki/cli version unknown, bin sift." },
|
|
2131
|
-
authCheck(input.config, input.now),
|
|
2132
|
-
scopeCheck(input.config)
|
|
2133
|
-
];
|
|
2134
|
-
const apiCheck = await checkApi(input.executor);
|
|
2135
|
-
checks.push(apiCheck);
|
|
2136
|
-
const availableTools3 = await discoverToolNames(input.executor);
|
|
2137
|
-
checks.push(readToolsCheck(availableTools3));
|
|
2138
|
-
checks.push(writeToolsCheck(input.config, availableTools3));
|
|
2139
|
-
checks.push(recordGetCheck(availableTools3));
|
|
2140
|
-
const result2 = {
|
|
2141
|
-
ok: !checks.some((check) => check.status === "failed"),
|
|
2142
|
-
apiBaseUrl: input.config.apiBaseUrl.trim().length > 0 ? input.config.apiBaseUrl : void 0,
|
|
2143
|
-
scope: scopeResult(input.config),
|
|
2144
|
-
checks
|
|
2145
|
-
};
|
|
2146
|
-
return {
|
|
2147
|
-
exitCode: result2.ok ? 0 : 1,
|
|
2148
|
-
stdout: input.json ? `${JSON.stringify(result2)}
|
|
2149
|
-
` : renderDoctorResult(result2),
|
|
2150
|
-
stderr: ""
|
|
2151
|
-
};
|
|
2152
|
-
}
|
|
2153
|
-
async function discoverToolNames(executor) {
|
|
2154
|
-
if (executor === void 0) return void 0;
|
|
2155
|
-
if (executor.listAvailableToolNames !== void 0) return executor.listAvailableToolNames();
|
|
2156
|
-
const result2 = await executor.execute("tools.list", {});
|
|
2157
|
-
return toolNamesFromResult(result2);
|
|
2158
|
-
}
|
|
2159
|
-
async function apiReachability(executor) {
|
|
2160
|
-
if (executor === void 0) return { reachable: false, detail: "No API executor is configured." };
|
|
2380
|
+
// src/roamImport.ts
|
|
2381
|
+
async function runRoamImportCommand(input) {
|
|
2161
2382
|
try {
|
|
2162
|
-
|
|
2163
|
-
|
|
2383
|
+
const parsed = parseOptions(input.rest);
|
|
2384
|
+
const scope = parseCliRoamScope(optionalOption(parsed, "scope") ?? "sift-tag");
|
|
2385
|
+
const mode = parseCliRoamMode(optionalOption(parsed, "mode") ?? "personal");
|
|
2386
|
+
const limit = parseIntegerOption(parsed, "limit", 100);
|
|
2387
|
+
if (limit < 1 || limit > 100) {
|
|
2388
|
+
throw new Error("Option --limit must be between 1 and 100.");
|
|
2389
|
+
}
|
|
2390
|
+
const wholeGraphConfirmed = input.rest.includes("--confirm-whole-graph") || input.rest.includes("--yes");
|
|
2391
|
+
if (scope === "whole_graph" && !wholeGraphConfirmed) {
|
|
2392
|
+
throw new Error("Option --confirm-whole-graph is required.");
|
|
2393
|
+
}
|
|
2394
|
+
const workspaceAttestation = input.rest.includes("--workspace-attestation") || input.rest.includes("--confirm-workspace");
|
|
2395
|
+
if (mode === "workspace" && !workspaceAttestation) {
|
|
2396
|
+
throw new Error("Option --workspace-attestation is required.");
|
|
2397
|
+
}
|
|
2398
|
+
if (input.reader === void 0) {
|
|
2399
|
+
throw new Error(
|
|
2400
|
+
"Roam import needs the local Roam helper. Run this command through the Sift CLI package."
|
|
2401
|
+
);
|
|
2402
|
+
}
|
|
2403
|
+
if (input.importer === void 0) {
|
|
2404
|
+
throw new Error("Not signed in. Run 'sift login', then retry 'sift roam import'.");
|
|
2405
|
+
}
|
|
2406
|
+
const records = await input.reader.exportPages({
|
|
2407
|
+
scope,
|
|
2408
|
+
graph: optionalOption(parsed, "graph"),
|
|
2409
|
+
limit,
|
|
2410
|
+
now: input.now
|
|
2411
|
+
});
|
|
2412
|
+
if (records.length === 0) {
|
|
2413
|
+
throw new Error(
|
|
2414
|
+
scope === "sift_tag" ? "No Roam pages marked [[Sift]] were found." : "No Roam pages were found for import."
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
const result = await input.importer.importRecords({
|
|
2418
|
+
mode,
|
|
2419
|
+
scope,
|
|
2420
|
+
records,
|
|
2421
|
+
defaultVisibility: visibilityOption(parsed),
|
|
2422
|
+
workspaceAttestation,
|
|
2423
|
+
wholeGraphConfirmed
|
|
2424
|
+
});
|
|
2425
|
+
return ok(input.json ? `${JSON.stringify(result)}
|
|
2426
|
+
` : renderRoamImportResult(result));
|
|
2164
2427
|
} catch (error) {
|
|
2165
|
-
return
|
|
2166
|
-
reachable: false,
|
|
2167
|
-
detail: error instanceof Error ? error.message : "Hosted Sift API request failed."
|
|
2168
|
-
};
|
|
2428
|
+
return errorResult(error, input.json);
|
|
2169
2429
|
}
|
|
2170
2430
|
}
|
|
2171
|
-
function
|
|
2172
|
-
|
|
2173
|
-
return void 0;
|
|
2174
|
-
}
|
|
2431
|
+
function createSiftRoamImportClient(input) {
|
|
2432
|
+
const fetchImpl = input.fetch ?? globalThis.fetch;
|
|
2175
2433
|
return {
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2434
|
+
async importRecords(request) {
|
|
2435
|
+
const response = await fetchImpl(roamImportUrl(input.apiBaseUrl, input.workspaceId), {
|
|
2436
|
+
method: "POST",
|
|
2437
|
+
headers: {
|
|
2438
|
+
Authorization: `Bearer ${input.token}`,
|
|
2439
|
+
"Content-Type": "application/json"
|
|
2440
|
+
},
|
|
2441
|
+
body: JSON.stringify(request)
|
|
2442
|
+
});
|
|
2443
|
+
const body = await response.text();
|
|
2444
|
+
const parsed = body.length > 0 ? parseJson(body) : {};
|
|
2445
|
+
if (!response.ok) {
|
|
2446
|
+
throw new Error(responseError(parsed, response.status));
|
|
2447
|
+
}
|
|
2448
|
+
return parseRoamImportResult(parsed);
|
|
2449
|
+
}
|
|
2180
2450
|
};
|
|
2181
2451
|
}
|
|
2182
|
-
|
|
2183
|
-
if (
|
|
2184
|
-
return
|
|
2185
|
-
id: "api",
|
|
2186
|
-
status: "warning",
|
|
2187
|
-
label: "API",
|
|
2188
|
-
detail: "not checked because no API executor is configured.",
|
|
2189
|
-
fix: "Run sift login or configure hosted API credentials."
|
|
2190
|
-
};
|
|
2452
|
+
function parseCliRoamScope(value) {
|
|
2453
|
+
if (value === "sift-tag" || value === "sift_tag" || value === "marked" || value === "sift") {
|
|
2454
|
+
return "sift_tag";
|
|
2191
2455
|
}
|
|
2192
|
-
|
|
2193
|
-
|
|
2456
|
+
if (value === "whole-graph" || value === "whole_graph" || value === "everything") {
|
|
2457
|
+
return "whole_graph";
|
|
2458
|
+
}
|
|
2459
|
+
throw new Error("Roam scope must be sift-tag or whole-graph.");
|
|
2194
2460
|
}
|
|
2195
|
-
function
|
|
2196
|
-
if (
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2461
|
+
function parseCliRoamMode(value) {
|
|
2462
|
+
if (value === "personal" || value === "workspace") return value;
|
|
2463
|
+
throw new Error("Roam import mode must be personal or workspace.");
|
|
2464
|
+
}
|
|
2465
|
+
function visibilityOption(parsed) {
|
|
2466
|
+
const visibility = optionalOption(parsed, "visibility");
|
|
2467
|
+
if (visibility === void 0) return void 0;
|
|
2468
|
+
const values = visibility.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
2469
|
+
if (values.length === 0) {
|
|
2470
|
+
throw new Error("Option --visibility must include at least one visibility segment.");
|
|
2471
|
+
}
|
|
2472
|
+
return values;
|
|
2473
|
+
}
|
|
2474
|
+
function renderRoamImportResult(result) {
|
|
2475
|
+
return [
|
|
2476
|
+
`Imported ${result.importedCount} Roam pages.`,
|
|
2477
|
+
`Stored: ${result.storedCount}`,
|
|
2478
|
+
`Deduped: ${result.dedupedCount}`,
|
|
2479
|
+
`Rejected: ${result.rejectedCount}`,
|
|
2480
|
+
""
|
|
2481
|
+
].join("\n");
|
|
2482
|
+
}
|
|
2483
|
+
function roamImportUrl(apiBaseUrl, workspaceId) {
|
|
2484
|
+
const base = `${apiBaseUrl.replace(/\/+$/u, "")}/integrations/roam/import`;
|
|
2485
|
+
return workspaceId === void 0 ? base : `${base}?workspaceId=${encodeURIComponent(workspaceId)}`;
|
|
2486
|
+
}
|
|
2487
|
+
function parseJson(body) {
|
|
2488
|
+
try {
|
|
2489
|
+
return JSON.parse(body);
|
|
2490
|
+
} catch {
|
|
2491
|
+
throw new Error("Sift Roam import API returned invalid JSON.");
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
function responseError(parsed, status2) {
|
|
2495
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
2496
|
+
const record = parsed;
|
|
2497
|
+
const message = record.message;
|
|
2498
|
+
if (typeof message === "string" && message.trim().length > 0) return message;
|
|
2499
|
+
const error = record.error;
|
|
2500
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
2501
|
+
const nested = error.message;
|
|
2502
|
+
if (typeof nested === "string" && nested.trim().length > 0) return nested;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
return `Sift Roam import API failed with status ${status2}.`;
|
|
2506
|
+
}
|
|
2507
|
+
function parseRoamImportResult(value) {
|
|
2508
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2509
|
+
throw new Error("Sift Roam import API returned an invalid result.");
|
|
2510
|
+
}
|
|
2511
|
+
const record = value;
|
|
2512
|
+
if (record.providerKind !== "roam") {
|
|
2513
|
+
throw new Error("Sift Roam import API returned an unexpected provider kind.");
|
|
2514
|
+
}
|
|
2515
|
+
return {
|
|
2516
|
+
providerKind: "roam",
|
|
2517
|
+
importedCount: integerField(record, "importedCount"),
|
|
2518
|
+
storedCount: integerField(record, "storedCount"),
|
|
2519
|
+
dedupedCount: integerField(record, "dedupedCount"),
|
|
2520
|
+
rejectedCount: integerField(record, "rejectedCount")
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
function integerField(record, key) {
|
|
2524
|
+
const value = record[key];
|
|
2525
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
2526
|
+
throw new Error(`Sift Roam import API result field ${key} must be an integer.`);
|
|
2527
|
+
}
|
|
2528
|
+
return value;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
// src/mcpServeCommand.ts
|
|
2532
|
+
async function mcpServe(input) {
|
|
2533
|
+
if (input.mcpServer === void 0) {
|
|
2534
|
+
return fail("No local MCP server is configured for mcp.serve.");
|
|
2535
|
+
}
|
|
2536
|
+
if (input.executor === void 0) {
|
|
2537
|
+
return fail("Not signed in. Run 'sift login', then 'sift mcp serve'.");
|
|
2538
|
+
}
|
|
2539
|
+
const result = await input.mcpServer.serve({
|
|
2540
|
+
config: input.config,
|
|
2541
|
+
executor: input.executor,
|
|
2542
|
+
transport: "local_mcp"
|
|
2543
|
+
});
|
|
2544
|
+
if (result === void 0) return ok("");
|
|
2545
|
+
return ok(`${JSON.stringify(result)}
|
|
2546
|
+
`);
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
// src/scopeCurrentCommand.ts
|
|
2550
|
+
function scopeCurrent(config, json) {
|
|
2551
|
+
const scope = {
|
|
2552
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
2553
|
+
tokenLabel: config.tokenLabel,
|
|
2554
|
+
tokenExpiresAt: config.tokenExpiresAt,
|
|
2555
|
+
principalId: config.principalId,
|
|
2556
|
+
workspaceId: config.workspaceId,
|
|
2557
|
+
brainId: config.brainId,
|
|
2558
|
+
capabilities: config.capabilities
|
|
2559
|
+
};
|
|
2560
|
+
return ok(json ? `${JSON.stringify(scope)}
|
|
2561
|
+
` : renderScope(scope));
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// src/specialCommands.ts
|
|
2565
|
+
import { readFile } from "fs/promises";
|
|
2566
|
+
|
|
2567
|
+
// src/doctor.ts
|
|
2568
|
+
async function doctor(input) {
|
|
2569
|
+
const checks = [
|
|
2570
|
+
{ id: "bin", status: "ok", label: "Bin", detail: "sift command entrypoint is configured." },
|
|
2571
|
+
{ id: "package", status: "ok", label: "Package", detail: "@sift-wiki/cli version unknown, bin sift." },
|
|
2572
|
+
authCheck(input.config, input.now),
|
|
2573
|
+
scopeCheck(input.config)
|
|
2574
|
+
];
|
|
2575
|
+
const apiCheck = await checkApi(input.executor);
|
|
2576
|
+
checks.push(apiCheck);
|
|
2577
|
+
const availableTools3 = await discoverToolNames(input.executor);
|
|
2578
|
+
checks.push(readToolsCheck(availableTools3));
|
|
2579
|
+
checks.push(writeToolsCheck(input.config, availableTools3));
|
|
2580
|
+
checks.push(recordGetCheck(availableTools3));
|
|
2581
|
+
const result = {
|
|
2582
|
+
ok: !checks.some((check) => check.status === "failed"),
|
|
2583
|
+
apiBaseUrl: input.config.apiBaseUrl.trim().length > 0 ? input.config.apiBaseUrl : void 0,
|
|
2584
|
+
scope: scopeResult(input.config),
|
|
2585
|
+
checks
|
|
2586
|
+
};
|
|
2587
|
+
return {
|
|
2588
|
+
exitCode: result.ok ? 0 : 1,
|
|
2589
|
+
stdout: input.json ? `${JSON.stringify(result)}
|
|
2590
|
+
` : renderDoctorResult(result),
|
|
2591
|
+
stderr: ""
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
async function discoverToolNames(executor) {
|
|
2595
|
+
if (executor === void 0) return void 0;
|
|
2596
|
+
if (executor.listAvailableToolNames !== void 0) return executor.listAvailableToolNames();
|
|
2597
|
+
const result = await executor.execute("tools.list", {});
|
|
2598
|
+
return toolNamesFromResult(result);
|
|
2599
|
+
}
|
|
2600
|
+
async function apiReachability(executor) {
|
|
2601
|
+
if (executor === void 0) return { reachable: false, detail: "No API executor is configured." };
|
|
2602
|
+
try {
|
|
2603
|
+
await executor.execute("whoami", {});
|
|
2604
|
+
return { reachable: true, detail: "whoami succeeded." };
|
|
2605
|
+
} catch (error) {
|
|
2606
|
+
return {
|
|
2607
|
+
reachable: false,
|
|
2608
|
+
detail: error instanceof Error ? error.message : "Hosted Sift API request failed."
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
function scopeResult(config) {
|
|
2613
|
+
if (config.workspaceId.trim().length === 0 || config.brainId.trim().length === 0 || config.principalId.trim().length === 0) {
|
|
2614
|
+
return void 0;
|
|
2615
|
+
}
|
|
2616
|
+
return {
|
|
2617
|
+
workspaceId: config.workspaceId,
|
|
2618
|
+
brainId: config.brainId,
|
|
2619
|
+
principalId: config.principalId,
|
|
2620
|
+
capabilities: [...config.capabilities]
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
async function checkApi(executor) {
|
|
2624
|
+
if (executor === void 0) {
|
|
2625
|
+
return {
|
|
2626
|
+
id: "api",
|
|
2627
|
+
status: "warning",
|
|
2628
|
+
label: "API",
|
|
2629
|
+
detail: "not checked because no API executor is configured.",
|
|
2630
|
+
fix: "Run sift login or configure hosted API credentials."
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
const api = await apiReachability(executor);
|
|
2634
|
+
return api.reachable ? { id: "api", status: "ok", label: "API", detail: "whoami succeeded." } : { id: "api", status: "failed", label: "API", detail: api.detail };
|
|
2635
|
+
}
|
|
2636
|
+
function authCheck(config, now) {
|
|
2637
|
+
if (config.apiBaseUrl.trim().length === 0 || config.workspaceId.trim().length === 0 || config.brainId.trim().length === 0 || config.principalId.trim().length === 0) {
|
|
2638
|
+
return {
|
|
2639
|
+
id: "auth",
|
|
2640
|
+
status: "failed",
|
|
2641
|
+
label: "Auth",
|
|
2642
|
+
detail: "no authenticated CLI profile is loaded.",
|
|
2643
|
+
fix: "Run sift login."
|
|
2203
2644
|
};
|
|
2204
2645
|
}
|
|
2205
|
-
if (
|
|
2646
|
+
if (config.tokenExpiresAt !== void 0 && Date.parse(config.tokenExpiresAt) <= now.getTime()) {
|
|
2206
2647
|
return {
|
|
2207
2648
|
id: "auth",
|
|
2208
2649
|
status: "failed",
|
|
@@ -2213,8 +2654,8 @@ function authCheck(config2, now) {
|
|
|
2213
2654
|
}
|
|
2214
2655
|
return { id: "auth", status: "ok", label: "Auth", detail: "authenticated profile loaded." };
|
|
2215
2656
|
}
|
|
2216
|
-
function scopeCheck(
|
|
2217
|
-
if (
|
|
2657
|
+
function scopeCheck(config) {
|
|
2658
|
+
if (config.workspaceId.trim().length === 0 || config.brainId.trim().length === 0) {
|
|
2218
2659
|
return {
|
|
2219
2660
|
id: "scope",
|
|
2220
2661
|
status: "failed",
|
|
@@ -2227,14 +2668,14 @@ function scopeCheck(config2) {
|
|
|
2227
2668
|
id: "scope",
|
|
2228
2669
|
status: "ok",
|
|
2229
2670
|
label: "Scope",
|
|
2230
|
-
detail: `${
|
|
2671
|
+
detail: `${config.workspaceId}/${config.brainId}`
|
|
2231
2672
|
};
|
|
2232
2673
|
}
|
|
2233
2674
|
function readToolsCheck(names) {
|
|
2234
2675
|
return toolSetCheck("read-tools", "Read tools", ["context.assemble", "search.query"], names);
|
|
2235
2676
|
}
|
|
2236
|
-
function writeToolsCheck(
|
|
2237
|
-
const hasWrite =
|
|
2677
|
+
function writeToolsCheck(config, names) {
|
|
2678
|
+
const hasWrite = config.capabilities.includes("record:write") || config.capabilities.includes("source:write");
|
|
2238
2679
|
if (!hasWrite) {
|
|
2239
2680
|
return {
|
|
2240
2681
|
id: "write-tools",
|
|
@@ -2273,9 +2714,9 @@ function toolSetCheck(id, label, required, names) {
|
|
|
2273
2714
|
const missing = required.filter((name) => !names.includes(name));
|
|
2274
2715
|
return missing.length === 0 ? { id, status: "ok", label, detail: "required tools are available." } : { id, status: "failed", label, detail: `missing ${missing.join(", ")}.` };
|
|
2275
2716
|
}
|
|
2276
|
-
function toolNamesFromResult(
|
|
2277
|
-
if (!Array.isArray(
|
|
2278
|
-
return
|
|
2717
|
+
function toolNamesFromResult(result) {
|
|
2718
|
+
if (!Array.isArray(result)) return [];
|
|
2719
|
+
return result.flatMap((item) => {
|
|
2279
2720
|
if (typeof item === "object" && item !== null && "name" in item) {
|
|
2280
2721
|
const name = item.name;
|
|
2281
2722
|
return typeof name === "string" ? [name] : [];
|
|
@@ -2287,6 +2728,7 @@ function toolNamesFromResult(result2) {
|
|
|
2287
2728
|
// src/simpleCommands.ts
|
|
2288
2729
|
var knownTopLevelCommands = /* @__PURE__ */ new Set([
|
|
2289
2730
|
"add",
|
|
2731
|
+
"agent",
|
|
2290
2732
|
"ask",
|
|
2291
2733
|
"audit",
|
|
2292
2734
|
"auth",
|
|
@@ -2307,6 +2749,7 @@ var knownTopLevelCommands = /* @__PURE__ */ new Set([
|
|
|
2307
2749
|
"mcp",
|
|
2308
2750
|
"record",
|
|
2309
2751
|
"remember",
|
|
2752
|
+
"roam",
|
|
2310
2753
|
"scope",
|
|
2311
2754
|
"search",
|
|
2312
2755
|
"show",
|
|
@@ -2399,11 +2842,11 @@ async function ask(executor, rest, json) {
|
|
|
2399
2842
|
}
|
|
2400
2843
|
const parsed = parseOptions(rest);
|
|
2401
2844
|
const query = argsWithoutOptions(rest).join(" ").trim();
|
|
2402
|
-
const
|
|
2845
|
+
const result = await executor.execute("context.assemble", {
|
|
2403
2846
|
query,
|
|
2404
2847
|
maxChars: parseIntegerOption(parsed, "max-chars", 8e3)
|
|
2405
2848
|
});
|
|
2406
|
-
return ok(json ? aliasJson("ask", "context.assemble",
|
|
2849
|
+
return ok(json ? aliasJson("ask", "context.assemble", result) : renderSiftFound(result));
|
|
2407
2850
|
}
|
|
2408
2851
|
async function simpleSearch(executor, rest, json) {
|
|
2409
2852
|
if (executor === void 0) {
|
|
@@ -2411,11 +2854,11 @@ async function simpleSearch(executor, rest, json) {
|
|
|
2411
2854
|
}
|
|
2412
2855
|
const parsed = parseOptions(rest);
|
|
2413
2856
|
const query = argsWithoutOptions(rest).join(" ").trim();
|
|
2414
|
-
const
|
|
2857
|
+
const result = await executor.execute("search.query", {
|
|
2415
2858
|
query,
|
|
2416
2859
|
limit: parseIntegerOption(parsed, "limit", 10)
|
|
2417
2860
|
});
|
|
2418
|
-
return ok(json ? aliasJson("search", "search.query",
|
|
2861
|
+
return ok(json ? aliasJson("search", "search.query", result) : renderSearchResult(result));
|
|
2419
2862
|
}
|
|
2420
2863
|
async function remember(executor, rest, json, readStdin2, now) {
|
|
2421
2864
|
if (executor === void 0) {
|
|
@@ -2434,8 +2877,8 @@ async function remember(executor, rest, json, readStdin2, now) {
|
|
|
2434
2877
|
visibility: [optionalOption(parsed, "visibility") ?? DEFAULT_CLI_VISIBILITY],
|
|
2435
2878
|
markdown
|
|
2436
2879
|
};
|
|
2437
|
-
const
|
|
2438
|
-
return ok(json ? aliasJson("remember", "capture.text",
|
|
2880
|
+
const result = await executor.execute("capture.text", input);
|
|
2881
|
+
return ok(json ? aliasJson("remember", "capture.text", result) : renderWriteReceipt("Remember", result));
|
|
2439
2882
|
}
|
|
2440
2883
|
async function addFile(executor, fileReader, rest, json) {
|
|
2441
2884
|
if (executor === void 0) {
|
|
@@ -2457,8 +2900,8 @@ async function addFile(executor, fileReader, rest, json) {
|
|
|
2457
2900
|
bytes,
|
|
2458
2901
|
visibility: [optionalOption(parsed, "visibility") ?? DEFAULT_CLI_VISIBILITY]
|
|
2459
2902
|
};
|
|
2460
|
-
const
|
|
2461
|
-
return ok(json ? aliasJson("add", "capture.file",
|
|
2903
|
+
const result = await executor.execute("capture.file", input);
|
|
2904
|
+
return ok(json ? aliasJson("add", "capture.file", result) : renderWriteReceipt("Add", result));
|
|
2462
2905
|
}
|
|
2463
2906
|
async function edit(executor, rest, json) {
|
|
2464
2907
|
if (executor === void 0) {
|
|
@@ -2483,8 +2926,8 @@ async function edit(executor, rest, json) {
|
|
|
2483
2926
|
if (expectedMarkdown !== void 0) {
|
|
2484
2927
|
input.expectedMarkdown = expectedMarkdown;
|
|
2485
2928
|
}
|
|
2486
|
-
const
|
|
2487
|
-
return ok(json ? aliasJson("edit", "record.patch_section",
|
|
2929
|
+
const result = await executor.execute("record.patch_section", input);
|
|
2930
|
+
return ok(json ? aliasJson("edit", "record.patch_section", result) : renderWriteReceipt("Edit", result));
|
|
2488
2931
|
}
|
|
2489
2932
|
async function decide(executor, rest, json) {
|
|
2490
2933
|
if (executor === void 0) {
|
|
@@ -2497,8 +2940,8 @@ async function decide(executor, rest, json) {
|
|
|
2497
2940
|
visibility: [optionalOption(parsed, "visibility") ?? DEFAULT_CLI_VISIBILITY]
|
|
2498
2941
|
};
|
|
2499
2942
|
addOptionalWorkAliasMetadata(input, parsed);
|
|
2500
|
-
const
|
|
2501
|
-
return ok(json ? aliasJson("decide", "decision.create",
|
|
2943
|
+
const result = await executor.execute("decision.create", input);
|
|
2944
|
+
return ok(json ? aliasJson("decide", "decision.create", result) : renderWriteReceipt("Decision", result));
|
|
2502
2945
|
}
|
|
2503
2946
|
async function todo(executor, rest, json) {
|
|
2504
2947
|
if (executor === void 0) {
|
|
@@ -2515,8 +2958,8 @@ async function todo(executor, rest, json) {
|
|
|
2515
2958
|
const dueDate = optionalOption(parsed, "due-date");
|
|
2516
2959
|
if (dueDate !== void 0) input.dueDate = dueDate;
|
|
2517
2960
|
addOptionalWorkAliasMetadata(input, parsed);
|
|
2518
|
-
const
|
|
2519
|
-
return ok(json ? aliasJson("todo", "task.create",
|
|
2961
|
+
const result = await executor.execute("task.create", input);
|
|
2962
|
+
return ok(json ? aliasJson("todo", "task.create", result) : renderWriteReceipt("Task", result));
|
|
2520
2963
|
}
|
|
2521
2964
|
async function show(executor, rest, json) {
|
|
2522
2965
|
if (executor === void 0) {
|
|
@@ -2539,21 +2982,21 @@ async function show(executor, rest, json) {
|
|
|
2539
2982
|
if (sectionAnchor !== void 0) {
|
|
2540
2983
|
input.sectionAnchor = sectionAnchor;
|
|
2541
2984
|
}
|
|
2542
|
-
const
|
|
2543
|
-
return ok(json ? aliasJson("show", "record.get",
|
|
2985
|
+
const result = await executor.execute("record.get", input);
|
|
2986
|
+
return ok(json ? aliasJson("show", "record.get", result) : renderRecordResult(result));
|
|
2544
2987
|
}
|
|
2545
|
-
async function status(
|
|
2988
|
+
async function status(config, executor, json) {
|
|
2546
2989
|
const scope = {
|
|
2547
|
-
apiBaseUrl:
|
|
2548
|
-
principalId:
|
|
2549
|
-
workspaceId:
|
|
2550
|
-
brainId:
|
|
2551
|
-
capabilities: [...
|
|
2990
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
2991
|
+
principalId: config.principalId,
|
|
2992
|
+
workspaceId: config.workspaceId,
|
|
2993
|
+
brainId: config.brainId,
|
|
2994
|
+
capabilities: [...config.capabilities]
|
|
2552
2995
|
};
|
|
2553
2996
|
const api = await apiReachability(executor);
|
|
2554
|
-
const
|
|
2997
|
+
const result = { scope, api };
|
|
2555
2998
|
if (json) {
|
|
2556
|
-
return ok(`${JSON.stringify({ command: "status", result
|
|
2999
|
+
return ok(`${JSON.stringify({ command: "status", result })}
|
|
2557
3000
|
`);
|
|
2558
3001
|
}
|
|
2559
3002
|
return ok(`${renderScope({ ...scope, tokenLabel: "configured" })}API reachable: ${api.reachable}
|
|
@@ -2596,6 +3039,201 @@ function argsWithoutOptions(args) {
|
|
|
2596
3039
|
return positionals;
|
|
2597
3040
|
}
|
|
2598
3041
|
|
|
3042
|
+
// src/skill/skillCommands.ts
|
|
3043
|
+
import { access, mkdir as nodeMkdir, writeFile as nodeWriteFile } from "fs/promises";
|
|
3044
|
+
import { homedir } from "os";
|
|
3045
|
+
import { isAbsolute, join, resolve } from "path";
|
|
3046
|
+
|
|
3047
|
+
// src/skill/skillContent.ts
|
|
3048
|
+
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';
|
|
3049
|
+
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';
|
|
3050
|
+
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';
|
|
3051
|
+
|
|
3052
|
+
// src/skill/skillCommands.ts
|
|
3053
|
+
var BUNDLED_SKILLS = [
|
|
3054
|
+
{
|
|
3055
|
+
name: "sift-setup",
|
|
3056
|
+
summary: "Set up the Sift CLI and connect this agent to the brain (first run).",
|
|
3057
|
+
markdown: SIFT_SETUP_SKILL_MARKDOWN
|
|
3058
|
+
},
|
|
3059
|
+
{
|
|
3060
|
+
name: "sift-cli",
|
|
3061
|
+
summary: "Set up and use the Sift CLI as a thin client to the hosted brain.",
|
|
3062
|
+
markdown: SIFT_CLI_SKILL_MARKDOWN
|
|
3063
|
+
},
|
|
3064
|
+
{
|
|
3065
|
+
name: "sift-agent",
|
|
3066
|
+
summary: "Read, capture, and patch the Sift brain over CLI or MCP.",
|
|
3067
|
+
markdown: SIFT_AGENT_SKILL_MARKDOWN
|
|
3068
|
+
}
|
|
3069
|
+
];
|
|
3070
|
+
var DEFAULT_SKILL = "sift-setup";
|
|
3071
|
+
function defaultSkillIo(input) {
|
|
3072
|
+
return {
|
|
3073
|
+
writeFile: (path, data) => nodeWriteFile(path, data, "utf8"),
|
|
3074
|
+
mkdir: async (path) => {
|
|
3075
|
+
await nodeMkdir(path, { recursive: true });
|
|
3076
|
+
},
|
|
3077
|
+
pathExists: async (path) => {
|
|
3078
|
+
try {
|
|
3079
|
+
await access(path);
|
|
3080
|
+
return true;
|
|
3081
|
+
} catch {
|
|
3082
|
+
return false;
|
|
3083
|
+
}
|
|
3084
|
+
},
|
|
3085
|
+
homeDir: input?.homeDir ?? homedir(),
|
|
3086
|
+
cwd: input?.cwd ?? process.cwd()
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
async function runSkillCommand(input) {
|
|
3090
|
+
const [subcommand, ...rest] = input.rest;
|
|
3091
|
+
if (subcommand === void 0 || subcommand === "list") {
|
|
3092
|
+
return listSkills(input.json);
|
|
3093
|
+
}
|
|
3094
|
+
if (subcommand === "print" || subcommand === "show") {
|
|
3095
|
+
return printSkill(rest, input.json);
|
|
3096
|
+
}
|
|
3097
|
+
if (subcommand === "install") {
|
|
3098
|
+
return installSkill(rest, input.json, input.io);
|
|
3099
|
+
}
|
|
3100
|
+
return errorResultWithCode(
|
|
3101
|
+
"tool_unavailable",
|
|
3102
|
+
`Unknown skill subcommand '${subcommand}'. Use 'list', 'print [name]', or 'install [name]'.`,
|
|
3103
|
+
input.json
|
|
3104
|
+
);
|
|
3105
|
+
}
|
|
3106
|
+
function listSkills(json) {
|
|
3107
|
+
if (json) {
|
|
3108
|
+
return ok(
|
|
3109
|
+
`${JSON.stringify({
|
|
3110
|
+
skills: BUNDLED_SKILLS.map(({ name, summary }) => ({ name, summary }))
|
|
3111
|
+
})}
|
|
3112
|
+
`
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
const lines = BUNDLED_SKILLS.map((skill) => `${skill.name}: ${skill.summary}`);
|
|
3116
|
+
return ok(`${lines.join("\n")}
|
|
3117
|
+
`);
|
|
3118
|
+
}
|
|
3119
|
+
function printSkill(rest, json) {
|
|
3120
|
+
const requested = positionalArgs(rest)[0];
|
|
3121
|
+
const skill = findSkill(requested);
|
|
3122
|
+
if (skill === void 0) {
|
|
3123
|
+
return unknownSkill(requested, json);
|
|
3124
|
+
}
|
|
3125
|
+
if (json) {
|
|
3126
|
+
return ok(`${JSON.stringify({ skill: skill.name, markdown: skill.markdown })}
|
|
3127
|
+
`);
|
|
3128
|
+
}
|
|
3129
|
+
return ok(withTrailingNewline(skill.markdown));
|
|
3130
|
+
}
|
|
3131
|
+
async function installSkill(rest, json, io) {
|
|
3132
|
+
const requested = positionalArgs(rest)[0];
|
|
3133
|
+
const skill = findSkill(requested);
|
|
3134
|
+
if (skill === void 0) {
|
|
3135
|
+
return unknownSkill(requested, json);
|
|
3136
|
+
}
|
|
3137
|
+
const parsed = parseOptions(rest);
|
|
3138
|
+
const baseDir = resolveBaseDir({ rest, parsed, io });
|
|
3139
|
+
const targetDir = join(baseDir, skill.name);
|
|
3140
|
+
const targetPath = join(targetDir, "SKILL.md");
|
|
3141
|
+
const existed = await io.pathExists(targetPath);
|
|
3142
|
+
await io.mkdir(targetDir);
|
|
3143
|
+
const markdown = withTrailingNewline(skill.markdown);
|
|
3144
|
+
await io.writeFile(targetPath, markdown);
|
|
3145
|
+
const status2 = existed ? "updated" : "created";
|
|
3146
|
+
if (json) {
|
|
3147
|
+
return ok(
|
|
3148
|
+
`${JSON.stringify({
|
|
3149
|
+
installed: true,
|
|
3150
|
+
skill: skill.name,
|
|
3151
|
+
path: targetPath,
|
|
3152
|
+
status: status2,
|
|
3153
|
+
bytes: Buffer.byteLength(markdown, "utf8")
|
|
3154
|
+
})}
|
|
3155
|
+
`
|
|
3156
|
+
);
|
|
3157
|
+
}
|
|
3158
|
+
return ok(renderInstall(skill.name, targetPath, status2));
|
|
3159
|
+
}
|
|
3160
|
+
function resolveBaseDir(input) {
|
|
3161
|
+
const explicit = optionalOption(input.parsed, "dir");
|
|
3162
|
+
if (explicit !== void 0) {
|
|
3163
|
+
return isAbsolute(explicit) ? explicit : resolve(input.io.cwd, explicit);
|
|
3164
|
+
}
|
|
3165
|
+
if (input.rest.includes("--global")) {
|
|
3166
|
+
return join(input.io.homeDir, ".claude", "skills");
|
|
3167
|
+
}
|
|
3168
|
+
return resolve(input.io.cwd, ".claude", "skills");
|
|
3169
|
+
}
|
|
3170
|
+
function findSkill(name) {
|
|
3171
|
+
const target = name ?? DEFAULT_SKILL;
|
|
3172
|
+
return BUNDLED_SKILLS.find((skill) => skill.name === target);
|
|
3173
|
+
}
|
|
3174
|
+
function unknownSkill(name, json) {
|
|
3175
|
+
const available = BUNDLED_SKILLS.map((skill) => skill.name).join(", ");
|
|
3176
|
+
return errorResultWithCode(
|
|
3177
|
+
"validation_failure",
|
|
3178
|
+
`Unknown skill '${name}'. Available skills: ${available}.`,
|
|
3179
|
+
json
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
function renderInstall(name, path, status2) {
|
|
3183
|
+
const verb = status2 === "created" ? "Installed" : "Updated";
|
|
3184
|
+
return [
|
|
3185
|
+
`${verb} the Sift skill: ${name}`,
|
|
3186
|
+
`Path: ${path}`,
|
|
3187
|
+
"",
|
|
3188
|
+
"Next, open that SKILL.md and follow it to finish setup:",
|
|
3189
|
+
" 1. Put the CLI on PATH: npm install -g @sift-wiki/cli",
|
|
3190
|
+
" 2. Authenticate: sift login",
|
|
3191
|
+
' 3. Identify yourself: set SIFT_AGENT to your product name (e.g. "Claude Code"), then sift agent register',
|
|
3192
|
+
" 4. Confirm: sift doctor",
|
|
3193
|
+
"",
|
|
3194
|
+
"Then use Sift as your source of truth: search and assemble context before",
|
|
3195
|
+
"answering, and capture decisions and notes back into the brain.",
|
|
3196
|
+
""
|
|
3197
|
+
].join("\n");
|
|
3198
|
+
}
|
|
3199
|
+
function withTrailingNewline(markdown) {
|
|
3200
|
+
return markdown.endsWith("\n") ? markdown : `${markdown}
|
|
3201
|
+
`;
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// src/specialCommands.ts
|
|
3205
|
+
async function runSpecialCommand(input, args, json, group, command) {
|
|
3206
|
+
if (group === "doctor" && command === void 0) {
|
|
3207
|
+
return doctor({
|
|
3208
|
+
config: input.config,
|
|
3209
|
+
executor: input.executor,
|
|
3210
|
+
json,
|
|
3211
|
+
now: input.now ?? /* @__PURE__ */ new Date()
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
if (group === "skill") {
|
|
3215
|
+
const io = input.skillIo ?? defaultSkillIo({ cwd: input.cwd, homeDir: input.homeDir });
|
|
3216
|
+
return runSkillCommand({ rest: args.slice(1), json, io });
|
|
3217
|
+
}
|
|
3218
|
+
const simpleCommand = resolveSimpleCommand({
|
|
3219
|
+
args,
|
|
3220
|
+
json,
|
|
3221
|
+
config: input.config,
|
|
3222
|
+
executor: input.executor,
|
|
3223
|
+
readFile: input.readFile ?? readFile,
|
|
3224
|
+
readStdin: input.readStdin,
|
|
3225
|
+
now: input.now ?? /* @__PURE__ */ new Date()
|
|
3226
|
+
});
|
|
3227
|
+
if (simpleCommand === void 0) return void 0;
|
|
3228
|
+
try {
|
|
3229
|
+
validateAuthenticatedScope(input.config, input.now ?? /* @__PURE__ */ new Date());
|
|
3230
|
+
validateCommandCapability({ commandKey: simpleCommand.commandKey, config: input.config });
|
|
3231
|
+
return await simpleCommand.run();
|
|
3232
|
+
} catch (error) {
|
|
3233
|
+
return errorResult(error, json);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
|
|
2599
3237
|
// src/toolDiscovery.ts
|
|
2600
3238
|
function toolsList(input) {
|
|
2601
3239
|
if (input.executor !== void 0) {
|
|
@@ -2629,9 +3267,9 @@ function toolsHelp(input) {
|
|
|
2629
3267
|
return executeSimple(input.executor, "tools.help", { name }, input.json);
|
|
2630
3268
|
}
|
|
2631
3269
|
async function executeSimple(executor, name, toolInput, json) {
|
|
2632
|
-
const
|
|
2633
|
-
return ok(json ? `${JSON.stringify(
|
|
2634
|
-
` : `${JSON.stringify(
|
|
3270
|
+
const result = await executor.execute(name, toolInput);
|
|
3271
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3272
|
+
` : `${JSON.stringify(result)}
|
|
2635
3273
|
`);
|
|
2636
3274
|
}
|
|
2637
3275
|
|
|
@@ -2652,7 +3290,7 @@ function createHostedApiExecutor(input) {
|
|
|
2652
3290
|
body: JSON.stringify({ input: toolInput }, serializeJsonValue)
|
|
2653
3291
|
});
|
|
2654
3292
|
const body = await response.text();
|
|
2655
|
-
const parsed = body.length > 0 ?
|
|
3293
|
+
const parsed = body.length > 0 ? parseJson2(body) : {};
|
|
2656
3294
|
if (!response.ok) {
|
|
2657
3295
|
throw new Error(errorMessage(parsed, response.status));
|
|
2658
3296
|
}
|
|
@@ -2669,7 +3307,7 @@ function serializeJsonValue(_key, value) {
|
|
|
2669
3307
|
function toolUrl(apiBaseUrl, name) {
|
|
2670
3308
|
return `${apiBaseUrl.replace(/\/+$/u, "")}/agent-tools/${encodeURIComponent(name)}`;
|
|
2671
3309
|
}
|
|
2672
|
-
function
|
|
3310
|
+
function parseJson2(body) {
|
|
2673
3311
|
try {
|
|
2674
3312
|
return JSON.parse(body);
|
|
2675
3313
|
} catch {
|
|
@@ -2730,8 +3368,8 @@ async function runSiftCli(rawInput) {
|
|
|
2730
3368
|
json
|
|
2731
3369
|
}),
|
|
2732
3370
|
"capture:text": () => captureText(input.executor, rest, json),
|
|
2733
|
-
"capture:file": () => captureFile(input.executor, input.readFile ??
|
|
2734
|
-
"capture:batch": () => captureBatch(input.executor, input.readFile ??
|
|
3371
|
+
"capture:file": () => captureFile(input.executor, input.readFile ?? readFile2, rest, json),
|
|
3372
|
+
"capture:batch": () => captureBatch(input.executor, input.readFile ?? readFile2, rest, json),
|
|
2735
3373
|
"source:list": () => sourceList(input.executor, json),
|
|
2736
3374
|
"source:create": () => sourceCreate(input.executor, rest, json),
|
|
2737
3375
|
"source:get": () => sourceRead(input.executor, "get", rest, json),
|
|
@@ -2742,16 +3380,49 @@ async function runSiftCli(rawInput) {
|
|
|
2742
3380
|
"record:create-markdown": () => createMarkdownRecord(input.executor, rest, json),
|
|
2743
3381
|
"record:patch-section": () => patchRecordSection(input.executor, rest, json),
|
|
2744
3382
|
"record:versions": () => recordRead(input.executor, "record.versions", rest, json),
|
|
2745
|
-
"evidence:list": () => idTool({
|
|
2746
|
-
|
|
2747
|
-
|
|
3383
|
+
"evidence:list": () => idTool({
|
|
3384
|
+
executor: input.executor,
|
|
3385
|
+
toolName: "evidence.list",
|
|
3386
|
+
inputKey: "recordId",
|
|
3387
|
+
idLabel: "record ID",
|
|
3388
|
+
rest,
|
|
3389
|
+
json
|
|
3390
|
+
}),
|
|
3391
|
+
"evidence:get": () => idTool({
|
|
3392
|
+
executor: input.executor,
|
|
3393
|
+
toolName: "evidence.get",
|
|
3394
|
+
inputKey: "evidenceId",
|
|
3395
|
+
idLabel: "evidence ID",
|
|
3396
|
+
rest,
|
|
3397
|
+
json
|
|
3398
|
+
}),
|
|
3399
|
+
"graph:neighbors": () => idTool({
|
|
3400
|
+
executor: input.executor,
|
|
3401
|
+
toolName: "graph.neighbors",
|
|
3402
|
+
inputKey: "recordId",
|
|
3403
|
+
idLabel: "record ID",
|
|
3404
|
+
rest,
|
|
3405
|
+
json
|
|
3406
|
+
}),
|
|
2748
3407
|
"event:list": () => executeSimple2(input.executor, "event.list", {}, json),
|
|
2749
3408
|
"audit:events": () => auditEvents(input.executor, rest, json),
|
|
2750
3409
|
"decision:create": () => createDecision(input.executor, rest, json),
|
|
2751
3410
|
"task:create": () => createTask(input.executor, rest, json),
|
|
2752
3411
|
"agent:register": () => agentRegister(input.executor, input.agentName, rest, json),
|
|
2753
3412
|
"agent:status": () => executeSimple2(input.executor, "agent.status", {}, json),
|
|
2754
|
-
"mcp:serve": () => mcpServe(
|
|
3413
|
+
"mcp:serve": () => mcpServe({
|
|
3414
|
+
mcpServer: input.mcpServer,
|
|
3415
|
+
config: input.config,
|
|
3416
|
+
executor: rawInput.executor
|
|
3417
|
+
}),
|
|
3418
|
+
"roam:import": () => runRoamImportCommand({
|
|
3419
|
+
rest,
|
|
3420
|
+
json,
|
|
3421
|
+
config: input.config,
|
|
3422
|
+
reader: input.roamReader,
|
|
3423
|
+
importer: input.roamImporter,
|
|
3424
|
+
now: input.now ?? /* @__PURE__ */ new Date()
|
|
3425
|
+
}),
|
|
2755
3426
|
"login:": () => authCommand(input.authCommands, "login", { rest: commandRest, json }),
|
|
2756
3427
|
"auth:status": () => authCommand(input.authCommands, "status", { json }),
|
|
2757
3428
|
"logout:": () => authCommand(input.authCommands, "logout", { json })
|
|
@@ -2765,7 +3436,7 @@ async function runSiftCli(rawInput) {
|
|
|
2765
3436
|
);
|
|
2766
3437
|
}
|
|
2767
3438
|
try {
|
|
2768
|
-
if (isAuthCommand(commandKey)) {
|
|
3439
|
+
if (isAuthCommand(commandKey) || commandKey === "mcp:serve") {
|
|
2769
3440
|
return await handler();
|
|
2770
3441
|
}
|
|
2771
3442
|
validateAuthenticatedScope(input.config, input.now ?? /* @__PURE__ */ new Date());
|
|
@@ -2775,61 +3446,14 @@ async function runSiftCli(rawInput) {
|
|
|
2775
3446
|
return errorResult(error, json);
|
|
2776
3447
|
}
|
|
2777
3448
|
}
|
|
2778
|
-
async function runSpecialCommand(input, args, json, group, command) {
|
|
2779
|
-
if (group === "doctor" && command === void 0) {
|
|
2780
|
-
return doctor({ config: input.config, executor: input.executor, json, now: input.now ?? /* @__PURE__ */ new Date() });
|
|
2781
|
-
}
|
|
2782
|
-
const simpleCommand = resolveSimpleCommand({
|
|
2783
|
-
args,
|
|
2784
|
-
json,
|
|
2785
|
-
config: input.config,
|
|
2786
|
-
executor: input.executor,
|
|
2787
|
-
readFile: input.readFile ?? readFile,
|
|
2788
|
-
readStdin: input.readStdin,
|
|
2789
|
-
now: input.now ?? /* @__PURE__ */ new Date()
|
|
2790
|
-
});
|
|
2791
|
-
if (simpleCommand === void 0) return void 0;
|
|
2792
|
-
try {
|
|
2793
|
-
validateAuthenticatedScope(input.config, input.now ?? /* @__PURE__ */ new Date());
|
|
2794
|
-
validateCommandCapability({ commandKey: simpleCommand.commandKey, config: input.config });
|
|
2795
|
-
return await simpleCommand.run();
|
|
2796
|
-
} catch (error) {
|
|
2797
|
-
return errorResult(error, json);
|
|
2798
|
-
}
|
|
2799
|
-
}
|
|
2800
|
-
async function mcpServe(mcpServer, config2, executor, json) {
|
|
2801
|
-
if (mcpServer === void 0) {
|
|
2802
|
-
return fail("No local MCP server is configured for mcp.serve.");
|
|
2803
|
-
}
|
|
2804
|
-
if (executor === void 0) {
|
|
2805
|
-
return fail("No Sift API executor is configured for mcp.serve.");
|
|
2806
|
-
}
|
|
2807
|
-
const result2 = await mcpServer.serve({ config: config2, executor, transport: "local_mcp" });
|
|
2808
|
-
if (result2 === void 0) return ok("");
|
|
2809
|
-
return ok(`${JSON.stringify(result2)}
|
|
2810
|
-
`);
|
|
2811
|
-
}
|
|
2812
|
-
function scopeCurrent(config2, json) {
|
|
2813
|
-
const scope = {
|
|
2814
|
-
apiBaseUrl: config2.apiBaseUrl,
|
|
2815
|
-
tokenLabel: config2.tokenLabel,
|
|
2816
|
-
tokenExpiresAt: config2.tokenExpiresAt,
|
|
2817
|
-
principalId: config2.principalId,
|
|
2818
|
-
workspaceId: config2.workspaceId,
|
|
2819
|
-
brainId: config2.brainId,
|
|
2820
|
-
capabilities: config2.capabilities
|
|
2821
|
-
};
|
|
2822
|
-
return ok(json ? `${JSON.stringify(scope)}
|
|
2823
|
-
` : renderScope(scope));
|
|
2824
|
-
}
|
|
2825
3449
|
async function searchQuery(executor, rest, json) {
|
|
2826
3450
|
if (executor === void 0) {
|
|
2827
3451
|
return fail("No Sift API executor is configured for search.query.");
|
|
2828
3452
|
}
|
|
2829
3453
|
const query = rest.join(" ").trim();
|
|
2830
|
-
const
|
|
2831
|
-
return ok(json ? `${JSON.stringify(
|
|
2832
|
-
` : renderSearchResult(
|
|
3454
|
+
const result = await executor.execute("search.query", { query, limit: 10 });
|
|
3455
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3456
|
+
` : renderSearchResult(result));
|
|
2833
3457
|
}
|
|
2834
3458
|
async function contextAssemble(executor, rest, json) {
|
|
2835
3459
|
if (executor === void 0) {
|
|
@@ -2837,12 +3461,12 @@ async function contextAssemble(executor, rest, json) {
|
|
|
2837
3461
|
}
|
|
2838
3462
|
const parsed = parseOptions(rest);
|
|
2839
3463
|
const query = positionalArgs(rest).join(" ").trim();
|
|
2840
|
-
const
|
|
3464
|
+
const result = await executor.execute("context.assemble", {
|
|
2841
3465
|
query,
|
|
2842
3466
|
maxChars: parseIntegerOption(parsed, "max-chars", 4e3)
|
|
2843
3467
|
});
|
|
2844
|
-
return ok(json ? `${JSON.stringify(
|
|
2845
|
-
` : renderSearchResult(
|
|
3468
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3469
|
+
` : renderSearchResult(result));
|
|
2846
3470
|
}
|
|
2847
3471
|
async function contextProfile(executor, rest, json) {
|
|
2848
3472
|
if (executor === void 0) {
|
|
@@ -2856,17 +3480,17 @@ async function contextProfile(executor, rest, json) {
|
|
|
2856
3480
|
if (query.length > 0) {
|
|
2857
3481
|
input.query = query;
|
|
2858
3482
|
}
|
|
2859
|
-
const
|
|
2860
|
-
return ok(json ? `${JSON.stringify(
|
|
2861
|
-
` : renderProfileResult(
|
|
3483
|
+
const result = await executor.execute("context.profile", input);
|
|
3484
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3485
|
+
` : renderProfileResult(result));
|
|
2862
3486
|
}
|
|
2863
3487
|
async function executeSimple2(executor, name, toolInput, json) {
|
|
2864
3488
|
if (executor === void 0) {
|
|
2865
3489
|
return fail(`No Sift API executor is configured for ${name}.`);
|
|
2866
3490
|
}
|
|
2867
|
-
const
|
|
2868
|
-
return ok(json ? `${JSON.stringify(
|
|
2869
|
-
` : `${JSON.stringify(
|
|
3491
|
+
const result = await executor.execute(name, toolInput);
|
|
3492
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3493
|
+
` : `${JSON.stringify(result)}
|
|
2870
3494
|
`);
|
|
2871
3495
|
}
|
|
2872
3496
|
async function captureText(executor, rest, json) {
|
|
@@ -2874,15 +3498,15 @@ async function captureText(executor, rest, json) {
|
|
|
2874
3498
|
return fail("No Sift API executor is configured for capture.text.");
|
|
2875
3499
|
}
|
|
2876
3500
|
const parsed = parseOptions(rest);
|
|
2877
|
-
const
|
|
3501
|
+
const result = await executor.execute("capture.text", {
|
|
2878
3502
|
sourceName: requireOption(parsed, "source"),
|
|
2879
3503
|
externalId: requireOption(parsed, "external-id"),
|
|
2880
3504
|
title: requireOption(parsed, "title"),
|
|
2881
3505
|
visibility: [requireOption(parsed, "visibility")],
|
|
2882
3506
|
markdown: requireOption(parsed, "markdown")
|
|
2883
3507
|
});
|
|
2884
|
-
return ok(json ? `${JSON.stringify(
|
|
2885
|
-
` : `${JSON.stringify(
|
|
3508
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3509
|
+
` : `${JSON.stringify(result)}
|
|
2886
3510
|
`);
|
|
2887
3511
|
}
|
|
2888
3512
|
async function captureFile(executor, fileReader, rest, json) {
|
|
@@ -2895,7 +3519,7 @@ async function captureFile(executor, fileReader, rest, json) {
|
|
|
2895
3519
|
}
|
|
2896
3520
|
const parsed = parseOptions(optionArgs);
|
|
2897
3521
|
const bytes = await fileReader(path);
|
|
2898
|
-
const
|
|
3522
|
+
const result = await executor.execute("capture.file", {
|
|
2899
3523
|
sourceName: requireOption(parsed, "source"),
|
|
2900
3524
|
externalId: requireOption(parsed, "external-id"),
|
|
2901
3525
|
title: requireOption(parsed, "title"),
|
|
@@ -2904,8 +3528,8 @@ async function captureFile(executor, fileReader, rest, json) {
|
|
|
2904
3528
|
bytes,
|
|
2905
3529
|
visibility: [requireOption(parsed, "visibility")]
|
|
2906
3530
|
});
|
|
2907
|
-
return ok(json ? `${JSON.stringify(
|
|
2908
|
-
` : `${JSON.stringify(
|
|
3531
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3532
|
+
` : `${JSON.stringify(result)}
|
|
2909
3533
|
`);
|
|
2910
3534
|
}
|
|
2911
3535
|
async function captureBatch(executor, fileReader, rest, json) {
|
|
@@ -2917,9 +3541,9 @@ async function captureBatch(executor, fileReader, rest, json) {
|
|
|
2917
3541
|
return fail("Missing required manifest path for capture.batch.");
|
|
2918
3542
|
}
|
|
2919
3543
|
const manifest = parseBatchManifest(await fileReader(manifestPath));
|
|
2920
|
-
const
|
|
2921
|
-
return ok(json ? `${JSON.stringify(
|
|
2922
|
-
` : `${JSON.stringify(
|
|
3544
|
+
const result = await executor.execute("capture.batch", { items: manifest });
|
|
3545
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3546
|
+
` : `${JSON.stringify(result)}
|
|
2923
3547
|
`);
|
|
2924
3548
|
}
|
|
2925
3549
|
function parseBatchManifest(bytes) {
|
|
@@ -2944,18 +3568,18 @@ async function createDecision(executor, rest, json) {
|
|
|
2944
3568
|
input.rationale = rationale;
|
|
2945
3569
|
}
|
|
2946
3570
|
addOptionalWorkMetadata(input, parsed);
|
|
2947
|
-
const
|
|
2948
|
-
return ok(json ? `${JSON.stringify(
|
|
2949
|
-
` : `${JSON.stringify(
|
|
3571
|
+
const result = await executor.execute("decision.create", input);
|
|
3572
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3573
|
+
` : `${JSON.stringify(result)}
|
|
2950
3574
|
`);
|
|
2951
3575
|
}
|
|
2952
3576
|
async function sourceList(executor, json) {
|
|
2953
3577
|
if (executor === void 0) {
|
|
2954
3578
|
return fail("No Sift API executor is configured for source.list.");
|
|
2955
3579
|
}
|
|
2956
|
-
const
|
|
2957
|
-
return ok(json ? `${JSON.stringify(
|
|
2958
|
-
` : `${JSON.stringify(
|
|
3580
|
+
const result = await executor.execute("source.list", {});
|
|
3581
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3582
|
+
` : `${JSON.stringify(result)}
|
|
2959
3583
|
`);
|
|
2960
3584
|
}
|
|
2961
3585
|
async function sourceCreate(executor, rest, json) {
|
|
@@ -2963,12 +3587,12 @@ async function sourceCreate(executor, rest, json) {
|
|
|
2963
3587
|
return fail("No Sift API executor is configured for source.create.");
|
|
2964
3588
|
}
|
|
2965
3589
|
const parsed = parseOptions(rest);
|
|
2966
|
-
const
|
|
3590
|
+
const result = await executor.execute("source.create", {
|
|
2967
3591
|
name: requireOption(parsed, "name"),
|
|
2968
3592
|
visibility: [requireOption(parsed, "visibility")]
|
|
2969
3593
|
});
|
|
2970
|
-
return ok(json ? `${JSON.stringify(
|
|
2971
|
-
` : `${JSON.stringify(
|
|
3594
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3595
|
+
` : `${JSON.stringify(result)}
|
|
2972
3596
|
`);
|
|
2973
3597
|
}
|
|
2974
3598
|
async function sourceRead(executor, command, rest, json) {
|
|
@@ -2980,9 +3604,9 @@ async function sourceRead(executor, command, rest, json) {
|
|
|
2980
3604
|
if (sourceId === void 0 || sourceId.trim().length === 0) {
|
|
2981
3605
|
return fail(`Missing required source ID for ${toolName}.`);
|
|
2982
3606
|
}
|
|
2983
|
-
const
|
|
2984
|
-
return ok(json ? `${JSON.stringify(
|
|
2985
|
-
` : `${JSON.stringify(
|
|
3607
|
+
const result = await executor.execute(toolName, { sourceId });
|
|
3608
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3609
|
+
` : `${JSON.stringify(result)}
|
|
2986
3610
|
`);
|
|
2987
3611
|
}
|
|
2988
3612
|
async function ingestionStatus(executor, rest, json) {
|
|
@@ -2993,18 +3617,18 @@ async function ingestionStatus(executor, rest, json) {
|
|
|
2993
3617
|
if (jobId === void 0 || jobId.trim().length === 0) {
|
|
2994
3618
|
return fail("Missing required job ID for ingestion.status.");
|
|
2995
3619
|
}
|
|
2996
|
-
const
|
|
2997
|
-
return ok(json ? `${JSON.stringify(
|
|
2998
|
-
` : `${JSON.stringify(
|
|
3620
|
+
const result = await executor.execute("ingestion.status", { jobId });
|
|
3621
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3622
|
+
` : `${JSON.stringify(result)}
|
|
2999
3623
|
`);
|
|
3000
3624
|
}
|
|
3001
3625
|
async function recordList(executor, json) {
|
|
3002
3626
|
if (executor === void 0) {
|
|
3003
3627
|
return fail("No Sift API executor is configured for record.list.");
|
|
3004
3628
|
}
|
|
3005
|
-
const
|
|
3006
|
-
return ok(json ? `${JSON.stringify(
|
|
3007
|
-
` : `${JSON.stringify(
|
|
3629
|
+
const result = await executor.execute("record.list", {});
|
|
3630
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3631
|
+
` : `${JSON.stringify(result)}
|
|
3008
3632
|
`);
|
|
3009
3633
|
}
|
|
3010
3634
|
async function recordRead(executor, toolName, rest, json) {
|
|
@@ -3022,23 +3646,23 @@ async function recordRead(executor, toolName, rest, json) {
|
|
|
3022
3646
|
input.sectionAnchor = sectionAnchor;
|
|
3023
3647
|
}
|
|
3024
3648
|
}
|
|
3025
|
-
const
|
|
3026
|
-
return ok(json ? `${JSON.stringify(
|
|
3027
|
-
` : renderRecordResult(
|
|
3649
|
+
const result = await executor.execute(toolName, input);
|
|
3650
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3651
|
+
` : renderRecordResult(result));
|
|
3028
3652
|
}
|
|
3029
3653
|
async function createMarkdownRecord(executor, rest, json) {
|
|
3030
3654
|
if (executor === void 0) {
|
|
3031
3655
|
return fail("No Sift API executor is configured for record.create_markdown.");
|
|
3032
3656
|
}
|
|
3033
3657
|
const parsed = parseOptions(rest);
|
|
3034
|
-
const
|
|
3658
|
+
const result = await executor.execute("record.create_markdown", {
|
|
3035
3659
|
recordType: requireOption(parsed, "type"),
|
|
3036
3660
|
title: requireOption(parsed, "title"),
|
|
3037
3661
|
markdown: requireOption(parsed, "markdown"),
|
|
3038
3662
|
visibility: [requireOption(parsed, "visibility")]
|
|
3039
3663
|
});
|
|
3040
|
-
return ok(json ? `${JSON.stringify(
|
|
3041
|
-
` : `${JSON.stringify(
|
|
3664
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3665
|
+
` : `${JSON.stringify(result)}
|
|
3042
3666
|
`);
|
|
3043
3667
|
}
|
|
3044
3668
|
async function patchRecordSection(executor, rest, json) {
|
|
@@ -3062,9 +3686,9 @@ async function patchRecordSection(executor, rest, json) {
|
|
|
3062
3686
|
if (expectedMarkdown !== void 0) {
|
|
3063
3687
|
input.expectedMarkdown = expectedMarkdown;
|
|
3064
3688
|
}
|
|
3065
|
-
const
|
|
3066
|
-
return ok(json ? `${JSON.stringify(
|
|
3067
|
-
` : `${JSON.stringify(
|
|
3689
|
+
const result = await executor.execute("record.patch_section", input);
|
|
3690
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3691
|
+
` : `${JSON.stringify(result)}
|
|
3068
3692
|
`);
|
|
3069
3693
|
}
|
|
3070
3694
|
async function createTask(executor, rest, json) {
|
|
@@ -3093,9 +3717,9 @@ async function createTask(executor, rest, json) {
|
|
|
3093
3717
|
input.rationale = rationale;
|
|
3094
3718
|
}
|
|
3095
3719
|
addOptionalWorkMetadata(input, parsed);
|
|
3096
|
-
const
|
|
3097
|
-
return ok(json ? `${JSON.stringify(
|
|
3098
|
-
` : `${JSON.stringify(
|
|
3720
|
+
const result = await executor.execute("task.create", input);
|
|
3721
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3722
|
+
` : `${JSON.stringify(result)}
|
|
3099
3723
|
`);
|
|
3100
3724
|
}
|
|
3101
3725
|
async function auditEvents(executor, rest, json) {
|
|
@@ -3107,9 +3731,9 @@ async function auditEvents(executor, rest, json) {
|
|
|
3107
3731
|
if (targetId !== void 0 && targetId.trim().length > 0) {
|
|
3108
3732
|
input.targetId = targetId;
|
|
3109
3733
|
}
|
|
3110
|
-
const
|
|
3111
|
-
return ok(json ? `${JSON.stringify(
|
|
3112
|
-
` : `${JSON.stringify(
|
|
3734
|
+
const result = await executor.execute("audit.events", input);
|
|
3735
|
+
return ok(json ? `${JSON.stringify(result)}
|
|
3736
|
+
` : `${JSON.stringify(result)}
|
|
3113
3737
|
`);
|
|
3114
3738
|
}
|
|
3115
3739
|
async function idTool(input) {
|
|
@@ -3120,22 +3744,25 @@ async function idTool(input) {
|
|
|
3120
3744
|
if (id === void 0 || id.trim().length === 0) {
|
|
3121
3745
|
return fail(`Missing required ${input.idLabel} for ${input.toolName}.`);
|
|
3122
3746
|
}
|
|
3123
|
-
const
|
|
3124
|
-
return ok(input.json ? `${JSON.stringify(
|
|
3125
|
-
` : `${JSON.stringify(
|
|
3747
|
+
const result = await input.executor.execute(input.toolName, { [input.inputKey]: id });
|
|
3748
|
+
return ok(input.json ? `${JSON.stringify(result)}
|
|
3749
|
+
` : `${JSON.stringify(result)}
|
|
3126
3750
|
`);
|
|
3127
3751
|
}
|
|
3128
3752
|
|
|
3129
3753
|
// src/auth/configStore.ts
|
|
3130
|
-
import { mkdir, readFile as
|
|
3131
|
-
import { dirname, join } from "path";
|
|
3754
|
+
import { mkdir, readFile as readFile3, rm, writeFile, chmod } from "fs/promises";
|
|
3755
|
+
import { dirname, join as join2 } from "path";
|
|
3756
|
+
function refreshSlotTokenId(tokenId) {
|
|
3757
|
+
return `refresh:${tokenId}`;
|
|
3758
|
+
}
|
|
3132
3759
|
function resolveSiftConfigPath(input) {
|
|
3133
|
-
return
|
|
3760
|
+
return join2(input.homeDir, ".sift", "config.json");
|
|
3134
3761
|
}
|
|
3135
3762
|
async function readStoredSiftConfig(input) {
|
|
3136
3763
|
let raw;
|
|
3137
3764
|
try {
|
|
3138
|
-
raw = await
|
|
3765
|
+
raw = await readFile3(resolveSiftConfigPath(input), "utf8");
|
|
3139
3766
|
} catch (error) {
|
|
3140
3767
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
3141
3768
|
return void 0;
|
|
@@ -3145,10 +3772,10 @@ async function readStoredSiftConfig(input) {
|
|
|
3145
3772
|
return parseStoredSiftConfig(JSON.parse(raw));
|
|
3146
3773
|
}
|
|
3147
3774
|
async function writeStoredSiftConfig(input) {
|
|
3148
|
-
const
|
|
3775
|
+
const config = parseStoredSiftConfig(input.config);
|
|
3149
3776
|
const path = resolveSiftConfigPath(input);
|
|
3150
3777
|
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
3151
|
-
await writeFile(path, `${JSON.stringify(
|
|
3778
|
+
await writeFile(path, `${JSON.stringify(config, null, 2)}
|
|
3152
3779
|
`, { mode: 384 });
|
|
3153
3780
|
await chmod(path, 384);
|
|
3154
3781
|
}
|
|
@@ -3168,18 +3795,19 @@ async function loadCliAuthConfig(input) {
|
|
|
3168
3795
|
if (profile === void 0) {
|
|
3169
3796
|
throw new Error(`Stored Sift profile '${stored.currentProfile}' was not found.`);
|
|
3170
3797
|
}
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3798
|
+
const tokenKind = profile.tokenKind ?? "legacy";
|
|
3799
|
+
const expired = Date.parse(profile.tokenExpiresAt) <= input.now.getTime();
|
|
3800
|
+
const token = await resolveStoredToken({
|
|
3801
|
+
homeDir: input.homeDir,
|
|
3802
|
+
credentialStore: input.credentialStore,
|
|
3803
|
+
profile,
|
|
3804
|
+
tokenKind,
|
|
3805
|
+
expired,
|
|
3806
|
+
oauthRefresher: input.oauthRefresher
|
|
3177
3807
|
});
|
|
3178
|
-
if (token === void 0) {
|
|
3179
|
-
throw new Error("Stored Sift credential store secret is missing; run `sift login` again.");
|
|
3180
|
-
}
|
|
3181
3808
|
return {
|
|
3182
3809
|
source: "stored",
|
|
3810
|
+
tokenKind,
|
|
3183
3811
|
token,
|
|
3184
3812
|
config: {
|
|
3185
3813
|
apiBaseUrl: profile.apiBaseUrl,
|
|
@@ -3192,24 +3820,81 @@ async function loadCliAuthConfig(input) {
|
|
|
3192
3820
|
}
|
|
3193
3821
|
};
|
|
3194
3822
|
}
|
|
3195
|
-
function
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3823
|
+
async function resolveStoredToken(input) {
|
|
3824
|
+
const { profile } = input;
|
|
3825
|
+
if (input.expired && input.tokenKind === "oauth" && profile.refreshable === true && input.oauthRefresher !== void 0) {
|
|
3826
|
+
return refreshOAuthToken({
|
|
3827
|
+
homeDir: input.homeDir,
|
|
3828
|
+
credentialStore: input.credentialStore,
|
|
3829
|
+
profile,
|
|
3830
|
+
oauthRefresher: input.oauthRefresher
|
|
3831
|
+
});
|
|
3832
|
+
}
|
|
3833
|
+
if (input.expired) {
|
|
3834
|
+
throw new Error("Stored Sift CLI auth has expired; run `sift login` again.");
|
|
3835
|
+
}
|
|
3836
|
+
const token = await input.credentialStore.read({
|
|
3837
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3838
|
+
tokenId: profile.tokenId
|
|
3839
|
+
});
|
|
3840
|
+
if (token === void 0) {
|
|
3841
|
+
throw new Error("Stored Sift credential store secret is missing; run `sift login` again.");
|
|
3842
|
+
}
|
|
3843
|
+
return token;
|
|
3844
|
+
}
|
|
3845
|
+
async function refreshOAuthToken(input) {
|
|
3846
|
+
const { profile } = input;
|
|
3847
|
+
const refreshToken = await input.credentialStore.read({
|
|
3848
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3849
|
+
tokenId: refreshSlotTokenId(profile.tokenId)
|
|
3850
|
+
});
|
|
3851
|
+
if (refreshToken === void 0) {
|
|
3852
|
+
throw new Error("Stored Sift OAuth refresh token is missing; run `sift login` again.");
|
|
3853
|
+
}
|
|
3854
|
+
const refreshed = await input.oauthRefresher({
|
|
3855
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3856
|
+
refreshToken
|
|
3857
|
+
});
|
|
3858
|
+
await input.credentialStore.write({
|
|
3859
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3860
|
+
tokenId: profile.tokenId,
|
|
3861
|
+
secret: refreshed.accessToken
|
|
3862
|
+
});
|
|
3863
|
+
if (refreshed.refreshToken !== void 0) {
|
|
3864
|
+
await input.credentialStore.write({
|
|
3865
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
3866
|
+
tokenId: refreshSlotTokenId(profile.tokenId),
|
|
3867
|
+
secret: refreshed.refreshToken
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
const updated = {
|
|
3871
|
+
...profile,
|
|
3872
|
+
tokenExpiresAt: refreshed.expiresAt ?? profile.tokenExpiresAt
|
|
3873
|
+
};
|
|
3874
|
+
await writeStoredSiftConfig({
|
|
3875
|
+
homeDir: input.homeDir,
|
|
3876
|
+
config: { currentProfile: "default", profiles: { default: updated } }
|
|
3877
|
+
});
|
|
3878
|
+
return refreshed.accessToken;
|
|
3879
|
+
}
|
|
3880
|
+
function loadEnvAuth(env, token) {
|
|
3881
|
+
return {
|
|
3882
|
+
source: "env",
|
|
3883
|
+
token,
|
|
3884
|
+
config: {
|
|
3885
|
+
apiBaseUrl: requiredEnv(env, "SIFT_API_BASE_URL").replace(/\/+$/u, ""),
|
|
3886
|
+
tokenLabel: clean(env.SIFT_TOKEN_LABEL) ?? "env-token",
|
|
3887
|
+
tokenExpiresAt: clean(env.SIFT_TOKEN_EXPIRES_AT),
|
|
3888
|
+
workspaceId: requiredEnv(env, "SIFT_WORKSPACE_ID"),
|
|
3889
|
+
brainId: requiredEnv(env, "SIFT_BRAIN_ID"),
|
|
3890
|
+
principalId: requiredEnv(env, "SIFT_PRINCIPAL_ID"),
|
|
3891
|
+
capabilities: (clean(env.SIFT_TOKEN_CAPABILITIES) ?? "").split(",").map((item) => item.trim()).filter((item) => item.length > 0)
|
|
3892
|
+
}
|
|
3893
|
+
};
|
|
3894
|
+
}
|
|
3895
|
+
function parseStoredSiftConfig(value) {
|
|
3896
|
+
const record = objectValue(value, "config");
|
|
3897
|
+
const currentProfile = stringValue(record.currentProfile, "currentProfile");
|
|
3213
3898
|
const profilesRecord = objectValue(record.profiles, "profiles");
|
|
3214
3899
|
const profiles = {};
|
|
3215
3900
|
for (const [name, profileValue] of Object.entries(profilesRecord)) {
|
|
@@ -3222,10 +3907,10 @@ function parseStoredSiftConfig(value) {
|
|
|
3222
3907
|
}
|
|
3223
3908
|
function parseStoredSiftProfile(value) {
|
|
3224
3909
|
const record = objectValue(value, "profile");
|
|
3225
|
-
if ("token" in record || "secret" in record || "tokenSecret" in record) {
|
|
3910
|
+
if ("token" in record || "secret" in record || "tokenSecret" in record || "accessToken" in record || "refreshToken" in record) {
|
|
3226
3911
|
throw new Error("Stored Sift config must not contain token secrets.");
|
|
3227
3912
|
}
|
|
3228
|
-
|
|
3913
|
+
const profile = {
|
|
3229
3914
|
apiBaseUrl: stringValue(record.apiBaseUrl, "apiBaseUrl").replace(/\/+$/u, ""),
|
|
3230
3915
|
appBaseUrl: stringValue(record.appBaseUrl, "appBaseUrl").replace(/\/+$/u, ""),
|
|
3231
3916
|
workspaceId: stringValue(record.workspaceId, "workspaceId"),
|
|
@@ -3236,6 +3921,21 @@ function parseStoredSiftProfile(value) {
|
|
|
3236
3921
|
tokenExpiresAt: stringValue(record.tokenExpiresAt, "tokenExpiresAt"),
|
|
3237
3922
|
capabilities: stringArray(record.capabilities, "capabilities")
|
|
3238
3923
|
};
|
|
3924
|
+
const tokenKind = tokenKindValue(record.tokenKind);
|
|
3925
|
+
if (tokenKind !== void 0) {
|
|
3926
|
+
profile.tokenKind = tokenKind;
|
|
3927
|
+
}
|
|
3928
|
+
if (record.refreshable === true) {
|
|
3929
|
+
profile.refreshable = true;
|
|
3930
|
+
}
|
|
3931
|
+
return profile;
|
|
3932
|
+
}
|
|
3933
|
+
function tokenKindValue(value) {
|
|
3934
|
+
if (value === void 0) return void 0;
|
|
3935
|
+
if (value === "legacy" || value === "oauth" || value === "service") {
|
|
3936
|
+
return value;
|
|
3937
|
+
}
|
|
3938
|
+
throw new Error("tokenKind must be one of legacy, oauth, service.");
|
|
3239
3939
|
}
|
|
3240
3940
|
function requiredEnv(env, name) {
|
|
3241
3941
|
const value = clean(env[name]);
|
|
@@ -3296,14 +3996,14 @@ function createMacOSKeychainStore(input = {}) {
|
|
|
3296
3996
|
return {
|
|
3297
3997
|
async assertAvailable() {
|
|
3298
3998
|
await requireSupported();
|
|
3299
|
-
const
|
|
3300
|
-
if (
|
|
3999
|
+
const result = await runCommand(securityPath, ["list-keychains"]);
|
|
4000
|
+
if (result.exitCode !== 0) {
|
|
3301
4001
|
throw new UnsupportedCredentialStoreError();
|
|
3302
4002
|
}
|
|
3303
4003
|
},
|
|
3304
4004
|
async read(readInput) {
|
|
3305
4005
|
await requireSupported();
|
|
3306
|
-
const
|
|
4006
|
+
const result = await runCommand(securityPath, [
|
|
3307
4007
|
"find-generic-password",
|
|
3308
4008
|
"-s",
|
|
3309
4009
|
serviceName,
|
|
@@ -3311,15 +4011,15 @@ function createMacOSKeychainStore(input = {}) {
|
|
|
3311
4011
|
account(readInput),
|
|
3312
4012
|
"-w"
|
|
3313
4013
|
]);
|
|
3314
|
-
if (
|
|
4014
|
+
if (result.exitCode !== 0) {
|
|
3315
4015
|
return void 0;
|
|
3316
4016
|
}
|
|
3317
|
-
const secret =
|
|
4017
|
+
const secret = result.stdout.trim();
|
|
3318
4018
|
return secret.length === 0 ? void 0 : secret;
|
|
3319
4019
|
},
|
|
3320
4020
|
async write(writeInput) {
|
|
3321
4021
|
await requireSupported();
|
|
3322
|
-
const
|
|
4022
|
+
const result = await runCommand(securityPath, [
|
|
3323
4023
|
"add-generic-password",
|
|
3324
4024
|
"-U",
|
|
3325
4025
|
"-s",
|
|
@@ -3329,7 +4029,7 @@ function createMacOSKeychainStore(input = {}) {
|
|
|
3329
4029
|
"-w",
|
|
3330
4030
|
writeInput.secret
|
|
3331
4031
|
]);
|
|
3332
|
-
if (
|
|
4032
|
+
if (result.exitCode !== 0) {
|
|
3333
4033
|
throw new Error("Failed to write Sift CLI token secret to macOS Keychain.");
|
|
3334
4034
|
}
|
|
3335
4035
|
},
|
|
@@ -3347,8 +4047,8 @@ function createMacOSKeychainStore(input = {}) {
|
|
|
3347
4047
|
}
|
|
3348
4048
|
async function runSecurityCommand(file, args) {
|
|
3349
4049
|
try {
|
|
3350
|
-
const
|
|
3351
|
-
return { stdout:
|
|
4050
|
+
const result = await execFileAsync(file, args);
|
|
4051
|
+
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
3352
4052
|
} catch (error) {
|
|
3353
4053
|
if (isExecError(error)) {
|
|
3354
4054
|
return {
|
|
@@ -3369,21 +4069,116 @@ function isExecError(error) {
|
|
|
3369
4069
|
|
|
3370
4070
|
// src/auth/loginFlow.ts
|
|
3371
4071
|
import { execFile as execFile2 } from "child_process";
|
|
3372
|
-
import { hostname } from "os";
|
|
4072
|
+
import { hostname as hostname3 } from "os";
|
|
3373
4073
|
import { promisify as promisify2 } from "util";
|
|
3374
4074
|
|
|
4075
|
+
// src/auth/loginHelpers.ts
|
|
4076
|
+
var DEFAULT_SIFT_API_BASE_URL = "https://sift-wiki-api.fly.dev";
|
|
4077
|
+
async function resolveLoginApiBaseUrl(input) {
|
|
4078
|
+
const options = parseOptions(input.argv);
|
|
4079
|
+
const fromFlag = clean2(options.get("api-base-url"));
|
|
4080
|
+
if (fromFlag !== void 0) return normalizeUrl(fromFlag);
|
|
4081
|
+
const fromEnv = clean2(input.env.SIFT_API_BASE_URL);
|
|
4082
|
+
if (fromEnv !== void 0) return normalizeUrl(fromEnv);
|
|
4083
|
+
const stored = await readStoredSiftConfig({ homeDir: input.homeDir });
|
|
4084
|
+
const profile = stored?.profiles[stored.currentProfile];
|
|
4085
|
+
if (profile !== void 0) return normalizeUrl(profile.apiBaseUrl);
|
|
4086
|
+
return DEFAULT_SIFT_API_BASE_URL;
|
|
4087
|
+
}
|
|
4088
|
+
function requestedCapabilities(rest) {
|
|
4089
|
+
const option = parseOptions(rest).get("capability");
|
|
4090
|
+
return option === void 0 ? ["record:read"] : option.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
4091
|
+
}
|
|
4092
|
+
function errorMessage2(parsed, status2) {
|
|
4093
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
|
|
4094
|
+
const error = parsed.error;
|
|
4095
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
4096
|
+
const message = error.message;
|
|
4097
|
+
if (typeof message === "string") return message;
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
return `CLI auth request failed with status ${status2}.`;
|
|
4101
|
+
}
|
|
4102
|
+
function normalizeUrl(value) {
|
|
4103
|
+
return value.replace(/\/+$/u, "");
|
|
4104
|
+
}
|
|
4105
|
+
function clean2(value) {
|
|
4106
|
+
const trimmed = value?.trim();
|
|
4107
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
4108
|
+
}
|
|
4109
|
+
|
|
4110
|
+
// src/auth/oauthConfig.ts
|
|
4111
|
+
var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
4112
|
+
function oauthLoginSelected(input) {
|
|
4113
|
+
if (input.argv.includes("--no-oauth")) return false;
|
|
4114
|
+
if (input.argv.includes("--oauth")) return true;
|
|
4115
|
+
const fromEnv = input.env.SIFT_OAUTH?.trim().toLowerCase();
|
|
4116
|
+
return fromEnv !== void 0 && TRUE_VALUES.has(fromEnv);
|
|
4117
|
+
}
|
|
4118
|
+
function resolveCliOAuthConfig(input) {
|
|
4119
|
+
const options = parseOptions(input.argv);
|
|
4120
|
+
const authorizeUrl = clean3(options.get("oauth-authorize-url")) ?? clean3(input.env.SIFT_OAUTH_AUTHORIZE_URL);
|
|
4121
|
+
const tokenUrl = clean3(options.get("oauth-token-url")) ?? clean3(input.env.SIFT_OAUTH_TOKEN_URL);
|
|
4122
|
+
const clientId = clean3(options.get("oauth-client-id")) ?? clean3(input.env.SIFT_OAUTH_CLIENT_ID);
|
|
4123
|
+
if (authorizeUrl === void 0 || tokenUrl === void 0 || clientId === void 0) {
|
|
4124
|
+
return void 0;
|
|
4125
|
+
}
|
|
4126
|
+
const registrationUrl = clean3(options.get("oauth-registration-url")) ?? clean3(input.env.SIFT_OAUTH_REGISTRATION_URL);
|
|
4127
|
+
const config = { authorizeUrl, tokenUrl, clientId };
|
|
4128
|
+
if (registrationUrl !== void 0) {
|
|
4129
|
+
config.registrationUrl = registrationUrl;
|
|
4130
|
+
}
|
|
4131
|
+
const scopes = parseScopeList(clean3(options.get("oauth-scopes")) ?? clean3(input.env.SIFT_OAUTH_SCOPES));
|
|
4132
|
+
if (scopes.length > 0) {
|
|
4133
|
+
config.defaultScopes = scopes;
|
|
4134
|
+
}
|
|
4135
|
+
return config;
|
|
4136
|
+
}
|
|
4137
|
+
function scopesForCapabilities(capabilities) {
|
|
4138
|
+
const scopes = /* @__PURE__ */ new Set(["read"]);
|
|
4139
|
+
for (const capability of capabilities) {
|
|
4140
|
+
if (capability.endsWith(":write")) {
|
|
4141
|
+
scopes.add("write");
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
return [...scopes];
|
|
4145
|
+
}
|
|
4146
|
+
function parseScopeList(value) {
|
|
4147
|
+
if (value === void 0) return [];
|
|
4148
|
+
return value.split(/[\s,]+/u).map((item) => item.trim()).filter((item) => item.length > 0);
|
|
4149
|
+
}
|
|
4150
|
+
function clean3(value) {
|
|
4151
|
+
const trimmed = value?.trim();
|
|
4152
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
// src/auth/oauthLoginFlow.ts
|
|
4156
|
+
import { hostname } from "os";
|
|
4157
|
+
|
|
3375
4158
|
// src/auth/localCallback.ts
|
|
3376
4159
|
import { createServer } from "http";
|
|
3377
4160
|
async function createLocalCallbackServer() {
|
|
3378
4161
|
let resolveCallback;
|
|
3379
4162
|
let rejectCallback;
|
|
3380
|
-
const callbackPromise = new Promise((
|
|
3381
|
-
resolveCallback =
|
|
4163
|
+
const callbackPromise = new Promise((resolve2, reject) => {
|
|
4164
|
+
resolveCallback = resolve2;
|
|
3382
4165
|
rejectCallback = reject;
|
|
3383
4166
|
});
|
|
3384
4167
|
const server = createServer((request, response) => {
|
|
3385
4168
|
try {
|
|
3386
4169
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
4170
|
+
const error = url.searchParams.get("error");
|
|
4171
|
+
if (error !== null) {
|
|
4172
|
+
const description = url.searchParams.get("error_description");
|
|
4173
|
+
response.writeHead(400, { "Content-Type": "text/plain" });
|
|
4174
|
+
response.end("Sift CLI authorization failed. You can return to the terminal.");
|
|
4175
|
+
rejectCallback?.(
|
|
4176
|
+
new Error(
|
|
4177
|
+
description === null || description.trim().length === 0 ? `Authorization failed: ${error}.` : `Authorization failed: ${error}: ${description}`
|
|
4178
|
+
)
|
|
4179
|
+
);
|
|
4180
|
+
return;
|
|
4181
|
+
}
|
|
3387
4182
|
const code = url.searchParams.get("code");
|
|
3388
4183
|
const state = url.searchParams.get("state");
|
|
3389
4184
|
if (code === null || state === null) {
|
|
@@ -3411,21 +4206,21 @@ async function createLocalCallbackServer() {
|
|
|
3411
4206
|
};
|
|
3412
4207
|
}
|
|
3413
4208
|
function listen(server) {
|
|
3414
|
-
return new Promise((
|
|
4209
|
+
return new Promise((resolve2, reject) => {
|
|
3415
4210
|
server.once("error", reject);
|
|
3416
4211
|
server.listen(0, "127.0.0.1", () => {
|
|
3417
4212
|
server.off("error", reject);
|
|
3418
|
-
|
|
4213
|
+
resolve2();
|
|
3419
4214
|
});
|
|
3420
4215
|
});
|
|
3421
4216
|
}
|
|
3422
4217
|
function closeServer(server) {
|
|
3423
|
-
return new Promise((
|
|
4218
|
+
return new Promise((resolve2, reject) => {
|
|
3424
4219
|
server.close((error) => {
|
|
3425
4220
|
if (error) {
|
|
3426
4221
|
reject(error);
|
|
3427
4222
|
} else {
|
|
3428
|
-
|
|
4223
|
+
resolve2();
|
|
3429
4224
|
}
|
|
3430
4225
|
});
|
|
3431
4226
|
});
|
|
@@ -3451,15 +4246,445 @@ function sha256Base64Url(value) {
|
|
|
3451
4246
|
return createHash2("sha256").update(value).digest("base64url");
|
|
3452
4247
|
}
|
|
3453
4248
|
|
|
4249
|
+
// src/auth/oauthLoginFlow.ts
|
|
4250
|
+
async function oauthBrowserLogin(input) {
|
|
4251
|
+
await input.credentialStore.assertAvailable();
|
|
4252
|
+
const callbackServer = await (input.createCallbackServer ?? createLocalCallbackServer)();
|
|
4253
|
+
try {
|
|
4254
|
+
const pkce = createPkceState({ nextSecret: input.nextSecret });
|
|
4255
|
+
const scopes = mergeScopes(scopesForCapabilities(input.capabilities), input.oauth.defaultScopes);
|
|
4256
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
4257
|
+
oauth: input.oauth,
|
|
4258
|
+
redirectUri: callbackServer.redirectUri,
|
|
4259
|
+
codeChallenge: pkce.codeChallenge,
|
|
4260
|
+
state: pkce.state,
|
|
4261
|
+
scopes
|
|
4262
|
+
});
|
|
4263
|
+
await tryOpenBrowser(input.openBrowser, authorizeUrl);
|
|
4264
|
+
const callback = await callbackServer.waitForCallback();
|
|
4265
|
+
if (callback.state !== pkce.state) {
|
|
4266
|
+
throw new Error("OAuth callback state mismatch.");
|
|
4267
|
+
}
|
|
4268
|
+
const tokens = await exchangeAuthorizationCode({
|
|
4269
|
+
oauth: input.oauth,
|
|
4270
|
+
fetch: input.fetch,
|
|
4271
|
+
code: callback.code,
|
|
4272
|
+
codeVerifier: pkce.codeVerifier,
|
|
4273
|
+
redirectUri: callbackServer.redirectUri
|
|
4274
|
+
});
|
|
4275
|
+
return finalizeOAuthLogin(input, tokens);
|
|
4276
|
+
} finally {
|
|
4277
|
+
await callbackServer.close();
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
async function oauthRefresh(input) {
|
|
4281
|
+
const body = new URLSearchParams({
|
|
4282
|
+
grant_type: "refresh_token",
|
|
4283
|
+
refresh_token: input.refreshToken,
|
|
4284
|
+
client_id: input.oauth.clientId
|
|
4285
|
+
});
|
|
4286
|
+
const tokens = await postForm(input.fetch, input.oauth.tokenUrl, body);
|
|
4287
|
+
return toTokenSet(tokens);
|
|
4288
|
+
}
|
|
4289
|
+
async function finalizeOAuthLogin(input, tokens) {
|
|
4290
|
+
const scope = await input.resolveScope({
|
|
4291
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
4292
|
+
token: tokens.accessToken,
|
|
4293
|
+
fetch: input.fetch
|
|
4294
|
+
});
|
|
4295
|
+
const profile = {
|
|
4296
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
4297
|
+
appBaseUrl: input.appBaseUrl,
|
|
4298
|
+
workspaceId: scope.workspaceId,
|
|
4299
|
+
brainId: scope.brainId,
|
|
4300
|
+
principalId: scope.principalId,
|
|
4301
|
+
// Synthetic, non-secret slot id so the converged token reuses the same
|
|
4302
|
+
// keychain account scheme (apiBaseUrl|tokenId) as the legacy flow.
|
|
4303
|
+
tokenId: "oauth",
|
|
4304
|
+
tokenLabel: tokens.tokenLabel,
|
|
4305
|
+
tokenExpiresAt: tokens.expiresAt ?? farFuture(),
|
|
4306
|
+
capabilities: scope.capabilities,
|
|
4307
|
+
tokenKind: "oauth",
|
|
4308
|
+
refreshable: tokens.refreshToken !== void 0
|
|
4309
|
+
};
|
|
4310
|
+
const result = { profile, accessToken: tokens.accessToken };
|
|
4311
|
+
if (tokens.refreshToken !== void 0) {
|
|
4312
|
+
result.refreshToken = tokens.refreshToken;
|
|
4313
|
+
}
|
|
4314
|
+
return result;
|
|
4315
|
+
}
|
|
4316
|
+
function buildAuthorizeUrl(input) {
|
|
4317
|
+
const url = new URL(input.oauth.authorizeUrl);
|
|
4318
|
+
url.searchParams.set("response_type", "code");
|
|
4319
|
+
url.searchParams.set("client_id", input.oauth.clientId);
|
|
4320
|
+
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
4321
|
+
url.searchParams.set("code_challenge", input.codeChallenge);
|
|
4322
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
4323
|
+
url.searchParams.set("state", input.state);
|
|
4324
|
+
if (input.scopes.length > 0) {
|
|
4325
|
+
url.searchParams.set("scope", input.scopes.join(" "));
|
|
4326
|
+
}
|
|
4327
|
+
return url.toString();
|
|
4328
|
+
}
|
|
4329
|
+
async function exchangeAuthorizationCode(input) {
|
|
4330
|
+
const body = new URLSearchParams({
|
|
4331
|
+
grant_type: "authorization_code",
|
|
4332
|
+
code: input.code,
|
|
4333
|
+
redirect_uri: input.redirectUri,
|
|
4334
|
+
client_id: input.oauth.clientId,
|
|
4335
|
+
code_verifier: input.codeVerifier
|
|
4336
|
+
});
|
|
4337
|
+
const tokens = await postForm(input.fetch, input.oauth.tokenUrl, body);
|
|
4338
|
+
return toTokenSet(tokens);
|
|
4339
|
+
}
|
|
4340
|
+
async function postForm(fetchImpl, url, body) {
|
|
4341
|
+
const response = await fetchImpl(url, {
|
|
4342
|
+
method: "POST",
|
|
4343
|
+
headers: {
|
|
4344
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
4345
|
+
Accept: "application/json"
|
|
4346
|
+
},
|
|
4347
|
+
body: body.toString()
|
|
4348
|
+
});
|
|
4349
|
+
const text = await response.text();
|
|
4350
|
+
const parsed = text.length === 0 ? {} : JSON.parse(text);
|
|
4351
|
+
if (!response.ok) {
|
|
4352
|
+
throw new Error(oauthTokenError(parsed, response.status));
|
|
4353
|
+
}
|
|
4354
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
4355
|
+
throw new Error("OAuth token endpoint returned a non-object response.");
|
|
4356
|
+
}
|
|
4357
|
+
return parsed;
|
|
4358
|
+
}
|
|
4359
|
+
function toTokenSet(tokens) {
|
|
4360
|
+
const accessToken = tokens.access_token;
|
|
4361
|
+
if (typeof accessToken !== "string" || accessToken.trim().length === 0) {
|
|
4362
|
+
throw new Error("OAuth token endpoint did not return an access token.");
|
|
4363
|
+
}
|
|
4364
|
+
const set = { accessToken, tokenLabel: oauthTokenLabel() };
|
|
4365
|
+
const refreshToken = tokens.refresh_token;
|
|
4366
|
+
if (typeof refreshToken === "string" && refreshToken.trim().length > 0) {
|
|
4367
|
+
set.refreshToken = refreshToken;
|
|
4368
|
+
}
|
|
4369
|
+
const expiresAt = expiresAtFrom(tokens.expires_in);
|
|
4370
|
+
if (expiresAt !== void 0) {
|
|
4371
|
+
set.expiresAt = expiresAt;
|
|
4372
|
+
}
|
|
4373
|
+
return set;
|
|
4374
|
+
}
|
|
4375
|
+
function expiresAtFrom(expiresIn) {
|
|
4376
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
4377
|
+
return void 0;
|
|
4378
|
+
}
|
|
4379
|
+
return new Date(Date.now() + expiresIn * 1e3).toISOString();
|
|
4380
|
+
}
|
|
4381
|
+
function mergeScopes(derived, defaults) {
|
|
4382
|
+
const merged = new Set(derived);
|
|
4383
|
+
for (const scope of defaults ?? []) {
|
|
4384
|
+
merged.add(scope);
|
|
4385
|
+
}
|
|
4386
|
+
return [...merged];
|
|
4387
|
+
}
|
|
4388
|
+
function oauthTokenLabel() {
|
|
4389
|
+
const name = hostname().trim();
|
|
4390
|
+
return name.length === 0 ? "oauth" : `oauth-${name}`;
|
|
4391
|
+
}
|
|
4392
|
+
function farFuture() {
|
|
4393
|
+
return new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toISOString();
|
|
4394
|
+
}
|
|
4395
|
+
async function tryOpenBrowser(openBrowser, url) {
|
|
4396
|
+
if (openBrowser === void 0) return;
|
|
4397
|
+
await openBrowser(url).catch(() => void 0);
|
|
4398
|
+
}
|
|
4399
|
+
function oauthTokenError(parsed, status2) {
|
|
4400
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
4401
|
+
const record = parsed;
|
|
4402
|
+
const error = typeof record.error === "string" ? record.error : void 0;
|
|
4403
|
+
const description = typeof record.error_description === "string" ? record.error_description : void 0;
|
|
4404
|
+
if (error !== void 0) {
|
|
4405
|
+
return description === void 0 ? `OAuth token request failed: ${error}.` : `OAuth token request failed: ${error}: ${description}`;
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
return `OAuth token request failed with status ${status2}.`;
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
// src/auth/serviceTokenLogin.ts
|
|
4412
|
+
import { hostname as hostname2 } from "os";
|
|
4413
|
+
async function serviceTokenLogin(input) {
|
|
4414
|
+
await input.credentialStore.assertAvailable();
|
|
4415
|
+
const callerBearer = await input.resolveCallerBearer();
|
|
4416
|
+
if (callerBearer === void 0) {
|
|
4417
|
+
throw new Error(
|
|
4418
|
+
"Headless login needs an authenticated caller. Set SIFT_API_TOKEN or run 'sift login' once interactively, then retry 'sift login --no-browser'."
|
|
4419
|
+
);
|
|
4420
|
+
}
|
|
4421
|
+
const options = parseOptions(input.rest);
|
|
4422
|
+
const requestBody = buildServiceTokenRequest({
|
|
4423
|
+
rest: input.rest,
|
|
4424
|
+
capabilities: input.capabilities,
|
|
4425
|
+
label: options.get("label"),
|
|
4426
|
+
workspaceId: options.get("workspace-id"),
|
|
4427
|
+
ttlDays: options.get("ttl-days")
|
|
4428
|
+
});
|
|
4429
|
+
const minted = await postServiceTokenMint(
|
|
4430
|
+
input.fetch,
|
|
4431
|
+
`${input.apiBaseUrl}/cli-auth/service-token`,
|
|
4432
|
+
callerBearer,
|
|
4433
|
+
requestBody
|
|
4434
|
+
);
|
|
4435
|
+
const profile = {
|
|
4436
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
4437
|
+
appBaseUrl: input.appBaseUrl,
|
|
4438
|
+
workspaceId: minted.workspaceId,
|
|
4439
|
+
brainId: minted.brainId,
|
|
4440
|
+
principalId: minted.principalId,
|
|
4441
|
+
tokenId: minted.tokenId,
|
|
4442
|
+
tokenLabel: minted.tokenLabel,
|
|
4443
|
+
tokenExpiresAt: minted.tokenExpiresAt,
|
|
4444
|
+
capabilities: minted.capabilities,
|
|
4445
|
+
tokenKind: "service"
|
|
4446
|
+
};
|
|
4447
|
+
return { profile, token: minted.token };
|
|
4448
|
+
}
|
|
4449
|
+
function buildServiceTokenRequest(input) {
|
|
4450
|
+
const body = {
|
|
4451
|
+
label: clean4(input.label) ?? defaultLabel()
|
|
4452
|
+
};
|
|
4453
|
+
const workspaceId = clean4(input.workspaceId);
|
|
4454
|
+
if (workspaceId !== void 0) {
|
|
4455
|
+
body.workspaceId = workspaceId;
|
|
4456
|
+
}
|
|
4457
|
+
if (capabilityFlagPresent(input.rest)) {
|
|
4458
|
+
body.capabilities = input.capabilities;
|
|
4459
|
+
}
|
|
4460
|
+
const ttlDays = clean4(input.ttlDays);
|
|
4461
|
+
if (ttlDays !== void 0) {
|
|
4462
|
+
const parsed = Number(ttlDays);
|
|
4463
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
4464
|
+
throw new Error("Option --ttl-days must be a positive integer.");
|
|
4465
|
+
}
|
|
4466
|
+
body.ttlDays = parsed;
|
|
4467
|
+
}
|
|
4468
|
+
return body;
|
|
4469
|
+
}
|
|
4470
|
+
function capabilityFlagPresent(rest) {
|
|
4471
|
+
return rest.includes("--capability");
|
|
4472
|
+
}
|
|
4473
|
+
async function postServiceTokenMint(fetchImpl, url, callerBearer, body) {
|
|
4474
|
+
const response = await fetchImpl(url, {
|
|
4475
|
+
method: "POST",
|
|
4476
|
+
headers: {
|
|
4477
|
+
"Content-Type": "application/json",
|
|
4478
|
+
Authorization: `Bearer ${callerBearer}`
|
|
4479
|
+
},
|
|
4480
|
+
body: JSON.stringify(body)
|
|
4481
|
+
});
|
|
4482
|
+
const text = await response.text();
|
|
4483
|
+
const parsed = text.length === 0 ? {} : JSON.parse(text);
|
|
4484
|
+
if (!response.ok) {
|
|
4485
|
+
throw new Error(serviceTokenError(parsed, response.status));
|
|
4486
|
+
}
|
|
4487
|
+
return assertServiceTokenResponse(parsed);
|
|
4488
|
+
}
|
|
4489
|
+
function assertServiceTokenResponse(parsed) {
|
|
4490
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
4491
|
+
throw new Error("Service-token mint returned a non-object response.");
|
|
4492
|
+
}
|
|
4493
|
+
const record = parsed;
|
|
4494
|
+
return {
|
|
4495
|
+
token: requiredString(record.token, "token"),
|
|
4496
|
+
tokenId: requiredString(record.tokenId, "tokenId"),
|
|
4497
|
+
tokenLabel: requiredString(record.tokenLabel, "tokenLabel"),
|
|
4498
|
+
tokenExpiresAt: requiredString(record.tokenExpiresAt, "tokenExpiresAt"),
|
|
4499
|
+
workspaceId: requiredString(record.workspaceId, "workspaceId"),
|
|
4500
|
+
brainId: requiredString(record.brainId, "brainId"),
|
|
4501
|
+
principalId: requiredString(record.principalId, "principalId"),
|
|
4502
|
+
capabilities: stringArray2(record.capabilities, "capabilities")
|
|
4503
|
+
};
|
|
4504
|
+
}
|
|
4505
|
+
function requiredString(value, name) {
|
|
4506
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4507
|
+
throw new Error(`Service-token mint response missing ${name}.`);
|
|
4508
|
+
}
|
|
4509
|
+
return value;
|
|
4510
|
+
}
|
|
4511
|
+
function stringArray2(value, name) {
|
|
4512
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
4513
|
+
throw new Error(`Service-token mint response field ${name} must be a string array.`);
|
|
4514
|
+
}
|
|
4515
|
+
return [...value];
|
|
4516
|
+
}
|
|
4517
|
+
function serviceTokenError(parsed, status2) {
|
|
4518
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
|
|
4519
|
+
const error = parsed.error;
|
|
4520
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
4521
|
+
const message = error.message;
|
|
4522
|
+
if (typeof message === "string" && message.trim().length > 0) {
|
|
4523
|
+
return message;
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
return `Service-token mint failed with status ${status2}.`;
|
|
4528
|
+
}
|
|
4529
|
+
function defaultLabel() {
|
|
4530
|
+
const name = hostname2().trim();
|
|
4531
|
+
return name.length === 0 ? "sift-cli-service" : `sift-cli-service-${name}`;
|
|
4532
|
+
}
|
|
4533
|
+
function clean4(value) {
|
|
4534
|
+
const trimmed = value?.trim();
|
|
4535
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
// src/auth/convergedLogin.ts
|
|
4539
|
+
function oauthRefresherFor(input, rest) {
|
|
4540
|
+
const oauth = input.oauthConfig ?? resolveCliOAuthConfig({ argv: rest, env: input.env });
|
|
4541
|
+
if (oauth === void 0) return void 0;
|
|
4542
|
+
return ({ refreshToken }) => oauthRefresh({ oauth, fetch: input.fetch, refreshToken });
|
|
4543
|
+
}
|
|
4544
|
+
async function oauthBrowserLoginFlow(input, rest, json) {
|
|
4545
|
+
const apiBaseUrl = await resolveLoginApiBaseUrl({ argv: rest, env: input.env, homeDir: input.homeDir });
|
|
4546
|
+
const oauth = resolveOAuthConfigOrThrow(input, rest);
|
|
4547
|
+
const result = await oauthBrowserLogin({
|
|
4548
|
+
apiBaseUrl,
|
|
4549
|
+
appBaseUrl: resolveAppBaseUrl(input.env, apiBaseUrl),
|
|
4550
|
+
oauth,
|
|
4551
|
+
capabilities: requestedCapabilities(rest),
|
|
4552
|
+
fetch: input.fetch,
|
|
4553
|
+
credentialStore: input.credentialStore,
|
|
4554
|
+
...input.openBrowser === void 0 ? {} : { openBrowser: input.openBrowser },
|
|
4555
|
+
...input.createCallbackServer === void 0 ? {} : { createCallbackServer: input.createCallbackServer },
|
|
4556
|
+
resolveScope: input.resolveScope ?? whoamiResolveScope,
|
|
4557
|
+
...input.nextSecret === void 0 ? {} : { nextSecret: input.nextSecret }
|
|
4558
|
+
});
|
|
4559
|
+
return persistConvergedLogin(
|
|
4560
|
+
input,
|
|
4561
|
+
{
|
|
4562
|
+
profile: result.profile,
|
|
4563
|
+
accessToken: result.accessToken,
|
|
4564
|
+
...result.refreshToken === void 0 ? {} : { refreshToken: result.refreshToken }
|
|
4565
|
+
},
|
|
4566
|
+
json
|
|
4567
|
+
);
|
|
4568
|
+
}
|
|
4569
|
+
async function serviceTokenLoginFlow(input, rest, json) {
|
|
4570
|
+
const apiBaseUrl = await resolveLoginApiBaseUrl({ argv: rest, env: input.env, homeDir: input.homeDir });
|
|
4571
|
+
const result = await serviceTokenLogin({
|
|
4572
|
+
apiBaseUrl,
|
|
4573
|
+
appBaseUrl: resolveAppBaseUrl(input.env, apiBaseUrl),
|
|
4574
|
+
rest,
|
|
4575
|
+
capabilities: requestedCapabilities(rest),
|
|
4576
|
+
fetch: input.fetch,
|
|
4577
|
+
credentialStore: input.credentialStore,
|
|
4578
|
+
resolveCallerBearer: defaultCallerBearerResolver(input)
|
|
4579
|
+
});
|
|
4580
|
+
return persistConvergedLogin(input, { profile: result.profile, accessToken: result.token }, json);
|
|
4581
|
+
}
|
|
4582
|
+
function resolveOAuthConfigOrThrow(input, rest) {
|
|
4583
|
+
const oauth = input.oauthConfig ?? resolveCliOAuthConfig({ argv: rest, env: input.env });
|
|
4584
|
+
if (oauth === void 0) {
|
|
4585
|
+
throw new Error(
|
|
4586
|
+
"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."
|
|
4587
|
+
);
|
|
4588
|
+
}
|
|
4589
|
+
return oauth;
|
|
4590
|
+
}
|
|
4591
|
+
function defaultCallerBearerResolver(input) {
|
|
4592
|
+
return async () => {
|
|
4593
|
+
const envToken = clean2(input.env.SIFT_API_TOKEN);
|
|
4594
|
+
if (envToken !== void 0) return envToken;
|
|
4595
|
+
const stored = await readStoredSiftConfig({ homeDir: input.homeDir });
|
|
4596
|
+
const profile = stored?.profiles[stored.currentProfile];
|
|
4597
|
+
if (profile === void 0) return void 0;
|
|
4598
|
+
return input.credentialStore.read({
|
|
4599
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
4600
|
+
tokenId: profile.tokenId
|
|
4601
|
+
});
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
var whoamiResolveScope = async ({ apiBaseUrl, token, fetch: fetchImpl }) => {
|
|
4605
|
+
const response = await fetchImpl(`${apiBaseUrl}/agent-tools/whoami`, {
|
|
4606
|
+
method: "POST",
|
|
4607
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
4608
|
+
body: JSON.stringify({ input: {} })
|
|
4609
|
+
});
|
|
4610
|
+
const text = await response.text();
|
|
4611
|
+
const parsed = text.length === 0 ? {} : JSON.parse(text);
|
|
4612
|
+
if (!response.ok) {
|
|
4613
|
+
throw new Error(errorMessage2(parsed, response.status));
|
|
4614
|
+
}
|
|
4615
|
+
return whoamiScopeFrom(parsed);
|
|
4616
|
+
};
|
|
4617
|
+
function whoamiScopeFrom(parsed) {
|
|
4618
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
4619
|
+
throw new Error("whoami returned a non-object response.");
|
|
4620
|
+
}
|
|
4621
|
+
const record = parsed;
|
|
4622
|
+
const principalId = nestedString(record.principal, "id");
|
|
4623
|
+
const workspaceId = nestedString(record.scope, "workspaceId");
|
|
4624
|
+
const brainId = nestedString(record.scope, "brainId");
|
|
4625
|
+
if (principalId === void 0 || workspaceId === void 0 || brainId === void 0) {
|
|
4626
|
+
throw new Error("whoami response is missing principal or scope fields.");
|
|
4627
|
+
}
|
|
4628
|
+
const capabilities = Array.isArray(record.capabilities) ? record.capabilities.filter((item) => typeof item === "string") : [];
|
|
4629
|
+
return { principalId, workspaceId, brainId, capabilities };
|
|
4630
|
+
}
|
|
4631
|
+
function nestedString(parent, key) {
|
|
4632
|
+
if (typeof parent !== "object" || parent === null) return void 0;
|
|
4633
|
+
const value = parent[key];
|
|
4634
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4635
|
+
}
|
|
4636
|
+
function resolveAppBaseUrl(env, apiBaseUrl) {
|
|
4637
|
+
const fromEnv = clean2(env.SIFT_APP_BASE_URL);
|
|
4638
|
+
if (fromEnv !== void 0) return normalizeUrl(fromEnv);
|
|
4639
|
+
return apiBaseUrl.replace(/\/\/api\./u, "//");
|
|
4640
|
+
}
|
|
4641
|
+
async function persistConvergedLogin(input, result, json) {
|
|
4642
|
+
const { profile } = result;
|
|
4643
|
+
try {
|
|
4644
|
+
await input.credentialStore.write({
|
|
4645
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
4646
|
+
tokenId: profile.tokenId,
|
|
4647
|
+
secret: result.accessToken
|
|
4648
|
+
});
|
|
4649
|
+
if (result.refreshToken !== void 0) {
|
|
4650
|
+
await input.credentialStore.write({
|
|
4651
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
4652
|
+
tokenId: refreshSlotTokenId(profile.tokenId),
|
|
4653
|
+
secret: result.refreshToken
|
|
4654
|
+
});
|
|
4655
|
+
}
|
|
4656
|
+
} catch (error) {
|
|
4657
|
+
return fail(
|
|
4658
|
+
`Sift CLI login storage failure: ${error instanceof Error ? error.message : "credential store write failed"}`
|
|
4659
|
+
);
|
|
4660
|
+
}
|
|
4661
|
+
await writeStoredSiftConfig({
|
|
4662
|
+
homeDir: input.homeDir,
|
|
4663
|
+
config: { currentProfile: "default", profiles: { default: profile } }
|
|
4664
|
+
});
|
|
4665
|
+
const scope = {
|
|
4666
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
4667
|
+
tokenLabel: profile.tokenLabel,
|
|
4668
|
+
tokenExpiresAt: profile.tokenExpiresAt,
|
|
4669
|
+
principalId: profile.principalId,
|
|
4670
|
+
workspaceId: profile.workspaceId,
|
|
4671
|
+
brainId: profile.brainId,
|
|
4672
|
+
capabilities: profile.capabilities
|
|
4673
|
+
};
|
|
4674
|
+
return ok(json ? `${JSON.stringify(scope)}
|
|
4675
|
+
` : `Authenticated Sift CLI
|
|
4676
|
+
${renderScope(scope)}`);
|
|
4677
|
+
}
|
|
4678
|
+
|
|
3454
4679
|
// src/auth/loginFlow.ts
|
|
3455
4680
|
var execFileAsync2 = promisify2(execFile2);
|
|
3456
4681
|
function createSiftCliAuthCommands(input) {
|
|
3457
4682
|
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
3458
|
-
const sleep = input.sleep ?? ((ms) => new Promise((
|
|
4683
|
+
const sleep = input.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
3459
4684
|
return {
|
|
3460
4685
|
async login({ rest, json }) {
|
|
3461
4686
|
try {
|
|
3462
|
-
return
|
|
4687
|
+
return await routeLogin(input, rest, sleep, json);
|
|
3463
4688
|
} catch (error) {
|
|
3464
4689
|
return json ? failJson(error instanceof Error ? error.message : "Login failed.") : fail(error instanceof Error ? error.message : "Login failed.");
|
|
3465
4690
|
}
|
|
@@ -3475,21 +4700,18 @@ function createSiftCliAuthCommands(input) {
|
|
|
3475
4700
|
env: input.env,
|
|
3476
4701
|
homeDir: input.homeDir,
|
|
3477
4702
|
credentialStore: input.credentialStore,
|
|
3478
|
-
now: now()
|
|
4703
|
+
now: now(),
|
|
4704
|
+
oauthRefresher: oauthRefresherFor(input, [])
|
|
3479
4705
|
});
|
|
3480
4706
|
}
|
|
3481
4707
|
};
|
|
3482
4708
|
}
|
|
3483
|
-
async function
|
|
3484
|
-
const
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
const stored = await readStoredSiftConfig({ homeDir: input.homeDir });
|
|
3490
|
-
const profile = stored?.profiles[stored.currentProfile];
|
|
3491
|
-
if (profile !== void 0) return normalizeUrl(profile.apiBaseUrl);
|
|
3492
|
-
return "https://api.sift.com";
|
|
4709
|
+
async function routeLogin(input, rest, sleep, json) {
|
|
4710
|
+
const noBrowser = rest.includes("--no-browser");
|
|
4711
|
+
if (oauthLoginSelected({ argv: rest, env: input.env })) {
|
|
4712
|
+
return noBrowser ? serviceTokenLoginFlow(input, rest, json) : oauthBrowserLoginFlow(input, rest, json);
|
|
4713
|
+
}
|
|
4714
|
+
return noBrowser ? deviceLogin(input, rest, sleep, json) : browserLogin(input, rest, json);
|
|
3493
4715
|
}
|
|
3494
4716
|
async function browserLogin(input, rest, json) {
|
|
3495
4717
|
await input.credentialStore.assertAvailable();
|
|
@@ -3503,10 +4725,10 @@ async function browserLogin(input, rest, json) {
|
|
|
3503
4725
|
codeChallenge: pkce.codeChallenge,
|
|
3504
4726
|
codeChallengeMethod: "S256",
|
|
3505
4727
|
stateHash: pkce.stateHash,
|
|
3506
|
-
deviceLabel: input.deviceLabel ??
|
|
4728
|
+
deviceLabel: input.deviceLabel ?? hostname3(),
|
|
3507
4729
|
requestedCapabilities: requestedCapabilities(rest)
|
|
3508
4730
|
});
|
|
3509
|
-
await
|
|
4731
|
+
await tryOpenBrowser2(input.openBrowser, request.authorizeUrl);
|
|
3510
4732
|
const callback = await callbackServer.waitForCallback();
|
|
3511
4733
|
if (callback.state !== pkce.stateHash) {
|
|
3512
4734
|
throw new Error("CLI auth callback state mismatch.");
|
|
@@ -3526,7 +4748,7 @@ async function deviceLogin(input, rest, sleep, json) {
|
|
|
3526
4748
|
await input.credentialStore.assertAvailable();
|
|
3527
4749
|
const apiBaseUrl = await resolveLoginApiBaseUrl({ argv: rest, env: input.env, homeDir: input.homeDir });
|
|
3528
4750
|
const request = await postJson(input.fetch, `${apiBaseUrl}/cli-auth/device`, {
|
|
3529
|
-
deviceLabel: input.deviceLabel ??
|
|
4751
|
+
deviceLabel: input.deviceLabel ?? hostname3(),
|
|
3530
4752
|
requestedCapabilities: requestedCapabilities(rest)
|
|
3531
4753
|
});
|
|
3532
4754
|
let intervalSeconds = request.intervalSeconds;
|
|
@@ -3538,9 +4760,9 @@ async function deviceLogin(input, rest, sleep, json) {
|
|
|
3538
4760
|
{ requestId: request.requestId, userCode: request.userCode }
|
|
3539
4761
|
);
|
|
3540
4762
|
if ("token" in token) {
|
|
3541
|
-
const
|
|
3542
|
-
return
|
|
3543
|
-
${
|
|
4763
|
+
const result = await persistLogin(input, token, json);
|
|
4764
|
+
return result.exitCode === 0 ? { ...result, stdout: `Code: ${request.userCode}
|
|
4765
|
+
${result.stdout}` } : result;
|
|
3544
4766
|
}
|
|
3545
4767
|
if (token.status === "authorization_pending" || token.status === "slow_down") {
|
|
3546
4768
|
intervalSeconds = token.intervalSeconds;
|
|
@@ -3564,8 +4786,8 @@ async function persistLogin(input, token, json) {
|
|
|
3564
4786
|
`Sift CLI login storage failure: ${error instanceof Error ? error.message : "credential store write failed"}`
|
|
3565
4787
|
);
|
|
3566
4788
|
}
|
|
3567
|
-
const
|
|
3568
|
-
await writeStoredSiftConfig({ homeDir: input.homeDir, config
|
|
4789
|
+
const config = configFromToken(token);
|
|
4790
|
+
await writeStoredSiftConfig({ homeDir: input.homeDir, config });
|
|
3569
4791
|
if (oldProfile !== void 0) {
|
|
3570
4792
|
const oldSecret = await input.credentialStore.read({
|
|
3571
4793
|
apiBaseUrl: oldProfile.apiBaseUrl,
|
|
@@ -3604,6 +4826,33 @@ async function authStatus(input, now, json) {
|
|
|
3604
4826
|
if (profile === void 0) {
|
|
3605
4827
|
return ok(json ? '{"auth":"none"}\n' : "Auth: none\n");
|
|
3606
4828
|
}
|
|
4829
|
+
const expired = Date.parse(profile.tokenExpiresAt) <= now.getTime();
|
|
4830
|
+
if (expired) {
|
|
4831
|
+
return staleStoredStatus(
|
|
4832
|
+
profile,
|
|
4833
|
+
"expired",
|
|
4834
|
+
"Stored Sift CLI auth has expired; run `sift login` again.",
|
|
4835
|
+
json
|
|
4836
|
+
);
|
|
4837
|
+
}
|
|
4838
|
+
let secret;
|
|
4839
|
+
try {
|
|
4840
|
+
secret = await input.credentialStore.read({
|
|
4841
|
+
apiBaseUrl: profile.apiBaseUrl,
|
|
4842
|
+
tokenId: profile.tokenId
|
|
4843
|
+
});
|
|
4844
|
+
} catch (error) {
|
|
4845
|
+
const message = error instanceof Error ? error.message : "Stored Sift credential store could not be read; run `sift login` again.";
|
|
4846
|
+
return staleStoredStatus(profile, "credential_store_unavailable", message, json);
|
|
4847
|
+
}
|
|
4848
|
+
if (secret === void 0) {
|
|
4849
|
+
return staleStoredStatus(
|
|
4850
|
+
profile,
|
|
4851
|
+
"credential_missing",
|
|
4852
|
+
"Stored Sift credential store secret is missing; run `sift login` again.",
|
|
4853
|
+
json
|
|
4854
|
+
);
|
|
4855
|
+
}
|
|
3607
4856
|
return ok(
|
|
3608
4857
|
json ? `${JSON.stringify({ auth: "stored", ...profile })}
|
|
3609
4858
|
` : [
|
|
@@ -3618,6 +4867,24 @@ async function authStatus(input, now, json) {
|
|
|
3618
4867
|
].join("\n")
|
|
3619
4868
|
);
|
|
3620
4869
|
}
|
|
4870
|
+
function staleStoredStatus(profile, reason, message, json) {
|
|
4871
|
+
if (json) {
|
|
4872
|
+
return ok(
|
|
4873
|
+
`${JSON.stringify({ auth: "stored", status: "stale", reason, message, ...profile })}
|
|
4874
|
+
`
|
|
4875
|
+
);
|
|
4876
|
+
}
|
|
4877
|
+
return ok(
|
|
4878
|
+
[
|
|
4879
|
+
"Auth: stale",
|
|
4880
|
+
`Reason: ${reason}`,
|
|
4881
|
+
message,
|
|
4882
|
+
`API: ${profile.apiBaseUrl}`,
|
|
4883
|
+
`Token: ${profile.tokenLabel}`,
|
|
4884
|
+
""
|
|
4885
|
+
].join("\n")
|
|
4886
|
+
);
|
|
4887
|
+
}
|
|
3621
4888
|
async function logout(input, now, json) {
|
|
3622
4889
|
if (clean2(input.env.SIFT_API_TOKEN) !== void 0) {
|
|
3623
4890
|
return ok(json ? '{"status":"env_auth_active"}\n' : "Auth: env\nUnset SIFT_API_TOKEN to log out.\n");
|
|
@@ -3652,10 +4919,6 @@ function okStatus(source, loaded, json) {
|
|
|
3652
4919
|
${renderScope(loaded.config)}`
|
|
3653
4920
|
);
|
|
3654
4921
|
}
|
|
3655
|
-
function requestedCapabilities(rest) {
|
|
3656
|
-
const option = parseOptions(rest).get("capability");
|
|
3657
|
-
return option === void 0 ? ["record:read"] : option.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
3658
|
-
}
|
|
3659
4922
|
function configFromToken(token) {
|
|
3660
4923
|
return {
|
|
3661
4924
|
currentProfile: "default",
|
|
@@ -3674,7 +4937,7 @@ function configFromToken(token) {
|
|
|
3674
4937
|
}
|
|
3675
4938
|
};
|
|
3676
4939
|
}
|
|
3677
|
-
async function
|
|
4940
|
+
async function tryOpenBrowser2(openBrowser, url) {
|
|
3678
4941
|
await (openBrowser ?? openBrowserUrl)(url).catch(() => void 0);
|
|
3679
4942
|
}
|
|
3680
4943
|
async function openBrowserUrl(url) {
|
|
@@ -3704,16 +4967,6 @@ async function postJson(fetchImpl, url, body, headers = {}) {
|
|
|
3704
4967
|
}
|
|
3705
4968
|
return parsed;
|
|
3706
4969
|
}
|
|
3707
|
-
function errorMessage2(parsed, status2) {
|
|
3708
|
-
if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
|
|
3709
|
-
const error = parsed.error;
|
|
3710
|
-
if (typeof error === "object" && error !== null && "message" in error) {
|
|
3711
|
-
const message = error.message;
|
|
3712
|
-
if (typeof message === "string") return message;
|
|
3713
|
-
}
|
|
3714
|
-
}
|
|
3715
|
-
return `CLI auth request failed with status ${status2}.`;
|
|
3716
|
-
}
|
|
3717
4970
|
function failJson(message) {
|
|
3718
4971
|
return {
|
|
3719
4972
|
exitCode: 1,
|
|
@@ -3722,12 +4975,243 @@ function failJson(message) {
|
|
|
3722
4975
|
stderr: ""
|
|
3723
4976
|
};
|
|
3724
4977
|
}
|
|
3725
|
-
|
|
3726
|
-
|
|
4978
|
+
|
|
4979
|
+
// src/roamMcpReader.ts
|
|
4980
|
+
import { spawn } from "child_process";
|
|
4981
|
+
function createRoamMcpReader(input = {}) {
|
|
4982
|
+
return {
|
|
4983
|
+
async exportPages(request) {
|
|
4984
|
+
const client = createRoamMcpJsonLineClient({
|
|
4985
|
+
command: input.command ?? "npx",
|
|
4986
|
+
args: input.args ?? ["-y", "@roam-research/roam-mcp"],
|
|
4987
|
+
spawnProcess: input.spawnProcess ?? spawn
|
|
4988
|
+
});
|
|
4989
|
+
try {
|
|
4990
|
+
return await exportRoamPagesFromMcp(client, request);
|
|
4991
|
+
} finally {
|
|
4992
|
+
await client.close();
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
};
|
|
3727
4996
|
}
|
|
3728
|
-
function
|
|
3729
|
-
const
|
|
3730
|
-
|
|
4997
|
+
async function exportRoamPagesFromMcp(client, input) {
|
|
4998
|
+
const graphId = await resolveGraphId(client, input.graph);
|
|
4999
|
+
await client.callTool("get_graph_guidelines", graphArgs(graphId)).catch(() => void 0);
|
|
5000
|
+
const tuples = input.scope === "sift_tag" ? await queryMarkedPageTuples(client, graphId, input.limit) : await queryWholeGraphPageTuples(client, graphId, input.limit);
|
|
5001
|
+
const records = [];
|
|
5002
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5003
|
+
for (const tuple of tuples) {
|
|
5004
|
+
const key = tuple.uid ?? tuple.title;
|
|
5005
|
+
if (seen.has(key)) continue;
|
|
5006
|
+
seen.add(key);
|
|
5007
|
+
const markdown = await fetchPageMarkdown(client, graphId, tuple);
|
|
5008
|
+
records.push({
|
|
5009
|
+
graphId,
|
|
5010
|
+
pageUid: tuple.uid ?? stableFallbackUid(tuple.title),
|
|
5011
|
+
pageTitle: tuple.title,
|
|
5012
|
+
markdown,
|
|
5013
|
+
scope: input.scope,
|
|
5014
|
+
blockCount: estimateBlockCount(markdown),
|
|
5015
|
+
importedAt: input.now.toISOString()
|
|
5016
|
+
});
|
|
5017
|
+
}
|
|
5018
|
+
return records;
|
|
5019
|
+
}
|
|
5020
|
+
async function resolveGraphId(client, graph) {
|
|
5021
|
+
if (graph !== void 0) return graph;
|
|
5022
|
+
const result = await client.callTool("list_graphs", {});
|
|
5023
|
+
const parsed = parseToolJson(result);
|
|
5024
|
+
const graphId = firstGraphId(parsed);
|
|
5025
|
+
if (graphId !== void 0) return graphId;
|
|
5026
|
+
const message = errorMessageFromParsedTool(parsed);
|
|
5027
|
+
if (message !== void 0) throw new Error(message);
|
|
5028
|
+
return "local-graph";
|
|
5029
|
+
}
|
|
5030
|
+
async function queryMarkedPageTuples(client, graphId, limit) {
|
|
5031
|
+
const result = await client.callTool("datalog_query", {
|
|
5032
|
+
...graphArgs(graphId),
|
|
5033
|
+
query: '[:find ?title ?uid :where [?tag :node/title "Sift"] [?block :block/refs ?tag] [?block :block/page ?page] [?page :node/title ?title] [?page :block/uid ?uid]]'
|
|
5034
|
+
});
|
|
5035
|
+
return tuplesFromToolResult(result).slice(0, limit);
|
|
5036
|
+
}
|
|
5037
|
+
async function queryWholeGraphPageTuples(client, graphId, limit) {
|
|
5038
|
+
const result = await client.callTool("datalog_query", {
|
|
5039
|
+
...graphArgs(graphId),
|
|
5040
|
+
query: "[:find ?title ?uid :where [?page :node/title ?title] [?page :block/uid ?uid]]"
|
|
5041
|
+
});
|
|
5042
|
+
return tuplesFromToolResult(result).slice(0, limit);
|
|
5043
|
+
}
|
|
5044
|
+
async function fetchPageMarkdown(client, graphId, tuple) {
|
|
5045
|
+
const result = await client.callTool("get_page", {
|
|
5046
|
+
...graphArgs(graphId),
|
|
5047
|
+
...tuple.uid === void 0 ? { title: tuple.title } : { uid: tuple.uid }
|
|
5048
|
+
});
|
|
5049
|
+
const text = toolText(result);
|
|
5050
|
+
const parsed = parseJsonIfPossible(text);
|
|
5051
|
+
const markdown = markdownFromParsed(parsed) ?? text;
|
|
5052
|
+
return stripRoamMetadataTags(markdown).trim();
|
|
5053
|
+
}
|
|
5054
|
+
function graphArgs(graphId) {
|
|
5055
|
+
return { graph: graphId };
|
|
5056
|
+
}
|
|
5057
|
+
function tuplesFromToolResult(result) {
|
|
5058
|
+
const parsed = parseToolJson(result);
|
|
5059
|
+
const values = candidateArrays(parsed);
|
|
5060
|
+
const tuples = [];
|
|
5061
|
+
for (const value of values) {
|
|
5062
|
+
if (Array.isArray(value) && typeof value[0] === "string") {
|
|
5063
|
+
tuples.push({ title: value[0], ...typeof value[1] === "string" ? { uid: value[1] } : {} });
|
|
5064
|
+
} else if (isRecord(value)) {
|
|
5065
|
+
const title = stringProperty(value, ["title", "pageTitle", "name"]);
|
|
5066
|
+
const uid = stringProperty(value, ["uid", "pageUid"]);
|
|
5067
|
+
if (title !== void 0) tuples.push({ title, ...uid === void 0 ? {} : { uid } });
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
return tuples;
|
|
5071
|
+
}
|
|
5072
|
+
function parseToolJson(result) {
|
|
5073
|
+
const text = toolText(result);
|
|
5074
|
+
return parseJsonIfPossible(text);
|
|
5075
|
+
}
|
|
5076
|
+
function toolText(result) {
|
|
5077
|
+
if (isRecord(result) && Array.isArray(result.content)) {
|
|
5078
|
+
return result.content.flatMap((item) => isRecord(item) && typeof item.text === "string" ? [item.text] : []).join("\n").trim();
|
|
5079
|
+
}
|
|
5080
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
5081
|
+
}
|
|
5082
|
+
function parseJsonIfPossible(text) {
|
|
5083
|
+
try {
|
|
5084
|
+
return JSON.parse(text);
|
|
5085
|
+
} catch {
|
|
5086
|
+
return text;
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
function candidateArrays(value) {
|
|
5090
|
+
if (Array.isArray(value)) return value;
|
|
5091
|
+
if (!isRecord(value)) return [];
|
|
5092
|
+
for (const key of ["results", "result", "data", "rows", "pages"]) {
|
|
5093
|
+
const candidate = value[key];
|
|
5094
|
+
if (Array.isArray(candidate)) return candidate;
|
|
5095
|
+
}
|
|
5096
|
+
return [];
|
|
5097
|
+
}
|
|
5098
|
+
function markdownFromParsed(value) {
|
|
5099
|
+
if (typeof value === "string") return value;
|
|
5100
|
+
if (!isRecord(value)) return void 0;
|
|
5101
|
+
return stringProperty(value, ["markdown", "content", "text"]);
|
|
5102
|
+
}
|
|
5103
|
+
function firstGraphId(value) {
|
|
5104
|
+
const graphs = isRecord(value) && Array.isArray(value.graphs) ? value.graphs : candidateArrays(value);
|
|
5105
|
+
for (const graph of graphs) {
|
|
5106
|
+
if (!isRecord(graph)) continue;
|
|
5107
|
+
const id = stringProperty(graph, ["nickname", "graph", "name", "id"]);
|
|
5108
|
+
if (id !== void 0) return id;
|
|
5109
|
+
}
|
|
5110
|
+
return void 0;
|
|
5111
|
+
}
|
|
5112
|
+
function errorMessageFromParsedTool(value) {
|
|
5113
|
+
if (!isRecord(value) || !isRecord(value.error)) return void 0;
|
|
5114
|
+
const message = value.error.message;
|
|
5115
|
+
return typeof message === "string" ? message : void 0;
|
|
5116
|
+
}
|
|
5117
|
+
function stringProperty(record, keys) {
|
|
5118
|
+
for (const key of keys) {
|
|
5119
|
+
const value = record[key];
|
|
5120
|
+
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
5121
|
+
}
|
|
5122
|
+
return void 0;
|
|
5123
|
+
}
|
|
5124
|
+
function stripRoamMetadataTags(markdown) {
|
|
5125
|
+
return markdown.replace(/<roam\b[^>]*>/giu, "").replace(/<\/roam>/giu, "");
|
|
5126
|
+
}
|
|
5127
|
+
function estimateBlockCount(markdown) {
|
|
5128
|
+
const lines = markdown.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
5129
|
+
return Math.max(1, lines.length);
|
|
5130
|
+
}
|
|
5131
|
+
function stableFallbackUid(title) {
|
|
5132
|
+
return title.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 80);
|
|
5133
|
+
}
|
|
5134
|
+
function isRecord(value) {
|
|
5135
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5136
|
+
}
|
|
5137
|
+
function createRoamMcpJsonLineClient(input) {
|
|
5138
|
+
const child = input.spawnProcess(input.command, input.args, {
|
|
5139
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5140
|
+
});
|
|
5141
|
+
child.stderr.resume();
|
|
5142
|
+
child.stdout.setEncoding("utf8");
|
|
5143
|
+
let nextId = 1;
|
|
5144
|
+
let buffer = "";
|
|
5145
|
+
const pending = /* @__PURE__ */ new Map();
|
|
5146
|
+
child.stdout.on("data", (chunk) => {
|
|
5147
|
+
buffer += chunk;
|
|
5148
|
+
let newline = buffer.indexOf("\n");
|
|
5149
|
+
while (newline >= 0) {
|
|
5150
|
+
const line = buffer.slice(0, newline).trim();
|
|
5151
|
+
buffer = buffer.slice(newline + 1);
|
|
5152
|
+
if (line.length > 0) handleJsonRpcLine(line, pending);
|
|
5153
|
+
newline = buffer.indexOf("\n");
|
|
5154
|
+
}
|
|
5155
|
+
});
|
|
5156
|
+
child.on("error", (error) => {
|
|
5157
|
+
for (const entry of pending.values()) entry.reject(error);
|
|
5158
|
+
pending.clear();
|
|
5159
|
+
});
|
|
5160
|
+
child.on("exit", () => {
|
|
5161
|
+
for (const entry of pending.values()) entry.reject(new Error("Roam MCP server exited."));
|
|
5162
|
+
pending.clear();
|
|
5163
|
+
});
|
|
5164
|
+
const request = (method, params) => {
|
|
5165
|
+
const id = nextId;
|
|
5166
|
+
nextId += 1;
|
|
5167
|
+
const promise = new Promise((resolve2, reject) => {
|
|
5168
|
+
pending.set(id, { resolve: resolve2, reject });
|
|
5169
|
+
});
|
|
5170
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
|
|
5171
|
+
`);
|
|
5172
|
+
return promise;
|
|
5173
|
+
};
|
|
5174
|
+
const initialized = request("initialize", {
|
|
5175
|
+
protocolVersion: "2025-11-25",
|
|
5176
|
+
capabilities: {},
|
|
5177
|
+
clientInfo: { name: "sift-roam-import", version: "0.1.0" }
|
|
5178
|
+
}).then(() => {
|
|
5179
|
+
child.stdin.write(
|
|
5180
|
+
`${JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })}
|
|
5181
|
+
`
|
|
5182
|
+
);
|
|
5183
|
+
});
|
|
5184
|
+
return {
|
|
5185
|
+
async callTool(name, args) {
|
|
5186
|
+
await initialized;
|
|
5187
|
+
return request("tools/call", { name, arguments: args });
|
|
5188
|
+
},
|
|
5189
|
+
async close() {
|
|
5190
|
+
child.stdin.end();
|
|
5191
|
+
child.kill("SIGTERM");
|
|
5192
|
+
}
|
|
5193
|
+
};
|
|
5194
|
+
}
|
|
5195
|
+
function handleJsonRpcLine(line, pending) {
|
|
5196
|
+
let parsed;
|
|
5197
|
+
try {
|
|
5198
|
+
parsed = JSON.parse(line);
|
|
5199
|
+
} catch {
|
|
5200
|
+
return;
|
|
5201
|
+
}
|
|
5202
|
+
if (typeof parsed.id !== "number") return;
|
|
5203
|
+
const entry = pending.get(parsed.id);
|
|
5204
|
+
if (entry === void 0) return;
|
|
5205
|
+
pending.delete(parsed.id);
|
|
5206
|
+
if (parsed.error !== void 0) {
|
|
5207
|
+
entry.reject(
|
|
5208
|
+
new Error(
|
|
5209
|
+
typeof parsed.error.message === "string" ? parsed.error.message : "Roam MCP error."
|
|
5210
|
+
)
|
|
5211
|
+
);
|
|
5212
|
+
return;
|
|
5213
|
+
}
|
|
5214
|
+
entry.resolve(parsed.result);
|
|
3731
5215
|
}
|
|
3732
5216
|
|
|
3733
5217
|
// src/bin/sift.ts
|
|
@@ -3738,49 +5222,92 @@ var authCommands = createSiftCliAuthCommands({
|
|
|
3738
5222
|
credentialStore,
|
|
3739
5223
|
fetch
|
|
3740
5224
|
});
|
|
3741
|
-
var loadedAuth = await authCommands.loadAuth();
|
|
3742
|
-
var config = loadedAuth?.config ?? {
|
|
3743
|
-
apiBaseUrl: "",
|
|
3744
|
-
tokenLabel: "unset",
|
|
3745
|
-
workspaceId: "",
|
|
3746
|
-
brainId: "",
|
|
3747
|
-
principalId: "",
|
|
3748
|
-
capabilities: []
|
|
3749
|
-
};
|
|
3750
5225
|
var { argv, agentName } = extractAgentName(process.argv.slice(2), process.env.SIFT_AGENT);
|
|
3751
|
-
var
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
5226
|
+
var startupAuth = await loadAuthForStartup(authCommands, argv);
|
|
5227
|
+
if (startupAuth.ok === false) {
|
|
5228
|
+
process.stderr.write(`${startupAuth.message}
|
|
5229
|
+
`);
|
|
5230
|
+
process.exitCode = 1;
|
|
5231
|
+
} else {
|
|
5232
|
+
const loadedAuth = startupAuth.loadedAuth;
|
|
5233
|
+
const config = loadedAuth?.config ?? {
|
|
5234
|
+
apiBaseUrl: "",
|
|
5235
|
+
tokenLabel: "unset",
|
|
5236
|
+
workspaceId: "",
|
|
5237
|
+
brainId: "",
|
|
5238
|
+
principalId: "",
|
|
5239
|
+
capabilities: []
|
|
5240
|
+
};
|
|
5241
|
+
const result = await runSiftCli({
|
|
5242
|
+
argv,
|
|
5243
|
+
config,
|
|
5244
|
+
readStdin,
|
|
5245
|
+
agentName,
|
|
5246
|
+
executor: loadedAuth === void 0 ? void 0 : createHostedApiExecutor({
|
|
5247
|
+
apiBaseUrl: loadedAuth.config.apiBaseUrl,
|
|
5248
|
+
token: loadedAuth.token,
|
|
5249
|
+
workspaceId: loadedAuth.config.workspaceId,
|
|
5250
|
+
brainId: loadedAuth.config.brainId,
|
|
5251
|
+
agentName
|
|
5252
|
+
}),
|
|
5253
|
+
roamReader: createRoamMcpReader(),
|
|
5254
|
+
roamImporter: loadedAuth === void 0 ? void 0 : createSiftRoamImportClient({
|
|
5255
|
+
apiBaseUrl: loadedAuth.config.apiBaseUrl,
|
|
5256
|
+
token: loadedAuth.token,
|
|
5257
|
+
workspaceId: loadedAuth.config.workspaceId
|
|
5258
|
+
}),
|
|
5259
|
+
authCommands,
|
|
5260
|
+
mcpServer: {
|
|
5261
|
+
serve: async ({ config: config2, executor }) => {
|
|
5262
|
+
if (executor === void 0) {
|
|
5263
|
+
throw new Error(
|
|
5264
|
+
"Not signed in. Run 'sift login' to authenticate, then 'sift mcp serve' to start the local MCP server."
|
|
5265
|
+
);
|
|
5266
|
+
}
|
|
5267
|
+
const { createLocalMcpStdioServer: createLocalMcpStdioServer2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
5268
|
+
return createLocalMcpStdioServer2({
|
|
5269
|
+
input: process.stdin,
|
|
5270
|
+
output: process.stdout,
|
|
5271
|
+
error: process.stderr
|
|
5272
|
+
}).serve({
|
|
5273
|
+
capabilities: config2.capabilities,
|
|
5274
|
+
executor
|
|
5275
|
+
});
|
|
3768
5276
|
}
|
|
3769
|
-
const { createLocalMcpStdioServer: createLocalMcpStdioServer2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
3770
|
-
return createLocalMcpStdioServer2({
|
|
3771
|
-
input: process.stdin,
|
|
3772
|
-
output: process.stdout,
|
|
3773
|
-
error: process.stderr
|
|
3774
|
-
}).serve({
|
|
3775
|
-
capabilities: config2.capabilities,
|
|
3776
|
-
executor
|
|
3777
|
-
});
|
|
3778
5277
|
}
|
|
5278
|
+
});
|
|
5279
|
+
process.stdout.write(result.stdout);
|
|
5280
|
+
process.stderr.write(result.stderr);
|
|
5281
|
+
process.exitCode = result.exitCode;
|
|
5282
|
+
}
|
|
5283
|
+
async function loadAuthForStartup(commands, args) {
|
|
5284
|
+
try {
|
|
5285
|
+
const loadedAuth = await commands.loadAuth();
|
|
5286
|
+
if (loadedAuth === void 0 && !canRunWithoutLoadedAuth(args)) {
|
|
5287
|
+
return { ok: false, message: missingAuthMessage(args) };
|
|
5288
|
+
}
|
|
5289
|
+
return { ok: true, loadedAuth };
|
|
5290
|
+
} catch (error) {
|
|
5291
|
+
if (canRunWithoutLoadedAuth(args)) {
|
|
5292
|
+
return { ok: true, loadedAuth: void 0 };
|
|
5293
|
+
}
|
|
5294
|
+
return {
|
|
5295
|
+
ok: false,
|
|
5296
|
+
message: error instanceof Error ? error.message : "Failed to load Sift CLI auth."
|
|
5297
|
+
};
|
|
3779
5298
|
}
|
|
3780
|
-
}
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
5299
|
+
}
|
|
5300
|
+
function missingAuthMessage(args) {
|
|
5301
|
+
const commandArgs = args.filter((arg) => arg !== "--json");
|
|
5302
|
+
const [group, command] = commandArgs;
|
|
5303
|
+
const capability = group === "roam" && command === "import" ? " --capability record:read,source:manage" : "";
|
|
5304
|
+
return `Not signed in. Run 'npx -y @sift-wiki/cli@latest login --api-base-url ${DEFAULT_SIFT_API_BASE_URL}${capability}', then retry this command.`;
|
|
5305
|
+
}
|
|
5306
|
+
function canRunWithoutLoadedAuth(args) {
|
|
5307
|
+
const commandArgs = args.filter((arg) => arg !== "--json");
|
|
5308
|
+
const [group, command] = commandArgs;
|
|
5309
|
+
return group === void 0 || group === "help" || group === "--help" || group === "doctor" || group === "skill" || group === "login" || group === "logout" || group === "auth" && command === "status";
|
|
5310
|
+
}
|
|
3784
5311
|
function extractAgentName(args, envAgentName) {
|
|
3785
5312
|
const flagIndex = args.indexOf("--as-agent");
|
|
3786
5313
|
if (flagIndex !== -1 && args[flagIndex + 1] !== void 0) {
|