@sidecar-ai/cli 0.1.0-alpha.0 → 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,40 +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 { createRequire } from "module";
1740
- import path6 from "path";
2103
+ import path7 from "path";
1741
2104
  async function collectProjectDiagnostics(rootDir, input) {
1742
2105
  const diagnostics = [];
1743
2106
  const tools = Array.isArray(input) ? input : input.tools;
1744
2107
  const resources = Array.isArray(input) ? [] : input.resources ?? [];
1745
2108
  const prompts = Array.isArray(input) ? [] : input.prompts ?? [];
1746
2109
  const config = Array.isArray(input) ? void 0 : input.config;
1747
- const hasAuthConfig = existsSync3(path6.join(rootDir, "auth.ts"));
1748
- const hasOpenAiAppsSdkUi = canResolveOpenAiAppsSdkUi(rootDir);
2110
+ const hasAuthConfig = existsSync4(path7.join(rootDir, "auth.ts"));
1749
2111
  for (const entry of tools) {
1750
- const toolPath = path6.join(rootDir, entry.sourceFile);
2112
+ const toolPath = path7.join(rootDir, entry.sourceFile);
1751
2113
  const toolSource = await readFile2(toolPath, "utf8");
1752
- diagnostics.push(...diagnoseToolSource(rootDir, entry, toolSource, hasAuthConfig));
2114
+ diagnostics.push(...diagnoseToolSource(entry, toolSource, hasAuthConfig));
1753
2115
  if (entry.widget) {
1754
- const widgetPath = path6.join(rootDir, entry.widget.sourceFile);
2116
+ const widgetPath = path7.join(rootDir, entry.widget.sourceFile);
1755
2117
  const widgetSource = await readFile2(widgetPath, "utf8");
1756
- diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource, hasOpenAiAppsSdkUi));
2118
+ diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource));
1757
2119
  }
1758
2120
  }
1759
2121
  for (const entry of resources) {
1760
- const resourcePath = path6.join(rootDir, entry.sourceFile);
2122
+ const resourcePath = path7.join(rootDir, entry.sourceFile);
1761
2123
  const resourceSource = await readFile2(resourcePath, "utf8");
1762
2124
  diagnostics.push(...diagnoseResourceSource(entry, resourceSource));
1763
2125
  }
1764
2126
  for (const entry of prompts) {
1765
- const promptPath = path6.join(rootDir, entry.sourceFile);
2127
+ const promptPath = path7.join(rootDir, entry.sourceFile);
1766
2128
  const promptSource = await readFile2(promptPath, "utf8");
1767
2129
  diagnostics.push(...diagnosePromptSource(entry, promptSource));
1768
2130
  }
@@ -1777,7 +2139,7 @@ function formatDiagnostic(diagnostic) {
1777
2139
  hint: ${diagnostic.hint}` : "";
1778
2140
  return `${location} - ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}${hint}`;
1779
2141
  }
1780
- function diagnoseToolSource(rootDir, entry, source, hasAuthConfig) {
2142
+ function diagnoseToolSource(entry, source, hasAuthConfig) {
1781
2143
  const diagnostics = [];
1782
2144
  const toolLocation = locate(source, "tool({");
1783
2145
  if (!entry.description.trim().startsWith("Use this when")) {
@@ -1854,7 +2216,7 @@ function diagnoseToolSource(rootDir, entry, source, hasAuthConfig) {
1854
2216
  }
1855
2217
  return diagnostics.filter((diagnostic) => !isIgnored(source, diagnostic.code));
1856
2218
  }
1857
- function diagnoseWidgetSource(_rootDir, entry, source, hasOpenAiAppsSdkUi) {
2219
+ function diagnoseWidgetSource(_rootDir, entry, source) {
1858
2220
  const diagnostics = [];
1859
2221
  const widgetFile = entry.widget?.sourceFile;
1860
2222
  if (!widgetFile) {
@@ -1890,16 +2252,6 @@ function diagnoseWidgetSource(_rootDir, entry, source, hasOpenAiAppsSdkUi) {
1890
2252
  hint: "Use @sidecar-ai/native for portable primitives. Keep @sidecar-ai/openai/components for widgets intentionally targeted to ChatGPT."
1891
2253
  });
1892
2254
  }
1893
- if (/@sidecar-ai\/openai\/components/.test(source) && !hasOpenAiAppsSdkUi && !isIgnored(source, "SIDECAR_OPENAI_UI_SDK_MISSING")) {
1894
- diagnostics.push({
1895
- severity: "error",
1896
- code: "SIDECAR_OPENAI_UI_SDK_MISSING",
1897
- message: `Widget for "${entry.id}" imports @sidecar-ai/openai/components but @openai/apps-sdk-ui is not installed.`,
1898
- filePath: widgetFile,
1899
- ...locate(source, "@sidecar-ai/openai/components"),
1900
- hint: "Install it with: npm install @openai/apps-sdk-ui"
1901
- });
1902
- }
1903
2255
  if (entry.widget?.variant !== "anthropic" && /@sidecar-ai\/anthropic\/components/.test(source) && !isIgnored(source, "SIDECAR_ANTHROPIC_COMPONENT_CROSS_HOST")) {
1904
2256
  diagnostics.push({
1905
2257
  severity: "warning",
@@ -1989,14 +2341,6 @@ function hasCompanyKnowledgeShape(schema) {
1989
2341
  const id = properties.id;
1990
2342
  return query?.type === "string" || url?.type === "string" || id?.type === "string";
1991
2343
  }
1992
- function canResolveOpenAiAppsSdkUi(rootDir) {
1993
- try {
1994
- createRequire(path6.join(rootDir, "package.json")).resolve("@openai/apps-sdk-ui/css");
1995
- return true;
1996
- } catch {
1997
- return false;
1998
- }
1999
- }
2000
2344
  function locate(source, needle) {
2001
2345
  const index = Math.max(0, source.indexOf(needle));
2002
2346
  const prefix = source.slice(0, index);
@@ -2012,21 +2356,21 @@ function isIgnored(source, code) {
2012
2356
 
2013
2357
  // packages/compiler/src/generated.ts
2014
2358
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2015
- import path7 from "path";
2359
+ import path8 from "path";
2016
2360
  async function writeGeneratedTypes(rootDir, tools) {
2017
- const generatedDir = path7.join(rootDir, ".sidecar", "generated");
2361
+ const generatedDir = path8.join(rootDir, ".sidecar", "generated");
2018
2362
  await mkdir2(generatedDir, { recursive: true });
2019
2363
  const appCallableTools = tools.filter(isAppCallable);
2020
2364
  const methodNames = uniqueMethodNames(appCallableTools.map((entry) => toIdentifier(entry.id)));
2021
2365
  const imports = appCallableTools.map(
2022
- (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" }))};`
2023
2367
  ).join("\n");
2024
2368
  const ids = appCallableTools.map((entry, index) => ` ${methodNames[index]}: ${JSON.stringify(entry.id)}`).join(",\n");
2025
2369
  const toolTypes = appCallableTools.map(
2026
- (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}>>;`
2027
2371
  ).join("\n");
2028
2372
  await writeFile2(
2029
- path7.join(generatedDir, "tools.ts"),
2373
+ path8.join(generatedDir, "tools.ts"),
2030
2374
  `/**
2031
2375
  * Generated Sidecar widget tool client.
2032
2376
  *
@@ -2074,19 +2418,15 @@ function uniqueMethodNames(names) {
2074
2418
  });
2075
2419
  }
2076
2420
 
2077
- // packages/compiler/src/plugins.ts
2078
- import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
2079
- import path14 from "path";
2080
-
2081
2421
  // packages/compiler/src/identity.ts
2082
2422
  import { readFile as readFile3 } from "fs/promises";
2083
- import path8 from "path";
2423
+ import path9 from "path";
2084
2424
  async function loadProjectIdentity(rootDir) {
2085
- const packageJson = await readOptionalJson(path8.join(rootDir, "package.json"));
2425
+ const packageJson = await readOptionalJson(path9.join(rootDir, "package.json"));
2086
2426
  const configText = await readOptionalText(
2087
- path8.join(rootDir, "sidecar.config.ts")
2427
+ path9.join(rootDir, "sidecar.config.ts")
2088
2428
  );
2089
- const name = readConfigString(configText, "name") ?? packageJson?.name ?? path8.basename(rootDir);
2429
+ const name = readConfigString(configText, "name") ?? packageJson?.name ?? path9.basename(rootDir);
2090
2430
  const version = readConfigString(configText, "version") ?? packageJson?.version ?? "0.0.0-dev";
2091
2431
  const description = readConfigString(configText, "description") ?? packageJson?.description ?? `${name} Sidecar app.`;
2092
2432
  return {
@@ -2118,30 +2458,34 @@ function readConfigString(configText, key) {
2118
2458
  return match?.[1];
2119
2459
  }
2120
2460
 
2461
+ // packages/compiler/src/plugins.ts
2462
+ import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
2463
+ import path15 from "path";
2464
+
2121
2465
  // packages/compiler/src/plugin-components/agents.ts
2122
2466
  import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
2123
- import path9 from "path";
2467
+ import path10 from "path";
2124
2468
  async function emitClaudeAgents(rootDir, destination) {
2125
- const source = path9.join(rootDir, "agents");
2469
+ const source = path10.join(rootDir, "agents");
2126
2470
  if (!existsSyncSafe(source)) {
2127
2471
  return;
2128
2472
  }
2129
2473
  const entries = await readdir2(source, { withFileTypes: true });
2130
2474
  const agentDirs = entries.filter(
2131
- (entry) => entry.isDirectory() && existsSyncSafe(path9.join(source, entry.name, "agent.ts"))
2475
+ (entry) => entry.isDirectory() && existsSyncSafe(path10.join(source, entry.name, "agent.ts"))
2132
2476
  );
2133
2477
  if (!agentDirs.length) {
2134
2478
  return;
2135
2479
  }
2136
2480
  await mkdir3(destination, { recursive: true });
2137
2481
  for (const entry of agentDirs) {
2138
- 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");
2139
2483
  const markdown = parseClaudeAgent(
2140
2484
  sourceText,
2141
2485
  entry.name
2142
2486
  );
2143
2487
  const agentName = readObjectString(sourceText, "name") ?? entry.name;
2144
- await writeFile3(path9.join(destination, `${safeFileStem(agentName)}.md`), markdown);
2488
+ await writeFile3(path10.join(destination, `${safeFileStem(agentName)}.md`), markdown);
2145
2489
  }
2146
2490
  }
2147
2491
  function parseClaudeAgent(source, fallbackName) {
@@ -2170,41 +2514,41 @@ ${prompt.trim()}
2170
2514
 
2171
2515
  // packages/compiler/src/plugin-components/commands.ts
2172
2516
  import { cp, lstat, mkdir as mkdir4, readdir as readdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
2173
- import path10 from "path";
2517
+ import path11 from "path";
2174
2518
  async function copyCommands(rootDir, destination) {
2175
- const source = path10.join(rootDir, "commands");
2519
+ const source = path11.join(rootDir, "commands");
2176
2520
  if (!existsSyncSafe(source)) {
2177
2521
  return;
2178
2522
  }
2179
2523
  await mkdir4(destination, { recursive: true });
2180
2524
  const entries = await readdir3(source, { withFileTypes: true });
2181
2525
  for (const entry of entries) {
2182
- const sourcePath = path10.join(source, entry.name);
2526
+ const sourcePath = path11.join(source, entry.name);
2183
2527
  if (entry.isFile() && entry.name.endsWith(".md")) {
2184
2528
  if (await safeCommandCopyFilter(sourcePath)) {
2185
- 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"));
2186
2530
  }
2187
2531
  continue;
2188
2532
  }
2189
2533
  if (!entry.isDirectory()) {
2190
2534
  continue;
2191
2535
  }
2192
- const dynamicCommand = path10.join(sourcePath, "command.ts");
2536
+ const dynamicCommand = path11.join(sourcePath, "command.ts");
2193
2537
  if (existsSyncSafe(dynamicCommand)) {
2194
2538
  const sourceText = await readFile5(dynamicCommand, "utf8");
2195
2539
  const markdown = parseDynamicCommand(sourceText, entry.name);
2196
2540
  const commandName = readObjectString(sourceText, "name") ?? entry.name;
2197
- await writeFile4(path10.join(destination, `${safeFileStem(commandName)}.md`), markdown);
2541
+ await writeFile4(path11.join(destination, `${safeFileStem(commandName)}.md`), markdown);
2198
2542
  continue;
2199
2543
  }
2200
- await cp(sourcePath, path10.join(destination, entry.name), {
2544
+ await cp(sourcePath, path11.join(destination, entry.name), {
2201
2545
  recursive: true,
2202
2546
  filter: safeCommandCopyFilter
2203
2547
  });
2204
2548
  }
2205
2549
  }
2206
2550
  async function safeCommandCopyFilter(sourcePath) {
2207
- const basename = path10.basename(sourcePath);
2551
+ const basename = path11.basename(sourcePath);
2208
2552
  if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
2209
2553
  return false;
2210
2554
  }
@@ -2233,32 +2577,32 @@ ${Object.entries(frontmatter).map(([key, value]) => `${key}: ${yamlScalar(value)
2233
2577
 
2234
2578
  // packages/compiler/src/plugin-components/hooks.ts
2235
2579
  import { mkdir as mkdir5, readdir as readdir4, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
2236
- import path11 from "path";
2580
+ import path12 from "path";
2237
2581
  import {
2238
2582
  Node as Node6,
2239
2583
  Project as Project2,
2240
2584
  SyntaxKind as SyntaxKind4
2241
2585
  } from "ts-morph";
2242
2586
  async function copyHooks(rootDir, destination) {
2243
- const source = path11.join(rootDir, "hooks");
2587
+ const source = path12.join(rootDir, "hooks");
2244
2588
  if (!existsSyncSafe(source)) {
2245
2589
  return;
2246
2590
  }
2247
2591
  const entries = await readdir4(source, { withFileTypes: true });
2248
2592
  const hookDirs = entries.filter(
2249
- (entry) => entry.isDirectory() && existsSyncSafe(path11.join(source, entry.name, "hook.ts"))
2593
+ (entry) => entry.isDirectory() && existsSyncSafe(path12.join(source, entry.name, "hook.ts"))
2250
2594
  );
2251
2595
  if (!hookDirs.length) {
2252
2596
  return;
2253
2597
  }
2254
2598
  const config = {};
2255
2599
  for (const entry of hookDirs) {
2256
- const filePath = path11.join(source, entry.name, "hook.ts");
2600
+ const filePath = path12.join(source, entry.name, "hook.ts");
2257
2601
  mergeHooks(config, parseHookFile(await readFile6(filePath, "utf8"), entry.name));
2258
2602
  }
2259
2603
  await mkdir5(destination, { recursive: true });
2260
2604
  await writeFile5(
2261
- path11.join(destination, "hooks.json"),
2605
+ path12.join(destination, "hooks.json"),
2262
2606
  `${JSON.stringify(config, null, 2)}
2263
2607
  `
2264
2608
  );
@@ -2285,7 +2629,7 @@ function parseHookFile(source, fallbackName) {
2285
2629
  throw new Error(`hooks/${fallbackName}/hook.ts must default-export hook({ ... }) or hooks({ ... }).`);
2286
2630
  }
2287
2631
  function parseHookDefinition(definition, fallbackName) {
2288
- const event = readStringProperty3(definition, "event");
2632
+ const event = readStringProperty4(definition, "event");
2289
2633
  if (!event) {
2290
2634
  throw new Error(`hooks/${fallbackName}/hook.ts must declare an event string.`);
2291
2635
  }
@@ -2295,7 +2639,7 @@ function parseHookDefinition(definition, fallbackName) {
2295
2639
  }
2296
2640
  return {
2297
2641
  event,
2298
- matcher: readStringProperty3(definition, "matcher"),
2642
+ matcher: readStringProperty4(definition, "matcher"),
2299
2643
  run: hooks
2300
2644
  };
2301
2645
  }
@@ -2315,7 +2659,7 @@ function parseHooksDefinition(definition, fallbackName) {
2315
2659
  throw new Error(`hooks/${fallbackName}/hook.ts event "${event}" entries must be objects.`);
2316
2660
  }
2317
2661
  return stripUndefined2({
2318
- matcher: readStringProperty3(entry, "matcher"),
2662
+ matcher: readStringProperty4(entry, "matcher"),
2319
2663
  hooks: readHookArray(entry, "run", fallbackName)
2320
2664
  });
2321
2665
  });
@@ -2338,7 +2682,7 @@ function parseHookHandlers(handlers, fallbackName) {
2338
2682
  }
2339
2683
  function parseHookHandler(handler, fallbackName) {
2340
2684
  if (Node6.isObjectLiteralExpression(handler)) {
2341
- const type = readStringProperty3(handler, "type");
2685
+ const type = readStringProperty4(handler, "type");
2342
2686
  if (type !== "command" && type !== "http") {
2343
2687
  throw new Error(`hooks/${fallbackName}/hook.ts hook handlers need type "command" or "http".`);
2344
2688
  }
@@ -2388,7 +2732,7 @@ function objectLiteralToRecord(object) {
2388
2732
  }
2389
2733
  return stripUndefined2(record);
2390
2734
  }
2391
- function readStringProperty3(object, propertyName) {
2735
+ function readStringProperty4(object, propertyName) {
2392
2736
  const property = object.getProperty(propertyName);
2393
2737
  if (!property || !Node6.isPropertyAssignment(property)) {
2394
2738
  return void 0;
@@ -2432,7 +2776,7 @@ function mergeHooks(target, source) {
2432
2776
 
2433
2777
  // packages/compiler/src/plugin-components/passthrough.ts
2434
2778
  import { cp as cp2, lstat as lstat2 } from "fs/promises";
2435
- import path12 from "path";
2779
+ import path13 from "path";
2436
2780
  async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
2437
2781
  for (const directory of [
2438
2782
  "assets",
@@ -2441,18 +2785,18 @@ async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
2441
2785
  "output-styles",
2442
2786
  "themes"
2443
2787
  ]) {
2444
- const source = path12.join(rootDir, directory);
2788
+ const source = path13.join(rootDir, directory);
2445
2789
  if (!existsSyncSafe(source)) {
2446
2790
  continue;
2447
2791
  }
2448
- await cp2(source, path12.join(pluginDir, directory), {
2792
+ await cp2(source, path13.join(pluginDir, directory), {
2449
2793
  recursive: true,
2450
2794
  filter: safePluginCopyFilter
2451
2795
  });
2452
2796
  }
2453
2797
  }
2454
2798
  async function safePluginCopyFilter(sourcePath) {
2455
- const basename = path12.basename(sourcePath);
2799
+ const basename = path13.basename(sourcePath);
2456
2800
  if (basename === "node_modules" || basename === ".git" || basename === ".env" || basename.startsWith(".env.")) {
2457
2801
  return false;
2458
2802
  }
@@ -2461,11 +2805,11 @@ async function safePluginCopyFilter(sourcePath) {
2461
2805
  }
2462
2806
 
2463
2807
  // packages/compiler/src/plugin-components/skills.ts
2464
- import { existsSync as existsSync4 } from "fs";
2808
+ import { existsSync as existsSync5 } from "fs";
2465
2809
  import { cp as cp3, lstat as lstat3, mkdir as mkdir6, readdir as readdir5, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
2466
- import path13 from "path";
2810
+ import path14 from "path";
2467
2811
  async function copySkills(rootDir, destination) {
2468
- const source = path13.join(rootDir, "skills");
2812
+ const source = path14.join(rootDir, "skills");
2469
2813
  if (!existsSyncSafe(source)) {
2470
2814
  return;
2471
2815
  }
@@ -2475,27 +2819,27 @@ async function copySkills(rootDir, destination) {
2475
2819
  if (!entry.isDirectory()) {
2476
2820
  continue;
2477
2821
  }
2478
- const sourceDir = path13.join(source, entry.name);
2479
- const destinationDir = path13.join(destination, entry.name);
2480
- const staticSkill = path13.join(sourceDir, "SKILL.md");
2481
- 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");
2482
2826
  await mkdir6(destinationDir, { recursive: true });
2483
- if (existsSync4(staticSkill)) {
2827
+ if (existsSync5(staticSkill)) {
2484
2828
  await cp3(sourceDir, destinationDir, {
2485
2829
  recursive: true,
2486
- 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)
2487
2831
  });
2488
- } else if (existsSync4(dynamicSkill)) {
2832
+ } else if (existsSync5(dynamicSkill)) {
2489
2833
  const generated = parseDynamicSkill(
2490
2834
  await readFile7(dynamicSkill, "utf8"),
2491
2835
  entry.name
2492
2836
  );
2493
- await writeFile6(path13.join(destinationDir, "SKILL.md"), generated);
2837
+ await writeFile6(path14.join(destinationDir, "SKILL.md"), generated);
2494
2838
  }
2495
2839
  }
2496
2840
  }
2497
2841
  async function safeSkillCopyFilter(sourcePath) {
2498
- const basename = path13.basename(sourcePath);
2842
+ const basename = path14.basename(sourcePath);
2499
2843
  if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
2500
2844
  return false;
2501
2845
  }
@@ -2521,25 +2865,25 @@ async function buildPluginPackages(rootDir, outRoot, manifest) {
2521
2865
  await buildClaudePlugin(rootDir, outRoot, identity, manifest);
2522
2866
  }
2523
2867
  async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
2524
- const pluginDir = path14.join(outRoot, "claude-plugin");
2525
- await mkdir7(path14.join(pluginDir, ".claude-plugin"), { recursive: true });
2526
- await copySkills(rootDir, path14.join(pluginDir, "skills"));
2527
- await copyCommands(rootDir, path14.join(pluginDir, "commands"));
2528
- await copyHooks(rootDir, path14.join(pluginDir, "hooks"));
2529
- 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"));
2530
2874
  await copyClaudePassthroughDirectories(rootDir, pluginDir);
2531
- await writeJson(path14.join(pluginDir, ".claude-plugin", "plugin.json"), {
2875
+ await writeJson(path15.join(pluginDir, ".claude-plugin", "plugin.json"), {
2532
2876
  name: identity.slug,
2533
2877
  version: identity.version,
2534
2878
  description: identity.description,
2535
2879
  displayName: identity.name,
2536
2880
  installationPreference: "available"
2537
2881
  });
2538
- await writeJson(path14.join(pluginDir, "version.json"), {
2882
+ await writeJson(path15.join(pluginDir, "version.json"), {
2539
2883
  version: identity.version,
2540
2884
  generatedBy: "sidecar"
2541
2885
  });
2542
- await writeJson(path14.join(pluginDir, ".mcp.json"), {
2886
+ await writeJson(path15.join(pluginDir, ".mcp.json"), {
2543
2887
  mcpServers: {
2544
2888
  [identity.slug]: {
2545
2889
  type: "http",
@@ -2547,7 +2891,7 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
2547
2891
  }
2548
2892
  }
2549
2893
  });
2550
- await writeJson(path14.join(pluginDir, "settings.json"), {
2894
+ await writeJson(path15.join(pluginDir, "settings.json"), {
2551
2895
  sidecar: {
2552
2896
  tools: manifest.tools.map((entry) => entry.id),
2553
2897
  resources: manifest.resources.map((entry) => entry.uri),
@@ -2555,21 +2899,21 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
2555
2899
  }
2556
2900
  });
2557
2901
  await writeFile7(
2558
- path14.join(pluginDir, "README.md"),
2902
+ path15.join(pluginDir, "README.md"),
2559
2903
  `# ${identity.name} Claude Plugin
2560
2904
 
2561
2905
  This package was generated by Sidecar.
2562
2906
 
2563
2907
  Set \`SIDECAR_MCP_URL\` to the hosted MCP endpoint for this app.
2564
2908
 
2565
- 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.
2566
2910
 
2567
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.
2568
2912
  `
2569
2913
  );
2570
2914
  }
2571
2915
  async function writeJson(filePath, value) {
2572
- await mkdir7(path14.dirname(filePath), { recursive: true });
2916
+ await mkdir7(path15.dirname(filePath), { recursive: true });
2573
2917
  await writeFile7(
2574
2918
  filePath,
2575
2919
  `${JSON.stringify(stripUndefined2(value), null, 2)}
@@ -2579,13 +2923,13 @@ async function writeJson(filePath, value) {
2579
2923
 
2580
2924
  // packages/compiler/src/prompts.ts
2581
2925
  import { readdir as readdir6 } from "fs/promises";
2582
- import path15 from "path";
2926
+ import path16 from "path";
2583
2927
  import {
2584
2928
  Node as Node7,
2585
2929
  SyntaxKind as SyntaxKind5
2586
2930
  } from "ts-morph";
2587
2931
  async function analyzeProjectPrompts(rootDir) {
2588
- const promptFiles = await findPromptFiles(path15.join(rootDir, "prompts"));
2932
+ const promptFiles = await findPromptFiles(path16.join(rootDir, "prompts"));
2589
2933
  if (promptFiles.length === 0) {
2590
2934
  return [];
2591
2935
  }
@@ -2597,7 +2941,7 @@ async function analyzeProjectPrompts(rootDir) {
2597
2941
  function analyzePromptFile(sourceFile, rootDir) {
2598
2942
  const definition = findPromptDefinition(sourceFile);
2599
2943
  const absoluteFile = sourceFile.getFilePath();
2600
- const directory = path15.basename(path15.dirname(absoluteFile));
2944
+ const directory = path16.basename(path16.dirname(absoluteFile));
2601
2945
  const name = getOptionalStringProperty2(definition, "name") ?? safePathSegment(directory);
2602
2946
  const title = getRequiredStringProperty2(definition, "title", sourceFile);
2603
2947
  const description = getOptionalStringProperty2(definition, "description");
@@ -2613,7 +2957,7 @@ function analyzePromptFile(sourceFile, rootDir) {
2613
2957
  throw new CompilerError(sourceFile, "prompt({ ... }) must include a run method.");
2614
2958
  }
2615
2959
  return {
2616
- sourceFile: path15.relative(rootDir, absoluteFile),
2960
+ sourceFile: path16.relative(rootDir, absoluteFile),
2617
2961
  directory,
2618
2962
  name,
2619
2963
  title,
@@ -2632,7 +2976,7 @@ async function findPromptFiles(promptsDir) {
2632
2976
  if (!entry.isDirectory()) {
2633
2977
  continue;
2634
2978
  }
2635
- const filePath = path15.join(promptsDir, entry.name, "prompt.ts");
2979
+ const filePath = path16.join(promptsDir, entry.name, "prompt.ts");
2636
2980
  if (existsSyncSafe(filePath)) {
2637
2981
  files.push(filePath);
2638
2982
  }
@@ -2754,7 +3098,7 @@ function readIcons(definition) {
2754
3098
  return [{
2755
3099
  src,
2756
3100
  mimeType: getOptionalStringProperty2(object, "mimeType"),
2757
- sizes: readStringArrayProperty3(object, "sizes")
3101
+ sizes: readStringArrayProperty4(object, "sizes")
2758
3102
  }];
2759
3103
  });
2760
3104
  return icons.length ? icons : void 0;
@@ -2767,7 +3111,7 @@ function readObjectProperty4(definition, propertyName) {
2767
3111
  const initializer = unwrapExpression(property.getInitializer());
2768
3112
  return initializer && Node7.isObjectLiteralExpression(initializer) ? initializer : void 0;
2769
3113
  }
2770
- function readStringArrayProperty3(definition, propertyName) {
3114
+ function readStringArrayProperty4(definition, propertyName) {
2771
3115
  const property = definition.getProperty(propertyName);
2772
3116
  if (!property || !Node7.isPropertyAssignment(property)) {
2773
3117
  return void 0;
@@ -2781,13 +3125,13 @@ function readStringArrayProperty3(definition, propertyName) {
2781
3125
 
2782
3126
  // packages/compiler/src/resources.ts
2783
3127
  import { readdir as readdir7 } from "fs/promises";
2784
- import path16 from "path";
3128
+ import path17 from "path";
2785
3129
  import {
2786
3130
  Node as Node8,
2787
3131
  SyntaxKind as SyntaxKind6
2788
3132
  } from "ts-morph";
2789
3133
  async function analyzeProjectResources(rootDir) {
2790
- const resourceFiles = await findResourceFiles(path16.join(rootDir, "resources"));
3134
+ const resourceFiles = await findResourceFiles(path17.join(rootDir, "resources"));
2791
3135
  if (resourceFiles.length === 0) {
2792
3136
  return [];
2793
3137
  }
@@ -2799,7 +3143,7 @@ async function analyzeProjectResources(rootDir) {
2799
3143
  function analyzeResourceFile(sourceFile, rootDir) {
2800
3144
  const definition = findResourceDefinition(sourceFile);
2801
3145
  const absoluteFile = sourceFile.getFilePath();
2802
- const directory = path16.basename(path16.dirname(absoluteFile));
3146
+ const directory = path17.basename(path17.dirname(absoluteFile));
2803
3147
  const defaultUri = `sidecar://resources/${safePathSegment(directory)}`;
2804
3148
  const name = getRequiredStringProperty3(definition, "name", sourceFile);
2805
3149
  const uri = getOptionalStringProperty3(definition, "uri") ?? defaultUri;
@@ -2817,7 +3161,7 @@ function analyzeResourceFile(sourceFile, rootDir) {
2817
3161
  throw new CompilerError(sourceFile, "resource({ ... }) must include a read method.");
2818
3162
  }
2819
3163
  return {
2820
- sourceFile: path16.relative(rootDir, absoluteFile),
3164
+ sourceFile: path17.relative(rootDir, absoluteFile),
2821
3165
  directory,
2822
3166
  uri,
2823
3167
  name,
@@ -2840,7 +3184,7 @@ async function findResourceFiles(resourcesDir) {
2840
3184
  if (!entry.isDirectory()) {
2841
3185
  continue;
2842
3186
  }
2843
- const filePath = path16.join(resourcesDir, entry.name, "resource.ts");
3187
+ const filePath = path17.join(resourcesDir, entry.name, "resource.ts");
2844
3188
  if (existsSyncSafe(filePath)) {
2845
3189
  files.push(filePath);
2846
3190
  }
@@ -2919,7 +3263,7 @@ function readAnnotations2(definition) {
2919
3263
  return void 0;
2920
3264
  }
2921
3265
  const annotations = {};
2922
- const audience = readStringArrayProperty4(initializer, "audience")?.filter((value) => value === "user" || value === "assistant");
3266
+ const audience = readStringArrayProperty5(initializer, "audience")?.filter((value) => value === "user" || value === "assistant");
2923
3267
  const priority = getOptionalNumberProperty(initializer, "priority");
2924
3268
  const lastModified = getOptionalStringProperty3(initializer, "lastModified");
2925
3269
  if (audience?.length) annotations.audience = audience;
@@ -2948,7 +3292,7 @@ function readIcons2(definition) {
2948
3292
  return [{
2949
3293
  src,
2950
3294
  mimeType: getOptionalStringProperty3(object, "mimeType"),
2951
- sizes: readStringArrayProperty4(object, "sizes")
3295
+ sizes: readStringArrayProperty5(object, "sizes")
2952
3296
  }];
2953
3297
  });
2954
3298
  return icons.length ? icons : void 0;
@@ -2961,7 +3305,7 @@ function readObjectProperty5(definition, propertyName) {
2961
3305
  const initializer = unwrapExpression(property.getInitializer());
2962
3306
  return initializer && Node8.isObjectLiteralExpression(initializer) ? initializer : void 0;
2963
3307
  }
2964
- function readStringArrayProperty4(definition, propertyName) {
3308
+ function readStringArrayProperty5(definition, propertyName) {
2965
3309
  const property = definition.getProperty(propertyName);
2966
3310
  if (!property || !Node8.isPropertyAssignment(property)) {
2967
3311
  return void 0;
@@ -2973,15 +3317,332 @@ function readStringArrayProperty4(definition, propertyName) {
2973
3317
  return initializer.getElements().filter(Node8.isStringLiteral).map((element) => element.getLiteralText());
2974
3318
  }
2975
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
+
2976
3634
  // packages/compiler/src/build.ts
2977
3635
  async function buildProject(options) {
2978
- const rootDir = path17.resolve(options.rootDir);
2979
- const target = options.target ?? "mcp";
3636
+ const rootDir = path19.resolve(options.rootDir);
2980
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;
2981
3641
  const tools = await analyzeProjectTools(rootDir, { target });
2982
3642
  const resources = await analyzeProjectResources(rootDir);
2983
3643
  const resourceTemplates = [];
2984
3644
  const prompts = await analyzeProjectPrompts(rootDir);
3645
+ const identity = await loadProjectIdentity(rootDir);
2985
3646
  const diagnostics = await collectProjectDiagnostics(rootDir, {
2986
3647
  tools,
2987
3648
  resources,
@@ -2992,11 +3653,13 @@ async function buildProject(options) {
2992
3653
  if (errors.length) {
2993
3654
  throw new Error(errors.map(formatDiagnostic).join("\n"));
2994
3655
  }
2995
- const outDir = resolveInsideRoot(rootDir, options.outDir ?? "out/mcp");
2996
- 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);
2997
3659
  const manifest = {
2998
3660
  version: 1,
2999
3661
  target,
3662
+ host,
3000
3663
  rootDir: ".",
3001
3664
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3002
3665
  config,
@@ -3006,23 +3669,47 @@ async function buildProject(options) {
3006
3669
  prompts,
3007
3670
  diagnostics
3008
3671
  };
3009
- await mkdir8(outDir, { recursive: true });
3010
- await writeFile8(
3011
- path17.join(outDir, "manifest.sidecar.json"),
3672
+ await mkdir9(runtimeOutDir, { recursive: true });
3673
+ await writeFile9(
3674
+ path19.join(runtimeOutDir, "manifest.sidecar.json"),
3012
3675
  `${JSON.stringify(manifest, null, 2)}
3013
3676
  `
3014
3677
  );
3015
- await writeFile8(path17.join(outDir, "README.md"), renderMcpReadme(manifest));
3678
+ await writeFile9(path19.join(runtimeOutDir, "README.md"), renderMcpReadme(manifest));
3016
3679
  await writeGeneratedTypes(rootDir, tools);
3017
- if (options.plugins) {
3018
- 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);
3019
3685
  }
3020
3686
  return manifest;
3021
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
+ }
3022
3709
  function resolveInsideRoot(rootDir, output) {
3023
- const resolved = path17.resolve(rootDir, output);
3024
- const relative = path17.relative(rootDir, resolved);
3025
- 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)) {
3026
3713
  throw new Error(`Build output must stay inside the project root: ${output}`);
3027
3714
  }
3028
3715
  return resolved;
@@ -3047,9 +3734,35 @@ ${resources || "No resources detected."}
3047
3734
 
3048
3735
  ${prompts || "No prompts detected."}
3049
3736
 
3737
+ ${manifest.host === "vercel" ? renderVercelReadmeSection() : renderNodeReadmeSection()}
3738
+
3050
3739
  ## Local HTTPS Testing
3051
3740
 
3052
- 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.
3053
3766
  `;
3054
3767
  }
3055
3768
 
@@ -3062,7 +3775,7 @@ import { randomUUID } from "crypto";
3062
3775
  var proxyBrand = /* @__PURE__ */ Symbol.for("sidecar.proxy");
3063
3776
  function isSidecarProxy(value) {
3064
3777
  return Boolean(
3065
- value && typeof value === "object" && value[proxyBrand] === true
3778
+ value && typeof value === "object" && (value[proxyBrand] === true || value.kind === "sidecar.proxy")
3066
3779
  );
3067
3780
  }
3068
3781
  async function runProxy(proxyConfig, request) {
@@ -3075,8 +3788,90 @@ async function runProxy(proxyConfig, request) {
3075
3788
  return void 0;
3076
3789
  }
3077
3790
 
3078
- // packages/server/src/index.ts
3791
+ // packages/server/src/sse.ts
3079
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";
3080
3875
  var SIDECAR_MCP_PROTOCOL_VERSION = "2025-11-25";
3081
3876
  var SidecarMcpServer = class {
3082
3877
  constructor(options) {
@@ -3120,6 +3915,8 @@ var SidecarMcpServer = class {
3120
3915
  resources = /* @__PURE__ */ new Map();
3121
3916
  prompts = /* @__PURE__ */ new Map();
3122
3917
  resourceTemplates;
3918
+ activeRequests = /* @__PURE__ */ new Map();
3919
+ subscribedResourceUris = /* @__PURE__ */ new Set();
3123
3920
  /** Returns all tool descriptors exposed through `tools/list`. */
3124
3921
  descriptors() {
3125
3922
  return [...this.tools.values()].map((loaded) => loaded.descriptor);
@@ -3144,13 +3941,16 @@ var SidecarMcpServer = class {
3144
3941
  const authorizedContext = { ...context, authSession };
3145
3942
  const result = await this.dispatch(request, authorizedContext);
3146
3943
  return {
3147
- jsonrpc: JSONRPC_VERSION,
3944
+ jsonrpc: JSONRPC_VERSION2,
3148
3945
  id: request.id,
3149
3946
  result
3150
3947
  };
3151
3948
  } catch (error) {
3949
+ if (error instanceof RequestCancelledError) {
3950
+ return void 0;
3951
+ }
3152
3952
  return {
3153
- jsonrpc: JSONRPC_VERSION,
3953
+ jsonrpc: JSONRPC_VERSION2,
3154
3954
  id: request.id,
3155
3955
  error: normalizeError(error)
3156
3956
  };
@@ -3168,6 +3968,8 @@ var SidecarMcpServer = class {
3168
3968
  version: this.options.version ?? "0.0.0-dev"
3169
3969
  }
3170
3970
  };
3971
+ case "ping":
3972
+ return {};
3171
3973
  case "tools/list":
3172
3974
  return this.listTools(request, context);
3173
3975
  case "tools/call":
@@ -3262,7 +4064,7 @@ var SidecarMcpServer = class {
3262
4064
  /** Returns the server-chosen page size for all built-in list pagination. */
3263
4065
  pageSize() {
3264
4066
  const configured = this.options.pagination?.pageSize;
3265
- return configured && configured > 0 ? Math.floor(configured) : 10;
4067
+ return configured && configured > 0 ? Math.floor(configured) : 50;
3266
4068
  }
3267
4069
  /** Executes a tool after request-level and tool-level auth checks. */
3268
4070
  async callTool(request, context) {
@@ -3280,29 +4082,39 @@ var SidecarMcpServer = class {
3280
4082
  if (authSession !== void 0) {
3281
4083
  ctx.auth = authSession;
3282
4084
  }
4085
+ ctx.notify = this.createNotifications(context, request);
3283
4086
  const controller = new AbortController();
4087
+ if (request.id !== null && request.id !== void 0) {
4088
+ this.activeRequests.set(request.id, controller);
4089
+ }
3284
4090
  ctx.request = {
3285
4091
  ...ctx.request,
3286
4092
  signal: controller.signal
3287
4093
  };
3288
- const args = loaded.descriptorProvided ? validateAgainstSchema(
3289
- loaded.descriptor.inputSchema,
3290
- params?.arguments ?? {},
3291
- `Invalid parameters for tool "${name}".`
3292
- ) : params?.arguments ?? {};
3293
- const result = await withTimeout(
3294
- executeTool(loaded.tool, args, ctx),
3295
- this.options.toolTimeoutMs,
3296
- controller
3297
- );
3298
- if (loaded.descriptorProvided) {
3299
- validateAgainstSchema(
3300
- loaded.descriptor.outputSchema,
3301
- result.structuredContent,
3302
- `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
3303
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
+ }
3304
4117
  }
3305
- return result;
3306
4118
  }
3307
4119
  /** Authenticates the whole HTTP MCP request when auth.ts is configured. */
3308
4120
  async authorizeEndpoint(context) {
@@ -3354,12 +4166,14 @@ var SidecarMcpServer = class {
3354
4166
  if (context.authSession !== void 0) {
3355
4167
  toolContext.auth = context.authSession;
3356
4168
  }
4169
+ toolContext.notify = this.createNotifications(context, request);
3357
4170
  return executeResource(resource.resource, toResourceContext(toolContext), {
3358
4171
  uri,
3359
4172
  mimeType: resource.descriptor.mimeType
3360
4173
  });
3361
4174
  }
3362
4175
  return {
4176
+ _meta: resource._meta,
3363
4177
  contents: [
3364
4178
  {
3365
4179
  uri,
@@ -3379,6 +4193,7 @@ var SidecarMcpServer = class {
3379
4193
  if (!this.resources.has(uri)) {
3380
4194
  throw new JsonRpcError(-32002, `Resource not found: "${uri}".`, { uri });
3381
4195
  }
4196
+ this.subscribedResourceUris.add(uri);
3382
4197
  return {};
3383
4198
  }
3384
4199
  /** Accepts a resource unsubscription when server-level support is enabled. */
@@ -3390,6 +4205,7 @@ var SidecarMcpServer = class {
3390
4205
  if (!this.resources.has(uri)) {
3391
4206
  throw new JsonRpcError(-32002, `Resource not found: "${uri}".`, { uri });
3392
4207
  }
4208
+ this.subscribedResourceUris.delete(uri);
3393
4209
  return {};
3394
4210
  }
3395
4211
  /** Renders one named prompt with validated arguments. */
@@ -3407,20 +4223,61 @@ var SidecarMcpServer = class {
3407
4223
  if (context.authSession !== void 0) {
3408
4224
  toolContext.auth = context.authSession;
3409
4225
  }
4226
+ toolContext.notify = this.createNotifications(context, request);
3410
4227
  return executePrompt(loaded.prompt, params?.arguments ?? {}, toPromptContext(toolContext));
3411
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
+ }
3412
4243
  /** Accepts notifications without side effects for client compatibility. */
3413
- 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));
3414
4262
  }
3415
4263
  };
3416
4264
  function createSidecarMcpServer(options) {
3417
4265
  return new SidecarMcpServer(options);
3418
4266
  }
3419
4267
  function createSidecarHttpServer(options) {
3420
- const mcp = createSidecarMcpServer(options);
4268
+ return createServer(createSidecarHttpHandler(options));
4269
+ }
4270
+ function createSidecarHttpHandler(options) {
3421
4271
  const endpoint = options.path ?? "/mcp";
3422
4272
  const maxBodyBytes = options.maxBodyBytes ?? 1e6;
3423
- 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
+ });
3424
4281
  if (isRejectedOrigin(request, options.allowedOrigins)) {
3425
4282
  response.writeHead(403, { "content-type": "application/json" });
3426
4283
  response.end(JSON.stringify({ error: "forbidden_origin" }));
@@ -3432,23 +4289,59 @@ function createSidecarHttpServer(options) {
3432
4289
  response.end(proxyResult.body ?? "");
3433
4290
  return;
3434
4291
  }
3435
- const pathname = request.url?.split("?")[0];
3436
4292
  if (options.auth && request.method === "GET" && isProtectedResourceMetadataPath(pathname, endpoint)) {
3437
4293
  response.writeHead(200, { "content-type": "application/json" });
3438
4294
  response.end(JSON.stringify(options.auth.metadata()));
3439
4295
  return;
3440
4296
  }
4297
+ if (options.auth && request.method === "GET" && pathname === "/.well-known/oauth-authorization-server") {
4298
+ await proxyAuthorizationServerMetadata(options.auth, response);
4299
+ return;
4300
+ }
3441
4301
  if (request.method === "GET" && pathname === endpoint) {
3442
- response.writeHead(405, { "content-type": "application/json" });
3443
- 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
+ }
3444
4324
  return;
3445
4325
  }
3446
- if (request.method !== "POST" || pathname !== endpoint) {
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" }));
4332
+ return;
4333
+ }
4334
+ if (pathname !== endpoint) {
3447
4335
  response.writeHead(404, { "content-type": "application/json" });
3448
4336
  response.end(JSON.stringify({ error: "not_found" }));
3449
4337
  return;
3450
4338
  }
3451
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
+ }
3452
4345
  validateProtocolVersion(request);
3453
4346
  validatePostHeaders(request);
3454
4347
  const body = await readJson(request, maxBodyBytes);
@@ -3461,14 +4354,23 @@ function createSidecarHttpServer(options) {
3461
4354
  return;
3462
4355
  }
3463
4356
  const rpcRequest = assertJsonRpcRequest(body);
3464
- const fetchRequest = toFetchRequest(request, options.publicUrl ?? options.auth?.resource);
3465
- const authSession = options.auth ? await authorizeHttpRequest(options.auth, fetchRequest, response) : void 0;
3466
- 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();
3467
4368
  return;
3468
4369
  }
3469
4370
  const payload = await mcp.handle(rpcRequest, {
3470
4371
  request: fetchRequest,
3471
- authSession
4372
+ authSession,
4373
+ notifications: streamHub
3472
4374
  }) ?? null;
3473
4375
  const responses = payload === null ? [] : [payload];
3474
4376
  if (!responses.length) {
@@ -3487,13 +4389,13 @@ function createSidecarHttpServer(options) {
3487
4389
  response.writeHead(status, { "content-type": "application/json" });
3488
4390
  response.end(
3489
4391
  JSON.stringify({
3490
- jsonrpc: JSONRPC_VERSION,
4392
+ jsonrpc: JSONRPC_VERSION2,
3491
4393
  id: null,
3492
4394
  error: normalizeHttpError(error)
3493
4395
  })
3494
4396
  );
3495
4397
  }
3496
- });
4398
+ };
3497
4399
  }
3498
4400
  var DEFAULT_ALLOWED_ORIGINS = [
3499
4401
  "https://chatgpt.com",
@@ -3543,6 +4445,73 @@ function escapeRegExp(value) {
3543
4445
  function isProtectedResourceMetadataPath(pathname, endpoint) {
3544
4446
  return pathname === "/.well-known/oauth-protected-resource" || pathname === `/.well-known/oauth-protected-resource${endpoint}`;
3545
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
+ }
3546
4515
  var JsonRpcError = class extends Error {
3547
4516
  constructor(code, message, data) {
3548
4517
  super(message);
@@ -3561,6 +4530,12 @@ var JsonRpcHttpError = class extends JsonRpcError {
3561
4530
  }
3562
4531
  status;
3563
4532
  };
4533
+ var RequestCancelledError = class extends Error {
4534
+ constructor(message = "Request cancelled.") {
4535
+ super(message);
4536
+ this.name = "RequestCancelledError";
4537
+ }
4538
+ };
3564
4539
  var AUTH_RESPONSE_SENT = /* @__PURE__ */ Symbol("sidecar.auth.response-sent");
3565
4540
  async function authorizeHttpRequest(auth, request, response) {
3566
4541
  const result = await auth.authorizeRequest(request);
@@ -3573,7 +4548,7 @@ async function authorizeHttpRequest(auth, request, response) {
3573
4548
  });
3574
4549
  response.end(
3575
4550
  JSON.stringify({
3576
- jsonrpc: JSONRPC_VERSION,
4551
+ jsonrpc: JSONRPC_VERSION2,
3577
4552
  id: null,
3578
4553
  error: {
3579
4554
  code: result.status === 401 ? -32001 : -32003,
@@ -3616,9 +4591,57 @@ function createDefaultContext(request) {
3616
4591
  memory.delete(key);
3617
4592
  }
3618
4593
  },
4594
+ notify: noopNotifications,
3619
4595
  env: process.env
3620
4596
  };
3621
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
+ }
3622
4645
  function toResourceContext(ctx) {
3623
4646
  return {
3624
4647
  auth: ctx.auth,
@@ -3626,6 +4649,7 @@ function toResourceContext(ctx) {
3626
4649
  services: ctx.services,
3627
4650
  log: ctx.log,
3628
4651
  storage: ctx.storage,
4652
+ notify: ctx.notify,
3629
4653
  env: ctx.env
3630
4654
  };
3631
4655
  }
@@ -3636,6 +4660,7 @@ function toPromptContext(ctx) {
3636
4660
  services: ctx.services,
3637
4661
  log: ctx.log,
3638
4662
  storage: ctx.storage,
4663
+ notify: ctx.notify,
3639
4664
  env: ctx.env
3640
4665
  };
3641
4666
  }
@@ -3657,6 +4682,27 @@ function readUriParam(request, method) {
3657
4682
  }
3658
4683
  return uri;
3659
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
+ }
3660
4706
  function selectPaginationOverride(override, operation) {
3661
4707
  if (!override) {
3662
4708
  return void 0;
@@ -3745,18 +4791,48 @@ function validatePostHeaders(request) {
3745
4791
  );
3746
4792
  }
3747
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
+ }
3748
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
+ });
3749
4817
  if (!timeoutMs || timeoutMs <= 0) {
3750
- return promise;
4818
+ try {
4819
+ return await Promise.race([promise, abortPromise]);
4820
+ } finally {
4821
+ if (abortListener) {
4822
+ controller.signal.removeEventListener("abort", abortListener);
4823
+ }
4824
+ }
3751
4825
  }
3752
4826
  let timeout;
3753
4827
  try {
3754
4828
  return await Promise.race([
3755
4829
  promise,
4830
+ abortPromise,
3756
4831
  new Promise((_resolve, reject) => {
3757
4832
  timeout = setTimeout(() => {
3758
- controller.abort();
3759
- 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);
3760
4836
  }, timeoutMs);
3761
4837
  })
3762
4838
  ]);
@@ -3764,8 +4840,17 @@ async function withTimeout(promise, timeoutMs, controller) {
3764
4840
  if (timeout) {
3765
4841
  clearTimeout(timeout);
3766
4842
  }
4843
+ if (abortListener) {
4844
+ controller.signal.removeEventListener("abort", abortListener);
4845
+ }
3767
4846
  }
3768
4847
  }
4848
+ function abortReason(reason) {
4849
+ if (reason instanceof Error) {
4850
+ return reason;
4851
+ }
4852
+ return new RequestCancelledError();
4853
+ }
3769
4854
  function validateAgainstSchema(schema, value, message, options = {}) {
3770
4855
  if (!schema || value === void 0 && options.optional) {
3771
4856
  return value;
@@ -3776,80 +4861,96 @@ function validateAgainstSchema(schema, value, message, options = {}) {
3776
4861
  }
3777
4862
  return value;
3778
4863
  }
3779
- function schemaFailure(schema, value, path19) {
3780
- if (schema.anyOf?.length && !schema.anyOf.some((entry) => !schemaFailure(entry, value, path19))) {
3781
- 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.`;
3782
4867
  }
3783
- if (schema.oneOf?.length && schema.oneOf.filter((entry) => !schemaFailure(entry, value, path19)).length !== 1) {
3784
- 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.`;
3785
4870
  }
3786
4871
  if (schema.allOf?.length) {
3787
4872
  for (const entry of schema.allOf) {
3788
- const failure = schemaFailure(entry, value, path19);
4873
+ const failure = schemaFailure(entry, value, path21);
3789
4874
  if (failure) return failure;
3790
4875
  }
3791
4876
  }
3792
4877
  if (schema.const !== void 0 && value !== schema.const) {
3793
- return `${path19} must equal ${JSON.stringify(schema.const)}.`;
4878
+ return `${path21} must equal ${JSON.stringify(schema.const)}.`;
3794
4879
  }
3795
4880
  if (schema.enum && !schema.enum.some((entry) => JSON.stringify(entry) === JSON.stringify(value))) {
3796
- return `${path19} must be one of the declared enum values.`;
4881
+ return `${path21} must be one of the declared enum values.`;
3797
4882
  }
3798
4883
  if (schema.type) {
3799
4884
  const types = Array.isArray(schema.type) ? schema.type : [schema.type];
3800
4885
  if (!types.some((type) => matchesJsonSchemaType(type, value))) {
3801
- return `${path19} must be ${types.join(" or ")}.`;
4886
+ return `${path21} must be ${types.join(" or ")}.`;
3802
4887
  }
3803
4888
  }
3804
4889
  if (schema.type === "object" || schema.properties || schema.required) {
3805
4890
  if (!value || typeof value !== "object" || Array.isArray(value)) {
3806
- return `${path19} must be an object.`;
4891
+ return `${path21} must be an object.`;
3807
4892
  }
3808
4893
  const record = value;
3809
4894
  for (const required of schema.required ?? []) {
3810
4895
  if (!(required in record)) {
3811
- return `${path19}.${required} is required.`;
4896
+ return `${path21}.${required} is required.`;
3812
4897
  }
3813
4898
  }
3814
4899
  for (const [key, propertySchema] of Object.entries(schema.properties ?? {})) {
3815
4900
  if (key in record) {
3816
- const failure = schemaFailure(propertySchema, record[key], `${path19}.${key}`);
4901
+ const failure = schemaFailure(propertySchema, record[key], `${path21}.${key}`);
3817
4902
  if (failure) return failure;
3818
4903
  }
3819
4904
  }
4905
+ const allowed = new Set(Object.keys(schema.properties ?? {}));
3820
4906
  if (schema.additionalProperties === false) {
3821
- const allowed = new Set(Object.keys(schema.properties ?? {}));
3822
4907
  const extra = Object.keys(record).find((key) => !allowed.has(key));
3823
4908
  if (extra) {
3824
- 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
+ }
3825
4917
  }
3826
4918
  }
3827
4919
  }
3828
4920
  if (schema.type === "array" || schema.items) {
3829
4921
  if (!Array.isArray(value)) {
3830
- 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.`;
3831
4929
  }
3832
4930
  if (schema.items) {
3833
4931
  for (const [index, entry] of value.entries()) {
3834
- const failure = schemaFailure(schema.items, entry, `${path19}[${index}]`);
4932
+ const failure = schemaFailure(schema.items, entry, `${path21}[${index}]`);
3835
4933
  if (failure) return failure;
3836
4934
  }
3837
4935
  }
3838
4936
  }
3839
4937
  if (typeof value === "string") {
3840
4938
  if (schema.minLength !== void 0 && value.length < schema.minLength) {
3841
- return `${path19} is shorter than ${schema.minLength}.`;
4939
+ return `${path21} is shorter than ${schema.minLength}.`;
3842
4940
  }
3843
4941
  if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
3844
- 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}.`;
3845
4946
  }
3846
4947
  }
3847
4948
  if (typeof value === "number") {
3848
4949
  if (schema.minimum !== void 0 && value < schema.minimum) {
3849
- return `${path19} is less than ${schema.minimum}.`;
4950
+ return `${path21} is less than ${schema.minimum}.`;
3850
4951
  }
3851
4952
  if (schema.maximum !== void 0 && value > schema.maximum) {
3852
- return `${path19} is greater than ${schema.maximum}.`;
4953
+ return `${path21} is greater than ${schema.maximum}.`;
3853
4954
  }
3854
4955
  }
3855
4956
  return void 0;
@@ -3957,14 +5058,14 @@ function assertJsonRpcRequest(value) {
3957
5058
  throw new JsonRpcError(-32600, "Request must be a JSON object.");
3958
5059
  }
3959
5060
  const record = value;
3960
- if (record.jsonrpc !== JSONRPC_VERSION || typeof record.method !== "string") {
5061
+ if (record.jsonrpc !== JSONRPC_VERSION2 || typeof record.method !== "string") {
3961
5062
  throw new JsonRpcError(-32600, "Invalid JSON-RPC request.");
3962
5063
  }
3963
5064
  if ("id" in record && record.id !== void 0 && record.id !== null && typeof record.id !== "string" && typeof record.id !== "number") {
3964
5065
  throw new JsonRpcError(-32600, "JSON-RPC id must be a string, number, or null.");
3965
5066
  }
3966
5067
  return {
3967
- jsonrpc: JSONRPC_VERSION,
5068
+ jsonrpc: JSONRPC_VERSION2,
3968
5069
  id: typeof record.id === "string" || typeof record.id === "number" || record.id === null ? record.id : void 0,
3969
5070
  method: record.method,
3970
5071
  params: record.params
@@ -3975,7 +5076,7 @@ function isJsonRpcResponseMessage(value) {
3975
5076
  return false;
3976
5077
  }
3977
5078
  const record = value;
3978
- 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)) {
3979
5080
  return false;
3980
5081
  }
3981
5082
  return record.id === null || typeof record.id === "string" || typeof record.id === "number";
@@ -4027,11 +5128,31 @@ import { stdin, stdout } from "process";
4027
5128
  async function startTunnel(options) {
4028
5129
  const provider = await resolveProvider(options.provider);
4029
5130
  const localUrl = `http://127.0.0.1:${options.port}`;
4030
- const path19 = options.path ?? "/mcp";
5131
+ const path21 = options.path ?? "/mcp";
4031
5132
  if (provider === "cloudflared") {
4032
- return startCloudflared(localUrl, path19, options.timeoutMs);
4033
- }
4034
- 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);
4035
5156
  }
4036
5157
  function tunnelInstallMessage(provider = "auto") {
4037
5158
  if (provider === "wrangler") {
@@ -4135,7 +5256,7 @@ function assertNpxAvailable() {
4135
5256
  throw new Error(tunnelInstallMessage("wrangler"));
4136
5257
  }
4137
5258
  }
4138
- async function startCloudflared(localUrl, path19, timeoutMs = 2e4) {
5259
+ async function startCloudflared(localUrl, path21, timeoutMs = 2e4) {
4139
5260
  const child = spawn("cloudflared", ["tunnel", "--url", localUrl, "--no-autoupdate"], {
4140
5261
  stdio: ["ignore", "pipe", "pipe"]
4141
5262
  });
@@ -4143,13 +5264,13 @@ async function startCloudflared(localUrl, path19, timeoutMs = 2e4) {
4143
5264
  return {
4144
5265
  provider: "cloudflared",
4145
5266
  publicUrl,
4146
- mcpUrl: appendPath(publicUrl, path19),
5267
+ mcpUrl: appendPath(publicUrl, path21),
4147
5268
  close() {
4148
5269
  child.kill("SIGTERM");
4149
5270
  }
4150
5271
  };
4151
5272
  }
4152
- async function startWrangler(localUrl, path19, timeoutMs = 2e4) {
5273
+ async function startWrangler(localUrl, path21, timeoutMs = 2e4) {
4153
5274
  const child = spawn("npx", ["--yes", "wrangler", "tunnel", "quick-start", localUrl], {
4154
5275
  stdio: ["ignore", "pipe", "pipe"]
4155
5276
  });
@@ -4157,7 +5278,7 @@ async function startWrangler(localUrl, path19, timeoutMs = 2e4) {
4157
5278
  return {
4158
5279
  provider: "wrangler",
4159
5280
  publicUrl,
4160
- mcpUrl: appendPath(publicUrl, path19),
5281
+ mcpUrl: appendPath(publicUrl, path21),
4161
5282
  close() {
4162
5283
  child.kill("SIGTERM");
4163
5284
  }
@@ -4166,15 +5287,20 @@ async function startWrangler(localUrl, path19, timeoutMs = 2e4) {
4166
5287
  function waitForTunnelUrl(child, pattern, timeoutMs) {
4167
5288
  return new Promise((resolve, reject) => {
4168
5289
  let settled = false;
5290
+ let output = "";
4169
5291
  const timeout = setTimeout(() => {
4170
5292
  if (!settled) {
4171
5293
  settled = true;
4172
5294
  child.kill("SIGTERM");
4173
- 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")));
4174
5299
  }
4175
5300
  }, timeoutMs);
4176
5301
  const inspect = (chunk) => {
4177
- const match = chunk.toString("utf8").match(pattern);
5302
+ output = tail(`${output}${chunk.toString("utf8")}`, 4e3);
5303
+ const match = output.match(pattern);
4178
5304
  if (match?.[0] && !settled) {
4179
5305
  settled = true;
4180
5306
  clearTimeout(timeout);
@@ -4194,11 +5320,209 @@ function waitForTunnelUrl(child, pattern, timeoutMs) {
4194
5320
  if (!settled) {
4195
5321
  settled = true;
4196
5322
  clearTimeout(timeout);
4197
- 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")));
4198
5327
  }
4199
5328
  });
4200
5329
  });
4201
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
+ }
4202
5526
  function hasCommand(command) {
4203
5527
  return spawnSync("which", [command], { stdio: "ignore" }).status === 0;
4204
5528
  }
@@ -4215,8 +5539,11 @@ function runInherited(command, args) {
4215
5539
  });
4216
5540
  });
4217
5541
  }
4218
- function appendPath(origin, path19) {
4219
- 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));
4220
5547
  }
4221
5548
 
4222
5549
  // packages/cli/src/index.ts
@@ -4225,19 +5552,27 @@ async function main(argv) {
4225
5552
  const rootDir = readOption(argv, "--cwd") ?? cwd();
4226
5553
  switch (command) {
4227
5554
  case "build": {
4228
- const target = readTarget(argv);
4229
- const outDir = readOption(argv, "--out") ?? `out/${target}`;
4230
- 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);
4231
5559
  const strict = argv.includes("--strict");
4232
- 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);
4233
5562
  printDiagnostics(manifest.diagnostics ?? []);
4234
5563
  if (strict && manifest.diagnostics?.length) {
4235
5564
  exit(1);
4236
5565
  }
4237
5566
  console.log(
4238
- `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}.`
4239
5568
  );
4240
- 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) {
4241
5576
  console.log("Built claude-plugin package.");
4242
5577
  }
4243
5578
  return;
@@ -4268,18 +5603,20 @@ async function main(argv) {
4268
5603
  const target = readTarget(argv);
4269
5604
  const tunnelProvider = readTunnelProvider(argv);
4270
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);
4271
5611
  const manifest = await buildProject({ rootDir, outDir, target });
4272
5612
  printDiagnostics(manifest.diagnostics ?? []);
4273
- const tools = await loadRuntimeTools(rootDir, manifest);
5613
+ const tools = attachBuiltToolDescriptors(runtimeTools, manifest);
4274
5614
  let tunnel;
4275
5615
  if (tunnelProvider) {
4276
5616
  tunnel = await startTunnel({ provider: tunnelProvider, port, path: "/mcp" });
4277
5617
  process.env.SIDECAR_MCP_URL = tunnel.mcpUrl;
4278
5618
  }
4279
- const loadedAuth = await loadRuntimeAuth(rootDir);
4280
- const runtimeConfig = await loadRuntimeConfig(rootDir);
4281
5619
  const auth = loadedAuth && tunnel ? loadedAuth.withResource(tunnel.mcpUrl) : loadedAuth;
4282
- const proxy = await loadRuntimeProxy(rootDir);
4283
5620
  const resources = await loadResources(rootDir, outDir, manifest);
4284
5621
  const prompts = await loadRuntimePrompts(rootDir, manifest);
4285
5622
  const server = createSidecarHttpServer({
@@ -4300,14 +5637,29 @@ async function main(argv) {
4300
5637
  pageSize: manifest.config.pagination.pageSize
4301
5638
  }
4302
5639
  });
4303
- 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
+ }
4304
5650
  console.log(`MCP running on Streamable HTTP (${target}) at http://127.0.0.1:${port}/mcp`);
4305
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"}.`);
4306
5652
  if (tunnel) {
4307
- console.log(`HTTPS tunnel (${tunnel.provider}) ready: ${tunnel.mcpUrl}`);
5653
+ console.log(`HTTPS tunnel (${tunnel.provider}) validated: ${tunnel.mcpUrl}`);
4308
5654
  console.log("Use this HTTPS MCP URL in ChatGPT, Claude, or a Claude plugin install.");
4309
5655
  }
4310
- });
5656
+ } catch (error) {
5657
+ tunnel?.close();
5658
+ if (listening) {
5659
+ await closeServer(server);
5660
+ }
5661
+ throw error;
5662
+ }
4311
5663
  const shutdown = () => {
4312
5664
  tunnel?.close();
4313
5665
  server.close(() => exit(0));
@@ -4350,7 +5702,7 @@ async function main(argv) {
4350
5702
  console.log(`Sidecar
4351
5703
 
4352
5704
  Usage:
4353
- 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]
4354
5706
  sidecar check [--cwd <dir>] [--target mcp|chatgpt|claude] [--strict]
4355
5707
  sidecar dev [--cwd <dir>] [--target mcp|chatgpt|claude] [--port <port>] [--tunnel [cloudflared|wrangler]]
4356
5708
  sidecar inspect [--cwd <dir>] [--target mcp|chatgpt|claude]
@@ -4358,16 +5710,54 @@ Usage:
4358
5710
  `);
4359
5711
  }
4360
5712
  }
5713
+ function defaultBuildOutDir2(host, target) {
5714
+ if (host === "vercel") {
5715
+ return ".vercel/output";
5716
+ }
5717
+ return `out/${target}`;
5718
+ }
4361
5719
  function readTarget(argv) {
4362
- 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
+ }
4363
5728
  if (target === "mcp" || target === "chatgpt" || target === "claude") {
4364
5729
  return target;
4365
5730
  }
4366
5731
  throw new Error(`Unsupported Sidecar target "${target}". Expected mcp, chatgpt, or claude.`);
4367
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
+ }
4368
5758
  async function previewComponents(options) {
4369
- const cliDir = path18.dirname(fileURLToPath(import.meta.url));
4370
- 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(() => "");
4371
5761
  const html = renderComponentPreviewHtml(
4372
5762
  options.host,
4373
5763
  options.compare,
@@ -4407,10 +5797,10 @@ async function previewComponents(options) {
4407
5797
  server.close();
4408
5798
  }
4409
5799
  async function writeComponentApproval(rootDir, approval) {
4410
- const dir = path18.join(rootDir, ".sidecar", "approvals");
4411
- await mkdir9(dir, { recursive: true });
4412
- await writeFile9(
4413
- 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`),
4414
5804
  `${JSON.stringify(approval, null, 2)}
4415
5805
  `
4416
5806
  );
@@ -4762,84 +6152,98 @@ function printDiagnostics(diagnostics) {
4762
6152
  }
4763
6153
  }
4764
6154
  async function loadRuntimeAuth(rootDir) {
4765
- const authPath = path18.join(rootDir, "auth.ts");
4766
- if (!existsSync5(authPath)) {
6155
+ const authPath = path20.join(rootDir, "auth.ts");
6156
+ if (!existsSync7(authPath)) {
4767
6157
  return void 0;
4768
6158
  }
4769
- const parentURL = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4770
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4771
- const tsconfig = existsSync5(tsconfigPath) ? tsconfigPath : false;
4772
- 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, {
4773
6163
  parentURL,
4774
6164
  tsconfig
4775
6165
  });
4776
- if (!isSidecarAuth(module.default)) {
6166
+ const defaultExport = unwrapRuntimeDefault2(module.default);
6167
+ if (!isSidecarAuth(defaultExport)) {
4777
6168
  throw new Error("auth.ts must default-export auth({ ... }) from sidecar-ai.");
4778
6169
  }
4779
- return module.default;
6170
+ return defaultExport;
4780
6171
  }
4781
6172
  async function loadRuntimeProxy(rootDir) {
4782
- const proxyPath = path18.join(rootDir, "proxy.ts");
4783
- if (!existsSync5(proxyPath)) {
6173
+ const proxyPath = path20.join(rootDir, "proxy.ts");
6174
+ if (!existsSync7(proxyPath)) {
4784
6175
  return void 0;
4785
6176
  }
4786
- const parentURL = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4787
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4788
- const tsconfig = existsSync5(tsconfigPath) ? tsconfigPath : false;
4789
- 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, {
4790
6181
  parentURL,
4791
6182
  tsconfig
4792
6183
  });
4793
- if (!isSidecarProxy(module.default)) {
6184
+ const defaultExport = unwrapRuntimeDefault2(module.default);
6185
+ if (!isSidecarProxy(defaultExport)) {
4794
6186
  throw new Error("proxy.ts must default-export proxy({ ... }) from @sidecar-ai/server/proxy.");
4795
6187
  }
4796
- return module.default;
6188
+ return defaultExport;
4797
6189
  }
4798
6190
  async function loadRuntimeConfig(rootDir) {
4799
- const configPath = path18.join(rootDir, "sidecar.config.ts");
4800
- if (!existsSync5(configPath)) {
6191
+ const configPath = path20.join(rootDir, "sidecar.config.ts");
6192
+ if (!existsSync7(configPath)) {
4801
6193
  return void 0;
4802
6194
  }
4803
- const parentURL = pathToFileURL(configPath).href;
4804
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4805
- const tsconfig = existsSync5(tsconfigPath) ? tsconfigPath : false;
4806
- 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, {
4807
6199
  parentURL,
4808
6200
  tsconfig
4809
6201
  });
4810
- if (!module.default || typeof module.default !== "object") {
6202
+ const defaultExport = unwrapRuntimeDefault2(module.default);
6203
+ if (!defaultExport || typeof defaultExport !== "object") {
4811
6204
  throw new Error("sidecar.config.ts must default-export defineConfig({ ... }) from sidecar-ai.");
4812
6205
  }
4813
- return module.default;
6206
+ return defaultExport;
4814
6207
  }
4815
- async function loadRuntimeTools(rootDir, manifest) {
4816
- const parentURL = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4817
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4818
- 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;
4819
6212
  const loaded = [];
4820
- for (const entry of manifest.tools) {
4821
- const sourcePath = path18.join(rootDir, entry.sourceFile);
4822
- 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, {
4823
6216
  parentURL,
4824
6217
  tsconfig
4825
6218
  });
4826
- if (!isSidecarTool(module.default)) {
6219
+ const defaultExport = unwrapRuntimeDefault2(module.default);
6220
+ if (!isSidecarTool(defaultExport)) {
4827
6221
  throw new Error(`${entry.sourceFile} did not default-export a Sidecar tool.`);
4828
6222
  }
4829
6223
  loaded.push({
4830
- tool: module.default,
6224
+ sourceFile: entry.sourceFile,
6225
+ tool: defaultExport,
4831
6226
  descriptor: entry.descriptor
4832
6227
  });
4833
6228
  }
4834
6229
  return loaded;
4835
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
+ }
4836
6240
  async function loadResources(rootDir, outDir, manifest) {
4837
6241
  const resources = [];
4838
6242
  for (const entry of manifest.tools) {
4839
6243
  if (!entry.widget?.outputFile) {
4840
6244
  continue;
4841
6245
  }
4842
- 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");
4843
6247
  resources.push({
4844
6248
  uri: entry.widget.resourceUri,
4845
6249
  name: entry.name,
@@ -4849,47 +6253,72 @@ async function loadResources(rootDir, outDir, manifest) {
4849
6253
  _meta: entry.widget.resourceMeta
4850
6254
  });
4851
6255
  }
4852
- const parentURL = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4853
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4854
- 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;
4855
6259
  for (const entry of manifest.resources) {
4856
- const sourcePath = path18.join(rootDir, entry.sourceFile);
4857
- const module = await tsImport(pathToFileURL(sourcePath).href, {
6260
+ const sourcePath = path20.join(rootDir, entry.sourceFile);
6261
+ const module = await tsImport2(pathToFileURL3(sourcePath).href, {
4858
6262
  parentURL,
4859
6263
  tsconfig
4860
6264
  });
4861
- if (!isSidecarResource(module.default)) {
6265
+ const defaultExport = unwrapRuntimeDefault2(module.default);
6266
+ if (!isSidecarResource(defaultExport)) {
4862
6267
  throw new Error(`${entry.sourceFile} did not default-export a Sidecar resource.`);
4863
6268
  }
4864
6269
  resources.push({
4865
6270
  uri: entry.uri,
4866
6271
  descriptor: entry.descriptor,
4867
- resource: module.default
6272
+ resource: defaultExport
4868
6273
  });
4869
6274
  }
4870
6275
  return resources;
4871
6276
  }
4872
6277
  async function loadRuntimePrompts(rootDir, manifest) {
4873
- const parentURL = pathToFileURL(path18.join(rootDir, "sidecar.config.ts")).href;
4874
- const tsconfigPath = path18.join(rootDir, "tsconfig.json");
4875
- 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;
4876
6281
  const loaded = [];
4877
6282
  for (const entry of manifest.prompts) {
4878
- const sourcePath = path18.join(rootDir, entry.sourceFile);
4879
- const module = await tsImport(pathToFileURL(sourcePath).href, {
6283
+ const sourcePath = path20.join(rootDir, entry.sourceFile);
6284
+ const module = await tsImport2(pathToFileURL3(sourcePath).href, {
4880
6285
  parentURL,
4881
6286
  tsconfig
4882
6287
  });
4883
- if (!isSidecarPrompt(module.default)) {
6288
+ const defaultExport = unwrapRuntimeDefault2(module.default);
6289
+ if (!isSidecarPrompt(defaultExport)) {
4884
6290
  throw new Error(`${entry.sourceFile} did not default-export a Sidecar prompt.`);
4885
6291
  }
4886
6292
  loaded.push({
4887
- prompt: module.default,
6293
+ prompt: defaultExport,
4888
6294
  descriptor: entry.descriptor
4889
6295
  });
4890
6296
  }
4891
6297
  return loaded;
4892
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
+ }
4893
6322
  function readOption(argv, name) {
4894
6323
  const index = argv.indexOf(name);
4895
6324
  if (index === -1) {
@@ -4903,7 +6332,7 @@ function isDirectRun() {
4903
6332
  return false;
4904
6333
  }
4905
6334
  const entryPath = realpathSync.native(entry);
4906
- return import.meta.url === pathToFileURL(entryPath).href;
6335
+ return import.meta.url === pathToFileURL3(entryPath).href;
4907
6336
  }
4908
6337
  if (isDirectRun()) {
4909
6338
  main(process.argv).catch((error) => {