@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 CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // packages/cli/src/index.ts
4
- import { existsSync as existsSync5, realpathSync } from "fs";
5
- import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
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 path18 from "path";
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(value && typeof value === "object" && value[toolBrand]);
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(value && typeof value === "object" && value[resourceBrand]);
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(value && typeof value === "object" && value[promptBrand]);
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 path4 from "path";
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(definition, execute) {
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 existsSync2 } from "fs";
898
+ import { existsSync as existsSync3 } from "fs";
850
899
  import { mkdir, readFile, writeFile } from "fs/promises";
851
- import path3 from "path";
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
- async function buildWidgets(rootDir, outDir, tools) {
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 = path3.join(rootDir, ".sidecar", "cache", "widgets");
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 = path3.join(rootDir, entry.widget.sourceFile);
923
+ const sourceFile = path4.join(rootDir, entry.widget.sourceFile);
870
924
  const safeId = safePathSegment(entry.id);
871
- const entryFile = path3.join(cacheDir, `${safeId}.entry.tsx`);
872
- const importPath = toImportSpecifier(path3.dirname(entryFile), sourceFile);
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(path3.dirname(entryFile), appStyle))};` : ""}
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 bundled = await esbuild({
941
+ const baseOptions = enforceWidgetBuildInvariants({
888
942
  absWorkingDir: rootDir,
889
- alias: devSidecarBundleAliases(rootDir),
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: false,
951
+ minify: true,
895
952
  nodePaths: [
896
- path3.join(rootDir, "node_modules"),
897
- path3.join(process.cwd(), "node_modules")
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 javascript = bundled.outputFiles.find((file) => file.path.endsWith(".js"))?.text ?? "";
905
- const css = bundled.outputFiles.filter((file) => file.path.endsWith(".css")).map((file) => file.text).join("\n");
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 = path3.join(outDir, "public", "widgets", safeId);
909
- const outputFile = path3.join(outputDir, `widget.${hash}.html`);
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 = path3.relative(outDir, 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 = path3.join(repoRoot, "packages", "react", "src", "index.ts");
925
- if (!existsSync2(reactEntry)) {
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": path3.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
930
- "@sidecar-ai/client": path3.join(repoRoot, "packages", "client", "src", "index.ts"),
931
- "@sidecar-ai/core": path3.join(repoRoot, "packages", "core", "src", "index.ts"),
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": path3.join(repoRoot, "packages", "native", "src", "index.ts"),
934
- "@sidecar-ai/native/components": path3.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
935
- "@sidecar-ai/native/styles.css": path3.join(repoRoot, "packages", "native", "src", "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 = path3.resolve(startDir);
1148
+ let current = path4.resolve(startDir);
940
1149
  while (true) {
941
- if (existsSync2(path3.join(current, "packages", "core", "src", "index.ts"))) {
1150
+ if (existsSync3(path4.join(current, "packages", "core", "src", "index.ts"))) {
942
1151
  return current;
943
1152
  }
944
- const parent = path3.dirname(current);
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 = path3.join(rootDir, "style.css");
953
- if (!existsSync2(sourceFile)) {
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 = path3.join(cacheDir, "style.css");
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: path3.dirname(from) }));
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(path3.dirname(toolFile), target, toolVariant);
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: path3.relative(rootDir, selected.filePath),
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.csp?.resourceDomains ? [...options.csp.resourceDomains] : [],
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 = path3.join(directory, "widget.openai.tsx");
1166
- return existsSync2(filePath) ? { filePath, variant: "openai" } : void 0;
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 = path3.join(directory, "widget.anthropic.tsx");
1170
- return existsSync2(filePath) ? { filePath, variant: "anthropic" } : void 0;
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 = path3.join(directory, candidate.name);
1175
- if (existsSync2(filePath)) {
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(path4.join(rootDir, "server"), target);
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
- return toolFiles.map(
1233
- (candidate) => analyzeToolFile(project.addSourceFileAtPath(candidate.filePath), rootDir, {
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 = path4.basename(path4.dirname(absoluteFile));
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 = path4.join(rootDir, widget.sourceFile);
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: path4.relative(rootDir, absoluteFile),
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 = path4.join(rootDir, "auth.ts");
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 = path4.dirname(toolFile);
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(path4.join(directory, platformWidget))) {
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 = path4.join(serverDir, entry.name);
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 = path4.join(directory, candidate.name);
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 mkdir8, writeFile as writeFile8 } from "fs/promises";
1640
- import path17 from "path";
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 path5 from "path";
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 = path5.join(rootDir, "sidecar.config.ts");
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") ?? 10,
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: 10,
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 existsSync3 } from "fs";
2101
+ import { existsSync as existsSync4 } from "fs";
1738
2102
  import { readFile as readFile2 } from "fs/promises";
1739
- import path6 from "path";
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 = existsSync3(path6.join(rootDir, "auth.ts"));
2110
+ const hasAuthConfig = existsSync4(path7.join(rootDir, "auth.ts"));
1747
2111
  for (const entry of tools) {
1748
- const toolPath = path6.join(rootDir, entry.sourceFile);
2112
+ const toolPath = path7.join(rootDir, entry.sourceFile);
1749
2113
  const toolSource = await readFile2(toolPath, "utf8");
1750
- diagnostics.push(...diagnoseToolSource(rootDir, entry, toolSource, hasAuthConfig));
2114
+ diagnostics.push(...diagnoseToolSource(entry, toolSource, hasAuthConfig));
1751
2115
  if (entry.widget) {
1752
- const widgetPath = path6.join(rootDir, entry.widget.sourceFile);
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 = path6.join(rootDir, entry.sourceFile);
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 = path6.join(rootDir, entry.sourceFile);
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(rootDir, entry, source, hasAuthConfig) {
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 path7 from "path";
2359
+ import path8 from "path";
1996
2360
  async function writeGeneratedTypes(rootDir, tools) {
1997
- const generatedDir = path7.join(rootDir, ".sidecar", "generated");
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, path7.join(rootDir, entry.sourceFile), { extension: "js" }))};`
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
- (entry, index) => ` ${methodNames[index]}(params: ToolParams<typeof tool${index}>): Promise<ToolOutput<typeof tool${index}>>;`
2370
+ (_entry, index) => ` ${methodNames[index]}(params: ToolParams<typeof tool${index}>): Promise<ToolOutput<typeof tool${index}>>;`
2007
2371
  ).join("\n");
2008
2372
  await writeFile2(
2009
- path7.join(generatedDir, "tools.ts"),
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 path8 from "path";
2423
+ import path9 from "path";
2064
2424
  async function loadProjectIdentity(rootDir) {
2065
- const packageJson = await readOptionalJson(path8.join(rootDir, "package.json"));
2425
+ const packageJson = await readOptionalJson(path9.join(rootDir, "package.json"));
2066
2426
  const configText = await readOptionalText(
2067
- path8.join(rootDir, "sidecar.config.ts")
2427
+ path9.join(rootDir, "sidecar.config.ts")
2068
2428
  );
2069
- const name = readConfigString(configText, "name") ?? packageJson?.name ?? path8.basename(rootDir);
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 path9 from "path";
2467
+ import path10 from "path";
2104
2468
  async function emitClaudeAgents(rootDir, destination) {
2105
- const source = path9.join(rootDir, "agents");
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(path9.join(source, entry.name, "agent.ts"))
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(path9.join(source, entry.name, "agent.ts"), "utf8");
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(path9.join(destination, `${safeFileStem(agentName)}.md`), markdown);
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 path10 from "path";
2517
+ import path11 from "path";
2154
2518
  async function copyCommands(rootDir, destination) {
2155
- const source = path10.join(rootDir, "commands");
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 = path10.join(source, entry.name);
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, path10.join(destination, safeFileStem(entry.name.replace(/\.md$/, "")) + ".md"));
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 = path10.join(sourcePath, "command.ts");
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(path10.join(destination, `${safeFileStem(commandName)}.md`), markdown);
2541
+ await writeFile4(path11.join(destination, `${safeFileStem(commandName)}.md`), markdown);
2178
2542
  continue;
2179
2543
  }
2180
- await cp(sourcePath, path10.join(destination, entry.name), {
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 = path10.basename(sourcePath);
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 path11 from "path";
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 = path11.join(rootDir, "hooks");
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(path11.join(source, entry.name, "hook.ts"))
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 = path11.join(source, entry.name, "hook.ts");
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
- path11.join(destination, "hooks.json"),
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 = readStringProperty3(definition, "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: readStringProperty3(definition, "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: readStringProperty3(entry, "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 = readStringProperty3(handler, "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 readStringProperty3(object, propertyName) {
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 path12 from "path";
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 = path12.join(rootDir, directory);
2788
+ const source = path13.join(rootDir, directory);
2425
2789
  if (!existsSyncSafe(source)) {
2426
2790
  continue;
2427
2791
  }
2428
- await cp2(source, path12.join(pluginDir, directory), {
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 = path12.basename(sourcePath);
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 existsSync4 } from "fs";
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 path13 from "path";
2810
+ import path14 from "path";
2447
2811
  async function copySkills(rootDir, destination) {
2448
- const source = path13.join(rootDir, "skills");
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 = path13.join(source, entry.name);
2459
- const destinationDir = path13.join(destination, entry.name);
2460
- const staticSkill = path13.join(sourceDir, "SKILL.md");
2461
- const dynamicSkill = path13.join(sourceDir, "skill.ts");
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 (existsSync4(staticSkill)) {
2827
+ if (existsSync5(staticSkill)) {
2464
2828
  await cp3(sourceDir, destinationDir, {
2465
2829
  recursive: true,
2466
- filter: async (sourcePath) => !sourcePath.endsWith(`${path13.sep}skill.ts`) && await safeSkillCopyFilter(sourcePath)
2830
+ filter: async (sourcePath) => !sourcePath.endsWith(`${path14.sep}skill.ts`) && await safeSkillCopyFilter(sourcePath)
2467
2831
  });
2468
- } else if (existsSync4(dynamicSkill)) {
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(path13.join(destinationDir, "SKILL.md"), generated);
2837
+ await writeFile6(path14.join(destinationDir, "SKILL.md"), generated);
2474
2838
  }
2475
2839
  }
2476
2840
  }
2477
2841
  async function safeSkillCopyFilter(sourcePath) {
2478
- const basename = path13.basename(sourcePath);
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 = path14.join(outRoot, "claude-plugin");
2505
- await mkdir7(path14.join(pluginDir, ".claude-plugin"), { recursive: true });
2506
- await copySkills(rootDir, path14.join(pluginDir, "skills"));
2507
- await copyCommands(rootDir, path14.join(pluginDir, "commands"));
2508
- await copyHooks(rootDir, path14.join(pluginDir, "hooks"));
2509
- await emitClaudeAgents(rootDir, path14.join(pluginDir, "agents"));
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(path14.join(pluginDir, ".claude-plugin", "plugin.json"), {
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(path14.join(pluginDir, "version.json"), {
2882
+ await writeJson(path15.join(pluginDir, "version.json"), {
2519
2883
  version: identity.version,
2520
2884
  generatedBy: "sidecar"
2521
2885
  });
2522
- await writeJson(path14.join(pluginDir, ".mcp.json"), {
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(path14.join(pluginDir, "settings.json"), {
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
- path14.join(pluginDir, "README.md"),
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 printed HTTPS MCP URL.
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(path14.dirname(filePath), { recursive: true });
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 path15 from "path";
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(path15.join(rootDir, "prompts"));
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 = path15.basename(path15.dirname(absoluteFile));
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: path15.relative(rootDir, absoluteFile),
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 = path15.join(promptsDir, entry.name, "prompt.ts");
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: readStringArrayProperty3(object, "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 readStringArrayProperty3(definition, propertyName) {
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 path16 from "path";
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(path16.join(rootDir, "resources"));
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 = path16.basename(path16.dirname(absoluteFile));
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: path16.relative(rootDir, absoluteFile),
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 = path16.join(resourcesDir, entry.name, "resource.ts");
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 = readStringArrayProperty4(initializer, "audience")?.filter((value) => value === "user" || value === "assistant");
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: readStringArrayProperty4(object, "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 readStringArrayProperty4(definition, propertyName) {
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 = path17.resolve(options.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 ?? "out/mcp");
2976
- await buildWidgets(rootDir, outDir, tools);
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 mkdir8(outDir, { recursive: true });
2990
- await writeFile8(
2991
- path17.join(outDir, "manifest.sidecar.json"),
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 writeFile8(path17.join(outDir, "README.md"), renderMcpReadme(manifest));
3678
+ await writeFile9(path19.join(runtimeOutDir, "README.md"), renderMcpReadme(manifest));
2996
3679
  await writeGeneratedTypes(rootDir, tools);
2997
- if (options.plugins) {
2998
- await buildPluginPackages(rootDir, path17.dirname(outDir), manifest);
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 = path17.resolve(rootDir, output);
3004
- const relative = path17.relative(rootDir, resolved);
3005
- if (relative.startsWith("..") || path17.isAbsolute(relative)) {
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 an HTTPS MCP URL that can be added to ChatGPT, Claude, or a Claude plugin install.
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/index.ts
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: JSONRPC_VERSION,
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: JSONRPC_VERSION,
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) : 10;
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
- const args = loaded.descriptorProvided ? validateAgainstSchema(
3269
- loaded.descriptor.inputSchema,
3270
- params?.arguments ?? {},
3271
- `Invalid parameters for tool "${name}".`
3272
- ) : params?.arguments ?? {};
3273
- const result = await withTimeout(
3274
- executeTool(loaded.tool, args, ctx),
3275
- this.options.toolTimeoutMs,
3276
- controller
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(_request) {
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
- const mcp = createSidecarMcpServer(options);
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
- return createServer(async (request, response) => {
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
- response.writeHead(405, { "content-type": "application/json" });
3423
- response.end(JSON.stringify({ error: "sse_not_supported" }));
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 (request.method !== "POST" || pathname !== endpoint) {
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
- const fetchRequest = toFetchRequest(request, options.publicUrl ?? options.auth?.resource);
3445
- const authSession = options.auth ? await authorizeHttpRequest(options.auth, fetchRequest, response) : void 0;
3446
- if (options.auth && authSession === AUTH_RESPONSE_SENT) {
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: JSONRPC_VERSION,
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: JSONRPC_VERSION,
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
- return promise;
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
- controller.abort();
3739
- reject(new JsonRpcError(-32e3, "Tool execution timed out."));
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, path19) {
3760
- if (schema.anyOf?.length && !schema.anyOf.some((entry) => !schemaFailure(entry, value, path19))) {
3761
- return `${path19} must match one anyOf schema.`;
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, path19)).length !== 1) {
3764
- return `${path19} must match exactly one oneOf schema.`;
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, path19);
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 `${path19} must equal ${JSON.stringify(schema.const)}.`;
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 `${path19} must be one of the declared enum values.`;
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 `${path19} must be ${types.join(" or ")}.`;
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 `${path19} must be an object.`;
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 `${path19}.${required} is required.`;
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], `${path19}.${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 `${path19}.${extra} is not allowed.`;
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 `${path19} must be an array.`;
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, `${path19}[${index}]`);
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 `${path19} is shorter than ${schema.minLength}.`;
4939
+ return `${path21} is shorter than ${schema.minLength}.`;
3822
4940
  }
3823
4941
  if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
3824
- return `${path19} is longer than ${schema.maxLength}.`;
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 `${path19} is less than ${schema.minimum}.`;
4950
+ return `${path21} is less than ${schema.minimum}.`;
3830
4951
  }
3831
4952
  if (schema.maximum !== void 0 && value > schema.maximum) {
3832
- return `${path19} is greater than ${schema.maximum}.`;
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 !== JSONRPC_VERSION || typeof record.method !== "string") {
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: JSONRPC_VERSION,
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 !== JSONRPC_VERSION || !("id" in record) || !("result" in record || "error" in record)) {
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 path19 = options.path ?? "/mcp";
5131
+ const path21 = options.path ?? "/mcp";
4011
5132
  if (provider === "cloudflared") {
4012
- return startCloudflared(localUrl, path19, options.timeoutMs);
4013
- }
4014
- return startWrangler(localUrl, path19, options.timeoutMs);
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, path19, timeoutMs = 2e4) {
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, path19),
5267
+ mcpUrl: appendPath(publicUrl, path21),
4127
5268
  close() {
4128
5269
  child.kill("SIGTERM");
4129
5270
  }
4130
5271
  };
4131
5272
  }
4132
- async function startWrangler(localUrl, path19, timeoutMs = 2e4) {
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, path19),
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("Timed out waiting for HTTPS tunnel URL."));
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
- const match = chunk.toString("utf8").match(pattern);
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(`Tunnel process exited before producing a URL${code === null ? "" : ` with code ${code}`}.`));
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, path19) {
4199
- return `${origin.replace(/\/+$/, "")}/${path19.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 = readTarget(argv);
4209
- const outDir = readOption(argv, "--out") ?? `out/${target}`;
4210
- const plugins = argv.includes("--plugins");
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 ${outDir}.`
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
- if (plugins) {
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 = await loadRuntimeTools(rootDir, manifest);
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
- server.listen(port, "127.0.0.1", () => {
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}) ready: ${tunnel.mcpUrl}`);
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 = readOption(argv, "--target") ?? "mcp";
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 = path18.dirname(fileURLToPath(import.meta.url));
4350
- const css = await readFile8(path18.join(cliDir, "../../native/src/styles.css"), "utf8").catch(() => readFile8(path18.join(process.cwd(), "packages/native/src/styles.css"), "utf8")).catch(() => "");
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 = path18.join(rootDir, ".sidecar", "approvals");
4391
- await mkdir9(dir, { recursive: true });
4392
- await writeFile9(
4393
- path18.join(dir, `components.${approval.host}.json`),
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 = path18.join(rootDir, "auth.ts");
4746
- if (!existsSync5(authPath)) {
6155
+ const authPath = path20.join(rootDir, "auth.ts");
6156
+ if (!existsSync7(authPath)) {
4747
6157
  return void 0;
4748
6158
  }
4749
- const parentURL = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4750
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4751
- const tsconfig = existsSync5(tsconfigPath) ? tsconfigPath : false;
4752
- const module = await tsImport(pathToFileURL(authPath).href, {
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
- if (!isSidecarAuth(module.default)) {
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 module.default;
6170
+ return defaultExport;
4760
6171
  }
4761
6172
  async function loadRuntimeProxy(rootDir) {
4762
- const proxyPath = path18.join(rootDir, "proxy.ts");
4763
- if (!existsSync5(proxyPath)) {
6173
+ const proxyPath = path20.join(rootDir, "proxy.ts");
6174
+ if (!existsSync7(proxyPath)) {
4764
6175
  return void 0;
4765
6176
  }
4766
- const parentURL = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4767
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4768
- const tsconfig = existsSync5(tsconfigPath) ? tsconfigPath : false;
4769
- const module = await tsImport(pathToFileURL(proxyPath).href, {
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
- if (!isSidecarProxy(module.default)) {
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 module.default;
6188
+ return defaultExport;
4777
6189
  }
4778
6190
  async function loadRuntimeConfig(rootDir) {
4779
- const configPath = path18.join(rootDir, "sidecar.config.ts");
4780
- if (!existsSync5(configPath)) {
6191
+ const configPath = path20.join(rootDir, "sidecar.config.ts");
6192
+ if (!existsSync7(configPath)) {
4781
6193
  return void 0;
4782
6194
  }
4783
- const parentURL = pathToFileURL(configPath).href;
4784
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4785
- const tsconfig = existsSync5(tsconfigPath) ? tsconfigPath : false;
4786
- const module = await tsImport(pathToFileURL(configPath).href, {
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
- if (!module.default || typeof module.default !== "object") {
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 module.default;
6206
+ return defaultExport;
4794
6207
  }
4795
- async function loadRuntimeTools(rootDir, manifest) {
4796
- const parentURL = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4797
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4798
- const tsconfig = existsSync5(tsconfigPath) ? tsconfigPath : false;
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 manifest.tools) {
4801
- const sourcePath = path18.join(rootDir, entry.sourceFile);
4802
- const module = await tsImport(pathToFileURL(sourcePath).href, {
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
- if (!isSidecarTool(module.default)) {
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
- tool: module.default,
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(path18.join(rootDir, outDir, entry.widget.outputFile), "utf8");
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 = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4833
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4834
- const tsconfig = existsSync5(tsconfigPath) ? tsconfigPath : false;
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 = path18.join(rootDir, entry.sourceFile);
4837
- const module = await tsImport(pathToFileURL(sourcePath).href, {
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
- if (!isSidecarResource(module.default)) {
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: module.default
6272
+ resource: defaultExport
4848
6273
  });
4849
6274
  }
4850
6275
  return resources;
4851
6276
  }
4852
6277
  async function loadRuntimePrompts(rootDir, manifest) {
4853
- const parentURL = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4854
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4855
- const tsconfig = existsSync5(tsconfigPath) ? tsconfigPath : false;
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 = path18.join(rootDir, entry.sourceFile);
4859
- const module = await tsImport(pathToFileURL(sourcePath).href, {
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
- if (!isSidecarPrompt(module.default)) {
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: module.default,
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 === pathToFileURL(entryPath).href;
6335
+ return import.meta.url === pathToFileURL3(entryPath).href;
4887
6336
  }
4888
6337
  if (isDirectRun()) {
4889
6338
  main(process.argv).catch((error) => {