@sidecar-ai/cli 0.1.0-alpha.1 → 0.1.0-alpha.10
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/dist/index.js +1852 -403
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/dist/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// packages/cli/src/index.ts
|
|
4
|
-
import { existsSync as
|
|
5
|
-
import { mkdir as
|
|
4
|
+
import { existsSync as existsSync7, realpathSync } from "fs";
|
|
5
|
+
import { mkdir as mkdir10, readFile as readFile8, writeFile as writeFile10 } from "fs/promises";
|
|
6
6
|
import { createServer as createServer2 } from "http";
|
|
7
|
-
import
|
|
7
|
+
import path20 from "path";
|
|
8
8
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
9
|
-
import { fileURLToPath, pathToFileURL } from "url";
|
|
9
|
+
import { fileURLToPath, pathToFileURL as pathToFileURL3 } from "url";
|
|
10
10
|
import { cwd, exit, stdin as stdin2, stdout as stdout2 } from "process";
|
|
11
|
-
import { tsImport } from "tsx/esm/api";
|
|
11
|
+
import { tsImport as tsImport2 } from "tsx/esm/api";
|
|
12
12
|
|
|
13
13
|
// packages/auth/src/core.ts
|
|
14
14
|
var authBrand = /* @__PURE__ */ Symbol.for("sidecar.auth");
|
|
15
15
|
function isSidecarAuth(value) {
|
|
16
16
|
return Boolean(
|
|
17
|
-
value && typeof value === "object" && value[authBrand]
|
|
17
|
+
value && typeof value === "object" && (value[authBrand] || value.kind === "sidecar.auth")
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -26,13 +26,19 @@ var resourceBrand = /* @__PURE__ */ Symbol.for("sidecar.resource");
|
|
|
26
26
|
var resourceResultBrand = /* @__PURE__ */ Symbol.for("sidecar.resourceResult");
|
|
27
27
|
var promptBrand = /* @__PURE__ */ Symbol.for("sidecar.prompt");
|
|
28
28
|
function isSidecarTool(value) {
|
|
29
|
-
return Boolean(
|
|
29
|
+
return Boolean(
|
|
30
|
+
value && typeof value === "object" && (value[toolBrand] || value.kind === "sidecar.tool")
|
|
31
|
+
);
|
|
30
32
|
}
|
|
31
33
|
function isSidecarResource(value) {
|
|
32
|
-
return Boolean(
|
|
34
|
+
return Boolean(
|
|
35
|
+
value && typeof value === "object" && (value[resourceBrand] || value.kind === "sidecar.resource")
|
|
36
|
+
);
|
|
33
37
|
}
|
|
34
38
|
function isSidecarPrompt(value) {
|
|
35
|
-
return Boolean(
|
|
39
|
+
return Boolean(
|
|
40
|
+
value && typeof value === "object" && (value[promptBrand] || value.kind === "sidecar.prompt")
|
|
41
|
+
);
|
|
36
42
|
}
|
|
37
43
|
var toolResult = Object.assign(
|
|
38
44
|
createToolResult,
|
|
@@ -60,9 +66,9 @@ var resourceResult = Object.assign(
|
|
|
60
66
|
);
|
|
61
67
|
function createToolResult(input) {
|
|
62
68
|
const resultEnvelope = stripUndefined({
|
|
63
|
-
structuredContent: input.structuredContent,
|
|
69
|
+
structuredContent: stripJsonUndefined(input.structuredContent),
|
|
64
70
|
content: normalizeRequiredContent(input.content),
|
|
65
|
-
_meta: input.meta,
|
|
71
|
+
_meta: stripJsonUndefined(input.meta),
|
|
66
72
|
isError: input.isError
|
|
67
73
|
});
|
|
68
74
|
Object.defineProperty(resultEnvelope, toolResultBrand, {
|
|
@@ -106,9 +112,9 @@ function normalizeToolResult(value) {
|
|
|
106
112
|
);
|
|
107
113
|
}
|
|
108
114
|
return stripUndefined({
|
|
109
|
-
structuredContent: value.structuredContent,
|
|
115
|
+
structuredContent: stripJsonUndefined(value.structuredContent),
|
|
110
116
|
content: value.content ?? [],
|
|
111
|
-
_meta: value._meta,
|
|
117
|
+
_meta: stripJsonUndefined(value._meta),
|
|
112
118
|
isError: value.isError
|
|
113
119
|
});
|
|
114
120
|
}
|
|
@@ -447,10 +453,24 @@ function normalizePromptResult(value, defaultDescription) {
|
|
|
447
453
|
function stripUndefined(value) {
|
|
448
454
|
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
|
|
449
455
|
}
|
|
456
|
+
function stripJsonUndefined(value) {
|
|
457
|
+
if (value === void 0) {
|
|
458
|
+
return void 0;
|
|
459
|
+
}
|
|
460
|
+
if (Array.isArray(value)) {
|
|
461
|
+
return value.map((entry) => stripJsonUndefined(entry)).filter((entry) => entry !== void 0);
|
|
462
|
+
}
|
|
463
|
+
if (!value || typeof value !== "object") {
|
|
464
|
+
return value;
|
|
465
|
+
}
|
|
466
|
+
return Object.fromEntries(
|
|
467
|
+
Object.entries(value).map(([key, entry]) => [key, stripJsonUndefined(entry)]).filter(([, entry]) => entry !== void 0)
|
|
468
|
+
);
|
|
469
|
+
}
|
|
450
470
|
|
|
451
471
|
// packages/compiler/src/analyze.ts
|
|
452
472
|
import { readdir } from "fs/promises";
|
|
453
|
-
import
|
|
473
|
+
import path5 from "path";
|
|
454
474
|
import {
|
|
455
475
|
Node as Node4,
|
|
456
476
|
SyntaxKind as SyntaxKind2
|
|
@@ -617,14 +637,7 @@ function devSidecarTypePaths() {
|
|
|
617
637
|
import {
|
|
618
638
|
Node as Node2
|
|
619
639
|
} from "ts-morph";
|
|
620
|
-
function getParamsSchema(
|
|
621
|
-
const explicitParams = definition.getProperty("params");
|
|
622
|
-
if (explicitParams && Node2.isPropertyAssignment(explicitParams)) {
|
|
623
|
-
const inferred = getSchemaFromZodProperty(explicitParams);
|
|
624
|
-
if (inferred) {
|
|
625
|
-
return inferred;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
640
|
+
function getParamsSchema(_definition, execute) {
|
|
628
641
|
const [params] = execute.getParameters();
|
|
629
642
|
if (!params) {
|
|
630
643
|
return emptyObjectSchema();
|
|
@@ -645,74 +658,6 @@ function getOutputSchema(definition, execute) {
|
|
|
645
658
|
}
|
|
646
659
|
return typeToJsonSchema(returnType);
|
|
647
660
|
}
|
|
648
|
-
function getSchemaFromZodProperty(property) {
|
|
649
|
-
const initializer = property.getInitializer();
|
|
650
|
-
if (!initializer) {
|
|
651
|
-
return void 0;
|
|
652
|
-
}
|
|
653
|
-
if (Node2.isCallExpression(initializer) && initializer.getExpression().getText().endsWith(".object")) {
|
|
654
|
-
const [shape] = initializer.getArguments();
|
|
655
|
-
if (shape && Node2.isObjectLiteralExpression(shape)) {
|
|
656
|
-
return zodObjectLiteralToSchema(shape);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return void 0;
|
|
660
|
-
}
|
|
661
|
-
function zodObjectLiteralToSchema(shape) {
|
|
662
|
-
const properties = {};
|
|
663
|
-
const required = [];
|
|
664
|
-
for (const property of shape.getProperties()) {
|
|
665
|
-
if (!Node2.isPropertyAssignment(property)) {
|
|
666
|
-
continue;
|
|
667
|
-
}
|
|
668
|
-
const name = property.getName().replace(/^["']|["']$/g, "");
|
|
669
|
-
const initializer = property.getInitializer();
|
|
670
|
-
if (!initializer) {
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
|
-
const propertySchema = zodExpressionToSchema(initializer);
|
|
674
|
-
properties[name] = propertySchema.schema;
|
|
675
|
-
if (!propertySchema.optional) {
|
|
676
|
-
required.push(name);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return {
|
|
680
|
-
type: "object",
|
|
681
|
-
properties,
|
|
682
|
-
required,
|
|
683
|
-
additionalProperties: false
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
function zodExpressionToSchema(expression) {
|
|
687
|
-
const text = expression.getText();
|
|
688
|
-
const optional = /\.optional\(\)/.test(text) || /\.default\(/.test(text);
|
|
689
|
-
const schema = {};
|
|
690
|
-
if (/z\.string\(\)/.test(text)) {
|
|
691
|
-
schema.type = "string";
|
|
692
|
-
} else if (/z\.number\(\)/.test(text)) {
|
|
693
|
-
schema.type = "number";
|
|
694
|
-
} else if (/z\.boolean\(\)/.test(text)) {
|
|
695
|
-
schema.type = "boolean";
|
|
696
|
-
} else if (/z\.array\(/.test(text)) {
|
|
697
|
-
schema.type = "array";
|
|
698
|
-
schema.items = {};
|
|
699
|
-
} else {
|
|
700
|
-
schema.type = "object";
|
|
701
|
-
}
|
|
702
|
-
const min = text.match(/\.min\((\d+)/);
|
|
703
|
-
const max = text.match(/\.max\((\d+)/);
|
|
704
|
-
if (schema.type === "string") {
|
|
705
|
-
if (min) schema.minLength = Number(min[1]);
|
|
706
|
-
if (max) schema.maxLength = Number(max[1]);
|
|
707
|
-
} else {
|
|
708
|
-
if (min) schema.minimum = Number(min[1]);
|
|
709
|
-
if (max) schema.maximum = Number(max[1]);
|
|
710
|
-
}
|
|
711
|
-
if (/\.int\(\)/.test(text)) {
|
|
712
|
-
schema.type = "integer";
|
|
713
|
-
}
|
|
714
|
-
return { schema, optional };
|
|
715
|
-
}
|
|
716
661
|
function typeToJsonSchema(type, description) {
|
|
717
662
|
const withoutUndefined = removeUndefined(type);
|
|
718
663
|
const schema = typeToJsonSchemaInner(withoutUndefined);
|
|
@@ -755,12 +700,19 @@ function typeToJsonSchemaInner(type) {
|
|
|
755
700
|
return { anyOf: parts.map((part) => typeToJsonSchema(part)) };
|
|
756
701
|
}
|
|
757
702
|
const properties = type.getProperties();
|
|
703
|
+
const indexType = type.getStringIndexType() ?? type.getNumberIndexType();
|
|
758
704
|
if (properties.length > 0) {
|
|
759
|
-
return objectTypeToSchema(properties);
|
|
705
|
+
return objectTypeToSchema(properties, indexType);
|
|
706
|
+
}
|
|
707
|
+
if (indexType) {
|
|
708
|
+
return {
|
|
709
|
+
type: "object",
|
|
710
|
+
additionalProperties: indexTypeToAdditionalProperties(indexType)
|
|
711
|
+
};
|
|
760
712
|
}
|
|
761
713
|
return {};
|
|
762
714
|
}
|
|
763
|
-
function objectTypeToSchema(properties) {
|
|
715
|
+
function objectTypeToSchema(properties, indexType) {
|
|
764
716
|
const schemaProperties = {};
|
|
765
717
|
const required = [];
|
|
766
718
|
for (const property of properties) {
|
|
@@ -784,9 +736,16 @@ function objectTypeToSchema(properties) {
|
|
|
784
736
|
type: "object",
|
|
785
737
|
properties: schemaProperties,
|
|
786
738
|
required,
|
|
787
|
-
additionalProperties: false
|
|
739
|
+
additionalProperties: indexType ? indexTypeToAdditionalProperties(indexType) : false
|
|
788
740
|
};
|
|
789
741
|
}
|
|
742
|
+
function indexTypeToAdditionalProperties(type) {
|
|
743
|
+
if (type.isAny() || type.isUnknown()) {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
const schema = typeToJsonSchema(type);
|
|
747
|
+
return Object.keys(schema).length ? schema : true;
|
|
748
|
+
}
|
|
790
749
|
function literalOrPrimitive(type, primitive) {
|
|
791
750
|
if (isLiteralType(type)) {
|
|
792
751
|
return { const: literalValue(type) };
|
|
@@ -844,39 +803,134 @@ function schemaDescription(symbol) {
|
|
|
844
803
|
}).find((comment) => comment.trim().length > 0);
|
|
845
804
|
}
|
|
846
805
|
|
|
806
|
+
// packages/compiler/src/runtime-schema.ts
|
|
807
|
+
import { existsSync as existsSync2 } from "fs";
|
|
808
|
+
import path3 from "path";
|
|
809
|
+
import { pathToFileURL } from "url";
|
|
810
|
+
import { tsImport } from "tsx/esm/api";
|
|
811
|
+
async function readRuntimeToolInputSchema(rootDir, sourcePath) {
|
|
812
|
+
let sidecarTool;
|
|
813
|
+
try {
|
|
814
|
+
sidecarTool = await importRuntimeTool(rootDir, sourcePath);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
warnRuntimeSchemaFallback(sourcePath, error);
|
|
817
|
+
return void 0;
|
|
818
|
+
}
|
|
819
|
+
if (!sidecarTool.params) {
|
|
820
|
+
return void 0;
|
|
821
|
+
}
|
|
822
|
+
try {
|
|
823
|
+
return await paramsToJsonSchema(sidecarTool.params);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
warnRuntimeSchemaFallback(sourcePath, error);
|
|
826
|
+
return void 0;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
async function importRuntimeTool(rootDir, sourcePath) {
|
|
830
|
+
const module = await tsImport(pathToFileURL(sourcePath).href, {
|
|
831
|
+
parentURL: runtimeParentUrl(rootDir),
|
|
832
|
+
tsconfig: runtimeTsconfig(rootDir)
|
|
833
|
+
});
|
|
834
|
+
const defaultExport = unwrapRuntimeDefault(module.default);
|
|
835
|
+
if (!isSidecarTool(defaultExport)) {
|
|
836
|
+
throw new Error(`${path3.relative(rootDir, sourcePath)} must default-export tool({ ... }).`);
|
|
837
|
+
}
|
|
838
|
+
return defaultExport;
|
|
839
|
+
}
|
|
840
|
+
async function paramsToJsonSchema(params) {
|
|
841
|
+
const instanceConverter = readInstanceConverter(params);
|
|
842
|
+
if (instanceConverter) {
|
|
843
|
+
return ensureJsonSchema(instanceConverter({ io: "input" }));
|
|
844
|
+
}
|
|
845
|
+
if (isZodMiniSchema(params)) {
|
|
846
|
+
const zod = await import("zod/v4-mini");
|
|
847
|
+
if (typeof zod.toJSONSchema === "function") {
|
|
848
|
+
return ensureJsonSchema(zod.toJSONSchema(params, { io: "input" }));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return void 0;
|
|
852
|
+
}
|
|
853
|
+
function readInstanceConverter(params) {
|
|
854
|
+
if (!params || typeof params !== "object") {
|
|
855
|
+
return void 0;
|
|
856
|
+
}
|
|
857
|
+
const record = params;
|
|
858
|
+
const converter = record.toJSONSchema ?? record.toJsonSchema;
|
|
859
|
+
return typeof converter === "function" ? converter.bind(params) : void 0;
|
|
860
|
+
}
|
|
861
|
+
function isZodMiniSchema(params) {
|
|
862
|
+
return Boolean(params && typeof params === "object" && "_zod" in params);
|
|
863
|
+
}
|
|
864
|
+
function ensureJsonSchema(value) {
|
|
865
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
866
|
+
return void 0;
|
|
867
|
+
}
|
|
868
|
+
return value;
|
|
869
|
+
}
|
|
870
|
+
function runtimeParentUrl(rootDir) {
|
|
871
|
+
const configPath = path3.join(rootDir, "sidecar.config.ts");
|
|
872
|
+
return pathToFileURL(existsSync2(configPath) ? configPath : rootDir).href;
|
|
873
|
+
}
|
|
874
|
+
function runtimeTsconfig(rootDir) {
|
|
875
|
+
const projectTsconfig = path3.join(rootDir, "tsconfig.json");
|
|
876
|
+
if (existsSync2(projectTsconfig)) {
|
|
877
|
+
return projectTsconfig;
|
|
878
|
+
}
|
|
879
|
+
const repoTsconfig = path3.join(process.cwd(), "tsconfig.json");
|
|
880
|
+
const repoCore = path3.join(process.cwd(), "packages", "core", "src", "index.ts");
|
|
881
|
+
return existsSyncSafe(repoTsconfig) && existsSyncSafe(repoCore) ? repoTsconfig : false;
|
|
882
|
+
}
|
|
883
|
+
function unwrapRuntimeDefault(value) {
|
|
884
|
+
if (value && typeof value === "object" && "default" in value && Object.keys(value).every((key) => key === "default" || key === "__esModule")) {
|
|
885
|
+
return unwrapRuntimeDefault(value.default);
|
|
886
|
+
}
|
|
887
|
+
return value;
|
|
888
|
+
}
|
|
889
|
+
function warnRuntimeSchemaFallback(sourcePath, error) {
|
|
890
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
891
|
+
console.warn(
|
|
892
|
+
`[sidecar] Could not convert runtime params schema for ${sourcePath}; falling back to TypeScript parameter inference. ${message}`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
|
|
847
896
|
// packages/compiler/src/widgets.ts
|
|
848
897
|
import { createHash } from "crypto";
|
|
849
|
-
import { existsSync as
|
|
898
|
+
import { existsSync as existsSync3 } from "fs";
|
|
850
899
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
851
|
-
import
|
|
900
|
+
import { createRequire } from "module";
|
|
901
|
+
import path4 from "path";
|
|
902
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
852
903
|
import { build as esbuild } from "esbuild";
|
|
853
904
|
import postcss from "postcss";
|
|
854
905
|
import {
|
|
855
906
|
Node as Node3,
|
|
856
907
|
SyntaxKind
|
|
857
908
|
} from "ts-morph";
|
|
858
|
-
|
|
909
|
+
var CLAUDE_FONT_RESOURCE_DOMAIN = "https://assets.claude.ai";
|
|
910
|
+
var requireFromCompiler = createRequire(import.meta.url);
|
|
911
|
+
async function buildWidgets(rootDir, outDir, tools, config = void 0) {
|
|
859
912
|
const widgets = tools.filter(
|
|
860
913
|
(entry) => Boolean(entry.widget)
|
|
861
914
|
);
|
|
862
915
|
if (!widgets.length) {
|
|
863
916
|
return;
|
|
864
917
|
}
|
|
865
|
-
const cacheDir =
|
|
918
|
+
const cacheDir = path4.join(rootDir, ".sidecar", "cache", "widgets");
|
|
866
919
|
await mkdir(cacheDir, { recursive: true });
|
|
867
920
|
const appStyle = await prepareAppStyle(rootDir, cacheDir);
|
|
921
|
+
const configure = await loadWidgetBundlerHook(rootDir, cacheDir, config?.configure);
|
|
868
922
|
for (const entry of widgets) {
|
|
869
|
-
const sourceFile =
|
|
923
|
+
const sourceFile = path4.join(rootDir, entry.widget.sourceFile);
|
|
870
924
|
const safeId = safePathSegment(entry.id);
|
|
871
|
-
const entryFile =
|
|
872
|
-
const importPath = toImportSpecifier(
|
|
925
|
+
const entryFile = path4.join(cacheDir, `${safeId}.entry.tsx`);
|
|
926
|
+
const importPath = toImportSpecifier(path4.dirname(entryFile), sourceFile);
|
|
873
927
|
await writeFile(
|
|
874
928
|
entryFile,
|
|
875
929
|
`import React from "react";
|
|
876
930
|
import { createRoot } from "react-dom/client";
|
|
877
931
|
import { SidecarWidgetRoot } from "@sidecar-ai/react";
|
|
878
932
|
import "@sidecar-ai/native/styles.css";
|
|
879
|
-
${appStyle ? `import ${JSON.stringify(toImportSpecifier(
|
|
933
|
+
${appStyle ? `import ${JSON.stringify(toImportSpecifier(path4.dirname(entryFile), appStyle))};` : ""}
|
|
880
934
|
import Component from ${JSON.stringify(importPath)};
|
|
881
935
|
|
|
882
936
|
createRoot(document.getElementById("root")!).render(
|
|
@@ -884,64 +938,219 @@ createRoot(document.getElementById("root")!).render(
|
|
|
884
938
|
);
|
|
885
939
|
`
|
|
886
940
|
);
|
|
887
|
-
const
|
|
941
|
+
const baseOptions = enforceWidgetBuildInvariants({
|
|
888
942
|
absWorkingDir: rootDir,
|
|
889
|
-
alias:
|
|
943
|
+
alias: sidecarWidgetAliases(rootDir),
|
|
890
944
|
bundle: true,
|
|
945
|
+
define: {
|
|
946
|
+
"process.env.NODE_ENV": JSON.stringify("production")
|
|
947
|
+
},
|
|
891
948
|
entryPoints: [entryFile],
|
|
892
949
|
format: "iife",
|
|
893
950
|
jsx: "automatic",
|
|
894
|
-
minify:
|
|
951
|
+
minify: true,
|
|
895
952
|
nodePaths: [
|
|
896
|
-
|
|
897
|
-
|
|
953
|
+
path4.join(rootDir, "node_modules"),
|
|
954
|
+
path4.join(process.cwd(), "node_modules")
|
|
898
955
|
],
|
|
899
956
|
outfile: "widget.js",
|
|
900
957
|
platform: "browser",
|
|
901
958
|
sourcemap: false,
|
|
902
959
|
write: false
|
|
903
960
|
});
|
|
904
|
-
const
|
|
905
|
-
const
|
|
961
|
+
const staticOptions = widgetBuildOptionsFromConfig(rootDir, config);
|
|
962
|
+
const configuredOptions = configure ? await configureWidgetBuild(configure, {
|
|
963
|
+
rootDir,
|
|
964
|
+
outDir,
|
|
965
|
+
entryFile,
|
|
966
|
+
widget: {
|
|
967
|
+
toolId: entry.id,
|
|
968
|
+
toolName: entry.name,
|
|
969
|
+
target: entry.target,
|
|
970
|
+
sourceFile: entry.widget.sourceFile
|
|
971
|
+
},
|
|
972
|
+
esbuildOptions: mergeWidgetBuildOptions(baseOptions, staticOptions)
|
|
973
|
+
}) : void 0;
|
|
974
|
+
const bundled = await esbuild(enforceWidgetBuildInvariants(
|
|
975
|
+
mergeWidgetBuildOptions(baseOptions, staticOptions, configuredOptions),
|
|
976
|
+
{ rootDir, entryFile }
|
|
977
|
+
));
|
|
978
|
+
const outputFiles = bundled.outputFiles ?? [];
|
|
979
|
+
const javascript = outputFiles.find((file) => file.path.endsWith(".js"))?.text ?? "";
|
|
980
|
+
const css = outputFiles.filter((file) => file.path.endsWith(".css")).map((file) => file.text).join("\n");
|
|
906
981
|
const html = renderWidgetHtml(entry.name, javascript, css);
|
|
907
982
|
const hash = createHash("sha256").update(html).digest("hex").slice(0, 12);
|
|
908
|
-
const outputDir =
|
|
909
|
-
const outputFile =
|
|
983
|
+
const outputDir = path4.join(outDir, "public", "widgets", safeId);
|
|
984
|
+
const outputFile = path4.join(outputDir, `widget.${hash}.html`);
|
|
910
985
|
const resourceUri = `ui://${safeId}/widget.${hash}.html`;
|
|
911
986
|
await mkdir(outputDir, { recursive: true });
|
|
912
987
|
await writeFile(outputFile, html);
|
|
913
988
|
entry.widget.resourceUri = resourceUri;
|
|
914
989
|
entry.widget.resourceMeta = widgetResourceMeta(entry.widget.options, entry.target);
|
|
915
|
-
entry.widget.outputFile =
|
|
990
|
+
entry.widget.outputFile = path4.relative(outDir, outputFile);
|
|
916
991
|
entry.descriptor._meta = mergeWidgetMeta(entry.descriptor._meta, widgetMeta(resourceUri, entry.widget.options, entry.target));
|
|
917
992
|
}
|
|
918
993
|
}
|
|
994
|
+
function widgetBuildOptionsFromConfig(rootDir, config) {
|
|
995
|
+
const esbuildConfig = config?.esbuild;
|
|
996
|
+
if (!esbuildConfig) {
|
|
997
|
+
return void 0;
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
alias: normalizeAliases(rootDir, esbuildConfig.alias),
|
|
1001
|
+
conditions: esbuildConfig.conditions,
|
|
1002
|
+
define: esbuildConfig.define,
|
|
1003
|
+
external: esbuildConfig.external,
|
|
1004
|
+
jsx: esbuildConfig.jsx,
|
|
1005
|
+
jsxImportSource: esbuildConfig.jsxImportSource,
|
|
1006
|
+
loader: esbuildConfig.loader,
|
|
1007
|
+
mainFields: esbuildConfig.mainFields
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
async function loadWidgetBundlerHook(rootDir, cacheDir, hookPath) {
|
|
1011
|
+
if (!hookPath) {
|
|
1012
|
+
return void 0;
|
|
1013
|
+
}
|
|
1014
|
+
const sourcePath = path4.resolve(rootDir, hookPath);
|
|
1015
|
+
const relative = path4.relative(rootDir, sourcePath);
|
|
1016
|
+
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
1017
|
+
throw new Error(`Widget bundler hook must stay inside the project root: ${hookPath}`);
|
|
1018
|
+
}
|
|
1019
|
+
if (!existsSync3(sourcePath)) {
|
|
1020
|
+
throw new Error(`Widget bundler hook not found: ${hookPath}`);
|
|
1021
|
+
}
|
|
1022
|
+
const outputFile = path4.join(cacheDir, `widget-bundler.${contentHash(hookPath)}.mjs`);
|
|
1023
|
+
await esbuild({
|
|
1024
|
+
absWorkingDir: rootDir,
|
|
1025
|
+
alias: devSidecarBundleAliases(rootDir),
|
|
1026
|
+
bundle: true,
|
|
1027
|
+
entryPoints: [sourcePath],
|
|
1028
|
+
format: "esm",
|
|
1029
|
+
nodePaths: [
|
|
1030
|
+
path4.join(rootDir, "node_modules"),
|
|
1031
|
+
path4.join(process.cwd(), "node_modules")
|
|
1032
|
+
],
|
|
1033
|
+
outfile: outputFile,
|
|
1034
|
+
packages: "external",
|
|
1035
|
+
platform: "node",
|
|
1036
|
+
target: "node20"
|
|
1037
|
+
});
|
|
1038
|
+
const module = await import(`${pathToFileURL2(outputFile).href}?t=${Date.now()}`);
|
|
1039
|
+
if (typeof module.default !== "function") {
|
|
1040
|
+
throw new Error(`Widget bundler hook must default-export a function: ${hookPath}`);
|
|
1041
|
+
}
|
|
1042
|
+
return module.default;
|
|
1043
|
+
}
|
|
1044
|
+
async function configureWidgetBuild(hook, input) {
|
|
1045
|
+
const result = await hook(input);
|
|
1046
|
+
if (!result) {
|
|
1047
|
+
return void 0;
|
|
1048
|
+
}
|
|
1049
|
+
if (typeof result === "object" && "esbuildOptions" in result) {
|
|
1050
|
+
return result.esbuildOptions;
|
|
1051
|
+
}
|
|
1052
|
+
return result;
|
|
1053
|
+
}
|
|
1054
|
+
function mergeWidgetBuildOptions(...options) {
|
|
1055
|
+
const merged = {};
|
|
1056
|
+
for (const option of options) {
|
|
1057
|
+
if (!option) {
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
const previousAlias = merged.alias;
|
|
1061
|
+
const previousDefine = merged.define;
|
|
1062
|
+
const previousLoader = merged.loader;
|
|
1063
|
+
const previousExternal = merged.external;
|
|
1064
|
+
const previousNodePaths = merged.nodePaths;
|
|
1065
|
+
const previousPlugins = merged.plugins;
|
|
1066
|
+
Object.assign(merged, option);
|
|
1067
|
+
merged.alias = { ...previousAlias ?? {}, ...option.alias ?? {} };
|
|
1068
|
+
merged.define = { ...previousDefine ?? {}, ...option.define ?? {} };
|
|
1069
|
+
merged.loader = { ...previousLoader ?? {}, ...option.loader ?? {} };
|
|
1070
|
+
merged.external = uniqueStrings([...previousExternal ?? [], ...option.external ?? []]);
|
|
1071
|
+
merged.nodePaths = uniqueStrings([...previousNodePaths ?? [], ...option.nodePaths ?? []]);
|
|
1072
|
+
merged.plugins = [...previousPlugins ?? [], ...option.plugins ?? []];
|
|
1073
|
+
}
|
|
1074
|
+
return merged;
|
|
1075
|
+
}
|
|
1076
|
+
function enforceWidgetBuildInvariants(options, required) {
|
|
1077
|
+
const aliases = required ? { ...options.alias ?? {}, ...reactSingletonAliases(required.rootDir) } : options.alias;
|
|
1078
|
+
return {
|
|
1079
|
+
...options,
|
|
1080
|
+
absWorkingDir: required?.rootDir ?? options.absWorkingDir,
|
|
1081
|
+
alias: aliases,
|
|
1082
|
+
bundle: true,
|
|
1083
|
+
entryPoints: required ? [required.entryFile] : options.entryPoints,
|
|
1084
|
+
format: "iife",
|
|
1085
|
+
outfile: "widget.js",
|
|
1086
|
+
platform: "browser",
|
|
1087
|
+
write: false
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function sidecarWidgetAliases(rootDir) {
|
|
1091
|
+
return {
|
|
1092
|
+
...devSidecarBundleAliases(rootDir) ?? {},
|
|
1093
|
+
...reactSingletonAliases(rootDir)
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function reactSingletonAliases(rootDir) {
|
|
1097
|
+
return {
|
|
1098
|
+
react: resolveProjectModule(rootDir, "react"),
|
|
1099
|
+
"react/jsx-runtime": resolveProjectModule(rootDir, "react/jsx-runtime"),
|
|
1100
|
+
"react/jsx-dev-runtime": resolveProjectModule(rootDir, "react/jsx-dev-runtime"),
|
|
1101
|
+
"react-dom": resolveProjectModule(rootDir, "react-dom"),
|
|
1102
|
+
"react-dom/client": resolveProjectModule(rootDir, "react-dom/client"),
|
|
1103
|
+
"react-dom/server": resolveProjectModule(rootDir, "react-dom/server")
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function resolveProjectModule(rootDir, specifier) {
|
|
1107
|
+
return requireFromCompiler.resolve(specifier, {
|
|
1108
|
+
paths: [rootDir, process.cwd()]
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
function normalizeAliases(rootDir, aliases) {
|
|
1112
|
+
if (!aliases) {
|
|
1113
|
+
return void 0;
|
|
1114
|
+
}
|
|
1115
|
+
return Object.fromEntries(
|
|
1116
|
+
Object.entries(aliases).map(([key, value]) => [
|
|
1117
|
+
key,
|
|
1118
|
+
value.startsWith(".") ? path4.resolve(rootDir, value) : value
|
|
1119
|
+
])
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
function uniqueStrings(values) {
|
|
1123
|
+
return [...new Set(values)];
|
|
1124
|
+
}
|
|
1125
|
+
function contentHash(value) {
|
|
1126
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
1127
|
+
}
|
|
919
1128
|
function devSidecarBundleAliases(rootDir) {
|
|
920
1129
|
const repoRoot = findSidecarRepoRoot(rootDir) ?? findSidecarRepoRoot(process.cwd());
|
|
921
1130
|
if (!repoRoot) {
|
|
922
1131
|
return void 0;
|
|
923
1132
|
}
|
|
924
|
-
const reactEntry =
|
|
925
|
-
if (!
|
|
1133
|
+
const reactEntry = path4.join(repoRoot, "packages", "react", "src", "index.ts");
|
|
1134
|
+
if (!existsSync3(reactEntry)) {
|
|
926
1135
|
return void 0;
|
|
927
1136
|
}
|
|
928
1137
|
return {
|
|
929
|
-
"sidecar-ai":
|
|
930
|
-
"@sidecar-ai/client":
|
|
931
|
-
"@sidecar-ai/core":
|
|
1138
|
+
"sidecar-ai": path4.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
|
|
1139
|
+
"@sidecar-ai/client": path4.join(repoRoot, "packages", "client", "src", "index.ts"),
|
|
1140
|
+
"@sidecar-ai/core": path4.join(repoRoot, "packages", "core", "src", "index.ts"),
|
|
932
1141
|
"@sidecar-ai/react": reactEntry,
|
|
933
|
-
"@sidecar-ai/native":
|
|
934
|
-
"@sidecar-ai/native/components":
|
|
935
|
-
"@sidecar-ai/native/styles.css":
|
|
1142
|
+
"@sidecar-ai/native": path4.join(repoRoot, "packages", "native", "src", "index.ts"),
|
|
1143
|
+
"@sidecar-ai/native/components": path4.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
|
|
1144
|
+
"@sidecar-ai/native/styles.css": path4.join(repoRoot, "packages", "native", "src", "styles.css")
|
|
936
1145
|
};
|
|
937
1146
|
}
|
|
938
1147
|
function findSidecarRepoRoot(startDir) {
|
|
939
|
-
let current =
|
|
1148
|
+
let current = path4.resolve(startDir);
|
|
940
1149
|
while (true) {
|
|
941
|
-
if (
|
|
1150
|
+
if (existsSync3(path4.join(current, "packages", "core", "src", "index.ts"))) {
|
|
942
1151
|
return current;
|
|
943
1152
|
}
|
|
944
|
-
const parent =
|
|
1153
|
+
const parent = path4.dirname(current);
|
|
945
1154
|
if (parent === current) {
|
|
946
1155
|
return void 0;
|
|
947
1156
|
}
|
|
@@ -949,13 +1158,13 @@ function findSidecarRepoRoot(startDir) {
|
|
|
949
1158
|
}
|
|
950
1159
|
}
|
|
951
1160
|
async function prepareAppStyle(rootDir, cacheDir) {
|
|
952
|
-
const sourceFile =
|
|
953
|
-
if (!
|
|
1161
|
+
const sourceFile = path4.join(rootDir, "style.css");
|
|
1162
|
+
if (!existsSync3(sourceFile)) {
|
|
954
1163
|
return void 0;
|
|
955
1164
|
}
|
|
956
1165
|
const source = await readFile(sourceFile, "utf8");
|
|
957
1166
|
const css = await processAppStyle(source, sourceFile);
|
|
958
|
-
const outputFile =
|
|
1167
|
+
const outputFile = path4.join(cacheDir, "style.css");
|
|
959
1168
|
await writeFile(outputFile, css);
|
|
960
1169
|
return outputFile;
|
|
961
1170
|
}
|
|
@@ -967,7 +1176,7 @@ async function processAppStyle(source, from) {
|
|
|
967
1176
|
const plugins = [];
|
|
968
1177
|
if (cssSource.includes("@tailwind")) {
|
|
969
1178
|
const tailwind = await import("@tailwindcss/postcss");
|
|
970
|
-
plugins.push(tailwind.default({ base:
|
|
1179
|
+
plugins.push(tailwind.default({ base: path4.dirname(from) }));
|
|
971
1180
|
}
|
|
972
1181
|
const autoprefixer = await import("autoprefixer");
|
|
973
1182
|
plugins.push(autoprefixer.default());
|
|
@@ -981,13 +1190,13 @@ function needsPostcss(source) {
|
|
|
981
1190
|
return /@import\s+["']tailwindcss["']|@tailwind|@source|@theme|@plugin/.test(source);
|
|
982
1191
|
}
|
|
983
1192
|
function findWidget(rootDir, toolFile, id, target = "mcp", toolVariant = "shared") {
|
|
984
|
-
const selected = selectWidgetFile(
|
|
1193
|
+
const selected = selectWidgetFile(path4.dirname(toolFile), target, toolVariant);
|
|
985
1194
|
if (!selected) {
|
|
986
1195
|
return void 0;
|
|
987
1196
|
}
|
|
988
1197
|
const safeId = safePathSegment(id);
|
|
989
1198
|
return {
|
|
990
|
-
sourceFile:
|
|
1199
|
+
sourceFile: path4.relative(rootDir, selected.filePath),
|
|
991
1200
|
variant: selected.variant,
|
|
992
1201
|
resourceUri: `ui://${safeId}/widget.html`
|
|
993
1202
|
};
|
|
@@ -1022,7 +1231,8 @@ function widgetMeta(resourceUri, options = {}, target = "mcp") {
|
|
|
1022
1231
|
const standard = {
|
|
1023
1232
|
ui: {
|
|
1024
1233
|
resourceUri
|
|
1025
|
-
}
|
|
1234
|
+
},
|
|
1235
|
+
"ui/resourceUri": resourceUri
|
|
1026
1236
|
};
|
|
1027
1237
|
if (target !== "chatgpt") {
|
|
1028
1238
|
return standard;
|
|
@@ -1038,7 +1248,7 @@ function widgetMeta(resourceUri, options = {}, target = "mcp") {
|
|
|
1038
1248
|
function widgetResourceMeta(options = {}, target = "mcp") {
|
|
1039
1249
|
const csp = stripUndefined3({
|
|
1040
1250
|
connectDomains: options.csp?.connectDomains ? [...options.csp.connectDomains] : [],
|
|
1041
|
-
resourceDomains: options
|
|
1251
|
+
resourceDomains: widgetResourceDomains(options, target),
|
|
1042
1252
|
frameDomains: options.csp?.frameDomains ? [...options.csp.frameDomains] : void 0,
|
|
1043
1253
|
baseUriDomains: options.csp?.baseUriDomains ? [...options.csp.baseUriDomains] : void 0
|
|
1044
1254
|
});
|
|
@@ -1051,6 +1261,13 @@ function widgetResourceMeta(options = {}, target = "mcp") {
|
|
|
1051
1261
|
});
|
|
1052
1262
|
return Object.keys(ui).length ? { ui } : void 0;
|
|
1053
1263
|
}
|
|
1264
|
+
function widgetResourceDomains(options, target) {
|
|
1265
|
+
const domains = [...options.csp?.resourceDomains ?? []];
|
|
1266
|
+
if (target === "claude" && !domains.includes(CLAUDE_FONT_RESOURCE_DOMAIN)) {
|
|
1267
|
+
domains.push(CLAUDE_FONT_RESOURCE_DOMAIN);
|
|
1268
|
+
}
|
|
1269
|
+
return domains;
|
|
1270
|
+
}
|
|
1054
1271
|
function findWidgetDefinition(sourceFile) {
|
|
1055
1272
|
const call = resolveDefaultExportCall(sourceFile, "widget");
|
|
1056
1273
|
if (!call) {
|
|
@@ -1162,17 +1379,17 @@ function readStringArrayProperty(definition, propertyName) {
|
|
|
1162
1379
|
}
|
|
1163
1380
|
function selectWidgetFile(directory, target, toolVariant) {
|
|
1164
1381
|
if (toolVariant === "openai") {
|
|
1165
|
-
const filePath =
|
|
1166
|
-
return
|
|
1382
|
+
const filePath = path4.join(directory, "widget.openai.tsx");
|
|
1383
|
+
return existsSync3(filePath) ? { filePath, variant: "openai" } : void 0;
|
|
1167
1384
|
}
|
|
1168
1385
|
if (toolVariant === "anthropic") {
|
|
1169
|
-
const filePath =
|
|
1170
|
-
return
|
|
1386
|
+
const filePath = path4.join(directory, "widget.anthropic.tsx");
|
|
1387
|
+
return existsSync3(filePath) ? { filePath, variant: "anthropic" } : void 0;
|
|
1171
1388
|
}
|
|
1172
1389
|
const candidates = target === "chatgpt" || target === "claude" || target === "mcp" ? [{ name: "widget.tsx", variant: "shared" }] : [];
|
|
1173
1390
|
for (const candidate of candidates) {
|
|
1174
|
-
const filePath =
|
|
1175
|
-
if (
|
|
1391
|
+
const filePath = path4.join(directory, candidate.name);
|
|
1392
|
+
if (existsSync3(filePath)) {
|
|
1176
1393
|
return { filePath, variant: candidate.variant };
|
|
1177
1394
|
}
|
|
1178
1395
|
}
|
|
@@ -1223,17 +1440,23 @@ function stripUndefined3(value) {
|
|
|
1223
1440
|
// packages/compiler/src/analyze.ts
|
|
1224
1441
|
async function analyzeProjectTools(rootDir, options = {}) {
|
|
1225
1442
|
const target = options.target ?? "mcp";
|
|
1226
|
-
const toolFiles = await findToolFiles(
|
|
1443
|
+
const toolFiles = await findToolFiles(path5.join(rootDir, "server"), target);
|
|
1227
1444
|
if (toolFiles.length === 0) {
|
|
1228
1445
|
return [];
|
|
1229
1446
|
}
|
|
1230
1447
|
const project = createProject(rootDir);
|
|
1231
1448
|
const authScopes = readAuthScopeCatalog(project, rootDir);
|
|
1232
|
-
|
|
1233
|
-
|
|
1449
|
+
const sources = toolFiles.map((candidate) => ({
|
|
1450
|
+
candidate,
|
|
1451
|
+
sourceFile: project.addSourceFileAtPath(candidate.filePath)
|
|
1452
|
+
}));
|
|
1453
|
+
const runtimeInputSchemas = await readRuntimeInputSchemas(rootDir, sources);
|
|
1454
|
+
return sources.map(
|
|
1455
|
+
({ candidate, sourceFile }) => analyzeToolFile(sourceFile, rootDir, {
|
|
1234
1456
|
target,
|
|
1235
1457
|
variant: candidate.variant,
|
|
1236
|
-
authScopes
|
|
1458
|
+
authScopes,
|
|
1459
|
+
inputSchema: runtimeInputSchemas.get(candidate.filePath)
|
|
1237
1460
|
})
|
|
1238
1461
|
);
|
|
1239
1462
|
}
|
|
@@ -1242,7 +1465,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1242
1465
|
const variant = options.variant ?? "shared";
|
|
1243
1466
|
const definition = findToolDefinition(sourceFile);
|
|
1244
1467
|
const absoluteFile = sourceFile.getFilePath();
|
|
1245
|
-
const directory =
|
|
1468
|
+
const directory = path5.basename(path5.dirname(absoluteFile));
|
|
1246
1469
|
const name = getRequiredStringProperty(definition, "name", sourceFile);
|
|
1247
1470
|
const id = getOptionalStringProperty(definition, "id") ?? toMachineName(directory);
|
|
1248
1471
|
const description = getRequiredStringProperty(
|
|
@@ -1255,7 +1478,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1255
1478
|
const hosts = readHosts(definition);
|
|
1256
1479
|
const auth = readAuthPolicy(definition, options.authScopes ?? {});
|
|
1257
1480
|
const execute = getExecuteFunction(definition, sourceFile);
|
|
1258
|
-
const inputSchema = getParamsSchema(definition, execute);
|
|
1481
|
+
const inputSchema = options.inputSchema ?? getParamsSchema(definition, execute);
|
|
1259
1482
|
const outputSchema = getOutputSchema(definition, execute);
|
|
1260
1483
|
const descriptor = createToolDescriptor({
|
|
1261
1484
|
name,
|
|
@@ -1273,7 +1496,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1273
1496
|
validateWidgetHierarchy(sourceFile, absoluteFile, target, variant);
|
|
1274
1497
|
const widget = findWidget(rootDir, absoluteFile, id, target, variant);
|
|
1275
1498
|
if (widget) {
|
|
1276
|
-
const widgetFile =
|
|
1499
|
+
const widgetFile = path5.join(rootDir, widget.sourceFile);
|
|
1277
1500
|
const project = sourceFile.getProject();
|
|
1278
1501
|
const widgetSourceFile = project.getSourceFile(widgetFile) ?? project.addSourceFileAtPath(widgetFile);
|
|
1279
1502
|
widget.options = {
|
|
@@ -1284,7 +1507,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1284
1507
|
descriptor._meta = mergeWidgetMeta(descriptor._meta, widgetMeta(widget.resourceUri, widget.options, target));
|
|
1285
1508
|
}
|
|
1286
1509
|
return {
|
|
1287
|
-
sourceFile:
|
|
1510
|
+
sourceFile: path5.relative(rootDir, absoluteFile),
|
|
1288
1511
|
variant,
|
|
1289
1512
|
target,
|
|
1290
1513
|
directory,
|
|
@@ -1299,6 +1522,26 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1299
1522
|
descriptor
|
|
1300
1523
|
};
|
|
1301
1524
|
}
|
|
1525
|
+
async function readRuntimeInputSchemas(rootDir, sources) {
|
|
1526
|
+
const schemas = /* @__PURE__ */ new Map();
|
|
1527
|
+
await Promise.all(sources.map(async ({ candidate, sourceFile }) => {
|
|
1528
|
+
const definition = findToolDefinition(sourceFile);
|
|
1529
|
+
if (!definitionHasRuntimeParams(definition)) {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const schema = await readRuntimeToolInputSchema(rootDir, candidate.filePath);
|
|
1533
|
+
if (schema) {
|
|
1534
|
+
schemas.set(candidate.filePath, schema);
|
|
1535
|
+
}
|
|
1536
|
+
}));
|
|
1537
|
+
return schemas;
|
|
1538
|
+
}
|
|
1539
|
+
function definitionHasRuntimeParams(definition) {
|
|
1540
|
+
if (definition.getProperty("params")) {
|
|
1541
|
+
return true;
|
|
1542
|
+
}
|
|
1543
|
+
return Boolean(getWithParamsCall(definition));
|
|
1544
|
+
}
|
|
1302
1545
|
function readAuthPolicy(definition, authScopes) {
|
|
1303
1546
|
const initializer = readObjectProperty2(definition, "auth");
|
|
1304
1547
|
if (!initializer) {
|
|
@@ -1315,7 +1558,7 @@ function readAuthPolicy(definition, authScopes) {
|
|
|
1315
1558
|
return { authenticated: true };
|
|
1316
1559
|
}
|
|
1317
1560
|
function readAuthScopeCatalog(project, rootDir) {
|
|
1318
|
-
const authPath =
|
|
1561
|
+
const authPath = path5.join(rootDir, "auth.ts");
|
|
1319
1562
|
if (!existsSyncSafe(authPath)) {
|
|
1320
1563
|
return {};
|
|
1321
1564
|
}
|
|
@@ -1397,12 +1640,12 @@ function isNamedCall(call, name) {
|
|
|
1397
1640
|
return callee === name || callee.endsWith(`.${name}`);
|
|
1398
1641
|
}
|
|
1399
1642
|
function validateWidgetHierarchy(sourceFile, toolFile, target, variant) {
|
|
1400
|
-
const directory =
|
|
1643
|
+
const directory = path5.dirname(toolFile);
|
|
1401
1644
|
const platformWidget = target === "chatgpt" ? "widget.openai.tsx" : target === "claude" ? "widget.anthropic.tsx" : void 0;
|
|
1402
1645
|
if (!platformWidget || variant !== "shared") {
|
|
1403
1646
|
return;
|
|
1404
1647
|
}
|
|
1405
|
-
if (existsSyncSafe(
|
|
1648
|
+
if (existsSyncSafe(path5.join(directory, platformWidget))) {
|
|
1406
1649
|
const expectedTool = target === "chatgpt" ? "tool.openai.ts" : "tool.anthropic.ts";
|
|
1407
1650
|
throw new CompilerError(
|
|
1408
1651
|
sourceFile,
|
|
@@ -1417,7 +1660,7 @@ async function findToolFiles(serverDir, target) {
|
|
|
1417
1660
|
const entries = await readdir(serverDir, { withFileTypes: true });
|
|
1418
1661
|
const files = [];
|
|
1419
1662
|
for (const entry of entries) {
|
|
1420
|
-
const entryPath =
|
|
1663
|
+
const entryPath = path5.join(serverDir, entry.name);
|
|
1421
1664
|
if (entry.isDirectory()) {
|
|
1422
1665
|
const candidate = selectToolFile(entryPath, target);
|
|
1423
1666
|
if (candidate) {
|
|
@@ -1436,7 +1679,7 @@ function selectToolFile(directory, target) {
|
|
|
1436
1679
|
{ name: "tool.ts", variant: "shared" }
|
|
1437
1680
|
] : [{ name: "tool.ts", variant: "shared" }];
|
|
1438
1681
|
for (const candidate of candidates) {
|
|
1439
|
-
const filePath =
|
|
1682
|
+
const filePath = path5.join(directory, candidate.name);
|
|
1440
1683
|
if (existsSyncSafe(filePath)) {
|
|
1441
1684
|
return { filePath, variant: candidate.variant };
|
|
1442
1685
|
}
|
|
@@ -1472,16 +1715,32 @@ function getExecuteFunction(definition, sourceFile) {
|
|
|
1472
1715
|
return property;
|
|
1473
1716
|
}
|
|
1474
1717
|
if (Node4.isPropertyAssignment(property)) {
|
|
1475
|
-
const initializer = property.getInitializer();
|
|
1718
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1476
1719
|
if (initializer && (Node4.isArrowFunction(initializer) || Node4.isFunctionExpression(initializer))) {
|
|
1477
1720
|
return initializer;
|
|
1478
1721
|
}
|
|
1722
|
+
const withParamsCall = getWithParamsCall(definition);
|
|
1723
|
+
const wrappedExecute = unwrapExpression(withParamsCall?.getArguments()[1]);
|
|
1724
|
+
if (wrappedExecute && (Node4.isArrowFunction(wrappedExecute) || Node4.isFunctionExpression(wrappedExecute))) {
|
|
1725
|
+
return wrappedExecute;
|
|
1726
|
+
}
|
|
1479
1727
|
}
|
|
1480
1728
|
throw new CompilerError(
|
|
1481
1729
|
sourceFile,
|
|
1482
1730
|
"execute must be a method, function expression, or arrow function."
|
|
1483
1731
|
);
|
|
1484
1732
|
}
|
|
1733
|
+
function getWithParamsCall(definition) {
|
|
1734
|
+
const property = definition.getProperty("execute");
|
|
1735
|
+
if (!property || !Node4.isPropertyAssignment(property)) {
|
|
1736
|
+
return void 0;
|
|
1737
|
+
}
|
|
1738
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1739
|
+
if (!initializer || !Node4.isCallExpression(initializer)) {
|
|
1740
|
+
return void 0;
|
|
1741
|
+
}
|
|
1742
|
+
return isNamedCall(initializer, "withParams") ? initializer : void 0;
|
|
1743
|
+
}
|
|
1485
1744
|
function getRequiredStringProperty(definition, propertyName, sourceFile) {
|
|
1486
1745
|
const value = getOptionalStringProperty(definition, propertyName);
|
|
1487
1746
|
if (value === void 0 || !value.trim()) {
|
|
@@ -1636,17 +1895,17 @@ function readStringArrayProperty2(definition, propertyName) {
|
|
|
1636
1895
|
}
|
|
1637
1896
|
|
|
1638
1897
|
// packages/compiler/src/build.ts
|
|
1639
|
-
import { mkdir as
|
|
1640
|
-
import
|
|
1898
|
+
import { mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
|
|
1899
|
+
import path19 from "path";
|
|
1641
1900
|
|
|
1642
1901
|
// packages/compiler/src/config.ts
|
|
1643
|
-
import
|
|
1902
|
+
import path6 from "path";
|
|
1644
1903
|
import {
|
|
1645
1904
|
Node as Node5,
|
|
1646
1905
|
SyntaxKind as SyntaxKind3
|
|
1647
1906
|
} from "ts-morph";
|
|
1648
1907
|
function analyzeProjectConfig(rootDir) {
|
|
1649
|
-
const configPath =
|
|
1908
|
+
const configPath = path6.join(rootDir, "sidecar.config.ts");
|
|
1650
1909
|
if (!existsSyncSafe(configPath)) {
|
|
1651
1910
|
return defaultCompilerConfig();
|
|
1652
1911
|
}
|
|
@@ -1658,6 +1917,13 @@ function analyzeProjectConfig(rootDir) {
|
|
|
1658
1917
|
return defaultCompilerConfig();
|
|
1659
1918
|
}
|
|
1660
1919
|
return {
|
|
1920
|
+
build: {
|
|
1921
|
+
target: readTargetNested(definition, "build", "target"),
|
|
1922
|
+
host: readHostNested(definition, "build", "host"),
|
|
1923
|
+
outDir: readStringNested(definition, "build", "outDir"),
|
|
1924
|
+
plugins: readBooleanNested(definition, "build", "plugins"),
|
|
1925
|
+
widgets: readWidgetBuildConfig(definition)
|
|
1926
|
+
},
|
|
1661
1927
|
resources: {
|
|
1662
1928
|
subscribe: readBooleanNested(definition, "resources", "subscribe") ?? false,
|
|
1663
1929
|
listChanged: readBooleanNested(definition, "resources", "listChanged") ?? false
|
|
@@ -1669,13 +1935,52 @@ function analyzeProjectConfig(rootDir) {
|
|
|
1669
1935
|
listChanged: readBooleanNested(definition, "tools", "listChanged") ?? false
|
|
1670
1936
|
},
|
|
1671
1937
|
pagination: {
|
|
1672
|
-
pageSize: readNumberNested(definition, "pagination", "pageSize") ??
|
|
1938
|
+
pageSize: readNumberNested(definition, "pagination", "pageSize") ?? 50,
|
|
1673
1939
|
hasOverride: hasProperty(readObjectProperty3(definition, "pagination"), "override")
|
|
1674
1940
|
}
|
|
1675
1941
|
};
|
|
1676
1942
|
}
|
|
1943
|
+
function readWidgetBuildConfig(definition) {
|
|
1944
|
+
const widgets = readObjectProperty3(readObjectProperty3(definition, "build"), "widgets");
|
|
1945
|
+
if (!widgets) {
|
|
1946
|
+
return void 0;
|
|
1947
|
+
}
|
|
1948
|
+
const config = {};
|
|
1949
|
+
const configure = readStringProperty3(widgets, "configure");
|
|
1950
|
+
const esbuild3 = readWidgetEsbuildConfig(widgets);
|
|
1951
|
+
if (configure) config.configure = configure;
|
|
1952
|
+
if (esbuild3) config.esbuild = esbuild3;
|
|
1953
|
+
return Object.keys(config).length ? config : void 0;
|
|
1954
|
+
}
|
|
1955
|
+
function readWidgetEsbuildConfig(widgets) {
|
|
1956
|
+
const esbuild3 = readObjectProperty3(widgets, "esbuild");
|
|
1957
|
+
if (!esbuild3) {
|
|
1958
|
+
return void 0;
|
|
1959
|
+
}
|
|
1960
|
+
const config = {};
|
|
1961
|
+
const alias = readStringRecordProperty(esbuild3, "alias");
|
|
1962
|
+
const define = readStringRecordProperty(esbuild3, "define");
|
|
1963
|
+
const external = readStringArrayProperty3(esbuild3, "external");
|
|
1964
|
+
const loader = readStringRecordProperty(esbuild3, "loader");
|
|
1965
|
+
const conditions = readStringArrayProperty3(esbuild3, "conditions");
|
|
1966
|
+
const mainFields = readStringArrayProperty3(esbuild3, "mainFields");
|
|
1967
|
+
const jsx = readStringProperty3(esbuild3, "jsx");
|
|
1968
|
+
const jsxImportSource = readStringProperty3(esbuild3, "jsxImportSource");
|
|
1969
|
+
if (alias) config.alias = alias;
|
|
1970
|
+
if (define) config.define = define;
|
|
1971
|
+
if (external) config.external = external;
|
|
1972
|
+
if (loader) config.loader = loader;
|
|
1973
|
+
if (conditions) config.conditions = conditions;
|
|
1974
|
+
if (mainFields) config.mainFields = mainFields;
|
|
1975
|
+
if (jsx === "automatic" || jsx === "transform" || jsx === "preserve") {
|
|
1976
|
+
config.jsx = jsx;
|
|
1977
|
+
}
|
|
1978
|
+
if (jsxImportSource) config.jsxImportSource = jsxImportSource;
|
|
1979
|
+
return Object.keys(config).length ? config : void 0;
|
|
1980
|
+
}
|
|
1677
1981
|
function defaultCompilerConfig() {
|
|
1678
1982
|
return {
|
|
1983
|
+
build: {},
|
|
1679
1984
|
resources: {
|
|
1680
1985
|
subscribe: false,
|
|
1681
1986
|
listChanged: false
|
|
@@ -1687,11 +1992,26 @@ function defaultCompilerConfig() {
|
|
|
1687
1992
|
listChanged: false
|
|
1688
1993
|
},
|
|
1689
1994
|
pagination: {
|
|
1690
|
-
pageSize:
|
|
1995
|
+
pageSize: 50,
|
|
1691
1996
|
hasOverride: false
|
|
1692
1997
|
}
|
|
1693
1998
|
};
|
|
1694
1999
|
}
|
|
2000
|
+
function readTargetNested(definition, section, propertyName) {
|
|
2001
|
+
const value = readStringNested(definition, section, propertyName);
|
|
2002
|
+
return value === "mcp" || value === "chatgpt" || value === "claude" ? value : void 0;
|
|
2003
|
+
}
|
|
2004
|
+
function readHostNested(definition, section, propertyName) {
|
|
2005
|
+
const value = readStringNested(definition, section, propertyName);
|
|
2006
|
+
return value === "node" || value === "vercel" ? value : void 0;
|
|
2007
|
+
}
|
|
2008
|
+
function readStringNested(definition, section, propertyName) {
|
|
2009
|
+
const object = readObjectProperty3(definition, section);
|
|
2010
|
+
if (!object) {
|
|
2011
|
+
return void 0;
|
|
2012
|
+
}
|
|
2013
|
+
return readStringProperty3(object, propertyName);
|
|
2014
|
+
}
|
|
1695
2015
|
function readBooleanNested(definition, section, propertyName) {
|
|
1696
2016
|
const object = readObjectProperty3(definition, section);
|
|
1697
2017
|
if (!object) {
|
|
@@ -1722,6 +2042,9 @@ function readNumberNested(definition, section, propertyName) {
|
|
|
1722
2042
|
return initializer && Node5.isNumericLiteral(initializer) ? Number(initializer.getLiteralText()) : void 0;
|
|
1723
2043
|
}
|
|
1724
2044
|
function readObjectProperty3(definition, propertyName) {
|
|
2045
|
+
if (!definition) {
|
|
2046
|
+
return void 0;
|
|
2047
|
+
}
|
|
1725
2048
|
const property = definition.getProperty(propertyName);
|
|
1726
2049
|
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
1727
2050
|
return void 0;
|
|
@@ -1729,38 +2052,79 @@ function readObjectProperty3(definition, propertyName) {
|
|
|
1729
2052
|
const initializer = unwrapExpression(property.getInitializer());
|
|
1730
2053
|
return initializer && Node5.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
1731
2054
|
}
|
|
2055
|
+
function readStringProperty3(definition, propertyName) {
|
|
2056
|
+
const property = definition.getProperty(propertyName);
|
|
2057
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
2058
|
+
return void 0;
|
|
2059
|
+
}
|
|
2060
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2061
|
+
return initializer && Node5.isStringLiteral(initializer) ? initializer.getLiteralText() : void 0;
|
|
2062
|
+
}
|
|
2063
|
+
function readStringArrayProperty3(definition, propertyName) {
|
|
2064
|
+
const property = definition.getProperty(propertyName);
|
|
2065
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
2066
|
+
return void 0;
|
|
2067
|
+
}
|
|
2068
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2069
|
+
if (!initializer || !Node5.isArrayLiteralExpression(initializer)) {
|
|
2070
|
+
return void 0;
|
|
2071
|
+
}
|
|
2072
|
+
const values = initializer.getElements().map((element) => {
|
|
2073
|
+
const unwrapped = unwrapExpression(element);
|
|
2074
|
+
return unwrapped && Node5.isStringLiteral(unwrapped) ? unwrapped.getLiteralText() : void 0;
|
|
2075
|
+
});
|
|
2076
|
+
return values.every((value) => value !== void 0) ? values : void 0;
|
|
2077
|
+
}
|
|
2078
|
+
function readStringRecordProperty(definition, propertyName) {
|
|
2079
|
+
const object = readObjectProperty3(definition, propertyName);
|
|
2080
|
+
if (!object) {
|
|
2081
|
+
return void 0;
|
|
2082
|
+
}
|
|
2083
|
+
const record = {};
|
|
2084
|
+
for (const property of object.getProperties()) {
|
|
2085
|
+
if (!Node5.isPropertyAssignment(property)) {
|
|
2086
|
+
return void 0;
|
|
2087
|
+
}
|
|
2088
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2089
|
+
if (!initializer || !Node5.isStringLiteral(initializer)) {
|
|
2090
|
+
return void 0;
|
|
2091
|
+
}
|
|
2092
|
+
record[property.getName().replace(/^["']|["']$/g, "")] = initializer.getLiteralText();
|
|
2093
|
+
}
|
|
2094
|
+
return record;
|
|
2095
|
+
}
|
|
1732
2096
|
function hasProperty(definition, propertyName) {
|
|
1733
2097
|
return Boolean(definition?.getProperty(propertyName));
|
|
1734
2098
|
}
|
|
1735
2099
|
|
|
1736
2100
|
// packages/compiler/src/diagnostics.ts
|
|
1737
|
-
import { existsSync as
|
|
2101
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1738
2102
|
import { readFile as readFile2 } from "fs/promises";
|
|
1739
|
-
import
|
|
2103
|
+
import path7 from "path";
|
|
1740
2104
|
async function collectProjectDiagnostics(rootDir, input) {
|
|
1741
2105
|
const diagnostics = [];
|
|
1742
2106
|
const tools = Array.isArray(input) ? input : input.tools;
|
|
1743
2107
|
const resources = Array.isArray(input) ? [] : input.resources ?? [];
|
|
1744
2108
|
const prompts = Array.isArray(input) ? [] : input.prompts ?? [];
|
|
1745
2109
|
const config = Array.isArray(input) ? void 0 : input.config;
|
|
1746
|
-
const hasAuthConfig =
|
|
2110
|
+
const hasAuthConfig = existsSync4(path7.join(rootDir, "auth.ts"));
|
|
1747
2111
|
for (const entry of tools) {
|
|
1748
|
-
const toolPath =
|
|
2112
|
+
const toolPath = path7.join(rootDir, entry.sourceFile);
|
|
1749
2113
|
const toolSource = await readFile2(toolPath, "utf8");
|
|
1750
|
-
diagnostics.push(...diagnoseToolSource(
|
|
2114
|
+
diagnostics.push(...diagnoseToolSource(entry, toolSource, hasAuthConfig));
|
|
1751
2115
|
if (entry.widget) {
|
|
1752
|
-
const widgetPath =
|
|
2116
|
+
const widgetPath = path7.join(rootDir, entry.widget.sourceFile);
|
|
1753
2117
|
const widgetSource = await readFile2(widgetPath, "utf8");
|
|
1754
2118
|
diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource));
|
|
1755
2119
|
}
|
|
1756
2120
|
}
|
|
1757
2121
|
for (const entry of resources) {
|
|
1758
|
-
const resourcePath =
|
|
2122
|
+
const resourcePath = path7.join(rootDir, entry.sourceFile);
|
|
1759
2123
|
const resourceSource = await readFile2(resourcePath, "utf8");
|
|
1760
2124
|
diagnostics.push(...diagnoseResourceSource(entry, resourceSource));
|
|
1761
2125
|
}
|
|
1762
2126
|
for (const entry of prompts) {
|
|
1763
|
-
const promptPath =
|
|
2127
|
+
const promptPath = path7.join(rootDir, entry.sourceFile);
|
|
1764
2128
|
const promptSource = await readFile2(promptPath, "utf8");
|
|
1765
2129
|
diagnostics.push(...diagnosePromptSource(entry, promptSource));
|
|
1766
2130
|
}
|
|
@@ -1775,7 +2139,7 @@ function formatDiagnostic(diagnostic) {
|
|
|
1775
2139
|
hint: ${diagnostic.hint}` : "";
|
|
1776
2140
|
return `${location} - ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}${hint}`;
|
|
1777
2141
|
}
|
|
1778
|
-
function diagnoseToolSource(
|
|
2142
|
+
function diagnoseToolSource(entry, source, hasAuthConfig) {
|
|
1779
2143
|
const diagnostics = [];
|
|
1780
2144
|
const toolLocation = locate(source, "tool({");
|
|
1781
2145
|
if (!entry.description.trim().startsWith("Use this when")) {
|
|
@@ -1992,21 +2356,21 @@ function isIgnored(source, code) {
|
|
|
1992
2356
|
|
|
1993
2357
|
// packages/compiler/src/generated.ts
|
|
1994
2358
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
1995
|
-
import
|
|
2359
|
+
import path8 from "path";
|
|
1996
2360
|
async function writeGeneratedTypes(rootDir, tools) {
|
|
1997
|
-
const generatedDir =
|
|
2361
|
+
const generatedDir = path8.join(rootDir, ".sidecar", "generated");
|
|
1998
2362
|
await mkdir2(generatedDir, { recursive: true });
|
|
1999
2363
|
const appCallableTools = tools.filter(isAppCallable);
|
|
2000
2364
|
const methodNames = uniqueMethodNames(appCallableTools.map((entry) => toIdentifier(entry.id)));
|
|
2001
2365
|
const imports = appCallableTools.map(
|
|
2002
|
-
(entry, index) => `import type tool${index} from ${JSON.stringify(toImportSpecifier(generatedDir,
|
|
2366
|
+
(entry, index) => `import type tool${index} from ${JSON.stringify(toImportSpecifier(generatedDir, path8.join(rootDir, entry.sourceFile), { extension: "js" }))};`
|
|
2003
2367
|
).join("\n");
|
|
2004
2368
|
const ids = appCallableTools.map((entry, index) => ` ${methodNames[index]}: ${JSON.stringify(entry.id)}`).join(",\n");
|
|
2005
2369
|
const toolTypes = appCallableTools.map(
|
|
2006
|
-
(
|
|
2370
|
+
(_entry, index) => ` ${methodNames[index]}(params: ToolParams<typeof tool${index}>): Promise<ToolOutput<typeof tool${index}>>;`
|
|
2007
2371
|
).join("\n");
|
|
2008
2372
|
await writeFile2(
|
|
2009
|
-
|
|
2373
|
+
path8.join(generatedDir, "tools.ts"),
|
|
2010
2374
|
`/**
|
|
2011
2375
|
* Generated Sidecar widget tool client.
|
|
2012
2376
|
*
|
|
@@ -2054,19 +2418,15 @@ function uniqueMethodNames(names) {
|
|
|
2054
2418
|
});
|
|
2055
2419
|
}
|
|
2056
2420
|
|
|
2057
|
-
// packages/compiler/src/plugins.ts
|
|
2058
|
-
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
2059
|
-
import path14 from "path";
|
|
2060
|
-
|
|
2061
2421
|
// packages/compiler/src/identity.ts
|
|
2062
2422
|
import { readFile as readFile3 } from "fs/promises";
|
|
2063
|
-
import
|
|
2423
|
+
import path9 from "path";
|
|
2064
2424
|
async function loadProjectIdentity(rootDir) {
|
|
2065
|
-
const packageJson = await readOptionalJson(
|
|
2425
|
+
const packageJson = await readOptionalJson(path9.join(rootDir, "package.json"));
|
|
2066
2426
|
const configText = await readOptionalText(
|
|
2067
|
-
|
|
2427
|
+
path9.join(rootDir, "sidecar.config.ts")
|
|
2068
2428
|
);
|
|
2069
|
-
const name = readConfigString(configText, "name") ?? packageJson?.name ??
|
|
2429
|
+
const name = readConfigString(configText, "name") ?? packageJson?.name ?? path9.basename(rootDir);
|
|
2070
2430
|
const version = readConfigString(configText, "version") ?? packageJson?.version ?? "0.0.0-dev";
|
|
2071
2431
|
const description = readConfigString(configText, "description") ?? packageJson?.description ?? `${name} Sidecar app.`;
|
|
2072
2432
|
return {
|
|
@@ -2098,30 +2458,34 @@ function readConfigString(configText, key) {
|
|
|
2098
2458
|
return match?.[1];
|
|
2099
2459
|
}
|
|
2100
2460
|
|
|
2461
|
+
// packages/compiler/src/plugins.ts
|
|
2462
|
+
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
2463
|
+
import path15 from "path";
|
|
2464
|
+
|
|
2101
2465
|
// packages/compiler/src/plugin-components/agents.ts
|
|
2102
2466
|
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
2103
|
-
import
|
|
2467
|
+
import path10 from "path";
|
|
2104
2468
|
async function emitClaudeAgents(rootDir, destination) {
|
|
2105
|
-
const source =
|
|
2469
|
+
const source = path10.join(rootDir, "agents");
|
|
2106
2470
|
if (!existsSyncSafe(source)) {
|
|
2107
2471
|
return;
|
|
2108
2472
|
}
|
|
2109
2473
|
const entries = await readdir2(source, { withFileTypes: true });
|
|
2110
2474
|
const agentDirs = entries.filter(
|
|
2111
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2475
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path10.join(source, entry.name, "agent.ts"))
|
|
2112
2476
|
);
|
|
2113
2477
|
if (!agentDirs.length) {
|
|
2114
2478
|
return;
|
|
2115
2479
|
}
|
|
2116
2480
|
await mkdir3(destination, { recursive: true });
|
|
2117
2481
|
for (const entry of agentDirs) {
|
|
2118
|
-
const sourceText = await readFile4(
|
|
2482
|
+
const sourceText = await readFile4(path10.join(source, entry.name, "agent.ts"), "utf8");
|
|
2119
2483
|
const markdown = parseClaudeAgent(
|
|
2120
2484
|
sourceText,
|
|
2121
2485
|
entry.name
|
|
2122
2486
|
);
|
|
2123
2487
|
const agentName = readObjectString(sourceText, "name") ?? entry.name;
|
|
2124
|
-
await writeFile3(
|
|
2488
|
+
await writeFile3(path10.join(destination, `${safeFileStem(agentName)}.md`), markdown);
|
|
2125
2489
|
}
|
|
2126
2490
|
}
|
|
2127
2491
|
function parseClaudeAgent(source, fallbackName) {
|
|
@@ -2150,41 +2514,41 @@ ${prompt.trim()}
|
|
|
2150
2514
|
|
|
2151
2515
|
// packages/compiler/src/plugin-components/commands.ts
|
|
2152
2516
|
import { cp, lstat, mkdir as mkdir4, readdir as readdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
2153
|
-
import
|
|
2517
|
+
import path11 from "path";
|
|
2154
2518
|
async function copyCommands(rootDir, destination) {
|
|
2155
|
-
const source =
|
|
2519
|
+
const source = path11.join(rootDir, "commands");
|
|
2156
2520
|
if (!existsSyncSafe(source)) {
|
|
2157
2521
|
return;
|
|
2158
2522
|
}
|
|
2159
2523
|
await mkdir4(destination, { recursive: true });
|
|
2160
2524
|
const entries = await readdir3(source, { withFileTypes: true });
|
|
2161
2525
|
for (const entry of entries) {
|
|
2162
|
-
const sourcePath =
|
|
2526
|
+
const sourcePath = path11.join(source, entry.name);
|
|
2163
2527
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
2164
2528
|
if (await safeCommandCopyFilter(sourcePath)) {
|
|
2165
|
-
await cp(sourcePath,
|
|
2529
|
+
await cp(sourcePath, path11.join(destination, safeFileStem(entry.name.replace(/\.md$/, "")) + ".md"));
|
|
2166
2530
|
}
|
|
2167
2531
|
continue;
|
|
2168
2532
|
}
|
|
2169
2533
|
if (!entry.isDirectory()) {
|
|
2170
2534
|
continue;
|
|
2171
2535
|
}
|
|
2172
|
-
const dynamicCommand =
|
|
2536
|
+
const dynamicCommand = path11.join(sourcePath, "command.ts");
|
|
2173
2537
|
if (existsSyncSafe(dynamicCommand)) {
|
|
2174
2538
|
const sourceText = await readFile5(dynamicCommand, "utf8");
|
|
2175
2539
|
const markdown = parseDynamicCommand(sourceText, entry.name);
|
|
2176
2540
|
const commandName = readObjectString(sourceText, "name") ?? entry.name;
|
|
2177
|
-
await writeFile4(
|
|
2541
|
+
await writeFile4(path11.join(destination, `${safeFileStem(commandName)}.md`), markdown);
|
|
2178
2542
|
continue;
|
|
2179
2543
|
}
|
|
2180
|
-
await cp(sourcePath,
|
|
2544
|
+
await cp(sourcePath, path11.join(destination, entry.name), {
|
|
2181
2545
|
recursive: true,
|
|
2182
2546
|
filter: safeCommandCopyFilter
|
|
2183
2547
|
});
|
|
2184
2548
|
}
|
|
2185
2549
|
}
|
|
2186
2550
|
async function safeCommandCopyFilter(sourcePath) {
|
|
2187
|
-
const basename =
|
|
2551
|
+
const basename = path11.basename(sourcePath);
|
|
2188
2552
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2189
2553
|
return false;
|
|
2190
2554
|
}
|
|
@@ -2213,32 +2577,32 @@ ${Object.entries(frontmatter).map(([key, value]) => `${key}: ${yamlScalar(value)
|
|
|
2213
2577
|
|
|
2214
2578
|
// packages/compiler/src/plugin-components/hooks.ts
|
|
2215
2579
|
import { mkdir as mkdir5, readdir as readdir4, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
2216
|
-
import
|
|
2580
|
+
import path12 from "path";
|
|
2217
2581
|
import {
|
|
2218
2582
|
Node as Node6,
|
|
2219
2583
|
Project as Project2,
|
|
2220
2584
|
SyntaxKind as SyntaxKind4
|
|
2221
2585
|
} from "ts-morph";
|
|
2222
2586
|
async function copyHooks(rootDir, destination) {
|
|
2223
|
-
const source =
|
|
2587
|
+
const source = path12.join(rootDir, "hooks");
|
|
2224
2588
|
if (!existsSyncSafe(source)) {
|
|
2225
2589
|
return;
|
|
2226
2590
|
}
|
|
2227
2591
|
const entries = await readdir4(source, { withFileTypes: true });
|
|
2228
2592
|
const hookDirs = entries.filter(
|
|
2229
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2593
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path12.join(source, entry.name, "hook.ts"))
|
|
2230
2594
|
);
|
|
2231
2595
|
if (!hookDirs.length) {
|
|
2232
2596
|
return;
|
|
2233
2597
|
}
|
|
2234
2598
|
const config = {};
|
|
2235
2599
|
for (const entry of hookDirs) {
|
|
2236
|
-
const filePath =
|
|
2600
|
+
const filePath = path12.join(source, entry.name, "hook.ts");
|
|
2237
2601
|
mergeHooks(config, parseHookFile(await readFile6(filePath, "utf8"), entry.name));
|
|
2238
2602
|
}
|
|
2239
2603
|
await mkdir5(destination, { recursive: true });
|
|
2240
2604
|
await writeFile5(
|
|
2241
|
-
|
|
2605
|
+
path12.join(destination, "hooks.json"),
|
|
2242
2606
|
`${JSON.stringify(config, null, 2)}
|
|
2243
2607
|
`
|
|
2244
2608
|
);
|
|
@@ -2265,7 +2629,7 @@ function parseHookFile(source, fallbackName) {
|
|
|
2265
2629
|
throw new Error(`hooks/${fallbackName}/hook.ts must default-export hook({ ... }) or hooks({ ... }).`);
|
|
2266
2630
|
}
|
|
2267
2631
|
function parseHookDefinition(definition, fallbackName) {
|
|
2268
|
-
const event =
|
|
2632
|
+
const event = readStringProperty4(definition, "event");
|
|
2269
2633
|
if (!event) {
|
|
2270
2634
|
throw new Error(`hooks/${fallbackName}/hook.ts must declare an event string.`);
|
|
2271
2635
|
}
|
|
@@ -2275,7 +2639,7 @@ function parseHookDefinition(definition, fallbackName) {
|
|
|
2275
2639
|
}
|
|
2276
2640
|
return {
|
|
2277
2641
|
event,
|
|
2278
|
-
matcher:
|
|
2642
|
+
matcher: readStringProperty4(definition, "matcher"),
|
|
2279
2643
|
run: hooks
|
|
2280
2644
|
};
|
|
2281
2645
|
}
|
|
@@ -2295,7 +2659,7 @@ function parseHooksDefinition(definition, fallbackName) {
|
|
|
2295
2659
|
throw new Error(`hooks/${fallbackName}/hook.ts event "${event}" entries must be objects.`);
|
|
2296
2660
|
}
|
|
2297
2661
|
return stripUndefined2({
|
|
2298
|
-
matcher:
|
|
2662
|
+
matcher: readStringProperty4(entry, "matcher"),
|
|
2299
2663
|
hooks: readHookArray(entry, "run", fallbackName)
|
|
2300
2664
|
});
|
|
2301
2665
|
});
|
|
@@ -2318,7 +2682,7 @@ function parseHookHandlers(handlers, fallbackName) {
|
|
|
2318
2682
|
}
|
|
2319
2683
|
function parseHookHandler(handler, fallbackName) {
|
|
2320
2684
|
if (Node6.isObjectLiteralExpression(handler)) {
|
|
2321
|
-
const type =
|
|
2685
|
+
const type = readStringProperty4(handler, "type");
|
|
2322
2686
|
if (type !== "command" && type !== "http") {
|
|
2323
2687
|
throw new Error(`hooks/${fallbackName}/hook.ts hook handlers need type "command" or "http".`);
|
|
2324
2688
|
}
|
|
@@ -2368,7 +2732,7 @@ function objectLiteralToRecord(object) {
|
|
|
2368
2732
|
}
|
|
2369
2733
|
return stripUndefined2(record);
|
|
2370
2734
|
}
|
|
2371
|
-
function
|
|
2735
|
+
function readStringProperty4(object, propertyName) {
|
|
2372
2736
|
const property = object.getProperty(propertyName);
|
|
2373
2737
|
if (!property || !Node6.isPropertyAssignment(property)) {
|
|
2374
2738
|
return void 0;
|
|
@@ -2412,7 +2776,7 @@ function mergeHooks(target, source) {
|
|
|
2412
2776
|
|
|
2413
2777
|
// packages/compiler/src/plugin-components/passthrough.ts
|
|
2414
2778
|
import { cp as cp2, lstat as lstat2 } from "fs/promises";
|
|
2415
|
-
import
|
|
2779
|
+
import path13 from "path";
|
|
2416
2780
|
async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
2417
2781
|
for (const directory of [
|
|
2418
2782
|
"assets",
|
|
@@ -2421,18 +2785,18 @@ async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
|
2421
2785
|
"output-styles",
|
|
2422
2786
|
"themes"
|
|
2423
2787
|
]) {
|
|
2424
|
-
const source =
|
|
2788
|
+
const source = path13.join(rootDir, directory);
|
|
2425
2789
|
if (!existsSyncSafe(source)) {
|
|
2426
2790
|
continue;
|
|
2427
2791
|
}
|
|
2428
|
-
await cp2(source,
|
|
2792
|
+
await cp2(source, path13.join(pluginDir, directory), {
|
|
2429
2793
|
recursive: true,
|
|
2430
2794
|
filter: safePluginCopyFilter
|
|
2431
2795
|
});
|
|
2432
2796
|
}
|
|
2433
2797
|
}
|
|
2434
2798
|
async function safePluginCopyFilter(sourcePath) {
|
|
2435
|
-
const basename =
|
|
2799
|
+
const basename = path13.basename(sourcePath);
|
|
2436
2800
|
if (basename === "node_modules" || basename === ".git" || basename === ".env" || basename.startsWith(".env.")) {
|
|
2437
2801
|
return false;
|
|
2438
2802
|
}
|
|
@@ -2441,11 +2805,11 @@ async function safePluginCopyFilter(sourcePath) {
|
|
|
2441
2805
|
}
|
|
2442
2806
|
|
|
2443
2807
|
// packages/compiler/src/plugin-components/skills.ts
|
|
2444
|
-
import { existsSync as
|
|
2808
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2445
2809
|
import { cp as cp3, lstat as lstat3, mkdir as mkdir6, readdir as readdir5, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
2446
|
-
import
|
|
2810
|
+
import path14 from "path";
|
|
2447
2811
|
async function copySkills(rootDir, destination) {
|
|
2448
|
-
const source =
|
|
2812
|
+
const source = path14.join(rootDir, "skills");
|
|
2449
2813
|
if (!existsSyncSafe(source)) {
|
|
2450
2814
|
return;
|
|
2451
2815
|
}
|
|
@@ -2455,27 +2819,27 @@ async function copySkills(rootDir, destination) {
|
|
|
2455
2819
|
if (!entry.isDirectory()) {
|
|
2456
2820
|
continue;
|
|
2457
2821
|
}
|
|
2458
|
-
const sourceDir =
|
|
2459
|
-
const destinationDir =
|
|
2460
|
-
const staticSkill =
|
|
2461
|
-
const dynamicSkill =
|
|
2822
|
+
const sourceDir = path14.join(source, entry.name);
|
|
2823
|
+
const destinationDir = path14.join(destination, entry.name);
|
|
2824
|
+
const staticSkill = path14.join(sourceDir, "SKILL.md");
|
|
2825
|
+
const dynamicSkill = path14.join(sourceDir, "skill.ts");
|
|
2462
2826
|
await mkdir6(destinationDir, { recursive: true });
|
|
2463
|
-
if (
|
|
2827
|
+
if (existsSync5(staticSkill)) {
|
|
2464
2828
|
await cp3(sourceDir, destinationDir, {
|
|
2465
2829
|
recursive: true,
|
|
2466
|
-
filter: async (sourcePath) => !sourcePath.endsWith(`${
|
|
2830
|
+
filter: async (sourcePath) => !sourcePath.endsWith(`${path14.sep}skill.ts`) && await safeSkillCopyFilter(sourcePath)
|
|
2467
2831
|
});
|
|
2468
|
-
} else if (
|
|
2832
|
+
} else if (existsSync5(dynamicSkill)) {
|
|
2469
2833
|
const generated = parseDynamicSkill(
|
|
2470
2834
|
await readFile7(dynamicSkill, "utf8"),
|
|
2471
2835
|
entry.name
|
|
2472
2836
|
);
|
|
2473
|
-
await writeFile6(
|
|
2837
|
+
await writeFile6(path14.join(destinationDir, "SKILL.md"), generated);
|
|
2474
2838
|
}
|
|
2475
2839
|
}
|
|
2476
2840
|
}
|
|
2477
2841
|
async function safeSkillCopyFilter(sourcePath) {
|
|
2478
|
-
const basename =
|
|
2842
|
+
const basename = path14.basename(sourcePath);
|
|
2479
2843
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2480
2844
|
return false;
|
|
2481
2845
|
}
|
|
@@ -2501,25 +2865,25 @@ async function buildPluginPackages(rootDir, outRoot, manifest) {
|
|
|
2501
2865
|
await buildClaudePlugin(rootDir, outRoot, identity, manifest);
|
|
2502
2866
|
}
|
|
2503
2867
|
async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
2504
|
-
const pluginDir =
|
|
2505
|
-
await mkdir7(
|
|
2506
|
-
await copySkills(rootDir,
|
|
2507
|
-
await copyCommands(rootDir,
|
|
2508
|
-
await copyHooks(rootDir,
|
|
2509
|
-
await emitClaudeAgents(rootDir,
|
|
2868
|
+
const pluginDir = path15.join(outRoot, "claude-plugin");
|
|
2869
|
+
await mkdir7(path15.join(pluginDir, ".claude-plugin"), { recursive: true });
|
|
2870
|
+
await copySkills(rootDir, path15.join(pluginDir, "skills"));
|
|
2871
|
+
await copyCommands(rootDir, path15.join(pluginDir, "commands"));
|
|
2872
|
+
await copyHooks(rootDir, path15.join(pluginDir, "hooks"));
|
|
2873
|
+
await emitClaudeAgents(rootDir, path15.join(pluginDir, "agents"));
|
|
2510
2874
|
await copyClaudePassthroughDirectories(rootDir, pluginDir);
|
|
2511
|
-
await writeJson(
|
|
2875
|
+
await writeJson(path15.join(pluginDir, ".claude-plugin", "plugin.json"), {
|
|
2512
2876
|
name: identity.slug,
|
|
2513
2877
|
version: identity.version,
|
|
2514
2878
|
description: identity.description,
|
|
2515
2879
|
displayName: identity.name,
|
|
2516
2880
|
installationPreference: "available"
|
|
2517
2881
|
});
|
|
2518
|
-
await writeJson(
|
|
2882
|
+
await writeJson(path15.join(pluginDir, "version.json"), {
|
|
2519
2883
|
version: identity.version,
|
|
2520
2884
|
generatedBy: "sidecar"
|
|
2521
2885
|
});
|
|
2522
|
-
await writeJson(
|
|
2886
|
+
await writeJson(path15.join(pluginDir, ".mcp.json"), {
|
|
2523
2887
|
mcpServers: {
|
|
2524
2888
|
[identity.slug]: {
|
|
2525
2889
|
type: "http",
|
|
@@ -2527,7 +2891,7 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2527
2891
|
}
|
|
2528
2892
|
}
|
|
2529
2893
|
});
|
|
2530
|
-
await writeJson(
|
|
2894
|
+
await writeJson(path15.join(pluginDir, "settings.json"), {
|
|
2531
2895
|
sidecar: {
|
|
2532
2896
|
tools: manifest.tools.map((entry) => entry.id),
|
|
2533
2897
|
resources: manifest.resources.map((entry) => entry.uri),
|
|
@@ -2535,21 +2899,21 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2535
2899
|
}
|
|
2536
2900
|
});
|
|
2537
2901
|
await writeFile7(
|
|
2538
|
-
|
|
2902
|
+
path15.join(pluginDir, "README.md"),
|
|
2539
2903
|
`# ${identity.name} Claude Plugin
|
|
2540
2904
|
|
|
2541
2905
|
This package was generated by Sidecar.
|
|
2542
2906
|
|
|
2543
2907
|
Set \`SIDECAR_MCP_URL\` to the hosted MCP endpoint for this app.
|
|
2544
2908
|
|
|
2545
|
-
For local testing, run \`sidecar dev --tunnel\` and use the
|
|
2909
|
+
For local testing, run \`sidecar dev --tunnel\` and use the validated HTTPS MCP URL it prints. Temporary quick tunnels are public and best-effort; use a configured tunnel/domain or deployed preview for repeatable testing.
|
|
2546
2910
|
|
|
2547
2911
|
Claude/Cowork plugin-specific components such as skills, slash commands, agents, hooks, output styles, themes, monitors, assets, and bin scripts will be emitted here when the source project defines them.
|
|
2548
2912
|
`
|
|
2549
2913
|
);
|
|
2550
2914
|
}
|
|
2551
2915
|
async function writeJson(filePath, value) {
|
|
2552
|
-
await mkdir7(
|
|
2916
|
+
await mkdir7(path15.dirname(filePath), { recursive: true });
|
|
2553
2917
|
await writeFile7(
|
|
2554
2918
|
filePath,
|
|
2555
2919
|
`${JSON.stringify(stripUndefined2(value), null, 2)}
|
|
@@ -2559,13 +2923,13 @@ async function writeJson(filePath, value) {
|
|
|
2559
2923
|
|
|
2560
2924
|
// packages/compiler/src/prompts.ts
|
|
2561
2925
|
import { readdir as readdir6 } from "fs/promises";
|
|
2562
|
-
import
|
|
2926
|
+
import path16 from "path";
|
|
2563
2927
|
import {
|
|
2564
2928
|
Node as Node7,
|
|
2565
2929
|
SyntaxKind as SyntaxKind5
|
|
2566
2930
|
} from "ts-morph";
|
|
2567
2931
|
async function analyzeProjectPrompts(rootDir) {
|
|
2568
|
-
const promptFiles = await findPromptFiles(
|
|
2932
|
+
const promptFiles = await findPromptFiles(path16.join(rootDir, "prompts"));
|
|
2569
2933
|
if (promptFiles.length === 0) {
|
|
2570
2934
|
return [];
|
|
2571
2935
|
}
|
|
@@ -2577,7 +2941,7 @@ async function analyzeProjectPrompts(rootDir) {
|
|
|
2577
2941
|
function analyzePromptFile(sourceFile, rootDir) {
|
|
2578
2942
|
const definition = findPromptDefinition(sourceFile);
|
|
2579
2943
|
const absoluteFile = sourceFile.getFilePath();
|
|
2580
|
-
const directory =
|
|
2944
|
+
const directory = path16.basename(path16.dirname(absoluteFile));
|
|
2581
2945
|
const name = getOptionalStringProperty2(definition, "name") ?? safePathSegment(directory);
|
|
2582
2946
|
const title = getRequiredStringProperty2(definition, "title", sourceFile);
|
|
2583
2947
|
const description = getOptionalStringProperty2(definition, "description");
|
|
@@ -2593,7 +2957,7 @@ function analyzePromptFile(sourceFile, rootDir) {
|
|
|
2593
2957
|
throw new CompilerError(sourceFile, "prompt({ ... }) must include a run method.");
|
|
2594
2958
|
}
|
|
2595
2959
|
return {
|
|
2596
|
-
sourceFile:
|
|
2960
|
+
sourceFile: path16.relative(rootDir, absoluteFile),
|
|
2597
2961
|
directory,
|
|
2598
2962
|
name,
|
|
2599
2963
|
title,
|
|
@@ -2612,7 +2976,7 @@ async function findPromptFiles(promptsDir) {
|
|
|
2612
2976
|
if (!entry.isDirectory()) {
|
|
2613
2977
|
continue;
|
|
2614
2978
|
}
|
|
2615
|
-
const filePath =
|
|
2979
|
+
const filePath = path16.join(promptsDir, entry.name, "prompt.ts");
|
|
2616
2980
|
if (existsSyncSafe(filePath)) {
|
|
2617
2981
|
files.push(filePath);
|
|
2618
2982
|
}
|
|
@@ -2734,7 +3098,7 @@ function readIcons(definition) {
|
|
|
2734
3098
|
return [{
|
|
2735
3099
|
src,
|
|
2736
3100
|
mimeType: getOptionalStringProperty2(object, "mimeType"),
|
|
2737
|
-
sizes:
|
|
3101
|
+
sizes: readStringArrayProperty4(object, "sizes")
|
|
2738
3102
|
}];
|
|
2739
3103
|
});
|
|
2740
3104
|
return icons.length ? icons : void 0;
|
|
@@ -2747,7 +3111,7 @@ function readObjectProperty4(definition, propertyName) {
|
|
|
2747
3111
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2748
3112
|
return initializer && Node7.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2749
3113
|
}
|
|
2750
|
-
function
|
|
3114
|
+
function readStringArrayProperty4(definition, propertyName) {
|
|
2751
3115
|
const property = definition.getProperty(propertyName);
|
|
2752
3116
|
if (!property || !Node7.isPropertyAssignment(property)) {
|
|
2753
3117
|
return void 0;
|
|
@@ -2761,13 +3125,13 @@ function readStringArrayProperty3(definition, propertyName) {
|
|
|
2761
3125
|
|
|
2762
3126
|
// packages/compiler/src/resources.ts
|
|
2763
3127
|
import { readdir as readdir7 } from "fs/promises";
|
|
2764
|
-
import
|
|
3128
|
+
import path17 from "path";
|
|
2765
3129
|
import {
|
|
2766
3130
|
Node as Node8,
|
|
2767
3131
|
SyntaxKind as SyntaxKind6
|
|
2768
3132
|
} from "ts-morph";
|
|
2769
3133
|
async function analyzeProjectResources(rootDir) {
|
|
2770
|
-
const resourceFiles = await findResourceFiles(
|
|
3134
|
+
const resourceFiles = await findResourceFiles(path17.join(rootDir, "resources"));
|
|
2771
3135
|
if (resourceFiles.length === 0) {
|
|
2772
3136
|
return [];
|
|
2773
3137
|
}
|
|
@@ -2779,7 +3143,7 @@ async function analyzeProjectResources(rootDir) {
|
|
|
2779
3143
|
function analyzeResourceFile(sourceFile, rootDir) {
|
|
2780
3144
|
const definition = findResourceDefinition(sourceFile);
|
|
2781
3145
|
const absoluteFile = sourceFile.getFilePath();
|
|
2782
|
-
const directory =
|
|
3146
|
+
const directory = path17.basename(path17.dirname(absoluteFile));
|
|
2783
3147
|
const defaultUri = `sidecar://resources/${safePathSegment(directory)}`;
|
|
2784
3148
|
const name = getRequiredStringProperty3(definition, "name", sourceFile);
|
|
2785
3149
|
const uri = getOptionalStringProperty3(definition, "uri") ?? defaultUri;
|
|
@@ -2797,7 +3161,7 @@ function analyzeResourceFile(sourceFile, rootDir) {
|
|
|
2797
3161
|
throw new CompilerError(sourceFile, "resource({ ... }) must include a read method.");
|
|
2798
3162
|
}
|
|
2799
3163
|
return {
|
|
2800
|
-
sourceFile:
|
|
3164
|
+
sourceFile: path17.relative(rootDir, absoluteFile),
|
|
2801
3165
|
directory,
|
|
2802
3166
|
uri,
|
|
2803
3167
|
name,
|
|
@@ -2820,7 +3184,7 @@ async function findResourceFiles(resourcesDir) {
|
|
|
2820
3184
|
if (!entry.isDirectory()) {
|
|
2821
3185
|
continue;
|
|
2822
3186
|
}
|
|
2823
|
-
const filePath =
|
|
3187
|
+
const filePath = path17.join(resourcesDir, entry.name, "resource.ts");
|
|
2824
3188
|
if (existsSyncSafe(filePath)) {
|
|
2825
3189
|
files.push(filePath);
|
|
2826
3190
|
}
|
|
@@ -2899,7 +3263,7 @@ function readAnnotations2(definition) {
|
|
|
2899
3263
|
return void 0;
|
|
2900
3264
|
}
|
|
2901
3265
|
const annotations = {};
|
|
2902
|
-
const audience =
|
|
3266
|
+
const audience = readStringArrayProperty5(initializer, "audience")?.filter((value) => value === "user" || value === "assistant");
|
|
2903
3267
|
const priority = getOptionalNumberProperty(initializer, "priority");
|
|
2904
3268
|
const lastModified = getOptionalStringProperty3(initializer, "lastModified");
|
|
2905
3269
|
if (audience?.length) annotations.audience = audience;
|
|
@@ -2928,7 +3292,7 @@ function readIcons2(definition) {
|
|
|
2928
3292
|
return [{
|
|
2929
3293
|
src,
|
|
2930
3294
|
mimeType: getOptionalStringProperty3(object, "mimeType"),
|
|
2931
|
-
sizes:
|
|
3295
|
+
sizes: readStringArrayProperty5(object, "sizes")
|
|
2932
3296
|
}];
|
|
2933
3297
|
});
|
|
2934
3298
|
return icons.length ? icons : void 0;
|
|
@@ -2941,7 +3305,7 @@ function readObjectProperty5(definition, propertyName) {
|
|
|
2941
3305
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2942
3306
|
return initializer && Node8.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2943
3307
|
}
|
|
2944
|
-
function
|
|
3308
|
+
function readStringArrayProperty5(definition, propertyName) {
|
|
2945
3309
|
const property = definition.getProperty(propertyName);
|
|
2946
3310
|
if (!property || !Node8.isPropertyAssignment(property)) {
|
|
2947
3311
|
return void 0;
|
|
@@ -2953,15 +3317,332 @@ function readStringArrayProperty4(definition, propertyName) {
|
|
|
2953
3317
|
return initializer.getElements().filter(Node8.isStringLiteral).map((element) => element.getLiteralText());
|
|
2954
3318
|
}
|
|
2955
3319
|
|
|
3320
|
+
// packages/compiler/src/server-output.ts
|
|
3321
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3322
|
+
import { mkdir as mkdir8, rm, writeFile as writeFile8 } from "fs/promises";
|
|
3323
|
+
import path18 from "path";
|
|
3324
|
+
import { build as esbuild2 } from "esbuild";
|
|
3325
|
+
var SERVER_ENTRYPOINT = "server/index.js";
|
|
3326
|
+
var VERCEL_FUNCTION_DIR = "functions/api/sidecar.func";
|
|
3327
|
+
var VERCEL_ENTRYPOINT = `${VERCEL_FUNCTION_DIR}/index.js`;
|
|
3328
|
+
var VERCEL_HANDLER_FILE = "index.js";
|
|
3329
|
+
async function buildServerOutput(rootDir, outDir, manifest, identity, host = "node", options = {}) {
|
|
3330
|
+
const cacheDir = path18.join(rootDir, ".sidecar", "cache", "server");
|
|
3331
|
+
await mkdir8(cacheDir, { recursive: true });
|
|
3332
|
+
const entryFile = path18.join(cacheDir, "index.ts");
|
|
3333
|
+
await writeFile8(entryFile, renderServerEntry(rootDir, entryFile, manifest, identity));
|
|
3334
|
+
const serverFile = path18.join(outDir, SERVER_ENTRYPOINT);
|
|
3335
|
+
await mkdir8(path18.dirname(serverFile), { recursive: true });
|
|
3336
|
+
await esbuild2({
|
|
3337
|
+
absWorkingDir: rootDir,
|
|
3338
|
+
alias: sidecarBundleAliases(rootDir),
|
|
3339
|
+
banner: {
|
|
3340
|
+
js: `import { createRequire as __sidecarCreateRequire } from "node:module";
|
|
3341
|
+
const require = __sidecarCreateRequire(import.meta.url);`
|
|
3342
|
+
},
|
|
3343
|
+
bundle: true,
|
|
3344
|
+
entryPoints: [entryFile],
|
|
3345
|
+
format: "esm",
|
|
3346
|
+
legalComments: "none",
|
|
3347
|
+
minify: false,
|
|
3348
|
+
nodePaths: [
|
|
3349
|
+
path18.join(rootDir, "node_modules"),
|
|
3350
|
+
path18.join(process.cwd(), "node_modules")
|
|
3351
|
+
],
|
|
3352
|
+
outfile: serverFile,
|
|
3353
|
+
platform: "node",
|
|
3354
|
+
sourcemap: false,
|
|
3355
|
+
target: "node20"
|
|
3356
|
+
});
|
|
3357
|
+
await writeFile8(path18.join(outDir, "package.json"), renderServerPackage(identity));
|
|
3358
|
+
if (host === "vercel") {
|
|
3359
|
+
const vercelOutputDir = options.vercelOutputDir ?? outDir;
|
|
3360
|
+
await rm(path18.join(vercelOutputDir, "api"), { recursive: true, force: true });
|
|
3361
|
+
await rm(path18.join(vercelOutputDir, "vercel.json"), { force: true });
|
|
3362
|
+
await writeFile8(path18.join(outDir, VERCEL_HANDLER_FILE), renderVercelEntrypoint());
|
|
3363
|
+
await writeFile8(path18.join(outDir, ".vc-config.json"), renderVercelFunctionConfig());
|
|
3364
|
+
await mkdir8(vercelOutputDir, { recursive: true });
|
|
3365
|
+
await writeFile8(path18.join(vercelOutputDir, "config.json"), renderVercelOutputConfig());
|
|
3366
|
+
} else {
|
|
3367
|
+
await rm(path18.join(outDir, "api"), { recursive: true, force: true });
|
|
3368
|
+
await rm(path18.join(outDir, "vercel.json"), { force: true });
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
function renderServerEntry(rootDir, entryFile, manifest, identity) {
|
|
3372
|
+
const entryDir = path18.dirname(entryFile);
|
|
3373
|
+
const tools = manifest.tools;
|
|
3374
|
+
const resources = manifest.resources;
|
|
3375
|
+
const prompts = manifest.prompts;
|
|
3376
|
+
const imports = [
|
|
3377
|
+
`import { readFileSync, realpathSync } from "node:fs";`,
|
|
3378
|
+
`import { createServer } from "node:http";`,
|
|
3379
|
+
`import { pathToFileURL } from "node:url";`,
|
|
3380
|
+
`import { createSidecarHttpHandler } from "@sidecar-ai/server";`,
|
|
3381
|
+
`import { isSidecarAuth } from "@sidecar-ai/auth";`,
|
|
3382
|
+
`import { isSidecarPrompt, isSidecarResource, isSidecarTool } from "@sidecar-ai/core";`,
|
|
3383
|
+
`import { isSidecarProxy } from "@sidecar-ai/server/proxy";`,
|
|
3384
|
+
...tools.map(
|
|
3385
|
+
(entry, index) => `import tool${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3386
|
+
),
|
|
3387
|
+
...resources.map(
|
|
3388
|
+
(entry, index) => `import resource${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3389
|
+
),
|
|
3390
|
+
...prompts.map(
|
|
3391
|
+
(entry, index) => `import prompt${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3392
|
+
),
|
|
3393
|
+
existsSync6(path18.join(rootDir, "auth.ts")) ? `import authExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "auth.ts")))};` : `const authExport = undefined;`,
|
|
3394
|
+
existsSync6(path18.join(rootDir, "proxy.ts")) ? `import proxyExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "proxy.ts")))};` : `const proxyExport = undefined;`,
|
|
3395
|
+
existsSync6(path18.join(rootDir, "sidecar.config.ts")) ? `import configExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "sidecar.config.ts")))};` : `const configExport = undefined;`
|
|
3396
|
+
].join("\n");
|
|
3397
|
+
return `${imports}
|
|
3398
|
+
|
|
3399
|
+
const manifest = ${JSON.stringify(manifest, null, 2)};
|
|
3400
|
+
const identity = ${JSON.stringify(identity, null, 2)};
|
|
3401
|
+
|
|
3402
|
+
const loadedAuth = authExport === undefined ? undefined : assertAuth(authExport);
|
|
3403
|
+
const auth = loadedAuth && process.env.SIDECAR_MCP_URL
|
|
3404
|
+
? loadedAuth.withResource(process.env.SIDECAR_MCP_URL)
|
|
3405
|
+
: loadedAuth;
|
|
3406
|
+
const proxy = proxyExport === undefined ? undefined : assertProxy(proxyExport);
|
|
3407
|
+
const runtimeConfig = configExport && typeof configExport === "object" ? configExport : undefined;
|
|
3408
|
+
|
|
3409
|
+
if (auth && !process.env.SIDECAR_MCP_URL) {
|
|
3410
|
+
console.warn("Sidecar auth is enabled. Set SIDECAR_MCP_URL to the public https://.../mcp URL before hosting.");
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
export const handler = createSidecarHttpHandler({
|
|
3414
|
+
name: identity.name,
|
|
3415
|
+
version: identity.version,
|
|
3416
|
+
path: process.env.SIDECAR_MCP_PATH ?? "/mcp",
|
|
3417
|
+
publicUrl: process.env.SIDECAR_PUBLIC_URL ?? process.env.SIDECAR_MCP_URL,
|
|
3418
|
+
auth,
|
|
3419
|
+
proxy,
|
|
3420
|
+
tools: [
|
|
3421
|
+
${tools.map((entry, index) => ` loadTool(${JSON.stringify(entry.sourceFile)}, tool${index}, manifest.tools[${index}].descriptor),`).join("\n")}
|
|
3422
|
+
],
|
|
3423
|
+
resources: [
|
|
3424
|
+
${renderWidgetResources(manifest)}
|
|
3425
|
+
${resources.map((entry, index) => ` loadResource(${JSON.stringify(entry.sourceFile)}, resource${index}, manifest.resources[${index}]),`).join("\n")}
|
|
3426
|
+
],
|
|
3427
|
+
resourceTemplates: manifest.resourceTemplates.map((entry) => ({ descriptor: entry.descriptor })),
|
|
3428
|
+
prompts: [
|
|
3429
|
+
${prompts.map((entry, index) => ` loadPrompt(${JSON.stringify(entry.sourceFile)}, prompt${index}, manifest.prompts[${index}].descriptor),`).join("\n")}
|
|
3430
|
+
],
|
|
3431
|
+
capabilities: {
|
|
3432
|
+
tools: runtimeConfig?.tools ?? manifest.config.tools,
|
|
3433
|
+
resources: runtimeConfig?.resources ?? manifest.config.resources,
|
|
3434
|
+
prompts: runtimeConfig?.prompts ?? manifest.config.prompts,
|
|
3435
|
+
},
|
|
3436
|
+
pagination: runtimeConfig?.pagination ?? {
|
|
3437
|
+
pageSize: manifest.config.pagination.pageSize,
|
|
3438
|
+
},
|
|
3439
|
+
});
|
|
3440
|
+
|
|
3441
|
+
export default handler;
|
|
3442
|
+
|
|
3443
|
+
export const server = createServer(handler);
|
|
3444
|
+
|
|
3445
|
+
if (isDirectRun()) {
|
|
3446
|
+
const port = readPort();
|
|
3447
|
+
const host = process.env.SIDECAR_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
3448
|
+
server.listen(port, host, () => {
|
|
3449
|
+
const localHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
3450
|
+
console.log(\`MCP running on Streamable HTTP at http://\${localHost}:\${port}\${process.env.SIDECAR_MCP_PATH ?? "/mcp"}\`);
|
|
3451
|
+
});
|
|
3452
|
+
|
|
3453
|
+
process.on("SIGTERM", () => shutdown());
|
|
3454
|
+
process.on("SIGINT", () => shutdown());
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
function loadTool(sourceFile, value, descriptor) {
|
|
3458
|
+
const tool = unwrapRuntimeDefault(value);
|
|
3459
|
+
if (!isSidecarTool(tool)) {
|
|
3460
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar tool.\`);
|
|
3461
|
+
}
|
|
3462
|
+
return { tool, descriptor };
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
function loadResource(sourceFile, value, entry) {
|
|
3466
|
+
const resource = unwrapRuntimeDefault(value);
|
|
3467
|
+
if (!isSidecarResource(resource)) {
|
|
3468
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar resource.\`);
|
|
3469
|
+
}
|
|
3470
|
+
return {
|
|
3471
|
+
uri: entry.uri,
|
|
3472
|
+
descriptor: entry.descriptor,
|
|
3473
|
+
resource,
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
function loadPrompt(sourceFile, value, descriptor) {
|
|
3478
|
+
const prompt = unwrapRuntimeDefault(value);
|
|
3479
|
+
if (!isSidecarPrompt(prompt)) {
|
|
3480
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar prompt.\`);
|
|
3481
|
+
}
|
|
3482
|
+
return { prompt, descriptor };
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
function assertAuth(value) {
|
|
3486
|
+
const authValue = unwrapRuntimeDefault(value);
|
|
3487
|
+
if (!isSidecarAuth(authValue)) {
|
|
3488
|
+
throw new Error("auth.ts must default-export auth({ ... }) from sidecar-ai.");
|
|
3489
|
+
}
|
|
3490
|
+
return authValue;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
function assertProxy(value) {
|
|
3494
|
+
const proxyValue = unwrapRuntimeDefault(value);
|
|
3495
|
+
if (!isSidecarProxy(proxyValue)) {
|
|
3496
|
+
throw new Error("proxy.ts must default-export proxy({ ... }) from @sidecar-ai/server/proxy.");
|
|
3497
|
+
}
|
|
3498
|
+
return proxyValue;
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
function unwrapRuntimeDefault(value) {
|
|
3502
|
+
if (
|
|
3503
|
+
value &&
|
|
3504
|
+
typeof value === "object" &&
|
|
3505
|
+
"default" in value &&
|
|
3506
|
+
Object.keys(value).every((key) => key === "default" || key === "__esModule")
|
|
3507
|
+
) {
|
|
3508
|
+
return unwrapRuntimeDefault(value.default);
|
|
3509
|
+
}
|
|
3510
|
+
return value;
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
function readWidget(outputFile) {
|
|
3514
|
+
return readFileSync(new URL(\`../\${outputFile}\`, import.meta.url), "utf8");
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
function readPort() {
|
|
3518
|
+
const raw = process.env.PORT ?? process.env.SIDECAR_PORT ?? "3001";
|
|
3519
|
+
const port = Number(raw);
|
|
3520
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
3521
|
+
throw new Error(\`Invalid PORT/SIDECAR_PORT value: \${raw}\`);
|
|
3522
|
+
}
|
|
3523
|
+
return port;
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
function isDirectRun() {
|
|
3527
|
+
const entry = process.argv[1];
|
|
3528
|
+
if (!entry) {
|
|
3529
|
+
return false;
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
return import.meta.url === pathToFileURL(realpathSync(entry)).href;
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
function shutdown() {
|
|
3536
|
+
server.close(() => process.exit(0));
|
|
3537
|
+
}
|
|
3538
|
+
`;
|
|
3539
|
+
}
|
|
3540
|
+
function renderWidgetResources(manifest) {
|
|
3541
|
+
return manifest.tools.filter((entry) => entry.widget?.outputFile).map((entry) => ` {
|
|
3542
|
+
uri: ${JSON.stringify(entry.widget?.resourceUri)},
|
|
3543
|
+
name: ${JSON.stringify(entry.name)},
|
|
3544
|
+
description: ${JSON.stringify(entry.widget?.options?.description)},
|
|
3545
|
+
mimeType: "text/html;profile=mcp-app",
|
|
3546
|
+
text: readWidget(${JSON.stringify(entry.widget?.outputFile)}),
|
|
3547
|
+
_meta: ${JSON.stringify(entry.widget?.resourceMeta ?? void 0)},
|
|
3548
|
+
},`).join("\n");
|
|
3549
|
+
}
|
|
3550
|
+
function renderServerPackage(identity) {
|
|
3551
|
+
return `${JSON.stringify({
|
|
3552
|
+
name: `${identity.slug}-sidecar-server`,
|
|
3553
|
+
version: identity.version,
|
|
3554
|
+
private: true,
|
|
3555
|
+
type: "module",
|
|
3556
|
+
scripts: {
|
|
3557
|
+
start: "node server/index.js"
|
|
3558
|
+
},
|
|
3559
|
+
engines: {
|
|
3560
|
+
node: ">=20"
|
|
3561
|
+
}
|
|
3562
|
+
}, null, 2)}
|
|
3563
|
+
`;
|
|
3564
|
+
}
|
|
3565
|
+
function renderVercelEntrypoint() {
|
|
3566
|
+
return `export { default } from "./server/index.js";
|
|
3567
|
+
`;
|
|
3568
|
+
}
|
|
3569
|
+
function renderVercelFunctionConfig() {
|
|
3570
|
+
return `${JSON.stringify({
|
|
3571
|
+
runtime: "nodejs22.x",
|
|
3572
|
+
handler: VERCEL_HANDLER_FILE,
|
|
3573
|
+
launcherType: "Nodejs",
|
|
3574
|
+
shouldAddHelpers: true,
|
|
3575
|
+
supportsResponseStreaming: true,
|
|
3576
|
+
maxDuration: 300
|
|
3577
|
+
}, null, 2)}
|
|
3578
|
+
`;
|
|
3579
|
+
}
|
|
3580
|
+
function renderVercelOutputConfig() {
|
|
3581
|
+
return `${JSON.stringify({
|
|
3582
|
+
version: 3,
|
|
3583
|
+
routes: [
|
|
3584
|
+
{
|
|
3585
|
+
src: "/(.*)",
|
|
3586
|
+
dest: "/api/sidecar"
|
|
3587
|
+
}
|
|
3588
|
+
]
|
|
3589
|
+
}, null, 2)}
|
|
3590
|
+
`;
|
|
3591
|
+
}
|
|
3592
|
+
function sidecarBundleAliases(rootDir) {
|
|
3593
|
+
const repoRoot = findSidecarRepoRoot2(rootDir) ?? findSidecarRepoRoot2(process.cwd());
|
|
3594
|
+
if (!repoRoot) {
|
|
3595
|
+
return void 0;
|
|
3596
|
+
}
|
|
3597
|
+
return {
|
|
3598
|
+
"sidecar-ai": path18.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
|
|
3599
|
+
"@sidecar-ai/auth": path18.join(repoRoot, "packages", "auth", "src", "index.ts"),
|
|
3600
|
+
"@sidecar-ai/core": path18.join(repoRoot, "packages", "core", "src", "index.ts"),
|
|
3601
|
+
"@sidecar-ai/server": path18.join(repoRoot, "packages", "server", "src", "index.ts"),
|
|
3602
|
+
"@sidecar-ai/server/proxy": path18.join(repoRoot, "packages", "server", "src", "proxy.ts"),
|
|
3603
|
+
"@sidecar-ai/client": path18.join(repoRoot, "packages", "client", "src", "index.ts"),
|
|
3604
|
+
"@sidecar-ai/react": path18.join(repoRoot, "packages", "react", "src", "index.ts"),
|
|
3605
|
+
"@sidecar-ai/native": path18.join(repoRoot, "packages", "native", "src", "index.ts"),
|
|
3606
|
+
"@sidecar-ai/native/components": path18.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
|
|
3607
|
+
"@sidecar-ai/openai": path18.join(repoRoot, "packages", "openai", "src", "index.ts"),
|
|
3608
|
+
"@sidecar-ai/openai/components": path18.join(repoRoot, "packages", "openai", "src", "components.tsx"),
|
|
3609
|
+
"@sidecar-ai/openai/official": path18.join(repoRoot, "packages", "openai", "src", "official.ts"),
|
|
3610
|
+
"@sidecar-ai/anthropic": path18.join(repoRoot, "packages", "anthropic", "src", "index.ts"),
|
|
3611
|
+
"@sidecar-ai/anthropic/agent": path18.join(repoRoot, "packages", "anthropic", "src", "agent.ts"),
|
|
3612
|
+
"@sidecar-ai/anthropic/command": path18.join(repoRoot, "packages", "anthropic", "src", "command.ts"),
|
|
3613
|
+
"@sidecar-ai/anthropic/components": path18.join(repoRoot, "packages", "anthropic", "src", "components.tsx"),
|
|
3614
|
+
"@sidecar-ai/anthropic/hooks": path18.join(repoRoot, "packages", "anthropic", "src", "hooks.ts"),
|
|
3615
|
+
"@sidecar-ai/anthropic/mcp": path18.join(repoRoot, "packages", "anthropic", "src", "mcp.ts"),
|
|
3616
|
+
"@sidecar-ai/anthropic/plugin": path18.join(repoRoot, "packages", "anthropic", "src", "plugin.ts"),
|
|
3617
|
+
"@sidecar-ai/anthropic/skill": path18.join(repoRoot, "packages", "anthropic", "src", "skill.ts")
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3620
|
+
function findSidecarRepoRoot2(startDir) {
|
|
3621
|
+
let current = path18.resolve(startDir);
|
|
3622
|
+
while (true) {
|
|
3623
|
+
if (existsSync6(path18.join(current, "packages", "core", "src", "index.ts"))) {
|
|
3624
|
+
return current;
|
|
3625
|
+
}
|
|
3626
|
+
const parent = path18.dirname(current);
|
|
3627
|
+
if (parent === current) {
|
|
3628
|
+
return void 0;
|
|
3629
|
+
}
|
|
3630
|
+
current = parent;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
|
|
2956
3634
|
// packages/compiler/src/build.ts
|
|
2957
3635
|
async function buildProject(options) {
|
|
2958
|
-
const rootDir =
|
|
2959
|
-
const target = options.target ?? "mcp";
|
|
3636
|
+
const rootDir = path19.resolve(options.rootDir);
|
|
2960
3637
|
const config = analyzeProjectConfig(rootDir);
|
|
3638
|
+
const target = options.target ?? config.build.target ?? "mcp";
|
|
3639
|
+
const host = options.host ?? config.build.host ?? "node";
|
|
3640
|
+
const plugins = options.plugins ?? config.build.plugins ?? false;
|
|
2961
3641
|
const tools = await analyzeProjectTools(rootDir, { target });
|
|
2962
3642
|
const resources = await analyzeProjectResources(rootDir);
|
|
2963
3643
|
const resourceTemplates = [];
|
|
2964
3644
|
const prompts = await analyzeProjectPrompts(rootDir);
|
|
3645
|
+
const identity = await loadProjectIdentity(rootDir);
|
|
2965
3646
|
const diagnostics = await collectProjectDiagnostics(rootDir, {
|
|
2966
3647
|
tools,
|
|
2967
3648
|
resources,
|
|
@@ -2972,11 +3653,13 @@ async function buildProject(options) {
|
|
|
2972
3653
|
if (errors.length) {
|
|
2973
3654
|
throw new Error(errors.map(formatDiagnostic).join("\n"));
|
|
2974
3655
|
}
|
|
2975
|
-
const outDir = resolveInsideRoot(rootDir, options.outDir ??
|
|
2976
|
-
|
|
3656
|
+
const outDir = resolveInsideRoot(rootDir, options.outDir ?? config.build.outDir ?? defaultBuildOutDir(host, target));
|
|
3657
|
+
const runtimeOutDir = resolveRuntimeOutputDir(outDir, host);
|
|
3658
|
+
await buildWidgets(rootDir, runtimeOutDir, tools, config.build.widgets);
|
|
2977
3659
|
const manifest = {
|
|
2978
3660
|
version: 1,
|
|
2979
3661
|
target,
|
|
3662
|
+
host,
|
|
2980
3663
|
rootDir: ".",
|
|
2981
3664
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2982
3665
|
config,
|
|
@@ -2986,23 +3669,47 @@ async function buildProject(options) {
|
|
|
2986
3669
|
prompts,
|
|
2987
3670
|
diagnostics
|
|
2988
3671
|
};
|
|
2989
|
-
await
|
|
2990
|
-
await
|
|
2991
|
-
|
|
3672
|
+
await mkdir9(runtimeOutDir, { recursive: true });
|
|
3673
|
+
await writeFile9(
|
|
3674
|
+
path19.join(runtimeOutDir, "manifest.sidecar.json"),
|
|
2992
3675
|
`${JSON.stringify(manifest, null, 2)}
|
|
2993
3676
|
`
|
|
2994
3677
|
);
|
|
2995
|
-
await
|
|
3678
|
+
await writeFile9(path19.join(runtimeOutDir, "README.md"), renderMcpReadme(manifest));
|
|
2996
3679
|
await writeGeneratedTypes(rootDir, tools);
|
|
2997
|
-
|
|
2998
|
-
|
|
3680
|
+
await buildServerOutput(rootDir, runtimeOutDir, manifest, identity, host, {
|
|
3681
|
+
vercelOutputDir: outDir
|
|
3682
|
+
});
|
|
3683
|
+
if (plugins) {
|
|
3684
|
+
await buildPluginPackages(rootDir, resolvePluginOutputBase(rootDir, outDir, host), manifest);
|
|
2999
3685
|
}
|
|
3000
3686
|
return manifest;
|
|
3001
3687
|
}
|
|
3688
|
+
function defaultBuildOutDir(host, target) {
|
|
3689
|
+
if (host === "vercel") {
|
|
3690
|
+
return ".vercel/output";
|
|
3691
|
+
}
|
|
3692
|
+
return `out/${target}`;
|
|
3693
|
+
}
|
|
3694
|
+
function resolveRuntimeOutputDir(outDir, host) {
|
|
3695
|
+
if (host === "vercel") {
|
|
3696
|
+
return path19.join(outDir, VERCEL_FUNCTION_DIR);
|
|
3697
|
+
}
|
|
3698
|
+
return outDir;
|
|
3699
|
+
}
|
|
3700
|
+
function resolvePluginOutputBase(rootDir, outDir, host) {
|
|
3701
|
+
if (host === "vercel" && isVercelBuildOutputDir(outDir)) {
|
|
3702
|
+
return path19.join(rootDir, "out");
|
|
3703
|
+
}
|
|
3704
|
+
return path19.dirname(outDir);
|
|
3705
|
+
}
|
|
3706
|
+
function isVercelBuildOutputDir(outDir) {
|
|
3707
|
+
return path19.basename(outDir) === "output" && path19.basename(path19.dirname(outDir)) === ".vercel";
|
|
3708
|
+
}
|
|
3002
3709
|
function resolveInsideRoot(rootDir, output) {
|
|
3003
|
-
const resolved =
|
|
3004
|
-
const relative =
|
|
3005
|
-
if (relative.startsWith("..") ||
|
|
3710
|
+
const resolved = path19.resolve(rootDir, output);
|
|
3711
|
+
const relative = path19.relative(rootDir, resolved);
|
|
3712
|
+
if (relative.startsWith("..") || path19.isAbsolute(relative)) {
|
|
3006
3713
|
throw new Error(`Build output must stay inside the project root: ${output}`);
|
|
3007
3714
|
}
|
|
3008
3715
|
return resolved;
|
|
@@ -3027,9 +3734,35 @@ ${resources || "No resources detected."}
|
|
|
3027
3734
|
|
|
3028
3735
|
${prompts || "No prompts detected."}
|
|
3029
3736
|
|
|
3737
|
+
${manifest.host === "vercel" ? renderVercelReadmeSection() : renderNodeReadmeSection()}
|
|
3738
|
+
|
|
3030
3739
|
## Local HTTPS Testing
|
|
3031
3740
|
|
|
3032
|
-
Run \`sidecar dev --tunnel\` from the project root to start the local MCP server on Streamable HTTP and print
|
|
3741
|
+
Run \`sidecar dev --tunnel\` from the project root to start the local MCP server on Streamable HTTP and print a validated HTTPS MCP URL that can be added to ChatGPT, Claude, or a Claude plugin install. Temporary quick tunnels are public and best-effort; use a configured tunnel/domain or deployed preview for repeatable testing.
|
|
3742
|
+
`;
|
|
3743
|
+
}
|
|
3744
|
+
function renderNodeReadmeSection() {
|
|
3745
|
+
return `## Run The Server
|
|
3746
|
+
|
|
3747
|
+
This output includes a standalone Node MCP server. Start it with:
|
|
3748
|
+
|
|
3749
|
+
\`\`\`sh
|
|
3750
|
+
npm start
|
|
3751
|
+
\`\`\`
|
|
3752
|
+
|
|
3753
|
+
or:
|
|
3754
|
+
|
|
3755
|
+
\`\`\`sh
|
|
3756
|
+
node server/index.js
|
|
3757
|
+
\`\`\`
|
|
3758
|
+
|
|
3759
|
+
Set \`PORT\` or \`SIDECAR_PORT\` to choose the listen port. Hosted/authenticated MCPs should set \`SIDECAR_MCP_URL\` to the public \`https://.../mcp\` URL before starting.
|
|
3760
|
+
`;
|
|
3761
|
+
}
|
|
3762
|
+
function renderVercelReadmeSection() {
|
|
3763
|
+
return `## Deploy To Vercel
|
|
3764
|
+
|
|
3765
|
+
This output uses Vercel's Build Output API. The MCP function is emitted at \`${VERCEL_FUNCTION_DIR}\`, and \`config.json\` routes Streamable HTTP traffic to it. Set \`SIDECAR_MCP_URL\` to the public \`https://.../mcp\` URL in Vercel.
|
|
3033
3766
|
`;
|
|
3034
3767
|
}
|
|
3035
3768
|
|
|
@@ -3042,7 +3775,7 @@ import { randomUUID } from "crypto";
|
|
|
3042
3775
|
var proxyBrand = /* @__PURE__ */ Symbol.for("sidecar.proxy");
|
|
3043
3776
|
function isSidecarProxy(value) {
|
|
3044
3777
|
return Boolean(
|
|
3045
|
-
value && typeof value === "object" && value[proxyBrand] === true
|
|
3778
|
+
value && typeof value === "object" && (value[proxyBrand] === true || value.kind === "sidecar.proxy")
|
|
3046
3779
|
);
|
|
3047
3780
|
}
|
|
3048
3781
|
async function runProxy(proxyConfig, request) {
|
|
@@ -3055,8 +3788,90 @@ async function runProxy(proxyConfig, request) {
|
|
|
3055
3788
|
return void 0;
|
|
3056
3789
|
}
|
|
3057
3790
|
|
|
3058
|
-
// packages/server/src/
|
|
3791
|
+
// packages/server/src/sse.ts
|
|
3059
3792
|
import { JSONRPC_VERSION } from "@modelcontextprotocol/sdk/types.js";
|
|
3793
|
+
var sseEventCounter = 0;
|
|
3794
|
+
function createSseHub() {
|
|
3795
|
+
const streams = /* @__PURE__ */ new Set();
|
|
3796
|
+
return {
|
|
3797
|
+
open(response) {
|
|
3798
|
+
const stream = createSseStream(response);
|
|
3799
|
+
streams.add(stream);
|
|
3800
|
+
response.on("close", () => {
|
|
3801
|
+
streams.delete(stream);
|
|
3802
|
+
});
|
|
3803
|
+
return stream;
|
|
3804
|
+
},
|
|
3805
|
+
send(method, params) {
|
|
3806
|
+
const stream = streams.values().next().value;
|
|
3807
|
+
stream?.send(method, params);
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
}
|
|
3811
|
+
function createSseStream(response, options = {}) {
|
|
3812
|
+
let closed = false;
|
|
3813
|
+
response.writeHead(200, {
|
|
3814
|
+
"content-type": "text/event-stream",
|
|
3815
|
+
"cache-control": "no-cache, no-transform",
|
|
3816
|
+
"connection": "keep-alive",
|
|
3817
|
+
"x-accel-buffering": "no"
|
|
3818
|
+
});
|
|
3819
|
+
response.flushHeaders?.();
|
|
3820
|
+
const keepAlive = setInterval(() => {
|
|
3821
|
+
writeRaw(": keepalive\n\n");
|
|
3822
|
+
}, 3e4);
|
|
3823
|
+
keepAlive.unref?.();
|
|
3824
|
+
response.on("close", () => {
|
|
3825
|
+
closed = true;
|
|
3826
|
+
clearInterval(keepAlive);
|
|
3827
|
+
});
|
|
3828
|
+
writeRaw(`id: ${nextSseEventId()}
|
|
3829
|
+
data:
|
|
3830
|
+
|
|
3831
|
+
`);
|
|
3832
|
+
function writeRaw(frame) {
|
|
3833
|
+
if (!closed && !response.destroyed) {
|
|
3834
|
+
response.write(frame);
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
function sendJson(value) {
|
|
3838
|
+
writeRaw([
|
|
3839
|
+
`id: ${nextSseEventId()}`,
|
|
3840
|
+
"event: message",
|
|
3841
|
+
`data: ${JSON.stringify(value)}`,
|
|
3842
|
+
"",
|
|
3843
|
+
""
|
|
3844
|
+
].join("\n"));
|
|
3845
|
+
}
|
|
3846
|
+
return {
|
|
3847
|
+
supportsRequestProgress: options.supportsRequestProgress,
|
|
3848
|
+
send(method, params) {
|
|
3849
|
+
sendJson(omitUndefined({
|
|
3850
|
+
jsonrpc: JSONRPC_VERSION,
|
|
3851
|
+
method,
|
|
3852
|
+
params
|
|
3853
|
+
}));
|
|
3854
|
+
},
|
|
3855
|
+
sendJson,
|
|
3856
|
+
end() {
|
|
3857
|
+
closed = true;
|
|
3858
|
+
clearInterval(keepAlive);
|
|
3859
|
+
response.end();
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
3862
|
+
}
|
|
3863
|
+
function nextSseEventId() {
|
|
3864
|
+
sseEventCounter += 1;
|
|
3865
|
+
return `sidecar-${Date.now()}-${sseEventCounter}`;
|
|
3866
|
+
}
|
|
3867
|
+
function omitUndefined(value) {
|
|
3868
|
+
return Object.fromEntries(
|
|
3869
|
+
Object.entries(value).filter(([, entry]) => entry !== void 0)
|
|
3870
|
+
);
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
// packages/server/src/index.ts
|
|
3874
|
+
import { JSONRPC_VERSION as JSONRPC_VERSION2 } from "@modelcontextprotocol/sdk/types.js";
|
|
3060
3875
|
var SIDECAR_MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
3061
3876
|
var SidecarMcpServer = class {
|
|
3062
3877
|
constructor(options) {
|
|
@@ -3100,6 +3915,8 @@ var SidecarMcpServer = class {
|
|
|
3100
3915
|
resources = /* @__PURE__ */ new Map();
|
|
3101
3916
|
prompts = /* @__PURE__ */ new Map();
|
|
3102
3917
|
resourceTemplates;
|
|
3918
|
+
activeRequests = /* @__PURE__ */ new Map();
|
|
3919
|
+
subscribedResourceUris = /* @__PURE__ */ new Set();
|
|
3103
3920
|
/** Returns all tool descriptors exposed through `tools/list`. */
|
|
3104
3921
|
descriptors() {
|
|
3105
3922
|
return [...this.tools.values()].map((loaded) => loaded.descriptor);
|
|
@@ -3124,13 +3941,16 @@ var SidecarMcpServer = class {
|
|
|
3124
3941
|
const authorizedContext = { ...context, authSession };
|
|
3125
3942
|
const result = await this.dispatch(request, authorizedContext);
|
|
3126
3943
|
return {
|
|
3127
|
-
jsonrpc:
|
|
3944
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3128
3945
|
id: request.id,
|
|
3129
3946
|
result
|
|
3130
3947
|
};
|
|
3131
3948
|
} catch (error) {
|
|
3949
|
+
if (error instanceof RequestCancelledError) {
|
|
3950
|
+
return void 0;
|
|
3951
|
+
}
|
|
3132
3952
|
return {
|
|
3133
|
-
jsonrpc:
|
|
3953
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3134
3954
|
id: request.id,
|
|
3135
3955
|
error: normalizeError(error)
|
|
3136
3956
|
};
|
|
@@ -3148,6 +3968,8 @@ var SidecarMcpServer = class {
|
|
|
3148
3968
|
version: this.options.version ?? "0.0.0-dev"
|
|
3149
3969
|
}
|
|
3150
3970
|
};
|
|
3971
|
+
case "ping":
|
|
3972
|
+
return {};
|
|
3151
3973
|
case "tools/list":
|
|
3152
3974
|
return this.listTools(request, context);
|
|
3153
3975
|
case "tools/call":
|
|
@@ -3242,7 +4064,7 @@ var SidecarMcpServer = class {
|
|
|
3242
4064
|
/** Returns the server-chosen page size for all built-in list pagination. */
|
|
3243
4065
|
pageSize() {
|
|
3244
4066
|
const configured = this.options.pagination?.pageSize;
|
|
3245
|
-
return configured && configured > 0 ? Math.floor(configured) :
|
|
4067
|
+
return configured && configured > 0 ? Math.floor(configured) : 50;
|
|
3246
4068
|
}
|
|
3247
4069
|
/** Executes a tool after request-level and tool-level auth checks. */
|
|
3248
4070
|
async callTool(request, context) {
|
|
@@ -3260,29 +4082,39 @@ var SidecarMcpServer = class {
|
|
|
3260
4082
|
if (authSession !== void 0) {
|
|
3261
4083
|
ctx.auth = authSession;
|
|
3262
4084
|
}
|
|
4085
|
+
ctx.notify = this.createNotifications(context, request);
|
|
3263
4086
|
const controller = new AbortController();
|
|
4087
|
+
if (request.id !== null && request.id !== void 0) {
|
|
4088
|
+
this.activeRequests.set(request.id, controller);
|
|
4089
|
+
}
|
|
3264
4090
|
ctx.request = {
|
|
3265
4091
|
...ctx.request,
|
|
3266
4092
|
signal: controller.signal
|
|
3267
4093
|
};
|
|
3268
|
-
|
|
3269
|
-
loaded.
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
if (loaded.descriptorProvided) {
|
|
3279
|
-
validateAgainstSchema(
|
|
3280
|
-
loaded.descriptor.outputSchema,
|
|
3281
|
-
result.structuredContent,
|
|
3282
|
-
`Invalid structuredContent returned by tool "${name}".`
|
|
4094
|
+
try {
|
|
4095
|
+
const args = loaded.descriptorProvided ? validateAgainstSchema(
|
|
4096
|
+
loaded.descriptor.inputSchema,
|
|
4097
|
+
params?.arguments ?? {},
|
|
4098
|
+
`Invalid parameters for tool "${name}".`
|
|
4099
|
+
) : params?.arguments ?? {};
|
|
4100
|
+
const result = await withTimeout(
|
|
4101
|
+
executeTool(loaded.tool, args, ctx),
|
|
4102
|
+
this.options.toolTimeoutMs,
|
|
4103
|
+
controller
|
|
3283
4104
|
);
|
|
4105
|
+
if (loaded.descriptorProvided) {
|
|
4106
|
+
validateAgainstSchema(
|
|
4107
|
+
loaded.descriptor.outputSchema,
|
|
4108
|
+
result.structuredContent,
|
|
4109
|
+
`Invalid structuredContent returned by tool "${name}".`
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
4112
|
+
return result;
|
|
4113
|
+
} finally {
|
|
4114
|
+
if (request.id !== null && request.id !== void 0) {
|
|
4115
|
+
this.activeRequests.delete(request.id);
|
|
4116
|
+
}
|
|
3284
4117
|
}
|
|
3285
|
-
return result;
|
|
3286
4118
|
}
|
|
3287
4119
|
/** Authenticates the whole HTTP MCP request when auth.ts is configured. */
|
|
3288
4120
|
async authorizeEndpoint(context) {
|
|
@@ -3334,12 +4166,14 @@ var SidecarMcpServer = class {
|
|
|
3334
4166
|
if (context.authSession !== void 0) {
|
|
3335
4167
|
toolContext.auth = context.authSession;
|
|
3336
4168
|
}
|
|
4169
|
+
toolContext.notify = this.createNotifications(context, request);
|
|
3337
4170
|
return executeResource(resource.resource, toResourceContext(toolContext), {
|
|
3338
4171
|
uri,
|
|
3339
4172
|
mimeType: resource.descriptor.mimeType
|
|
3340
4173
|
});
|
|
3341
4174
|
}
|
|
3342
4175
|
return {
|
|
4176
|
+
_meta: resource._meta,
|
|
3343
4177
|
contents: [
|
|
3344
4178
|
{
|
|
3345
4179
|
uri,
|
|
@@ -3359,6 +4193,7 @@ var SidecarMcpServer = class {
|
|
|
3359
4193
|
if (!this.resources.has(uri)) {
|
|
3360
4194
|
throw new JsonRpcError(-32002, `Resource not found: "${uri}".`, { uri });
|
|
3361
4195
|
}
|
|
4196
|
+
this.subscribedResourceUris.add(uri);
|
|
3362
4197
|
return {};
|
|
3363
4198
|
}
|
|
3364
4199
|
/** Accepts a resource unsubscription when server-level support is enabled. */
|
|
@@ -3370,6 +4205,7 @@ var SidecarMcpServer = class {
|
|
|
3370
4205
|
if (!this.resources.has(uri)) {
|
|
3371
4206
|
throw new JsonRpcError(-32002, `Resource not found: "${uri}".`, { uri });
|
|
3372
4207
|
}
|
|
4208
|
+
this.subscribedResourceUris.delete(uri);
|
|
3373
4209
|
return {};
|
|
3374
4210
|
}
|
|
3375
4211
|
/** Renders one named prompt with validated arguments. */
|
|
@@ -3387,20 +4223,61 @@ var SidecarMcpServer = class {
|
|
|
3387
4223
|
if (context.authSession !== void 0) {
|
|
3388
4224
|
toolContext.auth = context.authSession;
|
|
3389
4225
|
}
|
|
4226
|
+
toolContext.notify = this.createNotifications(context, request);
|
|
3390
4227
|
return executePrompt(loaded.prompt, params?.arguments ?? {}, toPromptContext(toolContext));
|
|
3391
4228
|
}
|
|
4229
|
+
/** Creates notification helpers scoped by advertised server capabilities. */
|
|
4230
|
+
createNotifications(context, request) {
|
|
4231
|
+
const configured = this.options.capabilities ?? {};
|
|
4232
|
+
return createRuntimeNotifications(
|
|
4233
|
+
context.notifications,
|
|
4234
|
+
readProgressToken(request),
|
|
4235
|
+
{
|
|
4236
|
+
toolsListChanged: Boolean(configured.tools?.listChanged),
|
|
4237
|
+
resourcesListChanged: Boolean(configured.resources?.listChanged),
|
|
4238
|
+
promptsListChanged: Boolean(configured.prompts?.listChanged),
|
|
4239
|
+
isResourceSubscribed: (uri) => this.subscribedResourceUris.has(uri)
|
|
4240
|
+
}
|
|
4241
|
+
);
|
|
4242
|
+
}
|
|
3392
4243
|
/** Accepts notifications without side effects for client compatibility. */
|
|
3393
|
-
async handleNotification(
|
|
4244
|
+
async handleNotification(request) {
|
|
4245
|
+
if (request.method === "notifications/cancelled") {
|
|
4246
|
+
this.cancelRequest(request);
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
/** Applies a client cancellation notification to an active request. */
|
|
4250
|
+
cancelRequest(request) {
|
|
4251
|
+
const params = request.params;
|
|
4252
|
+
const requestId = params?.requestId;
|
|
4253
|
+
if (typeof requestId !== "string" && typeof requestId !== "number") {
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
const controller = this.activeRequests.get(requestId);
|
|
4257
|
+
if (!controller || controller.signal.aborted) {
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
const reason = typeof params?.reason === "string" ? params.reason : "Request cancelled.";
|
|
4261
|
+
controller.abort(new RequestCancelledError(reason));
|
|
3394
4262
|
}
|
|
3395
4263
|
};
|
|
3396
4264
|
function createSidecarMcpServer(options) {
|
|
3397
4265
|
return new SidecarMcpServer(options);
|
|
3398
4266
|
}
|
|
3399
4267
|
function createSidecarHttpServer(options) {
|
|
3400
|
-
|
|
4268
|
+
return createServer(createSidecarHttpHandler(options));
|
|
4269
|
+
}
|
|
4270
|
+
function createSidecarHttpHandler(options) {
|
|
3401
4271
|
const endpoint = options.path ?? "/mcp";
|
|
3402
4272
|
const maxBodyBytes = options.maxBodyBytes ?? 1e6;
|
|
3403
|
-
|
|
4273
|
+
const streamHub = createSseHub();
|
|
4274
|
+
const mcp = createSidecarMcpServer(options);
|
|
4275
|
+
return async (request, response) => {
|
|
4276
|
+
const pathname = request.url?.split("?")[0];
|
|
4277
|
+
const requestLog = createHttpRequestLog(request, pathname);
|
|
4278
|
+
response.once("finish", () => {
|
|
4279
|
+
logHttpRequest(requestLog, response.statusCode);
|
|
4280
|
+
});
|
|
3404
4281
|
if (isRejectedOrigin(request, options.allowedOrigins)) {
|
|
3405
4282
|
response.writeHead(403, { "content-type": "application/json" });
|
|
3406
4283
|
response.end(JSON.stringify({ error: "forbidden_origin" }));
|
|
@@ -3412,23 +4289,59 @@ function createSidecarHttpServer(options) {
|
|
|
3412
4289
|
response.end(proxyResult.body ?? "");
|
|
3413
4290
|
return;
|
|
3414
4291
|
}
|
|
3415
|
-
const pathname = request.url?.split("?")[0];
|
|
3416
4292
|
if (options.auth && request.method === "GET" && isProtectedResourceMetadataPath(pathname, endpoint)) {
|
|
3417
4293
|
response.writeHead(200, { "content-type": "application/json" });
|
|
3418
4294
|
response.end(JSON.stringify(options.auth.metadata()));
|
|
3419
4295
|
return;
|
|
3420
4296
|
}
|
|
4297
|
+
if (options.auth && request.method === "GET" && pathname === "/.well-known/oauth-authorization-server") {
|
|
4298
|
+
await proxyAuthorizationServerMetadata(options.auth, response);
|
|
4299
|
+
return;
|
|
4300
|
+
}
|
|
3421
4301
|
if (request.method === "GET" && pathname === endpoint) {
|
|
3422
|
-
|
|
3423
|
-
|
|
4302
|
+
try {
|
|
4303
|
+
const fetchRequest = toFetchRequest(request, options.publicUrl ?? options.auth?.resource);
|
|
4304
|
+
if (options.auth) {
|
|
4305
|
+
const authSession = await authorizeHttpRequest(options.auth, fetchRequest, response);
|
|
4306
|
+
if (authSession === AUTH_RESPONSE_SENT) {
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
validateProtocolVersion(request);
|
|
4311
|
+
validateGetHeaders(request);
|
|
4312
|
+
streamHub.open(response);
|
|
4313
|
+
} catch (error) {
|
|
4314
|
+
const status = error instanceof JsonRpcHttpError ? error.status : 400;
|
|
4315
|
+
response.writeHead(status, { "content-type": "application/json" });
|
|
4316
|
+
response.end(
|
|
4317
|
+
JSON.stringify({
|
|
4318
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
4319
|
+
id: null,
|
|
4320
|
+
error: normalizeHttpError(error)
|
|
4321
|
+
})
|
|
4322
|
+
);
|
|
4323
|
+
}
|
|
4324
|
+
return;
|
|
4325
|
+
}
|
|
4326
|
+
if (pathname === endpoint && request.method !== "POST") {
|
|
4327
|
+
response.writeHead(405, {
|
|
4328
|
+
"allow": "GET, POST",
|
|
4329
|
+
"content-type": "application/json"
|
|
4330
|
+
});
|
|
4331
|
+
response.end(JSON.stringify({ error: "method_not_allowed" }));
|
|
3424
4332
|
return;
|
|
3425
4333
|
}
|
|
3426
|
-
if (
|
|
4334
|
+
if (pathname !== endpoint) {
|
|
3427
4335
|
response.writeHead(404, { "content-type": "application/json" });
|
|
3428
4336
|
response.end(JSON.stringify({ error: "not_found" }));
|
|
3429
4337
|
return;
|
|
3430
4338
|
}
|
|
3431
4339
|
try {
|
|
4340
|
+
const fetchRequest = toFetchRequest(request, options.publicUrl ?? options.auth?.resource);
|
|
4341
|
+
const authSession = options.auth ? await authorizeHttpRequest(options.auth, fetchRequest, response) : void 0;
|
|
4342
|
+
if (options.auth && authSession === AUTH_RESPONSE_SENT) {
|
|
4343
|
+
return;
|
|
4344
|
+
}
|
|
3432
4345
|
validateProtocolVersion(request);
|
|
3433
4346
|
validatePostHeaders(request);
|
|
3434
4347
|
const body = await readJson(request, maxBodyBytes);
|
|
@@ -3441,14 +4354,23 @@ function createSidecarHttpServer(options) {
|
|
|
3441
4354
|
return;
|
|
3442
4355
|
}
|
|
3443
4356
|
const rpcRequest = assertJsonRpcRequest(body);
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
4357
|
+
if (rpcRequest.id !== void 0 && hasProgressToken(rpcRequest)) {
|
|
4358
|
+
const stream = createSseStream(response, { supportsRequestProgress: true });
|
|
4359
|
+
const payload2 = await mcp.handle(rpcRequest, {
|
|
4360
|
+
request: fetchRequest,
|
|
4361
|
+
authSession,
|
|
4362
|
+
notifications: stream
|
|
4363
|
+
});
|
|
4364
|
+
if (payload2) {
|
|
4365
|
+
stream.sendJson(payload2);
|
|
4366
|
+
}
|
|
4367
|
+
stream.end();
|
|
3447
4368
|
return;
|
|
3448
4369
|
}
|
|
3449
4370
|
const payload = await mcp.handle(rpcRequest, {
|
|
3450
4371
|
request: fetchRequest,
|
|
3451
|
-
authSession
|
|
4372
|
+
authSession,
|
|
4373
|
+
notifications: streamHub
|
|
3452
4374
|
}) ?? null;
|
|
3453
4375
|
const responses = payload === null ? [] : [payload];
|
|
3454
4376
|
if (!responses.length) {
|
|
@@ -3467,13 +4389,13 @@ function createSidecarHttpServer(options) {
|
|
|
3467
4389
|
response.writeHead(status, { "content-type": "application/json" });
|
|
3468
4390
|
response.end(
|
|
3469
4391
|
JSON.stringify({
|
|
3470
|
-
jsonrpc:
|
|
4392
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3471
4393
|
id: null,
|
|
3472
4394
|
error: normalizeHttpError(error)
|
|
3473
4395
|
})
|
|
3474
4396
|
);
|
|
3475
4397
|
}
|
|
3476
|
-
}
|
|
4398
|
+
};
|
|
3477
4399
|
}
|
|
3478
4400
|
var DEFAULT_ALLOWED_ORIGINS = [
|
|
3479
4401
|
"https://chatgpt.com",
|
|
@@ -3523,6 +4445,73 @@ function escapeRegExp(value) {
|
|
|
3523
4445
|
function isProtectedResourceMetadataPath(pathname, endpoint) {
|
|
3524
4446
|
return pathname === "/.well-known/oauth-protected-resource" || pathname === `/.well-known/oauth-protected-resource${endpoint}`;
|
|
3525
4447
|
}
|
|
4448
|
+
function createHttpRequestLog(request, pathname) {
|
|
4449
|
+
return {
|
|
4450
|
+
method: request.method,
|
|
4451
|
+
path: pathname,
|
|
4452
|
+
host: truncateHeader(singleHeader(request.headers.host)),
|
|
4453
|
+
accept: truncateHeader(singleHeader(request.headers.accept)),
|
|
4454
|
+
contentType: truncateHeader(singleHeader(request.headers["content-type"])),
|
|
4455
|
+
contentLength: truncateHeader(singleHeader(request.headers["content-length"])),
|
|
4456
|
+
mcpProtocolVersion: truncateHeader(singleHeader(request.headers["mcp-protocol-version"])),
|
|
4457
|
+
origin: truncateHeader(singleHeader(request.headers.origin)),
|
|
4458
|
+
userAgent: truncateHeader(singleHeader(request.headers["user-agent"])),
|
|
4459
|
+
authorization: request.headers.authorization ? "present" : "absent",
|
|
4460
|
+
cookie: request.headers.cookie ? "present" : "absent"
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
function logHttpRequest(metadata, status) {
|
|
4464
|
+
const debug = process.env.SIDECAR_DEBUG === "1" || process.env.SIDECAR_LOG_LEVEL === "debug";
|
|
4465
|
+
if (!debug && status < 400) {
|
|
4466
|
+
return;
|
|
4467
|
+
}
|
|
4468
|
+
const message = JSON.stringify({
|
|
4469
|
+
event: "sidecar.mcp.http",
|
|
4470
|
+
status,
|
|
4471
|
+
...stripUndefined4(metadata)
|
|
4472
|
+
});
|
|
4473
|
+
if (status >= 500) {
|
|
4474
|
+
console.error(message);
|
|
4475
|
+
} else if (status >= 400) {
|
|
4476
|
+
console.warn(message);
|
|
4477
|
+
} else {
|
|
4478
|
+
console.info(message);
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
function singleHeader(value) {
|
|
4482
|
+
return Array.isArray(value) ? value.join(", ") : value;
|
|
4483
|
+
}
|
|
4484
|
+
function truncateHeader(value) {
|
|
4485
|
+
if (!value) {
|
|
4486
|
+
return void 0;
|
|
4487
|
+
}
|
|
4488
|
+
return value.length > 240 ? `${value.slice(0, 237)}...` : value;
|
|
4489
|
+
}
|
|
4490
|
+
async function proxyAuthorizationServerMetadata(auth, response) {
|
|
4491
|
+
const [authorizationServer] = auth.authorizationServers;
|
|
4492
|
+
if (!authorizationServer) {
|
|
4493
|
+
response.writeHead(404, { "content-type": "application/json" });
|
|
4494
|
+
response.end(JSON.stringify({ error: "authorization_server_not_configured" }));
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4497
|
+
try {
|
|
4498
|
+
const url = new URL("/.well-known/oauth-authorization-server", authorizationServer);
|
|
4499
|
+
const upstream = await fetch(url, { headers: { accept: "application/json" } });
|
|
4500
|
+
const body = await upstream.text();
|
|
4501
|
+
response.writeHead(upstream.ok ? 200 : 502, {
|
|
4502
|
+
"content-type": upstream.headers.get("content-type") ?? "application/json"
|
|
4503
|
+
});
|
|
4504
|
+
response.end(body);
|
|
4505
|
+
} catch (error) {
|
|
4506
|
+
console.warn(JSON.stringify({
|
|
4507
|
+
event: "sidecar.mcp.authorization_metadata_proxy_failed",
|
|
4508
|
+
authorizationServer,
|
|
4509
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
4510
|
+
}));
|
|
4511
|
+
response.writeHead(502, { "content-type": "application/json" });
|
|
4512
|
+
response.end(JSON.stringify({ error: "authorization_metadata_unavailable" }));
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
3526
4515
|
var JsonRpcError = class extends Error {
|
|
3527
4516
|
constructor(code, message, data) {
|
|
3528
4517
|
super(message);
|
|
@@ -3541,6 +4530,12 @@ var JsonRpcHttpError = class extends JsonRpcError {
|
|
|
3541
4530
|
}
|
|
3542
4531
|
status;
|
|
3543
4532
|
};
|
|
4533
|
+
var RequestCancelledError = class extends Error {
|
|
4534
|
+
constructor(message = "Request cancelled.") {
|
|
4535
|
+
super(message);
|
|
4536
|
+
this.name = "RequestCancelledError";
|
|
4537
|
+
}
|
|
4538
|
+
};
|
|
3544
4539
|
var AUTH_RESPONSE_SENT = /* @__PURE__ */ Symbol("sidecar.auth.response-sent");
|
|
3545
4540
|
async function authorizeHttpRequest(auth, request, response) {
|
|
3546
4541
|
const result = await auth.authorizeRequest(request);
|
|
@@ -3553,7 +4548,7 @@ async function authorizeHttpRequest(auth, request, response) {
|
|
|
3553
4548
|
});
|
|
3554
4549
|
response.end(
|
|
3555
4550
|
JSON.stringify({
|
|
3556
|
-
jsonrpc:
|
|
4551
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3557
4552
|
id: null,
|
|
3558
4553
|
error: {
|
|
3559
4554
|
code: result.status === 401 ? -32001 : -32003,
|
|
@@ -3596,9 +4591,57 @@ function createDefaultContext(request) {
|
|
|
3596
4591
|
memory.delete(key);
|
|
3597
4592
|
}
|
|
3598
4593
|
},
|
|
4594
|
+
notify: noopNotifications,
|
|
3599
4595
|
env: process.env
|
|
3600
4596
|
};
|
|
3601
4597
|
}
|
|
4598
|
+
var noopNotifications = {
|
|
4599
|
+
async progress() {
|
|
4600
|
+
},
|
|
4601
|
+
async toolsChanged() {
|
|
4602
|
+
},
|
|
4603
|
+
async resourcesChanged() {
|
|
4604
|
+
},
|
|
4605
|
+
async promptsChanged() {
|
|
4606
|
+
},
|
|
4607
|
+
async resourceUpdated() {
|
|
4608
|
+
}
|
|
4609
|
+
};
|
|
4610
|
+
function createRuntimeNotifications(sink, progressToken, options) {
|
|
4611
|
+
return {
|
|
4612
|
+
async progress(update) {
|
|
4613
|
+
if (!sink?.supportsRequestProgress || progressToken === void 0) {
|
|
4614
|
+
return;
|
|
4615
|
+
}
|
|
4616
|
+
sink.send("notifications/progress", stripUndefined4({
|
|
4617
|
+
progressToken,
|
|
4618
|
+
progress: update.progress,
|
|
4619
|
+
total: update.total,
|
|
4620
|
+
message: update.message
|
|
4621
|
+
}));
|
|
4622
|
+
},
|
|
4623
|
+
async toolsChanged() {
|
|
4624
|
+
if (options.toolsListChanged) {
|
|
4625
|
+
sink?.send("notifications/tools/list_changed");
|
|
4626
|
+
}
|
|
4627
|
+
},
|
|
4628
|
+
async resourcesChanged() {
|
|
4629
|
+
if (options.resourcesListChanged) {
|
|
4630
|
+
sink?.send("notifications/resources/list_changed");
|
|
4631
|
+
}
|
|
4632
|
+
},
|
|
4633
|
+
async promptsChanged() {
|
|
4634
|
+
if (options.promptsListChanged) {
|
|
4635
|
+
sink?.send("notifications/prompts/list_changed");
|
|
4636
|
+
}
|
|
4637
|
+
},
|
|
4638
|
+
async resourceUpdated(uri) {
|
|
4639
|
+
if (options.isResourceSubscribed(uri)) {
|
|
4640
|
+
sink?.send("notifications/resources/updated", { uri });
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
};
|
|
4644
|
+
}
|
|
3602
4645
|
function toResourceContext(ctx) {
|
|
3603
4646
|
return {
|
|
3604
4647
|
auth: ctx.auth,
|
|
@@ -3606,6 +4649,7 @@ function toResourceContext(ctx) {
|
|
|
3606
4649
|
services: ctx.services,
|
|
3607
4650
|
log: ctx.log,
|
|
3608
4651
|
storage: ctx.storage,
|
|
4652
|
+
notify: ctx.notify,
|
|
3609
4653
|
env: ctx.env
|
|
3610
4654
|
};
|
|
3611
4655
|
}
|
|
@@ -3616,6 +4660,7 @@ function toPromptContext(ctx) {
|
|
|
3616
4660
|
services: ctx.services,
|
|
3617
4661
|
log: ctx.log,
|
|
3618
4662
|
storage: ctx.storage,
|
|
4663
|
+
notify: ctx.notify,
|
|
3619
4664
|
env: ctx.env
|
|
3620
4665
|
};
|
|
3621
4666
|
}
|
|
@@ -3637,6 +4682,27 @@ function readUriParam(request, method) {
|
|
|
3637
4682
|
}
|
|
3638
4683
|
return uri;
|
|
3639
4684
|
}
|
|
4685
|
+
function readProgressToken(request) {
|
|
4686
|
+
const params = request.params;
|
|
4687
|
+
if (!params || typeof params !== "object") {
|
|
4688
|
+
return void 0;
|
|
4689
|
+
}
|
|
4690
|
+
const meta = params._meta;
|
|
4691
|
+
if (!meta || typeof meta !== "object") {
|
|
4692
|
+
return void 0;
|
|
4693
|
+
}
|
|
4694
|
+
const progressToken = meta.progressToken;
|
|
4695
|
+
if (typeof progressToken === "string") {
|
|
4696
|
+
return progressToken;
|
|
4697
|
+
}
|
|
4698
|
+
if (typeof progressToken === "number" && Number.isInteger(progressToken)) {
|
|
4699
|
+
return progressToken;
|
|
4700
|
+
}
|
|
4701
|
+
return void 0;
|
|
4702
|
+
}
|
|
4703
|
+
function hasProgressToken(request) {
|
|
4704
|
+
return readProgressToken(request) !== void 0;
|
|
4705
|
+
}
|
|
3640
4706
|
function selectPaginationOverride(override, operation) {
|
|
3641
4707
|
if (!override) {
|
|
3642
4708
|
return void 0;
|
|
@@ -3725,18 +4791,48 @@ function validatePostHeaders(request) {
|
|
|
3725
4791
|
);
|
|
3726
4792
|
}
|
|
3727
4793
|
}
|
|
4794
|
+
function validateGetHeaders(request) {
|
|
4795
|
+
const accept = request.headers.accept;
|
|
4796
|
+
const acceptValue = Array.isArray(accept) ? accept.join(",") : accept;
|
|
4797
|
+
if (!acceptValue || !acceptValue.toLowerCase().includes("text/event-stream")) {
|
|
4798
|
+
throw new JsonRpcHttpError(
|
|
4799
|
+
406,
|
|
4800
|
+
-32600,
|
|
4801
|
+
"GET Accept must include text/event-stream."
|
|
4802
|
+
);
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
3728
4805
|
async function withTimeout(promise, timeoutMs, controller) {
|
|
4806
|
+
let abortListener;
|
|
4807
|
+
const abortPromise = new Promise((_resolve, reject) => {
|
|
4808
|
+
abortListener = () => {
|
|
4809
|
+
reject(abortReason(controller.signal.reason));
|
|
4810
|
+
};
|
|
4811
|
+
if (controller.signal.aborted) {
|
|
4812
|
+
abortListener();
|
|
4813
|
+
return;
|
|
4814
|
+
}
|
|
4815
|
+
controller.signal.addEventListener("abort", abortListener, { once: true });
|
|
4816
|
+
});
|
|
3729
4817
|
if (!timeoutMs || timeoutMs <= 0) {
|
|
3730
|
-
|
|
4818
|
+
try {
|
|
4819
|
+
return await Promise.race([promise, abortPromise]);
|
|
4820
|
+
} finally {
|
|
4821
|
+
if (abortListener) {
|
|
4822
|
+
controller.signal.removeEventListener("abort", abortListener);
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
3731
4825
|
}
|
|
3732
4826
|
let timeout;
|
|
3733
4827
|
try {
|
|
3734
4828
|
return await Promise.race([
|
|
3735
4829
|
promise,
|
|
4830
|
+
abortPromise,
|
|
3736
4831
|
new Promise((_resolve, reject) => {
|
|
3737
4832
|
timeout = setTimeout(() => {
|
|
3738
|
-
|
|
3739
|
-
|
|
4833
|
+
const error = new JsonRpcError(-32e3, "Tool execution timed out.");
|
|
4834
|
+
controller.abort(error);
|
|
4835
|
+
reject(error);
|
|
3740
4836
|
}, timeoutMs);
|
|
3741
4837
|
})
|
|
3742
4838
|
]);
|
|
@@ -3744,7 +4840,16 @@ async function withTimeout(promise, timeoutMs, controller) {
|
|
|
3744
4840
|
if (timeout) {
|
|
3745
4841
|
clearTimeout(timeout);
|
|
3746
4842
|
}
|
|
4843
|
+
if (abortListener) {
|
|
4844
|
+
controller.signal.removeEventListener("abort", abortListener);
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
function abortReason(reason) {
|
|
4849
|
+
if (reason instanceof Error) {
|
|
4850
|
+
return reason;
|
|
3747
4851
|
}
|
|
4852
|
+
return new RequestCancelledError();
|
|
3748
4853
|
}
|
|
3749
4854
|
function validateAgainstSchema(schema, value, message, options = {}) {
|
|
3750
4855
|
if (!schema || value === void 0 && options.optional) {
|
|
@@ -3756,80 +4861,96 @@ function validateAgainstSchema(schema, value, message, options = {}) {
|
|
|
3756
4861
|
}
|
|
3757
4862
|
return value;
|
|
3758
4863
|
}
|
|
3759
|
-
function schemaFailure(schema, value,
|
|
3760
|
-
if (schema.anyOf?.length && !schema.anyOf.some((entry) => !schemaFailure(entry, value,
|
|
3761
|
-
return `${
|
|
4864
|
+
function schemaFailure(schema, value, path21) {
|
|
4865
|
+
if (schema.anyOf?.length && !schema.anyOf.some((entry) => !schemaFailure(entry, value, path21))) {
|
|
4866
|
+
return `${path21} must match one anyOf schema.`;
|
|
3762
4867
|
}
|
|
3763
|
-
if (schema.oneOf?.length && schema.oneOf.filter((entry) => !schemaFailure(entry, value,
|
|
3764
|
-
return `${
|
|
4868
|
+
if (schema.oneOf?.length && schema.oneOf.filter((entry) => !schemaFailure(entry, value, path21)).length !== 1) {
|
|
4869
|
+
return `${path21} must match exactly one oneOf schema.`;
|
|
3765
4870
|
}
|
|
3766
4871
|
if (schema.allOf?.length) {
|
|
3767
4872
|
for (const entry of schema.allOf) {
|
|
3768
|
-
const failure = schemaFailure(entry, value,
|
|
4873
|
+
const failure = schemaFailure(entry, value, path21);
|
|
3769
4874
|
if (failure) return failure;
|
|
3770
4875
|
}
|
|
3771
4876
|
}
|
|
3772
4877
|
if (schema.const !== void 0 && value !== schema.const) {
|
|
3773
|
-
return `${
|
|
4878
|
+
return `${path21} must equal ${JSON.stringify(schema.const)}.`;
|
|
3774
4879
|
}
|
|
3775
4880
|
if (schema.enum && !schema.enum.some((entry) => JSON.stringify(entry) === JSON.stringify(value))) {
|
|
3776
|
-
return `${
|
|
4881
|
+
return `${path21} must be one of the declared enum values.`;
|
|
3777
4882
|
}
|
|
3778
4883
|
if (schema.type) {
|
|
3779
4884
|
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
3780
4885
|
if (!types.some((type) => matchesJsonSchemaType(type, value))) {
|
|
3781
|
-
return `${
|
|
4886
|
+
return `${path21} must be ${types.join(" or ")}.`;
|
|
3782
4887
|
}
|
|
3783
4888
|
}
|
|
3784
4889
|
if (schema.type === "object" || schema.properties || schema.required) {
|
|
3785
4890
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3786
|
-
return `${
|
|
4891
|
+
return `${path21} must be an object.`;
|
|
3787
4892
|
}
|
|
3788
4893
|
const record = value;
|
|
3789
4894
|
for (const required of schema.required ?? []) {
|
|
3790
4895
|
if (!(required in record)) {
|
|
3791
|
-
return `${
|
|
4896
|
+
return `${path21}.${required} is required.`;
|
|
3792
4897
|
}
|
|
3793
4898
|
}
|
|
3794
4899
|
for (const [key, propertySchema] of Object.entries(schema.properties ?? {})) {
|
|
3795
4900
|
if (key in record) {
|
|
3796
|
-
const failure = schemaFailure(propertySchema, record[key], `${
|
|
4901
|
+
const failure = schemaFailure(propertySchema, record[key], `${path21}.${key}`);
|
|
3797
4902
|
if (failure) return failure;
|
|
3798
4903
|
}
|
|
3799
4904
|
}
|
|
4905
|
+
const allowed = new Set(Object.keys(schema.properties ?? {}));
|
|
3800
4906
|
if (schema.additionalProperties === false) {
|
|
3801
|
-
const allowed = new Set(Object.keys(schema.properties ?? {}));
|
|
3802
4907
|
const extra = Object.keys(record).find((key) => !allowed.has(key));
|
|
3803
4908
|
if (extra) {
|
|
3804
|
-
return `${
|
|
4909
|
+
return `${path21}.${extra} is not allowed.`;
|
|
4910
|
+
}
|
|
4911
|
+
} else if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
|
|
4912
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
4913
|
+
if (!allowed.has(key)) {
|
|
4914
|
+
const failure = schemaFailure(schema.additionalProperties, entry, `${path21}.${key}`);
|
|
4915
|
+
if (failure) return failure;
|
|
4916
|
+
}
|
|
3805
4917
|
}
|
|
3806
4918
|
}
|
|
3807
4919
|
}
|
|
3808
4920
|
if (schema.type === "array" || schema.items) {
|
|
3809
4921
|
if (!Array.isArray(value)) {
|
|
3810
|
-
return `${
|
|
4922
|
+
return `${path21} must be an array.`;
|
|
4923
|
+
}
|
|
4924
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
4925
|
+
return `${path21} must contain at least ${schema.minItems} items.`;
|
|
4926
|
+
}
|
|
4927
|
+
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
4928
|
+
return `${path21} must contain at most ${schema.maxItems} items.`;
|
|
3811
4929
|
}
|
|
3812
4930
|
if (schema.items) {
|
|
3813
4931
|
for (const [index, entry] of value.entries()) {
|
|
3814
|
-
const failure = schemaFailure(schema.items, entry, `${
|
|
4932
|
+
const failure = schemaFailure(schema.items, entry, `${path21}[${index}]`);
|
|
3815
4933
|
if (failure) return failure;
|
|
3816
4934
|
}
|
|
3817
4935
|
}
|
|
3818
4936
|
}
|
|
3819
4937
|
if (typeof value === "string") {
|
|
3820
4938
|
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
3821
|
-
return `${
|
|
4939
|
+
return `${path21} is shorter than ${schema.minLength}.`;
|
|
3822
4940
|
}
|
|
3823
4941
|
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
3824
|
-
return `${
|
|
4942
|
+
return `${path21} is longer than ${schema.maxLength}.`;
|
|
4943
|
+
}
|
|
4944
|
+
if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(value)) {
|
|
4945
|
+
return `${path21} must match pattern ${schema.pattern}.`;
|
|
3825
4946
|
}
|
|
3826
4947
|
}
|
|
3827
4948
|
if (typeof value === "number") {
|
|
3828
4949
|
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
3829
|
-
return `${
|
|
4950
|
+
return `${path21} is less than ${schema.minimum}.`;
|
|
3830
4951
|
}
|
|
3831
4952
|
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
3832
|
-
return `${
|
|
4953
|
+
return `${path21} is greater than ${schema.maximum}.`;
|
|
3833
4954
|
}
|
|
3834
4955
|
}
|
|
3835
4956
|
return void 0;
|
|
@@ -3937,14 +5058,14 @@ function assertJsonRpcRequest(value) {
|
|
|
3937
5058
|
throw new JsonRpcError(-32600, "Request must be a JSON object.");
|
|
3938
5059
|
}
|
|
3939
5060
|
const record = value;
|
|
3940
|
-
if (record.jsonrpc !==
|
|
5061
|
+
if (record.jsonrpc !== JSONRPC_VERSION2 || typeof record.method !== "string") {
|
|
3941
5062
|
throw new JsonRpcError(-32600, "Invalid JSON-RPC request.");
|
|
3942
5063
|
}
|
|
3943
5064
|
if ("id" in record && record.id !== void 0 && record.id !== null && typeof record.id !== "string" && typeof record.id !== "number") {
|
|
3944
5065
|
throw new JsonRpcError(-32600, "JSON-RPC id must be a string, number, or null.");
|
|
3945
5066
|
}
|
|
3946
5067
|
return {
|
|
3947
|
-
jsonrpc:
|
|
5068
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3948
5069
|
id: typeof record.id === "string" || typeof record.id === "number" || record.id === null ? record.id : void 0,
|
|
3949
5070
|
method: record.method,
|
|
3950
5071
|
params: record.params
|
|
@@ -3955,7 +5076,7 @@ function isJsonRpcResponseMessage(value) {
|
|
|
3955
5076
|
return false;
|
|
3956
5077
|
}
|
|
3957
5078
|
const record = value;
|
|
3958
|
-
if (record.jsonrpc !==
|
|
5079
|
+
if (record.jsonrpc !== JSONRPC_VERSION2 || !("id" in record) || !("result" in record || "error" in record)) {
|
|
3959
5080
|
return false;
|
|
3960
5081
|
}
|
|
3961
5082
|
return record.id === null || typeof record.id === "string" || typeof record.id === "number";
|
|
@@ -4007,11 +5128,31 @@ import { stdin, stdout } from "process";
|
|
|
4007
5128
|
async function startTunnel(options) {
|
|
4008
5129
|
const provider = await resolveProvider(options.provider);
|
|
4009
5130
|
const localUrl = `http://127.0.0.1:${options.port}`;
|
|
4010
|
-
const
|
|
5131
|
+
const path21 = options.path ?? "/mcp";
|
|
4011
5132
|
if (provider === "cloudflared") {
|
|
4012
|
-
return startCloudflared(localUrl,
|
|
4013
|
-
}
|
|
4014
|
-
return startWrangler(localUrl,
|
|
5133
|
+
return startCloudflared(localUrl, path21, options.timeoutMs);
|
|
5134
|
+
}
|
|
5135
|
+
return startWrangler(localUrl, path21, options.timeoutMs);
|
|
5136
|
+
}
|
|
5137
|
+
async function validateTunnelEndpoint(options) {
|
|
5138
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
5139
|
+
const mcpUrl = new URL(options.mcpUrl);
|
|
5140
|
+
if ((options.requireHttps ?? true) && mcpUrl.protocol !== "https:") {
|
|
5141
|
+
throw new Error(`Tunnel URL must use https://, received ${options.mcpUrl}.`);
|
|
5142
|
+
}
|
|
5143
|
+
await retryTunnelValidation(async () => {
|
|
5144
|
+
if (options.auth) {
|
|
5145
|
+
await validateProtectedResourceMetadata({
|
|
5146
|
+
mcpUrl,
|
|
5147
|
+
expectedResource: options.expectedResource ?? options.mcpUrl,
|
|
5148
|
+
timeoutMs
|
|
5149
|
+
});
|
|
5150
|
+
await validateAuthChallenge({ mcpUrl, timeoutMs });
|
|
5151
|
+
return;
|
|
5152
|
+
}
|
|
5153
|
+
await validateInitialize({ mcpUrl, timeoutMs });
|
|
5154
|
+
await validateToolsList({ mcpUrl, timeoutMs });
|
|
5155
|
+
}, timeoutMs);
|
|
4015
5156
|
}
|
|
4016
5157
|
function tunnelInstallMessage(provider = "auto") {
|
|
4017
5158
|
if (provider === "wrangler") {
|
|
@@ -4115,7 +5256,7 @@ function assertNpxAvailable() {
|
|
|
4115
5256
|
throw new Error(tunnelInstallMessage("wrangler"));
|
|
4116
5257
|
}
|
|
4117
5258
|
}
|
|
4118
|
-
async function startCloudflared(localUrl,
|
|
5259
|
+
async function startCloudflared(localUrl, path21, timeoutMs = 2e4) {
|
|
4119
5260
|
const child = spawn("cloudflared", ["tunnel", "--url", localUrl, "--no-autoupdate"], {
|
|
4120
5261
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4121
5262
|
});
|
|
@@ -4123,13 +5264,13 @@ async function startCloudflared(localUrl, path19, timeoutMs = 2e4) {
|
|
|
4123
5264
|
return {
|
|
4124
5265
|
provider: "cloudflared",
|
|
4125
5266
|
publicUrl,
|
|
4126
|
-
mcpUrl: appendPath(publicUrl,
|
|
5267
|
+
mcpUrl: appendPath(publicUrl, path21),
|
|
4127
5268
|
close() {
|
|
4128
5269
|
child.kill("SIGTERM");
|
|
4129
5270
|
}
|
|
4130
5271
|
};
|
|
4131
5272
|
}
|
|
4132
|
-
async function startWrangler(localUrl,
|
|
5273
|
+
async function startWrangler(localUrl, path21, timeoutMs = 2e4) {
|
|
4133
5274
|
const child = spawn("npx", ["--yes", "wrangler", "tunnel", "quick-start", localUrl], {
|
|
4134
5275
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4135
5276
|
});
|
|
@@ -4137,7 +5278,7 @@ async function startWrangler(localUrl, path19, timeoutMs = 2e4) {
|
|
|
4137
5278
|
return {
|
|
4138
5279
|
provider: "wrangler",
|
|
4139
5280
|
publicUrl,
|
|
4140
|
-
mcpUrl: appendPath(publicUrl,
|
|
5281
|
+
mcpUrl: appendPath(publicUrl, path21),
|
|
4141
5282
|
close() {
|
|
4142
5283
|
child.kill("SIGTERM");
|
|
4143
5284
|
}
|
|
@@ -4146,15 +5287,20 @@ async function startWrangler(localUrl, path19, timeoutMs = 2e4) {
|
|
|
4146
5287
|
function waitForTunnelUrl(child, pattern, timeoutMs) {
|
|
4147
5288
|
return new Promise((resolve, reject) => {
|
|
4148
5289
|
let settled = false;
|
|
5290
|
+
let output = "";
|
|
4149
5291
|
const timeout = setTimeout(() => {
|
|
4150
5292
|
if (!settled) {
|
|
4151
5293
|
settled = true;
|
|
4152
5294
|
child.kill("SIGTERM");
|
|
4153
|
-
reject(new Error(
|
|
5295
|
+
reject(new Error([
|
|
5296
|
+
"Timed out waiting for HTTPS tunnel URL.",
|
|
5297
|
+
tunnelOutputHint(output)
|
|
5298
|
+
].filter(Boolean).join("\n")));
|
|
4154
5299
|
}
|
|
4155
5300
|
}, timeoutMs);
|
|
4156
5301
|
const inspect = (chunk) => {
|
|
4157
|
-
|
|
5302
|
+
output = tail(`${output}${chunk.toString("utf8")}`, 4e3);
|
|
5303
|
+
const match = output.match(pattern);
|
|
4158
5304
|
if (match?.[0] && !settled) {
|
|
4159
5305
|
settled = true;
|
|
4160
5306
|
clearTimeout(timeout);
|
|
@@ -4174,11 +5320,209 @@ function waitForTunnelUrl(child, pattern, timeoutMs) {
|
|
|
4174
5320
|
if (!settled) {
|
|
4175
5321
|
settled = true;
|
|
4176
5322
|
clearTimeout(timeout);
|
|
4177
|
-
reject(new Error(
|
|
5323
|
+
reject(new Error([
|
|
5324
|
+
`Tunnel process exited before producing a URL${code === null ? "" : ` with code ${code}`}.`,
|
|
5325
|
+
tunnelOutputHint(output)
|
|
5326
|
+
].filter(Boolean).join("\n")));
|
|
4178
5327
|
}
|
|
4179
5328
|
});
|
|
4180
5329
|
});
|
|
4181
5330
|
}
|
|
5331
|
+
async function validateProtectedResourceMetadata(options) {
|
|
5332
|
+
const metadataUrl = protectedResourceMetadataUrl(options.mcpUrl);
|
|
5333
|
+
const response = await fetchWithTimeout(metadataUrl, {
|
|
5334
|
+
headers: { accept: "application/json" },
|
|
5335
|
+
timeoutMs: options.timeoutMs
|
|
5336
|
+
});
|
|
5337
|
+
const body = await readValidationBody(response);
|
|
5338
|
+
assertNotHtml(response, body, metadataUrl);
|
|
5339
|
+
if (response.status !== 200) {
|
|
5340
|
+
if (isTransientHttpStatus(response.status)) {
|
|
5341
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: ${metadataUrl} returned HTTP ${response.status}.`);
|
|
5342
|
+
}
|
|
5343
|
+
throw new Error(`Tunnel validation failed: ${metadataUrl} returned HTTP ${response.status}.`);
|
|
5344
|
+
}
|
|
5345
|
+
const metadata = parseJsonObject(body, metadataUrl);
|
|
5346
|
+
if (metadata.resource !== options.expectedResource) {
|
|
5347
|
+
throw new Error([
|
|
5348
|
+
"Tunnel validation failed: OAuth protected-resource metadata does not match the public MCP URL.",
|
|
5349
|
+
` expected resource: ${options.expectedResource}`,
|
|
5350
|
+
` actual resource: ${String(metadata.resource)}`
|
|
5351
|
+
].join("\n"));
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
async function validateAuthChallenge(options) {
|
|
5355
|
+
const response = await postJsonRpc(options.mcpUrl, {
|
|
5356
|
+
jsonrpc: "2.0",
|
|
5357
|
+
id: 1,
|
|
5358
|
+
method: "initialize",
|
|
5359
|
+
params: {
|
|
5360
|
+
protocolVersion: "2025-11-25",
|
|
5361
|
+
capabilities: {},
|
|
5362
|
+
clientInfo: { name: "sidecar-tunnel-check", version: "0.0.0" }
|
|
5363
|
+
}
|
|
5364
|
+
}, options.timeoutMs);
|
|
5365
|
+
const body = await readValidationBody(response);
|
|
5366
|
+
assertNotHtml(response, body, options.mcpUrl.toString());
|
|
5367
|
+
if (response.status !== 401) {
|
|
5368
|
+
if (isTransientHttpStatus(response.status)) {
|
|
5369
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: authenticated MCP endpoint returned HTTP ${response.status}.`);
|
|
5370
|
+
}
|
|
5371
|
+
throw new Error(`Tunnel validation failed: authenticated MCP endpoint should return HTTP 401 without a bearer token, received HTTP ${response.status}.`);
|
|
5372
|
+
}
|
|
5373
|
+
if (!response.headers.get("www-authenticate")?.toLowerCase().includes("bearer")) {
|
|
5374
|
+
throw new Error("Tunnel validation failed: authenticated MCP endpoint did not include a Bearer WWW-Authenticate challenge.");
|
|
5375
|
+
}
|
|
5376
|
+
parseJsonObject(body, options.mcpUrl.toString());
|
|
5377
|
+
}
|
|
5378
|
+
async function validateInitialize(options) {
|
|
5379
|
+
const response = await postJsonRpc(options.mcpUrl, {
|
|
5380
|
+
jsonrpc: "2.0",
|
|
5381
|
+
id: 1,
|
|
5382
|
+
method: "initialize",
|
|
5383
|
+
params: {
|
|
5384
|
+
protocolVersion: "2025-11-25",
|
|
5385
|
+
capabilities: {},
|
|
5386
|
+
clientInfo: { name: "sidecar-tunnel-check", version: "0.0.0" }
|
|
5387
|
+
}
|
|
5388
|
+
}, options.timeoutMs);
|
|
5389
|
+
const body = await readValidationBody(response);
|
|
5390
|
+
assertNotHtml(response, body, options.mcpUrl.toString());
|
|
5391
|
+
if (response.status !== 200) {
|
|
5392
|
+
if (isTransientHttpStatus(response.status)) {
|
|
5393
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: initialize returned HTTP ${response.status}.`);
|
|
5394
|
+
}
|
|
5395
|
+
throw new Error(`Tunnel validation failed: initialize returned HTTP ${response.status}.`);
|
|
5396
|
+
}
|
|
5397
|
+
const payload = parseJsonObject(body, options.mcpUrl.toString());
|
|
5398
|
+
if (!isRecord3(payload.result) || typeof payload.result.protocolVersion !== "string") {
|
|
5399
|
+
throw new Error("Tunnel validation failed: initialize did not return an MCP server result.");
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
async function validateToolsList(options) {
|
|
5403
|
+
const response = await postJsonRpc(options.mcpUrl, {
|
|
5404
|
+
jsonrpc: "2.0",
|
|
5405
|
+
id: 2,
|
|
5406
|
+
method: "tools/list"
|
|
5407
|
+
}, options.timeoutMs);
|
|
5408
|
+
const body = await readValidationBody(response);
|
|
5409
|
+
assertNotHtml(response, body, options.mcpUrl.toString());
|
|
5410
|
+
if (response.status !== 200) {
|
|
5411
|
+
if (isTransientHttpStatus(response.status)) {
|
|
5412
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: tools/list returned HTTP ${response.status}.`);
|
|
5413
|
+
}
|
|
5414
|
+
throw new Error(`Tunnel validation failed: tools/list returned HTTP ${response.status}.`);
|
|
5415
|
+
}
|
|
5416
|
+
const payload = parseJsonObject(body, options.mcpUrl.toString());
|
|
5417
|
+
if (!isRecord3(payload.result) || !Array.isArray(payload.result.tools)) {
|
|
5418
|
+
throw new Error("Tunnel validation failed: tools/list did not return a tools array.");
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
function postJsonRpc(url, payload, timeoutMs) {
|
|
5422
|
+
return fetchWithTimeout(url.toString(), {
|
|
5423
|
+
method: "POST",
|
|
5424
|
+
headers: {
|
|
5425
|
+
accept: "application/json, text/event-stream",
|
|
5426
|
+
"content-type": "application/json"
|
|
5427
|
+
},
|
|
5428
|
+
body: JSON.stringify(payload),
|
|
5429
|
+
timeoutMs
|
|
5430
|
+
});
|
|
5431
|
+
}
|
|
5432
|
+
async function fetchWithTimeout(url, options) {
|
|
5433
|
+
const controller = new AbortController();
|
|
5434
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
5435
|
+
try {
|
|
5436
|
+
return await fetch(url, {
|
|
5437
|
+
...options,
|
|
5438
|
+
signal: controller.signal
|
|
5439
|
+
});
|
|
5440
|
+
} catch (error) {
|
|
5441
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
5442
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: timed out fetching ${url}.`);
|
|
5443
|
+
}
|
|
5444
|
+
throw new TransientTunnelValidationError(
|
|
5445
|
+
`Tunnel validation failed: could not fetch ${url}: ${errorMessage(error)}.`
|
|
5446
|
+
);
|
|
5447
|
+
} finally {
|
|
5448
|
+
clearTimeout(timeout);
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
async function retryTunnelValidation(validate, timeoutMs) {
|
|
5452
|
+
const startedAt = Date.now();
|
|
5453
|
+
let lastError;
|
|
5454
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
5455
|
+
try {
|
|
5456
|
+
await validate();
|
|
5457
|
+
return;
|
|
5458
|
+
} catch (error) {
|
|
5459
|
+
lastError = error;
|
|
5460
|
+
if (!(error instanceof TransientTunnelValidationError)) {
|
|
5461
|
+
throw error;
|
|
5462
|
+
}
|
|
5463
|
+
await delay(500);
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
throw lastError instanceof Error ? lastError : new Error("Tunnel validation failed before the public endpoint became reachable.");
|
|
5467
|
+
}
|
|
5468
|
+
var TransientTunnelValidationError = class extends Error {
|
|
5469
|
+
constructor(message) {
|
|
5470
|
+
super(message);
|
|
5471
|
+
this.name = "TransientTunnelValidationError";
|
|
5472
|
+
}
|
|
5473
|
+
};
|
|
5474
|
+
function isTransientHttpStatus(status) {
|
|
5475
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
5476
|
+
}
|
|
5477
|
+
function delay(ms) {
|
|
5478
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5479
|
+
}
|
|
5480
|
+
async function readValidationBody(response) {
|
|
5481
|
+
const text = await response.text();
|
|
5482
|
+
return text.slice(0, 2e4);
|
|
5483
|
+
}
|
|
5484
|
+
function assertNotHtml(response, body, url) {
|
|
5485
|
+
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
5486
|
+
const trimmed = body.trimStart().toLowerCase();
|
|
5487
|
+
if (contentType.includes("text/html") || trimmed.startsWith("<!doctype html") || trimmed.startsWith("<html")) {
|
|
5488
|
+
throw new Error([
|
|
5489
|
+
`Tunnel validation failed: ${url} returned HTML instead of MCP JSON.`,
|
|
5490
|
+
"This usually means the tunnel provider returned an interstitial, warning page, or bad route."
|
|
5491
|
+
].join("\n"));
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
function parseJsonObject(body, url) {
|
|
5495
|
+
try {
|
|
5496
|
+
const parsed = JSON.parse(body);
|
|
5497
|
+
if (isRecord3(parsed)) {
|
|
5498
|
+
return parsed;
|
|
5499
|
+
}
|
|
5500
|
+
} catch {
|
|
5501
|
+
}
|
|
5502
|
+
throw new Error(`Tunnel validation failed: ${url} did not return a JSON object.`);
|
|
5503
|
+
}
|
|
5504
|
+
function protectedResourceMetadataUrl(mcpUrl) {
|
|
5505
|
+
const url = new URL(mcpUrl.origin);
|
|
5506
|
+
const pathname = mcpUrl.pathname.replace(/\/+$/, "") || "/";
|
|
5507
|
+
url.pathname = pathname === "/" ? "/.well-known/oauth-protected-resource" : `/.well-known/oauth-protected-resource${pathname}`;
|
|
5508
|
+
return url.toString();
|
|
5509
|
+
}
|
|
5510
|
+
function tunnelOutputHint(output) {
|
|
5511
|
+
const trimmed = output.trim();
|
|
5512
|
+
return trimmed ? `Tunnel output:
|
|
5513
|
+
${trimmed}` : "";
|
|
5514
|
+
}
|
|
5515
|
+
function tail(value, maxLength) {
|
|
5516
|
+
return value.length > maxLength ? value.slice(value.length - maxLength) : value;
|
|
5517
|
+
}
|
|
5518
|
+
function errorMessage(error) {
|
|
5519
|
+
if (error instanceof Error) {
|
|
5520
|
+
const cause = error.cause;
|
|
5521
|
+
const causeMessage = cause instanceof Error ? ` (${cause.message})` : "";
|
|
5522
|
+
return `${error.message}${causeMessage}`;
|
|
5523
|
+
}
|
|
5524
|
+
return String(error);
|
|
5525
|
+
}
|
|
4182
5526
|
function hasCommand(command) {
|
|
4183
5527
|
return spawnSync("which", [command], { stdio: "ignore" }).status === 0;
|
|
4184
5528
|
}
|
|
@@ -4195,8 +5539,11 @@ function runInherited(command, args) {
|
|
|
4195
5539
|
});
|
|
4196
5540
|
});
|
|
4197
5541
|
}
|
|
4198
|
-
function appendPath(origin,
|
|
4199
|
-
return `${origin.replace(/\/+$/, "")}/${
|
|
5542
|
+
function appendPath(origin, path21) {
|
|
5543
|
+
return `${origin.replace(/\/+$/, "")}/${path21.replace(/^\/+/, "")}`;
|
|
5544
|
+
}
|
|
5545
|
+
function isRecord3(value) {
|
|
5546
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4200
5547
|
}
|
|
4201
5548
|
|
|
4202
5549
|
// packages/cli/src/index.ts
|
|
@@ -4205,19 +5552,27 @@ async function main(argv) {
|
|
|
4205
5552
|
const rootDir = readOption(argv, "--cwd") ?? cwd();
|
|
4206
5553
|
switch (command) {
|
|
4207
5554
|
case "build": {
|
|
4208
|
-
const target =
|
|
4209
|
-
const
|
|
4210
|
-
const
|
|
5555
|
+
const target = readOptionalTarget(argv);
|
|
5556
|
+
const host = readOptionalHost(argv) ?? detectHostFromEnvironment();
|
|
5557
|
+
const outDir = readOption(argv, "--out");
|
|
5558
|
+
const plugins = readOptionalPlugins(argv);
|
|
4211
5559
|
const strict = argv.includes("--strict");
|
|
4212
|
-
const manifest = await buildProject({ rootDir, outDir, plugins, strict, target });
|
|
5560
|
+
const manifest = await buildProject({ rootDir, host, outDir, plugins, strict, target });
|
|
5561
|
+
const resolvedOutDir = outDir ?? manifest.config.build.outDir ?? defaultBuildOutDir2(manifest.host, manifest.target);
|
|
4213
5562
|
printDiagnostics(manifest.diagnostics ?? []);
|
|
4214
5563
|
if (strict && manifest.diagnostics?.length) {
|
|
4215
5564
|
exit(1);
|
|
4216
5565
|
}
|
|
4217
5566
|
console.log(
|
|
4218
|
-
`Built ${manifest.tools.length} ${target} tool${manifest.tools.length === 1 ? "" : "s"}, ${manifest.resources.length} resource${manifest.resources.length === 1 ? "" : "s"}, and ${manifest.prompts.length} prompt${manifest.prompts.length === 1 ? "" : "s"} to ${
|
|
5567
|
+
`Built ${manifest.tools.length} ${manifest.target} tool${manifest.tools.length === 1 ? "" : "s"}, ${manifest.resources.length} resource${manifest.resources.length === 1 ? "" : "s"}, and ${manifest.prompts.length} prompt${manifest.prompts.length === 1 ? "" : "s"} to ${resolvedOutDir}.`
|
|
4219
5568
|
);
|
|
4220
|
-
|
|
5569
|
+
console.log(`Host runtime: ${manifest.host}`);
|
|
5570
|
+
if (manifest.host === "vercel") {
|
|
5571
|
+
console.log(`Vercel MCP function: ${path20.join(resolvedOutDir, VERCEL_ENTRYPOINT)}`);
|
|
5572
|
+
} else {
|
|
5573
|
+
console.log(`Hostable MCP server: ${path20.join(resolvedOutDir, SERVER_ENTRYPOINT)}`);
|
|
5574
|
+
}
|
|
5575
|
+
if (plugins ?? manifest.config.build.plugins) {
|
|
4221
5576
|
console.log("Built claude-plugin package.");
|
|
4222
5577
|
}
|
|
4223
5578
|
return;
|
|
@@ -4248,18 +5603,20 @@ async function main(argv) {
|
|
|
4248
5603
|
const target = readTarget(argv);
|
|
4249
5604
|
const tunnelProvider = readTunnelProvider(argv);
|
|
4250
5605
|
const outDir = `.sidecar/dev/${target}`;
|
|
5606
|
+
const loadedAuth = await loadRuntimeAuth(rootDir);
|
|
5607
|
+
const runtimeConfig = await loadRuntimeConfig(rootDir);
|
|
5608
|
+
const proxy = await loadRuntimeProxy(rootDir);
|
|
5609
|
+
const runtimeToolEntries = await analyzeProjectTools(rootDir, { target });
|
|
5610
|
+
const runtimeTools = await loadRuntimeTools(rootDir, runtimeToolEntries);
|
|
4251
5611
|
const manifest = await buildProject({ rootDir, outDir, target });
|
|
4252
5612
|
printDiagnostics(manifest.diagnostics ?? []);
|
|
4253
|
-
const tools =
|
|
5613
|
+
const tools = attachBuiltToolDescriptors(runtimeTools, manifest);
|
|
4254
5614
|
let tunnel;
|
|
4255
5615
|
if (tunnelProvider) {
|
|
4256
5616
|
tunnel = await startTunnel({ provider: tunnelProvider, port, path: "/mcp" });
|
|
4257
5617
|
process.env.SIDECAR_MCP_URL = tunnel.mcpUrl;
|
|
4258
5618
|
}
|
|
4259
|
-
const loadedAuth = await loadRuntimeAuth(rootDir);
|
|
4260
|
-
const runtimeConfig = await loadRuntimeConfig(rootDir);
|
|
4261
5619
|
const auth = loadedAuth && tunnel ? loadedAuth.withResource(tunnel.mcpUrl) : loadedAuth;
|
|
4262
|
-
const proxy = await loadRuntimeProxy(rootDir);
|
|
4263
5620
|
const resources = await loadResources(rootDir, outDir, manifest);
|
|
4264
5621
|
const prompts = await loadRuntimePrompts(rootDir, manifest);
|
|
4265
5622
|
const server = createSidecarHttpServer({
|
|
@@ -4280,14 +5637,29 @@ async function main(argv) {
|
|
|
4280
5637
|
pageSize: manifest.config.pagination.pageSize
|
|
4281
5638
|
}
|
|
4282
5639
|
});
|
|
4283
|
-
|
|
5640
|
+
let listening = false;
|
|
5641
|
+
try {
|
|
5642
|
+
await listenOnLocalhost(server, port);
|
|
5643
|
+
listening = true;
|
|
5644
|
+
if (tunnel) {
|
|
5645
|
+
await validateTunnelEndpoint({
|
|
5646
|
+
mcpUrl: tunnel.mcpUrl,
|
|
5647
|
+
auth: Boolean(auth)
|
|
5648
|
+
});
|
|
5649
|
+
}
|
|
4284
5650
|
console.log(`MCP running on Streamable HTTP (${target}) at http://127.0.0.1:${port}/mcp`);
|
|
4285
5651
|
console.log(`Loaded ${tools.length} tool${tools.length === 1 ? "" : "s"}, ${resources.length} resource${resources.length === 1 ? "" : "s"}, and ${prompts.length} prompt${prompts.length === 1 ? "" : "s"}.`);
|
|
4286
5652
|
if (tunnel) {
|
|
4287
|
-
console.log(`HTTPS tunnel (${tunnel.provider})
|
|
5653
|
+
console.log(`HTTPS tunnel (${tunnel.provider}) validated: ${tunnel.mcpUrl}`);
|
|
4288
5654
|
console.log("Use this HTTPS MCP URL in ChatGPT, Claude, or a Claude plugin install.");
|
|
4289
5655
|
}
|
|
4290
|
-
})
|
|
5656
|
+
} catch (error) {
|
|
5657
|
+
tunnel?.close();
|
|
5658
|
+
if (listening) {
|
|
5659
|
+
await closeServer(server);
|
|
5660
|
+
}
|
|
5661
|
+
throw error;
|
|
5662
|
+
}
|
|
4291
5663
|
const shutdown = () => {
|
|
4292
5664
|
tunnel?.close();
|
|
4293
5665
|
server.close(() => exit(0));
|
|
@@ -4330,7 +5702,7 @@ async function main(argv) {
|
|
|
4330
5702
|
console.log(`Sidecar
|
|
4331
5703
|
|
|
4332
5704
|
Usage:
|
|
4333
|
-
sidecar build [--cwd <dir>] [--target mcp|chatgpt|claude] [--out <dir>] [--plugins] [--strict]
|
|
5705
|
+
sidecar build [--cwd <dir>] [--target mcp|chatgpt|claude] [--host node|vercel] [--out <dir>] [--plugins|--no-plugins] [--strict]
|
|
4334
5706
|
sidecar check [--cwd <dir>] [--target mcp|chatgpt|claude] [--strict]
|
|
4335
5707
|
sidecar dev [--cwd <dir>] [--target mcp|chatgpt|claude] [--port <port>] [--tunnel [cloudflared|wrangler]]
|
|
4336
5708
|
sidecar inspect [--cwd <dir>] [--target mcp|chatgpt|claude]
|
|
@@ -4338,16 +5710,54 @@ Usage:
|
|
|
4338
5710
|
`);
|
|
4339
5711
|
}
|
|
4340
5712
|
}
|
|
5713
|
+
function defaultBuildOutDir2(host, target) {
|
|
5714
|
+
if (host === "vercel") {
|
|
5715
|
+
return ".vercel/output";
|
|
5716
|
+
}
|
|
5717
|
+
return `out/${target}`;
|
|
5718
|
+
}
|
|
4341
5719
|
function readTarget(argv) {
|
|
4342
|
-
const target =
|
|
5720
|
+
const target = readOptionalTarget(argv) ?? "mcp";
|
|
5721
|
+
return target;
|
|
5722
|
+
}
|
|
5723
|
+
function readOptionalTarget(argv) {
|
|
5724
|
+
const target = readOption(argv, "--target");
|
|
5725
|
+
if (!target) {
|
|
5726
|
+
return void 0;
|
|
5727
|
+
}
|
|
4343
5728
|
if (target === "mcp" || target === "chatgpt" || target === "claude") {
|
|
4344
5729
|
return target;
|
|
4345
5730
|
}
|
|
4346
5731
|
throw new Error(`Unsupported Sidecar target "${target}". Expected mcp, chatgpt, or claude.`);
|
|
4347
5732
|
}
|
|
5733
|
+
function readOptionalHost(argv) {
|
|
5734
|
+
const host = readOption(argv, "--host");
|
|
5735
|
+
if (!host) {
|
|
5736
|
+
return void 0;
|
|
5737
|
+
}
|
|
5738
|
+
if (host === "node" || host === "vercel") {
|
|
5739
|
+
return host;
|
|
5740
|
+
}
|
|
5741
|
+
throw new Error(`Unsupported Sidecar host "${host}". Expected node or vercel.`);
|
|
5742
|
+
}
|
|
5743
|
+
function detectHostFromEnvironment() {
|
|
5744
|
+
if (process.env.VERCEL === "1") {
|
|
5745
|
+
return "vercel";
|
|
5746
|
+
}
|
|
5747
|
+
return void 0;
|
|
5748
|
+
}
|
|
5749
|
+
function readOptionalPlugins(argv) {
|
|
5750
|
+
if (argv.includes("--plugins")) {
|
|
5751
|
+
return true;
|
|
5752
|
+
}
|
|
5753
|
+
if (argv.includes("--no-plugins")) {
|
|
5754
|
+
return false;
|
|
5755
|
+
}
|
|
5756
|
+
return void 0;
|
|
5757
|
+
}
|
|
4348
5758
|
async function previewComponents(options) {
|
|
4349
|
-
const cliDir =
|
|
4350
|
-
const css = await readFile8(
|
|
5759
|
+
const cliDir = path20.dirname(fileURLToPath(import.meta.url));
|
|
5760
|
+
const css = await readFile8(path20.join(cliDir, "../../native/src/styles.css"), "utf8").catch(() => readFile8(path20.join(process.cwd(), "packages/native/src/styles.css"), "utf8")).catch(() => "");
|
|
4351
5761
|
const html = renderComponentPreviewHtml(
|
|
4352
5762
|
options.host,
|
|
4353
5763
|
options.compare,
|
|
@@ -4387,10 +5797,10 @@ async function previewComponents(options) {
|
|
|
4387
5797
|
server.close();
|
|
4388
5798
|
}
|
|
4389
5799
|
async function writeComponentApproval(rootDir, approval) {
|
|
4390
|
-
const dir =
|
|
4391
|
-
await
|
|
4392
|
-
await
|
|
4393
|
-
|
|
5800
|
+
const dir = path20.join(rootDir, ".sidecar", "approvals");
|
|
5801
|
+
await mkdir10(dir, { recursive: true });
|
|
5802
|
+
await writeFile10(
|
|
5803
|
+
path20.join(dir, `components.${approval.host}.json`),
|
|
4394
5804
|
`${JSON.stringify(approval, null, 2)}
|
|
4395
5805
|
`
|
|
4396
5806
|
);
|
|
@@ -4742,84 +6152,98 @@ function printDiagnostics(diagnostics) {
|
|
|
4742
6152
|
}
|
|
4743
6153
|
}
|
|
4744
6154
|
async function loadRuntimeAuth(rootDir) {
|
|
4745
|
-
const authPath =
|
|
4746
|
-
if (!
|
|
6155
|
+
const authPath = path20.join(rootDir, "auth.ts");
|
|
6156
|
+
if (!existsSync7(authPath)) {
|
|
4747
6157
|
return void 0;
|
|
4748
6158
|
}
|
|
4749
|
-
const parentURL =
|
|
4750
|
-
const tsconfigPath =
|
|
4751
|
-
const tsconfig =
|
|
4752
|
-
const module = await
|
|
6159
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6160
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6161
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
6162
|
+
const module = await tsImport2(pathToFileURL3(authPath).href, {
|
|
4753
6163
|
parentURL,
|
|
4754
6164
|
tsconfig
|
|
4755
6165
|
});
|
|
4756
|
-
|
|
6166
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6167
|
+
if (!isSidecarAuth(defaultExport)) {
|
|
4757
6168
|
throw new Error("auth.ts must default-export auth({ ... }) from sidecar-ai.");
|
|
4758
6169
|
}
|
|
4759
|
-
return
|
|
6170
|
+
return defaultExport;
|
|
4760
6171
|
}
|
|
4761
6172
|
async function loadRuntimeProxy(rootDir) {
|
|
4762
|
-
const proxyPath =
|
|
4763
|
-
if (!
|
|
6173
|
+
const proxyPath = path20.join(rootDir, "proxy.ts");
|
|
6174
|
+
if (!existsSync7(proxyPath)) {
|
|
4764
6175
|
return void 0;
|
|
4765
6176
|
}
|
|
4766
|
-
const parentURL =
|
|
4767
|
-
const tsconfigPath =
|
|
4768
|
-
const tsconfig =
|
|
4769
|
-
const module = await
|
|
6177
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6178
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6179
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
6180
|
+
const module = await tsImport2(pathToFileURL3(proxyPath).href, {
|
|
4770
6181
|
parentURL,
|
|
4771
6182
|
tsconfig
|
|
4772
6183
|
});
|
|
4773
|
-
|
|
6184
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6185
|
+
if (!isSidecarProxy(defaultExport)) {
|
|
4774
6186
|
throw new Error("proxy.ts must default-export proxy({ ... }) from @sidecar-ai/server/proxy.");
|
|
4775
6187
|
}
|
|
4776
|
-
return
|
|
6188
|
+
return defaultExport;
|
|
4777
6189
|
}
|
|
4778
6190
|
async function loadRuntimeConfig(rootDir) {
|
|
4779
|
-
const configPath =
|
|
4780
|
-
if (!
|
|
6191
|
+
const configPath = path20.join(rootDir, "sidecar.config.ts");
|
|
6192
|
+
if (!existsSync7(configPath)) {
|
|
4781
6193
|
return void 0;
|
|
4782
6194
|
}
|
|
4783
|
-
const parentURL =
|
|
4784
|
-
const tsconfigPath =
|
|
4785
|
-
const tsconfig =
|
|
4786
|
-
const module = await
|
|
6195
|
+
const parentURL = pathToFileURL3(configPath).href;
|
|
6196
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6197
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
6198
|
+
const module = await tsImport2(pathToFileURL3(configPath).href, {
|
|
4787
6199
|
parentURL,
|
|
4788
6200
|
tsconfig
|
|
4789
6201
|
});
|
|
4790
|
-
|
|
6202
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6203
|
+
if (!defaultExport || typeof defaultExport !== "object") {
|
|
4791
6204
|
throw new Error("sidecar.config.ts must default-export defineConfig({ ... }) from sidecar-ai.");
|
|
4792
6205
|
}
|
|
4793
|
-
return
|
|
6206
|
+
return defaultExport;
|
|
4794
6207
|
}
|
|
4795
|
-
async function loadRuntimeTools(rootDir,
|
|
4796
|
-
const parentURL =
|
|
4797
|
-
const tsconfigPath =
|
|
4798
|
-
const tsconfig =
|
|
6208
|
+
async function loadRuntimeTools(rootDir, entries) {
|
|
6209
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6210
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6211
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
4799
6212
|
const loaded = [];
|
|
4800
|
-
for (const entry of
|
|
4801
|
-
const sourcePath =
|
|
4802
|
-
const module = await
|
|
6213
|
+
for (const entry of entries) {
|
|
6214
|
+
const sourcePath = path20.join(rootDir, entry.sourceFile);
|
|
6215
|
+
const module = await tsImport2(pathToFileURL3(sourcePath).href, {
|
|
4803
6216
|
parentURL,
|
|
4804
6217
|
tsconfig
|
|
4805
6218
|
});
|
|
4806
|
-
|
|
6219
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6220
|
+
if (!isSidecarTool(defaultExport)) {
|
|
4807
6221
|
throw new Error(`${entry.sourceFile} did not default-export a Sidecar tool.`);
|
|
4808
6222
|
}
|
|
4809
6223
|
loaded.push({
|
|
4810
|
-
|
|
6224
|
+
sourceFile: entry.sourceFile,
|
|
6225
|
+
tool: defaultExport,
|
|
4811
6226
|
descriptor: entry.descriptor
|
|
4812
6227
|
});
|
|
4813
6228
|
}
|
|
4814
6229
|
return loaded;
|
|
4815
6230
|
}
|
|
6231
|
+
function attachBuiltToolDescriptors(loadedTools, manifest) {
|
|
6232
|
+
const descriptorsBySource = new Map(
|
|
6233
|
+
manifest.tools.map((entry) => [entry.sourceFile, entry.descriptor])
|
|
6234
|
+
);
|
|
6235
|
+
return loadedTools.map(({ sourceFile, tool, descriptor }) => ({
|
|
6236
|
+
tool,
|
|
6237
|
+
descriptor: descriptorsBySource.get(sourceFile) ?? descriptor
|
|
6238
|
+
}));
|
|
6239
|
+
}
|
|
4816
6240
|
async function loadResources(rootDir, outDir, manifest) {
|
|
4817
6241
|
const resources = [];
|
|
4818
6242
|
for (const entry of manifest.tools) {
|
|
4819
6243
|
if (!entry.widget?.outputFile) {
|
|
4820
6244
|
continue;
|
|
4821
6245
|
}
|
|
4822
|
-
const text = await readFile8(
|
|
6246
|
+
const text = await readFile8(path20.join(rootDir, outDir, entry.widget.outputFile), "utf8");
|
|
4823
6247
|
resources.push({
|
|
4824
6248
|
uri: entry.widget.resourceUri,
|
|
4825
6249
|
name: entry.name,
|
|
@@ -4829,47 +6253,72 @@ async function loadResources(rootDir, outDir, manifest) {
|
|
|
4829
6253
|
_meta: entry.widget.resourceMeta
|
|
4830
6254
|
});
|
|
4831
6255
|
}
|
|
4832
|
-
const parentURL =
|
|
4833
|
-
const tsconfigPath =
|
|
4834
|
-
const tsconfig =
|
|
6256
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6257
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6258
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
4835
6259
|
for (const entry of manifest.resources) {
|
|
4836
|
-
const sourcePath =
|
|
4837
|
-
const module = await
|
|
6260
|
+
const sourcePath = path20.join(rootDir, entry.sourceFile);
|
|
6261
|
+
const module = await tsImport2(pathToFileURL3(sourcePath).href, {
|
|
4838
6262
|
parentURL,
|
|
4839
6263
|
tsconfig
|
|
4840
6264
|
});
|
|
4841
|
-
|
|
6265
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6266
|
+
if (!isSidecarResource(defaultExport)) {
|
|
4842
6267
|
throw new Error(`${entry.sourceFile} did not default-export a Sidecar resource.`);
|
|
4843
6268
|
}
|
|
4844
6269
|
resources.push({
|
|
4845
6270
|
uri: entry.uri,
|
|
4846
6271
|
descriptor: entry.descriptor,
|
|
4847
|
-
resource:
|
|
6272
|
+
resource: defaultExport
|
|
4848
6273
|
});
|
|
4849
6274
|
}
|
|
4850
6275
|
return resources;
|
|
4851
6276
|
}
|
|
4852
6277
|
async function loadRuntimePrompts(rootDir, manifest) {
|
|
4853
|
-
const parentURL =
|
|
4854
|
-
const tsconfigPath =
|
|
4855
|
-
const tsconfig =
|
|
6278
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6279
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6280
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
4856
6281
|
const loaded = [];
|
|
4857
6282
|
for (const entry of manifest.prompts) {
|
|
4858
|
-
const sourcePath =
|
|
4859
|
-
const module = await
|
|
6283
|
+
const sourcePath = path20.join(rootDir, entry.sourceFile);
|
|
6284
|
+
const module = await tsImport2(pathToFileURL3(sourcePath).href, {
|
|
4860
6285
|
parentURL,
|
|
4861
6286
|
tsconfig
|
|
4862
6287
|
});
|
|
4863
|
-
|
|
6288
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6289
|
+
if (!isSidecarPrompt(defaultExport)) {
|
|
4864
6290
|
throw new Error(`${entry.sourceFile} did not default-export a Sidecar prompt.`);
|
|
4865
6291
|
}
|
|
4866
6292
|
loaded.push({
|
|
4867
|
-
prompt:
|
|
6293
|
+
prompt: defaultExport,
|
|
4868
6294
|
descriptor: entry.descriptor
|
|
4869
6295
|
});
|
|
4870
6296
|
}
|
|
4871
6297
|
return loaded;
|
|
4872
6298
|
}
|
|
6299
|
+
function unwrapRuntimeDefault2(value) {
|
|
6300
|
+
if (value && typeof value === "object" && "default" in value && Object.keys(value).every((key) => key === "default" || key === "__esModule")) {
|
|
6301
|
+
return unwrapRuntimeDefault2(value.default);
|
|
6302
|
+
}
|
|
6303
|
+
return value;
|
|
6304
|
+
}
|
|
6305
|
+
function listenOnLocalhost(server, port) {
|
|
6306
|
+
return new Promise((resolve, reject) => {
|
|
6307
|
+
const onError = (error) => {
|
|
6308
|
+
reject(error);
|
|
6309
|
+
};
|
|
6310
|
+
server.once("error", onError);
|
|
6311
|
+
server.listen(port, "127.0.0.1", () => {
|
|
6312
|
+
server.off("error", onError);
|
|
6313
|
+
resolve();
|
|
6314
|
+
});
|
|
6315
|
+
});
|
|
6316
|
+
}
|
|
6317
|
+
function closeServer(server) {
|
|
6318
|
+
return new Promise((resolve) => {
|
|
6319
|
+
server.close(() => resolve());
|
|
6320
|
+
});
|
|
6321
|
+
}
|
|
4873
6322
|
function readOption(argv, name) {
|
|
4874
6323
|
const index = argv.indexOf(name);
|
|
4875
6324
|
if (index === -1) {
|
|
@@ -4883,7 +6332,7 @@ function isDirectRun() {
|
|
|
4883
6332
|
return false;
|
|
4884
6333
|
}
|
|
4885
6334
|
const entryPath = realpathSync.native(entry);
|
|
4886
|
-
return import.meta.url ===
|
|
6335
|
+
return import.meta.url === pathToFileURL3(entryPath).href;
|
|
4887
6336
|
}
|
|
4888
6337
|
if (isDirectRun()) {
|
|
4889
6338
|
main(process.argv).catch((error) => {
|