@sidecar-ai/compiler 0.1.0-alpha.1 → 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +22 -3
- package/dist/index.js +983 -248
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// packages/core/src/index.ts
|
|
2
|
+
var toolBrand = /* @__PURE__ */ Symbol.for("sidecar.tool");
|
|
2
3
|
var toolResultBrand = /* @__PURE__ */ Symbol.for("sidecar.toolResult");
|
|
3
4
|
var resourceResultBrand = /* @__PURE__ */ Symbol.for("sidecar.resourceResult");
|
|
5
|
+
function isSidecarTool(value) {
|
|
6
|
+
return Boolean(
|
|
7
|
+
value && typeof value === "object" && (value[toolBrand] || value.kind === "sidecar.tool")
|
|
8
|
+
);
|
|
9
|
+
}
|
|
4
10
|
var toolResult = Object.assign(
|
|
5
11
|
createToolResult,
|
|
6
12
|
{
|
|
@@ -27,9 +33,9 @@ var resourceResult = Object.assign(
|
|
|
27
33
|
);
|
|
28
34
|
function createToolResult(input) {
|
|
29
35
|
const resultEnvelope = stripUndefined({
|
|
30
|
-
structuredContent: input.structuredContent,
|
|
36
|
+
structuredContent: stripJsonUndefined(input.structuredContent),
|
|
31
37
|
content: normalizeRequiredContent(input.content),
|
|
32
|
-
_meta: input.meta,
|
|
38
|
+
_meta: stripJsonUndefined(input.meta),
|
|
33
39
|
isError: input.isError
|
|
34
40
|
});
|
|
35
41
|
Object.defineProperty(resultEnvelope, toolResultBrand, {
|
|
@@ -271,10 +277,24 @@ function isReadonlyArray(value) {
|
|
|
271
277
|
function stripUndefined(value) {
|
|
272
278
|
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
|
|
273
279
|
}
|
|
280
|
+
function stripJsonUndefined(value) {
|
|
281
|
+
if (value === void 0) {
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
if (Array.isArray(value)) {
|
|
285
|
+
return value.map((entry) => stripJsonUndefined(entry)).filter((entry) => entry !== void 0);
|
|
286
|
+
}
|
|
287
|
+
if (!value || typeof value !== "object") {
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
return Object.fromEntries(
|
|
291
|
+
Object.entries(value).map(([key, entry]) => [key, stripJsonUndefined(entry)]).filter(([, entry]) => entry !== void 0)
|
|
292
|
+
);
|
|
293
|
+
}
|
|
274
294
|
|
|
275
295
|
// packages/compiler/src/analyze.ts
|
|
276
296
|
import { readdir } from "fs/promises";
|
|
277
|
-
import
|
|
297
|
+
import path5 from "path";
|
|
278
298
|
import {
|
|
279
299
|
Node as Node4,
|
|
280
300
|
SyntaxKind as SyntaxKind2
|
|
@@ -441,14 +461,7 @@ function devSidecarTypePaths() {
|
|
|
441
461
|
import {
|
|
442
462
|
Node as Node2
|
|
443
463
|
} from "ts-morph";
|
|
444
|
-
function getParamsSchema(
|
|
445
|
-
const explicitParams = definition.getProperty("params");
|
|
446
|
-
if (explicitParams && Node2.isPropertyAssignment(explicitParams)) {
|
|
447
|
-
const inferred = getSchemaFromZodProperty(explicitParams);
|
|
448
|
-
if (inferred) {
|
|
449
|
-
return inferred;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
464
|
+
function getParamsSchema(_definition, execute) {
|
|
452
465
|
const [params] = execute.getParameters();
|
|
453
466
|
if (!params) {
|
|
454
467
|
return emptyObjectSchema();
|
|
@@ -469,74 +482,6 @@ function getOutputSchema(definition, execute) {
|
|
|
469
482
|
}
|
|
470
483
|
return typeToJsonSchema(returnType);
|
|
471
484
|
}
|
|
472
|
-
function getSchemaFromZodProperty(property) {
|
|
473
|
-
const initializer = property.getInitializer();
|
|
474
|
-
if (!initializer) {
|
|
475
|
-
return void 0;
|
|
476
|
-
}
|
|
477
|
-
if (Node2.isCallExpression(initializer) && initializer.getExpression().getText().endsWith(".object")) {
|
|
478
|
-
const [shape] = initializer.getArguments();
|
|
479
|
-
if (shape && Node2.isObjectLiteralExpression(shape)) {
|
|
480
|
-
return zodObjectLiteralToSchema(shape);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return void 0;
|
|
484
|
-
}
|
|
485
|
-
function zodObjectLiteralToSchema(shape) {
|
|
486
|
-
const properties = {};
|
|
487
|
-
const required = [];
|
|
488
|
-
for (const property of shape.getProperties()) {
|
|
489
|
-
if (!Node2.isPropertyAssignment(property)) {
|
|
490
|
-
continue;
|
|
491
|
-
}
|
|
492
|
-
const name = property.getName().replace(/^["']|["']$/g, "");
|
|
493
|
-
const initializer = property.getInitializer();
|
|
494
|
-
if (!initializer) {
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
const propertySchema = zodExpressionToSchema(initializer);
|
|
498
|
-
properties[name] = propertySchema.schema;
|
|
499
|
-
if (!propertySchema.optional) {
|
|
500
|
-
required.push(name);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
return {
|
|
504
|
-
type: "object",
|
|
505
|
-
properties,
|
|
506
|
-
required,
|
|
507
|
-
additionalProperties: false
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
function zodExpressionToSchema(expression) {
|
|
511
|
-
const text = expression.getText();
|
|
512
|
-
const optional = /\.optional\(\)/.test(text) || /\.default\(/.test(text);
|
|
513
|
-
const schema = {};
|
|
514
|
-
if (/z\.string\(\)/.test(text)) {
|
|
515
|
-
schema.type = "string";
|
|
516
|
-
} else if (/z\.number\(\)/.test(text)) {
|
|
517
|
-
schema.type = "number";
|
|
518
|
-
} else if (/z\.boolean\(\)/.test(text)) {
|
|
519
|
-
schema.type = "boolean";
|
|
520
|
-
} else if (/z\.array\(/.test(text)) {
|
|
521
|
-
schema.type = "array";
|
|
522
|
-
schema.items = {};
|
|
523
|
-
} else {
|
|
524
|
-
schema.type = "object";
|
|
525
|
-
}
|
|
526
|
-
const min = text.match(/\.min\((\d+)/);
|
|
527
|
-
const max = text.match(/\.max\((\d+)/);
|
|
528
|
-
if (schema.type === "string") {
|
|
529
|
-
if (min) schema.minLength = Number(min[1]);
|
|
530
|
-
if (max) schema.maxLength = Number(max[1]);
|
|
531
|
-
} else {
|
|
532
|
-
if (min) schema.minimum = Number(min[1]);
|
|
533
|
-
if (max) schema.maximum = Number(max[1]);
|
|
534
|
-
}
|
|
535
|
-
if (/\.int\(\)/.test(text)) {
|
|
536
|
-
schema.type = "integer";
|
|
537
|
-
}
|
|
538
|
-
return { schema, optional };
|
|
539
|
-
}
|
|
540
485
|
function typeToJsonSchema(type, description) {
|
|
541
486
|
const withoutUndefined = removeUndefined(type);
|
|
542
487
|
const schema = typeToJsonSchemaInner(withoutUndefined);
|
|
@@ -579,12 +524,19 @@ function typeToJsonSchemaInner(type) {
|
|
|
579
524
|
return { anyOf: parts.map((part) => typeToJsonSchema(part)) };
|
|
580
525
|
}
|
|
581
526
|
const properties = type.getProperties();
|
|
527
|
+
const indexType = type.getStringIndexType() ?? type.getNumberIndexType();
|
|
582
528
|
if (properties.length > 0) {
|
|
583
|
-
return objectTypeToSchema(properties);
|
|
529
|
+
return objectTypeToSchema(properties, indexType);
|
|
530
|
+
}
|
|
531
|
+
if (indexType) {
|
|
532
|
+
return {
|
|
533
|
+
type: "object",
|
|
534
|
+
additionalProperties: indexTypeToAdditionalProperties(indexType)
|
|
535
|
+
};
|
|
584
536
|
}
|
|
585
537
|
return {};
|
|
586
538
|
}
|
|
587
|
-
function objectTypeToSchema(properties) {
|
|
539
|
+
function objectTypeToSchema(properties, indexType) {
|
|
588
540
|
const schemaProperties = {};
|
|
589
541
|
const required = [];
|
|
590
542
|
for (const property of properties) {
|
|
@@ -608,9 +560,16 @@ function objectTypeToSchema(properties) {
|
|
|
608
560
|
type: "object",
|
|
609
561
|
properties: schemaProperties,
|
|
610
562
|
required,
|
|
611
|
-
additionalProperties: false
|
|
563
|
+
additionalProperties: indexType ? indexTypeToAdditionalProperties(indexType) : false
|
|
612
564
|
};
|
|
613
565
|
}
|
|
566
|
+
function indexTypeToAdditionalProperties(type) {
|
|
567
|
+
if (type.isAny() || type.isUnknown()) {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
const schema = typeToJsonSchema(type);
|
|
571
|
+
return Object.keys(schema).length ? schema : true;
|
|
572
|
+
}
|
|
614
573
|
function literalOrPrimitive(type, primitive) {
|
|
615
574
|
if (isLiteralType(type)) {
|
|
616
575
|
return { const: literalValue(type) };
|
|
@@ -668,39 +627,134 @@ function schemaDescription(symbol) {
|
|
|
668
627
|
}).find((comment) => comment.trim().length > 0);
|
|
669
628
|
}
|
|
670
629
|
|
|
630
|
+
// packages/compiler/src/runtime-schema.ts
|
|
631
|
+
import { existsSync as existsSync2 } from "fs";
|
|
632
|
+
import path3 from "path";
|
|
633
|
+
import { pathToFileURL } from "url";
|
|
634
|
+
import { tsImport } from "tsx/esm/api";
|
|
635
|
+
async function readRuntimeToolInputSchema(rootDir, sourcePath) {
|
|
636
|
+
let sidecarTool;
|
|
637
|
+
try {
|
|
638
|
+
sidecarTool = await importRuntimeTool(rootDir, sourcePath);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
warnRuntimeSchemaFallback(sourcePath, error);
|
|
641
|
+
return void 0;
|
|
642
|
+
}
|
|
643
|
+
if (!sidecarTool.params) {
|
|
644
|
+
return void 0;
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
return await paramsToJsonSchema(sidecarTool.params);
|
|
648
|
+
} catch (error) {
|
|
649
|
+
warnRuntimeSchemaFallback(sourcePath, error);
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
async function importRuntimeTool(rootDir, sourcePath) {
|
|
654
|
+
const module = await tsImport(pathToFileURL(sourcePath).href, {
|
|
655
|
+
parentURL: runtimeParentUrl(rootDir),
|
|
656
|
+
tsconfig: runtimeTsconfig(rootDir)
|
|
657
|
+
});
|
|
658
|
+
const defaultExport = unwrapRuntimeDefault(module.default);
|
|
659
|
+
if (!isSidecarTool(defaultExport)) {
|
|
660
|
+
throw new Error(`${path3.relative(rootDir, sourcePath)} must default-export tool({ ... }).`);
|
|
661
|
+
}
|
|
662
|
+
return defaultExport;
|
|
663
|
+
}
|
|
664
|
+
async function paramsToJsonSchema(params) {
|
|
665
|
+
const instanceConverter = readInstanceConverter(params);
|
|
666
|
+
if (instanceConverter) {
|
|
667
|
+
return ensureJsonSchema(instanceConverter({ io: "input" }));
|
|
668
|
+
}
|
|
669
|
+
if (isZodMiniSchema(params)) {
|
|
670
|
+
const zod = await import("zod/v4-mini");
|
|
671
|
+
if (typeof zod.toJSONSchema === "function") {
|
|
672
|
+
return ensureJsonSchema(zod.toJSONSchema(params, { io: "input" }));
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return void 0;
|
|
676
|
+
}
|
|
677
|
+
function readInstanceConverter(params) {
|
|
678
|
+
if (!params || typeof params !== "object") {
|
|
679
|
+
return void 0;
|
|
680
|
+
}
|
|
681
|
+
const record = params;
|
|
682
|
+
const converter = record.toJSONSchema ?? record.toJsonSchema;
|
|
683
|
+
return typeof converter === "function" ? converter.bind(params) : void 0;
|
|
684
|
+
}
|
|
685
|
+
function isZodMiniSchema(params) {
|
|
686
|
+
return Boolean(params && typeof params === "object" && "_zod" in params);
|
|
687
|
+
}
|
|
688
|
+
function ensureJsonSchema(value) {
|
|
689
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
690
|
+
return void 0;
|
|
691
|
+
}
|
|
692
|
+
return value;
|
|
693
|
+
}
|
|
694
|
+
function runtimeParentUrl(rootDir) {
|
|
695
|
+
const configPath = path3.join(rootDir, "sidecar.config.ts");
|
|
696
|
+
return pathToFileURL(existsSync2(configPath) ? configPath : rootDir).href;
|
|
697
|
+
}
|
|
698
|
+
function runtimeTsconfig(rootDir) {
|
|
699
|
+
const projectTsconfig = path3.join(rootDir, "tsconfig.json");
|
|
700
|
+
if (existsSync2(projectTsconfig)) {
|
|
701
|
+
return projectTsconfig;
|
|
702
|
+
}
|
|
703
|
+
const repoTsconfig = path3.join(process.cwd(), "tsconfig.json");
|
|
704
|
+
const repoCore = path3.join(process.cwd(), "packages", "core", "src", "index.ts");
|
|
705
|
+
return existsSyncSafe(repoTsconfig) && existsSyncSafe(repoCore) ? repoTsconfig : false;
|
|
706
|
+
}
|
|
707
|
+
function unwrapRuntimeDefault(value) {
|
|
708
|
+
if (value && typeof value === "object" && "default" in value && Object.keys(value).every((key) => key === "default" || key === "__esModule")) {
|
|
709
|
+
return unwrapRuntimeDefault(value.default);
|
|
710
|
+
}
|
|
711
|
+
return value;
|
|
712
|
+
}
|
|
713
|
+
function warnRuntimeSchemaFallback(sourcePath, error) {
|
|
714
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
715
|
+
console.warn(
|
|
716
|
+
`[sidecar] Could not convert runtime params schema for ${sourcePath}; falling back to TypeScript parameter inference. ${message}`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
|
|
671
720
|
// packages/compiler/src/widgets.ts
|
|
672
721
|
import { createHash } from "crypto";
|
|
673
|
-
import { existsSync as
|
|
722
|
+
import { existsSync as existsSync3 } from "fs";
|
|
674
723
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
675
|
-
import
|
|
724
|
+
import { createRequire } from "module";
|
|
725
|
+
import path4 from "path";
|
|
726
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
676
727
|
import { build as esbuild } from "esbuild";
|
|
677
728
|
import postcss from "postcss";
|
|
678
729
|
import {
|
|
679
730
|
Node as Node3,
|
|
680
731
|
SyntaxKind
|
|
681
732
|
} from "ts-morph";
|
|
682
|
-
|
|
733
|
+
var CLAUDE_FONT_RESOURCE_DOMAIN = "https://assets.claude.ai";
|
|
734
|
+
var requireFromCompiler = createRequire(import.meta.url);
|
|
735
|
+
async function buildWidgets(rootDir, outDir, tools, config = void 0) {
|
|
683
736
|
const widgets = tools.filter(
|
|
684
737
|
(entry) => Boolean(entry.widget)
|
|
685
738
|
);
|
|
686
739
|
if (!widgets.length) {
|
|
687
740
|
return;
|
|
688
741
|
}
|
|
689
|
-
const cacheDir =
|
|
742
|
+
const cacheDir = path4.join(rootDir, ".sidecar", "cache", "widgets");
|
|
690
743
|
await mkdir(cacheDir, { recursive: true });
|
|
691
744
|
const appStyle = await prepareAppStyle(rootDir, cacheDir);
|
|
745
|
+
const configure = await loadWidgetBundlerHook(rootDir, cacheDir, config?.configure);
|
|
692
746
|
for (const entry of widgets) {
|
|
693
|
-
const sourceFile =
|
|
747
|
+
const sourceFile = path4.join(rootDir, entry.widget.sourceFile);
|
|
694
748
|
const safeId = safePathSegment(entry.id);
|
|
695
|
-
const entryFile =
|
|
696
|
-
const importPath = toImportSpecifier(
|
|
749
|
+
const entryFile = path4.join(cacheDir, `${safeId}.entry.tsx`);
|
|
750
|
+
const importPath = toImportSpecifier(path4.dirname(entryFile), sourceFile);
|
|
697
751
|
await writeFile(
|
|
698
752
|
entryFile,
|
|
699
753
|
`import React from "react";
|
|
700
754
|
import { createRoot } from "react-dom/client";
|
|
701
755
|
import { SidecarWidgetRoot } from "@sidecar-ai/react";
|
|
702
756
|
import "@sidecar-ai/native/styles.css";
|
|
703
|
-
${appStyle ? `import ${JSON.stringify(toImportSpecifier(
|
|
757
|
+
${appStyle ? `import ${JSON.stringify(toImportSpecifier(path4.dirname(entryFile), appStyle))};` : ""}
|
|
704
758
|
import Component from ${JSON.stringify(importPath)};
|
|
705
759
|
|
|
706
760
|
createRoot(document.getElementById("root")!).render(
|
|
@@ -708,64 +762,219 @@ createRoot(document.getElementById("root")!).render(
|
|
|
708
762
|
);
|
|
709
763
|
`
|
|
710
764
|
);
|
|
711
|
-
const
|
|
765
|
+
const baseOptions = enforceWidgetBuildInvariants({
|
|
712
766
|
absWorkingDir: rootDir,
|
|
713
|
-
alias:
|
|
767
|
+
alias: sidecarWidgetAliases(rootDir),
|
|
714
768
|
bundle: true,
|
|
769
|
+
define: {
|
|
770
|
+
"process.env.NODE_ENV": JSON.stringify("production")
|
|
771
|
+
},
|
|
715
772
|
entryPoints: [entryFile],
|
|
716
773
|
format: "iife",
|
|
717
774
|
jsx: "automatic",
|
|
718
|
-
minify:
|
|
775
|
+
minify: true,
|
|
719
776
|
nodePaths: [
|
|
720
|
-
|
|
721
|
-
|
|
777
|
+
path4.join(rootDir, "node_modules"),
|
|
778
|
+
path4.join(process.cwd(), "node_modules")
|
|
722
779
|
],
|
|
723
780
|
outfile: "widget.js",
|
|
724
781
|
platform: "browser",
|
|
725
782
|
sourcemap: false,
|
|
726
783
|
write: false
|
|
727
784
|
});
|
|
728
|
-
const
|
|
729
|
-
const
|
|
785
|
+
const staticOptions = widgetBuildOptionsFromConfig(rootDir, config);
|
|
786
|
+
const configuredOptions = configure ? await configureWidgetBuild(configure, {
|
|
787
|
+
rootDir,
|
|
788
|
+
outDir,
|
|
789
|
+
entryFile,
|
|
790
|
+
widget: {
|
|
791
|
+
toolId: entry.id,
|
|
792
|
+
toolName: entry.name,
|
|
793
|
+
target: entry.target,
|
|
794
|
+
sourceFile: entry.widget.sourceFile
|
|
795
|
+
},
|
|
796
|
+
esbuildOptions: mergeWidgetBuildOptions(baseOptions, staticOptions)
|
|
797
|
+
}) : void 0;
|
|
798
|
+
const bundled = await esbuild(enforceWidgetBuildInvariants(
|
|
799
|
+
mergeWidgetBuildOptions(baseOptions, staticOptions, configuredOptions),
|
|
800
|
+
{ rootDir, entryFile }
|
|
801
|
+
));
|
|
802
|
+
const outputFiles = bundled.outputFiles ?? [];
|
|
803
|
+
const javascript = outputFiles.find((file) => file.path.endsWith(".js"))?.text ?? "";
|
|
804
|
+
const css = outputFiles.filter((file) => file.path.endsWith(".css")).map((file) => file.text).join("\n");
|
|
730
805
|
const html = renderWidgetHtml(entry.name, javascript, css);
|
|
731
806
|
const hash = createHash("sha256").update(html).digest("hex").slice(0, 12);
|
|
732
|
-
const outputDir =
|
|
733
|
-
const outputFile =
|
|
807
|
+
const outputDir = path4.join(outDir, "public", "widgets", safeId);
|
|
808
|
+
const outputFile = path4.join(outputDir, `widget.${hash}.html`);
|
|
734
809
|
const resourceUri = `ui://${safeId}/widget.${hash}.html`;
|
|
735
810
|
await mkdir(outputDir, { recursive: true });
|
|
736
811
|
await writeFile(outputFile, html);
|
|
737
812
|
entry.widget.resourceUri = resourceUri;
|
|
738
813
|
entry.widget.resourceMeta = widgetResourceMeta(entry.widget.options, entry.target);
|
|
739
|
-
entry.widget.outputFile =
|
|
814
|
+
entry.widget.outputFile = path4.relative(outDir, outputFile);
|
|
740
815
|
entry.descriptor._meta = mergeWidgetMeta(entry.descriptor._meta, widgetMeta(resourceUri, entry.widget.options, entry.target));
|
|
741
816
|
}
|
|
742
817
|
}
|
|
818
|
+
function widgetBuildOptionsFromConfig(rootDir, config) {
|
|
819
|
+
const esbuildConfig = config?.esbuild;
|
|
820
|
+
if (!esbuildConfig) {
|
|
821
|
+
return void 0;
|
|
822
|
+
}
|
|
823
|
+
return {
|
|
824
|
+
alias: normalizeAliases(rootDir, esbuildConfig.alias),
|
|
825
|
+
conditions: esbuildConfig.conditions,
|
|
826
|
+
define: esbuildConfig.define,
|
|
827
|
+
external: esbuildConfig.external,
|
|
828
|
+
jsx: esbuildConfig.jsx,
|
|
829
|
+
jsxImportSource: esbuildConfig.jsxImportSource,
|
|
830
|
+
loader: esbuildConfig.loader,
|
|
831
|
+
mainFields: esbuildConfig.mainFields
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
async function loadWidgetBundlerHook(rootDir, cacheDir, hookPath) {
|
|
835
|
+
if (!hookPath) {
|
|
836
|
+
return void 0;
|
|
837
|
+
}
|
|
838
|
+
const sourcePath = path4.resolve(rootDir, hookPath);
|
|
839
|
+
const relative = path4.relative(rootDir, sourcePath);
|
|
840
|
+
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
841
|
+
throw new Error(`Widget bundler hook must stay inside the project root: ${hookPath}`);
|
|
842
|
+
}
|
|
843
|
+
if (!existsSync3(sourcePath)) {
|
|
844
|
+
throw new Error(`Widget bundler hook not found: ${hookPath}`);
|
|
845
|
+
}
|
|
846
|
+
const outputFile = path4.join(cacheDir, `widget-bundler.${contentHash(hookPath)}.mjs`);
|
|
847
|
+
await esbuild({
|
|
848
|
+
absWorkingDir: rootDir,
|
|
849
|
+
alias: devSidecarBundleAliases(rootDir),
|
|
850
|
+
bundle: true,
|
|
851
|
+
entryPoints: [sourcePath],
|
|
852
|
+
format: "esm",
|
|
853
|
+
nodePaths: [
|
|
854
|
+
path4.join(rootDir, "node_modules"),
|
|
855
|
+
path4.join(process.cwd(), "node_modules")
|
|
856
|
+
],
|
|
857
|
+
outfile: outputFile,
|
|
858
|
+
packages: "external",
|
|
859
|
+
platform: "node",
|
|
860
|
+
target: "node20"
|
|
861
|
+
});
|
|
862
|
+
const module = await import(`${pathToFileURL2(outputFile).href}?t=${Date.now()}`);
|
|
863
|
+
if (typeof module.default !== "function") {
|
|
864
|
+
throw new Error(`Widget bundler hook must default-export a function: ${hookPath}`);
|
|
865
|
+
}
|
|
866
|
+
return module.default;
|
|
867
|
+
}
|
|
868
|
+
async function configureWidgetBuild(hook, input) {
|
|
869
|
+
const result = await hook(input);
|
|
870
|
+
if (!result) {
|
|
871
|
+
return void 0;
|
|
872
|
+
}
|
|
873
|
+
if (typeof result === "object" && "esbuildOptions" in result) {
|
|
874
|
+
return result.esbuildOptions;
|
|
875
|
+
}
|
|
876
|
+
return result;
|
|
877
|
+
}
|
|
878
|
+
function mergeWidgetBuildOptions(...options) {
|
|
879
|
+
const merged = {};
|
|
880
|
+
for (const option of options) {
|
|
881
|
+
if (!option) {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
const previousAlias = merged.alias;
|
|
885
|
+
const previousDefine = merged.define;
|
|
886
|
+
const previousLoader = merged.loader;
|
|
887
|
+
const previousExternal = merged.external;
|
|
888
|
+
const previousNodePaths = merged.nodePaths;
|
|
889
|
+
const previousPlugins = merged.plugins;
|
|
890
|
+
Object.assign(merged, option);
|
|
891
|
+
merged.alias = { ...previousAlias ?? {}, ...option.alias ?? {} };
|
|
892
|
+
merged.define = { ...previousDefine ?? {}, ...option.define ?? {} };
|
|
893
|
+
merged.loader = { ...previousLoader ?? {}, ...option.loader ?? {} };
|
|
894
|
+
merged.external = uniqueStrings([...previousExternal ?? [], ...option.external ?? []]);
|
|
895
|
+
merged.nodePaths = uniqueStrings([...previousNodePaths ?? [], ...option.nodePaths ?? []]);
|
|
896
|
+
merged.plugins = [...previousPlugins ?? [], ...option.plugins ?? []];
|
|
897
|
+
}
|
|
898
|
+
return merged;
|
|
899
|
+
}
|
|
900
|
+
function enforceWidgetBuildInvariants(options, required) {
|
|
901
|
+
const aliases = required ? { ...options.alias ?? {}, ...reactSingletonAliases(required.rootDir) } : options.alias;
|
|
902
|
+
return {
|
|
903
|
+
...options,
|
|
904
|
+
absWorkingDir: required?.rootDir ?? options.absWorkingDir,
|
|
905
|
+
alias: aliases,
|
|
906
|
+
bundle: true,
|
|
907
|
+
entryPoints: required ? [required.entryFile] : options.entryPoints,
|
|
908
|
+
format: "iife",
|
|
909
|
+
outfile: "widget.js",
|
|
910
|
+
platform: "browser",
|
|
911
|
+
write: false
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function sidecarWidgetAliases(rootDir) {
|
|
915
|
+
return {
|
|
916
|
+
...devSidecarBundleAliases(rootDir) ?? {},
|
|
917
|
+
...reactSingletonAliases(rootDir)
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function reactSingletonAliases(rootDir) {
|
|
921
|
+
return {
|
|
922
|
+
react: resolveProjectModule(rootDir, "react"),
|
|
923
|
+
"react/jsx-runtime": resolveProjectModule(rootDir, "react/jsx-runtime"),
|
|
924
|
+
"react/jsx-dev-runtime": resolveProjectModule(rootDir, "react/jsx-dev-runtime"),
|
|
925
|
+
"react-dom": resolveProjectModule(rootDir, "react-dom"),
|
|
926
|
+
"react-dom/client": resolveProjectModule(rootDir, "react-dom/client"),
|
|
927
|
+
"react-dom/server": resolveProjectModule(rootDir, "react-dom/server")
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
function resolveProjectModule(rootDir, specifier) {
|
|
931
|
+
return requireFromCompiler.resolve(specifier, {
|
|
932
|
+
paths: [rootDir, process.cwd()]
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
function normalizeAliases(rootDir, aliases) {
|
|
936
|
+
if (!aliases) {
|
|
937
|
+
return void 0;
|
|
938
|
+
}
|
|
939
|
+
return Object.fromEntries(
|
|
940
|
+
Object.entries(aliases).map(([key, value]) => [
|
|
941
|
+
key,
|
|
942
|
+
value.startsWith(".") ? path4.resolve(rootDir, value) : value
|
|
943
|
+
])
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
function uniqueStrings(values) {
|
|
947
|
+
return [...new Set(values)];
|
|
948
|
+
}
|
|
949
|
+
function contentHash(value) {
|
|
950
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
951
|
+
}
|
|
743
952
|
function devSidecarBundleAliases(rootDir) {
|
|
744
953
|
const repoRoot = findSidecarRepoRoot(rootDir) ?? findSidecarRepoRoot(process.cwd());
|
|
745
954
|
if (!repoRoot) {
|
|
746
955
|
return void 0;
|
|
747
956
|
}
|
|
748
|
-
const reactEntry =
|
|
749
|
-
if (!
|
|
957
|
+
const reactEntry = path4.join(repoRoot, "packages", "react", "src", "index.ts");
|
|
958
|
+
if (!existsSync3(reactEntry)) {
|
|
750
959
|
return void 0;
|
|
751
960
|
}
|
|
752
961
|
return {
|
|
753
|
-
"sidecar-ai":
|
|
754
|
-
"@sidecar-ai/client":
|
|
755
|
-
"@sidecar-ai/core":
|
|
962
|
+
"sidecar-ai": path4.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
|
|
963
|
+
"@sidecar-ai/client": path4.join(repoRoot, "packages", "client", "src", "index.ts"),
|
|
964
|
+
"@sidecar-ai/core": path4.join(repoRoot, "packages", "core", "src", "index.ts"),
|
|
756
965
|
"@sidecar-ai/react": reactEntry,
|
|
757
|
-
"@sidecar-ai/native":
|
|
758
|
-
"@sidecar-ai/native/components":
|
|
759
|
-
"@sidecar-ai/native/styles.css":
|
|
966
|
+
"@sidecar-ai/native": path4.join(repoRoot, "packages", "native", "src", "index.ts"),
|
|
967
|
+
"@sidecar-ai/native/components": path4.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
|
|
968
|
+
"@sidecar-ai/native/styles.css": path4.join(repoRoot, "packages", "native", "src", "styles.css")
|
|
760
969
|
};
|
|
761
970
|
}
|
|
762
971
|
function findSidecarRepoRoot(startDir) {
|
|
763
|
-
let current =
|
|
972
|
+
let current = path4.resolve(startDir);
|
|
764
973
|
while (true) {
|
|
765
|
-
if (
|
|
974
|
+
if (existsSync3(path4.join(current, "packages", "core", "src", "index.ts"))) {
|
|
766
975
|
return current;
|
|
767
976
|
}
|
|
768
|
-
const parent =
|
|
977
|
+
const parent = path4.dirname(current);
|
|
769
978
|
if (parent === current) {
|
|
770
979
|
return void 0;
|
|
771
980
|
}
|
|
@@ -773,13 +982,13 @@ function findSidecarRepoRoot(startDir) {
|
|
|
773
982
|
}
|
|
774
983
|
}
|
|
775
984
|
async function prepareAppStyle(rootDir, cacheDir) {
|
|
776
|
-
const sourceFile =
|
|
777
|
-
if (!
|
|
985
|
+
const sourceFile = path4.join(rootDir, "style.css");
|
|
986
|
+
if (!existsSync3(sourceFile)) {
|
|
778
987
|
return void 0;
|
|
779
988
|
}
|
|
780
989
|
const source = await readFile(sourceFile, "utf8");
|
|
781
990
|
const css = await processAppStyle(source, sourceFile);
|
|
782
|
-
const outputFile =
|
|
991
|
+
const outputFile = path4.join(cacheDir, "style.css");
|
|
783
992
|
await writeFile(outputFile, css);
|
|
784
993
|
return outputFile;
|
|
785
994
|
}
|
|
@@ -791,7 +1000,7 @@ async function processAppStyle(source, from) {
|
|
|
791
1000
|
const plugins = [];
|
|
792
1001
|
if (cssSource.includes("@tailwind")) {
|
|
793
1002
|
const tailwind = await import("@tailwindcss/postcss");
|
|
794
|
-
plugins.push(tailwind.default({ base:
|
|
1003
|
+
plugins.push(tailwind.default({ base: path4.dirname(from) }));
|
|
795
1004
|
}
|
|
796
1005
|
const autoprefixer = await import("autoprefixer");
|
|
797
1006
|
plugins.push(autoprefixer.default());
|
|
@@ -805,13 +1014,13 @@ function needsPostcss(source) {
|
|
|
805
1014
|
return /@import\s+["']tailwindcss["']|@tailwind|@source|@theme|@plugin/.test(source);
|
|
806
1015
|
}
|
|
807
1016
|
function findWidget(rootDir, toolFile, id, target = "mcp", toolVariant = "shared") {
|
|
808
|
-
const selected = selectWidgetFile(
|
|
1017
|
+
const selected = selectWidgetFile(path4.dirname(toolFile), target, toolVariant);
|
|
809
1018
|
if (!selected) {
|
|
810
1019
|
return void 0;
|
|
811
1020
|
}
|
|
812
1021
|
const safeId = safePathSegment(id);
|
|
813
1022
|
return {
|
|
814
|
-
sourceFile:
|
|
1023
|
+
sourceFile: path4.relative(rootDir, selected.filePath),
|
|
815
1024
|
variant: selected.variant,
|
|
816
1025
|
resourceUri: `ui://${safeId}/widget.html`
|
|
817
1026
|
};
|
|
@@ -846,7 +1055,8 @@ function widgetMeta(resourceUri, options = {}, target = "mcp") {
|
|
|
846
1055
|
const standard = {
|
|
847
1056
|
ui: {
|
|
848
1057
|
resourceUri
|
|
849
|
-
}
|
|
1058
|
+
},
|
|
1059
|
+
"ui/resourceUri": resourceUri
|
|
850
1060
|
};
|
|
851
1061
|
if (target !== "chatgpt") {
|
|
852
1062
|
return standard;
|
|
@@ -862,7 +1072,7 @@ function widgetMeta(resourceUri, options = {}, target = "mcp") {
|
|
|
862
1072
|
function widgetResourceMeta(options = {}, target = "mcp") {
|
|
863
1073
|
const csp = stripUndefined3({
|
|
864
1074
|
connectDomains: options.csp?.connectDomains ? [...options.csp.connectDomains] : [],
|
|
865
|
-
resourceDomains: options
|
|
1075
|
+
resourceDomains: widgetResourceDomains(options, target),
|
|
866
1076
|
frameDomains: options.csp?.frameDomains ? [...options.csp.frameDomains] : void 0,
|
|
867
1077
|
baseUriDomains: options.csp?.baseUriDomains ? [...options.csp.baseUriDomains] : void 0
|
|
868
1078
|
});
|
|
@@ -875,6 +1085,13 @@ function widgetResourceMeta(options = {}, target = "mcp") {
|
|
|
875
1085
|
});
|
|
876
1086
|
return Object.keys(ui).length ? { ui } : void 0;
|
|
877
1087
|
}
|
|
1088
|
+
function widgetResourceDomains(options, target) {
|
|
1089
|
+
const domains = [...options.csp?.resourceDomains ?? []];
|
|
1090
|
+
if (target === "claude" && !domains.includes(CLAUDE_FONT_RESOURCE_DOMAIN)) {
|
|
1091
|
+
domains.push(CLAUDE_FONT_RESOURCE_DOMAIN);
|
|
1092
|
+
}
|
|
1093
|
+
return domains;
|
|
1094
|
+
}
|
|
878
1095
|
function findWidgetDefinition(sourceFile) {
|
|
879
1096
|
const call = resolveDefaultExportCall(sourceFile, "widget");
|
|
880
1097
|
if (!call) {
|
|
@@ -986,17 +1203,17 @@ function readStringArrayProperty(definition, propertyName) {
|
|
|
986
1203
|
}
|
|
987
1204
|
function selectWidgetFile(directory, target, toolVariant) {
|
|
988
1205
|
if (toolVariant === "openai") {
|
|
989
|
-
const filePath =
|
|
990
|
-
return
|
|
1206
|
+
const filePath = path4.join(directory, "widget.openai.tsx");
|
|
1207
|
+
return existsSync3(filePath) ? { filePath, variant: "openai" } : void 0;
|
|
991
1208
|
}
|
|
992
1209
|
if (toolVariant === "anthropic") {
|
|
993
|
-
const filePath =
|
|
994
|
-
return
|
|
1210
|
+
const filePath = path4.join(directory, "widget.anthropic.tsx");
|
|
1211
|
+
return existsSync3(filePath) ? { filePath, variant: "anthropic" } : void 0;
|
|
995
1212
|
}
|
|
996
1213
|
const candidates = target === "chatgpt" || target === "claude" || target === "mcp" ? [{ name: "widget.tsx", variant: "shared" }] : [];
|
|
997
1214
|
for (const candidate of candidates) {
|
|
998
|
-
const filePath =
|
|
999
|
-
if (
|
|
1215
|
+
const filePath = path4.join(directory, candidate.name);
|
|
1216
|
+
if (existsSync3(filePath)) {
|
|
1000
1217
|
return { filePath, variant: candidate.variant };
|
|
1001
1218
|
}
|
|
1002
1219
|
}
|
|
@@ -1047,17 +1264,23 @@ function stripUndefined3(value) {
|
|
|
1047
1264
|
// packages/compiler/src/analyze.ts
|
|
1048
1265
|
async function analyzeProjectTools(rootDir, options = {}) {
|
|
1049
1266
|
const target = options.target ?? "mcp";
|
|
1050
|
-
const toolFiles = await findToolFiles(
|
|
1267
|
+
const toolFiles = await findToolFiles(path5.join(rootDir, "server"), target);
|
|
1051
1268
|
if (toolFiles.length === 0) {
|
|
1052
1269
|
return [];
|
|
1053
1270
|
}
|
|
1054
1271
|
const project = createProject(rootDir);
|
|
1055
1272
|
const authScopes = readAuthScopeCatalog(project, rootDir);
|
|
1056
|
-
|
|
1057
|
-
|
|
1273
|
+
const sources = toolFiles.map((candidate) => ({
|
|
1274
|
+
candidate,
|
|
1275
|
+
sourceFile: project.addSourceFileAtPath(candidate.filePath)
|
|
1276
|
+
}));
|
|
1277
|
+
const runtimeInputSchemas = await readRuntimeInputSchemas(rootDir, sources);
|
|
1278
|
+
return sources.map(
|
|
1279
|
+
({ candidate, sourceFile }) => analyzeToolFile(sourceFile, rootDir, {
|
|
1058
1280
|
target,
|
|
1059
1281
|
variant: candidate.variant,
|
|
1060
|
-
authScopes
|
|
1282
|
+
authScopes,
|
|
1283
|
+
inputSchema: runtimeInputSchemas.get(candidate.filePath)
|
|
1061
1284
|
})
|
|
1062
1285
|
);
|
|
1063
1286
|
}
|
|
@@ -1066,7 +1289,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1066
1289
|
const variant = options.variant ?? "shared";
|
|
1067
1290
|
const definition = findToolDefinition(sourceFile);
|
|
1068
1291
|
const absoluteFile = sourceFile.getFilePath();
|
|
1069
|
-
const directory =
|
|
1292
|
+
const directory = path5.basename(path5.dirname(absoluteFile));
|
|
1070
1293
|
const name = getRequiredStringProperty(definition, "name", sourceFile);
|
|
1071
1294
|
const id = getOptionalStringProperty(definition, "id") ?? toMachineName(directory);
|
|
1072
1295
|
const description = getRequiredStringProperty(
|
|
@@ -1079,7 +1302,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1079
1302
|
const hosts = readHosts(definition);
|
|
1080
1303
|
const auth = readAuthPolicy(definition, options.authScopes ?? {});
|
|
1081
1304
|
const execute = getExecuteFunction(definition, sourceFile);
|
|
1082
|
-
const inputSchema = getParamsSchema(definition, execute);
|
|
1305
|
+
const inputSchema = options.inputSchema ?? getParamsSchema(definition, execute);
|
|
1083
1306
|
const outputSchema = getOutputSchema(definition, execute);
|
|
1084
1307
|
const descriptor = createToolDescriptor({
|
|
1085
1308
|
name,
|
|
@@ -1097,7 +1320,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1097
1320
|
validateWidgetHierarchy(sourceFile, absoluteFile, target, variant);
|
|
1098
1321
|
const widget = findWidget(rootDir, absoluteFile, id, target, variant);
|
|
1099
1322
|
if (widget) {
|
|
1100
|
-
const widgetFile =
|
|
1323
|
+
const widgetFile = path5.join(rootDir, widget.sourceFile);
|
|
1101
1324
|
const project = sourceFile.getProject();
|
|
1102
1325
|
const widgetSourceFile = project.getSourceFile(widgetFile) ?? project.addSourceFileAtPath(widgetFile);
|
|
1103
1326
|
widget.options = {
|
|
@@ -1108,7 +1331,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1108
1331
|
descriptor._meta = mergeWidgetMeta(descriptor._meta, widgetMeta(widget.resourceUri, widget.options, target));
|
|
1109
1332
|
}
|
|
1110
1333
|
return {
|
|
1111
|
-
sourceFile:
|
|
1334
|
+
sourceFile: path5.relative(rootDir, absoluteFile),
|
|
1112
1335
|
variant,
|
|
1113
1336
|
target,
|
|
1114
1337
|
directory,
|
|
@@ -1123,6 +1346,26 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1123
1346
|
descriptor
|
|
1124
1347
|
};
|
|
1125
1348
|
}
|
|
1349
|
+
async function readRuntimeInputSchemas(rootDir, sources) {
|
|
1350
|
+
const schemas = /* @__PURE__ */ new Map();
|
|
1351
|
+
await Promise.all(sources.map(async ({ candidate, sourceFile }) => {
|
|
1352
|
+
const definition = findToolDefinition(sourceFile);
|
|
1353
|
+
if (!definitionHasRuntimeParams(definition)) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
const schema = await readRuntimeToolInputSchema(rootDir, candidate.filePath);
|
|
1357
|
+
if (schema) {
|
|
1358
|
+
schemas.set(candidate.filePath, schema);
|
|
1359
|
+
}
|
|
1360
|
+
}));
|
|
1361
|
+
return schemas;
|
|
1362
|
+
}
|
|
1363
|
+
function definitionHasRuntimeParams(definition) {
|
|
1364
|
+
if (definition.getProperty("params")) {
|
|
1365
|
+
return true;
|
|
1366
|
+
}
|
|
1367
|
+
return Boolean(getWithParamsCall(definition));
|
|
1368
|
+
}
|
|
1126
1369
|
function readAuthPolicy(definition, authScopes) {
|
|
1127
1370
|
const initializer = readObjectProperty2(definition, "auth");
|
|
1128
1371
|
if (!initializer) {
|
|
@@ -1139,7 +1382,7 @@ function readAuthPolicy(definition, authScopes) {
|
|
|
1139
1382
|
return { authenticated: true };
|
|
1140
1383
|
}
|
|
1141
1384
|
function readAuthScopeCatalog(project, rootDir) {
|
|
1142
|
-
const authPath =
|
|
1385
|
+
const authPath = path5.join(rootDir, "auth.ts");
|
|
1143
1386
|
if (!existsSyncSafe(authPath)) {
|
|
1144
1387
|
return {};
|
|
1145
1388
|
}
|
|
@@ -1221,12 +1464,12 @@ function isNamedCall(call, name) {
|
|
|
1221
1464
|
return callee === name || callee.endsWith(`.${name}`);
|
|
1222
1465
|
}
|
|
1223
1466
|
function validateWidgetHierarchy(sourceFile, toolFile, target, variant) {
|
|
1224
|
-
const directory =
|
|
1467
|
+
const directory = path5.dirname(toolFile);
|
|
1225
1468
|
const platformWidget = target === "chatgpt" ? "widget.openai.tsx" : target === "claude" ? "widget.anthropic.tsx" : void 0;
|
|
1226
1469
|
if (!platformWidget || variant !== "shared") {
|
|
1227
1470
|
return;
|
|
1228
1471
|
}
|
|
1229
|
-
if (existsSyncSafe(
|
|
1472
|
+
if (existsSyncSafe(path5.join(directory, platformWidget))) {
|
|
1230
1473
|
const expectedTool = target === "chatgpt" ? "tool.openai.ts" : "tool.anthropic.ts";
|
|
1231
1474
|
throw new CompilerError(
|
|
1232
1475
|
sourceFile,
|
|
@@ -1241,7 +1484,7 @@ async function findToolFiles(serverDir, target) {
|
|
|
1241
1484
|
const entries = await readdir(serverDir, { withFileTypes: true });
|
|
1242
1485
|
const files = [];
|
|
1243
1486
|
for (const entry of entries) {
|
|
1244
|
-
const entryPath =
|
|
1487
|
+
const entryPath = path5.join(serverDir, entry.name);
|
|
1245
1488
|
if (entry.isDirectory()) {
|
|
1246
1489
|
const candidate = selectToolFile(entryPath, target);
|
|
1247
1490
|
if (candidate) {
|
|
@@ -1260,7 +1503,7 @@ function selectToolFile(directory, target) {
|
|
|
1260
1503
|
{ name: "tool.ts", variant: "shared" }
|
|
1261
1504
|
] : [{ name: "tool.ts", variant: "shared" }];
|
|
1262
1505
|
for (const candidate of candidates) {
|
|
1263
|
-
const filePath =
|
|
1506
|
+
const filePath = path5.join(directory, candidate.name);
|
|
1264
1507
|
if (existsSyncSafe(filePath)) {
|
|
1265
1508
|
return { filePath, variant: candidate.variant };
|
|
1266
1509
|
}
|
|
@@ -1296,16 +1539,32 @@ function getExecuteFunction(definition, sourceFile) {
|
|
|
1296
1539
|
return property;
|
|
1297
1540
|
}
|
|
1298
1541
|
if (Node4.isPropertyAssignment(property)) {
|
|
1299
|
-
const initializer = property.getInitializer();
|
|
1542
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1300
1543
|
if (initializer && (Node4.isArrowFunction(initializer) || Node4.isFunctionExpression(initializer))) {
|
|
1301
1544
|
return initializer;
|
|
1302
1545
|
}
|
|
1546
|
+
const withParamsCall = getWithParamsCall(definition);
|
|
1547
|
+
const wrappedExecute = unwrapExpression(withParamsCall?.getArguments()[1]);
|
|
1548
|
+
if (wrappedExecute && (Node4.isArrowFunction(wrappedExecute) || Node4.isFunctionExpression(wrappedExecute))) {
|
|
1549
|
+
return wrappedExecute;
|
|
1550
|
+
}
|
|
1303
1551
|
}
|
|
1304
1552
|
throw new CompilerError(
|
|
1305
1553
|
sourceFile,
|
|
1306
1554
|
"execute must be a method, function expression, or arrow function."
|
|
1307
1555
|
);
|
|
1308
1556
|
}
|
|
1557
|
+
function getWithParamsCall(definition) {
|
|
1558
|
+
const property = definition.getProperty("execute");
|
|
1559
|
+
if (!property || !Node4.isPropertyAssignment(property)) {
|
|
1560
|
+
return void 0;
|
|
1561
|
+
}
|
|
1562
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1563
|
+
if (!initializer || !Node4.isCallExpression(initializer)) {
|
|
1564
|
+
return void 0;
|
|
1565
|
+
}
|
|
1566
|
+
return isNamedCall(initializer, "withParams") ? initializer : void 0;
|
|
1567
|
+
}
|
|
1309
1568
|
function getRequiredStringProperty(definition, propertyName, sourceFile) {
|
|
1310
1569
|
const value = getOptionalStringProperty(definition, propertyName);
|
|
1311
1570
|
if (value === void 0 || !value.trim()) {
|
|
@@ -1460,17 +1719,17 @@ function readStringArrayProperty2(definition, propertyName) {
|
|
|
1460
1719
|
}
|
|
1461
1720
|
|
|
1462
1721
|
// packages/compiler/src/build.ts
|
|
1463
|
-
import { mkdir as
|
|
1464
|
-
import
|
|
1722
|
+
import { mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
|
|
1723
|
+
import path19 from "path";
|
|
1465
1724
|
|
|
1466
1725
|
// packages/compiler/src/config.ts
|
|
1467
|
-
import
|
|
1726
|
+
import path6 from "path";
|
|
1468
1727
|
import {
|
|
1469
1728
|
Node as Node5,
|
|
1470
1729
|
SyntaxKind as SyntaxKind3
|
|
1471
1730
|
} from "ts-morph";
|
|
1472
1731
|
function analyzeProjectConfig(rootDir) {
|
|
1473
|
-
const configPath =
|
|
1732
|
+
const configPath = path6.join(rootDir, "sidecar.config.ts");
|
|
1474
1733
|
if (!existsSyncSafe(configPath)) {
|
|
1475
1734
|
return defaultCompilerConfig();
|
|
1476
1735
|
}
|
|
@@ -1482,6 +1741,13 @@ function analyzeProjectConfig(rootDir) {
|
|
|
1482
1741
|
return defaultCompilerConfig();
|
|
1483
1742
|
}
|
|
1484
1743
|
return {
|
|
1744
|
+
build: {
|
|
1745
|
+
target: readTargetNested(definition, "build", "target"),
|
|
1746
|
+
host: readHostNested(definition, "build", "host"),
|
|
1747
|
+
outDir: readStringNested(definition, "build", "outDir"),
|
|
1748
|
+
plugins: readBooleanNested(definition, "build", "plugins"),
|
|
1749
|
+
widgets: readWidgetBuildConfig(definition)
|
|
1750
|
+
},
|
|
1485
1751
|
resources: {
|
|
1486
1752
|
subscribe: readBooleanNested(definition, "resources", "subscribe") ?? false,
|
|
1487
1753
|
listChanged: readBooleanNested(definition, "resources", "listChanged") ?? false
|
|
@@ -1493,13 +1759,52 @@ function analyzeProjectConfig(rootDir) {
|
|
|
1493
1759
|
listChanged: readBooleanNested(definition, "tools", "listChanged") ?? false
|
|
1494
1760
|
},
|
|
1495
1761
|
pagination: {
|
|
1496
|
-
pageSize: readNumberNested(definition, "pagination", "pageSize") ??
|
|
1762
|
+
pageSize: readNumberNested(definition, "pagination", "pageSize") ?? 50,
|
|
1497
1763
|
hasOverride: hasProperty(readObjectProperty3(definition, "pagination"), "override")
|
|
1498
1764
|
}
|
|
1499
1765
|
};
|
|
1500
1766
|
}
|
|
1767
|
+
function readWidgetBuildConfig(definition) {
|
|
1768
|
+
const widgets = readObjectProperty3(readObjectProperty3(definition, "build"), "widgets");
|
|
1769
|
+
if (!widgets) {
|
|
1770
|
+
return void 0;
|
|
1771
|
+
}
|
|
1772
|
+
const config = {};
|
|
1773
|
+
const configure = readStringProperty3(widgets, "configure");
|
|
1774
|
+
const esbuild3 = readWidgetEsbuildConfig(widgets);
|
|
1775
|
+
if (configure) config.configure = configure;
|
|
1776
|
+
if (esbuild3) config.esbuild = esbuild3;
|
|
1777
|
+
return Object.keys(config).length ? config : void 0;
|
|
1778
|
+
}
|
|
1779
|
+
function readWidgetEsbuildConfig(widgets) {
|
|
1780
|
+
const esbuild3 = readObjectProperty3(widgets, "esbuild");
|
|
1781
|
+
if (!esbuild3) {
|
|
1782
|
+
return void 0;
|
|
1783
|
+
}
|
|
1784
|
+
const config = {};
|
|
1785
|
+
const alias = readStringRecordProperty(esbuild3, "alias");
|
|
1786
|
+
const define = readStringRecordProperty(esbuild3, "define");
|
|
1787
|
+
const external = readStringArrayProperty3(esbuild3, "external");
|
|
1788
|
+
const loader = readStringRecordProperty(esbuild3, "loader");
|
|
1789
|
+
const conditions = readStringArrayProperty3(esbuild3, "conditions");
|
|
1790
|
+
const mainFields = readStringArrayProperty3(esbuild3, "mainFields");
|
|
1791
|
+
const jsx = readStringProperty3(esbuild3, "jsx");
|
|
1792
|
+
const jsxImportSource = readStringProperty3(esbuild3, "jsxImportSource");
|
|
1793
|
+
if (alias) config.alias = alias;
|
|
1794
|
+
if (define) config.define = define;
|
|
1795
|
+
if (external) config.external = external;
|
|
1796
|
+
if (loader) config.loader = loader;
|
|
1797
|
+
if (conditions) config.conditions = conditions;
|
|
1798
|
+
if (mainFields) config.mainFields = mainFields;
|
|
1799
|
+
if (jsx === "automatic" || jsx === "transform" || jsx === "preserve") {
|
|
1800
|
+
config.jsx = jsx;
|
|
1801
|
+
}
|
|
1802
|
+
if (jsxImportSource) config.jsxImportSource = jsxImportSource;
|
|
1803
|
+
return Object.keys(config).length ? config : void 0;
|
|
1804
|
+
}
|
|
1501
1805
|
function defaultCompilerConfig() {
|
|
1502
1806
|
return {
|
|
1807
|
+
build: {},
|
|
1503
1808
|
resources: {
|
|
1504
1809
|
subscribe: false,
|
|
1505
1810
|
listChanged: false
|
|
@@ -1511,11 +1816,26 @@ function defaultCompilerConfig() {
|
|
|
1511
1816
|
listChanged: false
|
|
1512
1817
|
},
|
|
1513
1818
|
pagination: {
|
|
1514
|
-
pageSize:
|
|
1819
|
+
pageSize: 50,
|
|
1515
1820
|
hasOverride: false
|
|
1516
1821
|
}
|
|
1517
1822
|
};
|
|
1518
1823
|
}
|
|
1824
|
+
function readTargetNested(definition, section, propertyName) {
|
|
1825
|
+
const value = readStringNested(definition, section, propertyName);
|
|
1826
|
+
return value === "mcp" || value === "chatgpt" || value === "claude" ? value : void 0;
|
|
1827
|
+
}
|
|
1828
|
+
function readHostNested(definition, section, propertyName) {
|
|
1829
|
+
const value = readStringNested(definition, section, propertyName);
|
|
1830
|
+
return value === "node" || value === "vercel" ? value : void 0;
|
|
1831
|
+
}
|
|
1832
|
+
function readStringNested(definition, section, propertyName) {
|
|
1833
|
+
const object = readObjectProperty3(definition, section);
|
|
1834
|
+
if (!object) {
|
|
1835
|
+
return void 0;
|
|
1836
|
+
}
|
|
1837
|
+
return readStringProperty3(object, propertyName);
|
|
1838
|
+
}
|
|
1519
1839
|
function readBooleanNested(definition, section, propertyName) {
|
|
1520
1840
|
const object = readObjectProperty3(definition, section);
|
|
1521
1841
|
if (!object) {
|
|
@@ -1546,6 +1866,9 @@ function readNumberNested(definition, section, propertyName) {
|
|
|
1546
1866
|
return initializer && Node5.isNumericLiteral(initializer) ? Number(initializer.getLiteralText()) : void 0;
|
|
1547
1867
|
}
|
|
1548
1868
|
function readObjectProperty3(definition, propertyName) {
|
|
1869
|
+
if (!definition) {
|
|
1870
|
+
return void 0;
|
|
1871
|
+
}
|
|
1549
1872
|
const property = definition.getProperty(propertyName);
|
|
1550
1873
|
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
1551
1874
|
return void 0;
|
|
@@ -1553,38 +1876,79 @@ function readObjectProperty3(definition, propertyName) {
|
|
|
1553
1876
|
const initializer = unwrapExpression(property.getInitializer());
|
|
1554
1877
|
return initializer && Node5.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
1555
1878
|
}
|
|
1879
|
+
function readStringProperty3(definition, propertyName) {
|
|
1880
|
+
const property = definition.getProperty(propertyName);
|
|
1881
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
1882
|
+
return void 0;
|
|
1883
|
+
}
|
|
1884
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1885
|
+
return initializer && Node5.isStringLiteral(initializer) ? initializer.getLiteralText() : void 0;
|
|
1886
|
+
}
|
|
1887
|
+
function readStringArrayProperty3(definition, propertyName) {
|
|
1888
|
+
const property = definition.getProperty(propertyName);
|
|
1889
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
1890
|
+
return void 0;
|
|
1891
|
+
}
|
|
1892
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1893
|
+
if (!initializer || !Node5.isArrayLiteralExpression(initializer)) {
|
|
1894
|
+
return void 0;
|
|
1895
|
+
}
|
|
1896
|
+
const values = initializer.getElements().map((element) => {
|
|
1897
|
+
const unwrapped = unwrapExpression(element);
|
|
1898
|
+
return unwrapped && Node5.isStringLiteral(unwrapped) ? unwrapped.getLiteralText() : void 0;
|
|
1899
|
+
});
|
|
1900
|
+
return values.every((value) => value !== void 0) ? values : void 0;
|
|
1901
|
+
}
|
|
1902
|
+
function readStringRecordProperty(definition, propertyName) {
|
|
1903
|
+
const object = readObjectProperty3(definition, propertyName);
|
|
1904
|
+
if (!object) {
|
|
1905
|
+
return void 0;
|
|
1906
|
+
}
|
|
1907
|
+
const record = {};
|
|
1908
|
+
for (const property of object.getProperties()) {
|
|
1909
|
+
if (!Node5.isPropertyAssignment(property)) {
|
|
1910
|
+
return void 0;
|
|
1911
|
+
}
|
|
1912
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1913
|
+
if (!initializer || !Node5.isStringLiteral(initializer)) {
|
|
1914
|
+
return void 0;
|
|
1915
|
+
}
|
|
1916
|
+
record[property.getName().replace(/^["']|["']$/g, "")] = initializer.getLiteralText();
|
|
1917
|
+
}
|
|
1918
|
+
return record;
|
|
1919
|
+
}
|
|
1556
1920
|
function hasProperty(definition, propertyName) {
|
|
1557
1921
|
return Boolean(definition?.getProperty(propertyName));
|
|
1558
1922
|
}
|
|
1559
1923
|
|
|
1560
1924
|
// packages/compiler/src/diagnostics.ts
|
|
1561
|
-
import { existsSync as
|
|
1925
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1562
1926
|
import { readFile as readFile2 } from "fs/promises";
|
|
1563
|
-
import
|
|
1927
|
+
import path7 from "path";
|
|
1564
1928
|
async function collectProjectDiagnostics(rootDir, input) {
|
|
1565
1929
|
const diagnostics = [];
|
|
1566
1930
|
const tools = Array.isArray(input) ? input : input.tools;
|
|
1567
1931
|
const resources = Array.isArray(input) ? [] : input.resources ?? [];
|
|
1568
1932
|
const prompts = Array.isArray(input) ? [] : input.prompts ?? [];
|
|
1569
1933
|
const config = Array.isArray(input) ? void 0 : input.config;
|
|
1570
|
-
const hasAuthConfig =
|
|
1934
|
+
const hasAuthConfig = existsSync4(path7.join(rootDir, "auth.ts"));
|
|
1571
1935
|
for (const entry of tools) {
|
|
1572
|
-
const toolPath =
|
|
1936
|
+
const toolPath = path7.join(rootDir, entry.sourceFile);
|
|
1573
1937
|
const toolSource = await readFile2(toolPath, "utf8");
|
|
1574
|
-
diagnostics.push(...diagnoseToolSource(
|
|
1938
|
+
diagnostics.push(...diagnoseToolSource(entry, toolSource, hasAuthConfig));
|
|
1575
1939
|
if (entry.widget) {
|
|
1576
|
-
const widgetPath =
|
|
1940
|
+
const widgetPath = path7.join(rootDir, entry.widget.sourceFile);
|
|
1577
1941
|
const widgetSource = await readFile2(widgetPath, "utf8");
|
|
1578
1942
|
diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource));
|
|
1579
1943
|
}
|
|
1580
1944
|
}
|
|
1581
1945
|
for (const entry of resources) {
|
|
1582
|
-
const resourcePath =
|
|
1946
|
+
const resourcePath = path7.join(rootDir, entry.sourceFile);
|
|
1583
1947
|
const resourceSource = await readFile2(resourcePath, "utf8");
|
|
1584
1948
|
diagnostics.push(...diagnoseResourceSource(entry, resourceSource));
|
|
1585
1949
|
}
|
|
1586
1950
|
for (const entry of prompts) {
|
|
1587
|
-
const promptPath =
|
|
1951
|
+
const promptPath = path7.join(rootDir, entry.sourceFile);
|
|
1588
1952
|
const promptSource = await readFile2(promptPath, "utf8");
|
|
1589
1953
|
diagnostics.push(...diagnosePromptSource(entry, promptSource));
|
|
1590
1954
|
}
|
|
@@ -1599,7 +1963,7 @@ function formatDiagnostic(diagnostic) {
|
|
|
1599
1963
|
hint: ${diagnostic.hint}` : "";
|
|
1600
1964
|
return `${location} - ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}${hint}`;
|
|
1601
1965
|
}
|
|
1602
|
-
function diagnoseToolSource(
|
|
1966
|
+
function diagnoseToolSource(entry, source, hasAuthConfig) {
|
|
1603
1967
|
const diagnostics = [];
|
|
1604
1968
|
const toolLocation = locate(source, "tool({");
|
|
1605
1969
|
if (!entry.description.trim().startsWith("Use this when")) {
|
|
@@ -1816,21 +2180,21 @@ function isIgnored(source, code) {
|
|
|
1816
2180
|
|
|
1817
2181
|
// packages/compiler/src/generated.ts
|
|
1818
2182
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
1819
|
-
import
|
|
2183
|
+
import path8 from "path";
|
|
1820
2184
|
async function writeGeneratedTypes(rootDir, tools) {
|
|
1821
|
-
const generatedDir =
|
|
2185
|
+
const generatedDir = path8.join(rootDir, ".sidecar", "generated");
|
|
1822
2186
|
await mkdir2(generatedDir, { recursive: true });
|
|
1823
2187
|
const appCallableTools = tools.filter(isAppCallable);
|
|
1824
2188
|
const methodNames = uniqueMethodNames(appCallableTools.map((entry) => toIdentifier(entry.id)));
|
|
1825
2189
|
const imports = appCallableTools.map(
|
|
1826
|
-
(entry, index) => `import type tool${index} from ${JSON.stringify(toImportSpecifier(generatedDir,
|
|
2190
|
+
(entry, index) => `import type tool${index} from ${JSON.stringify(toImportSpecifier(generatedDir, path8.join(rootDir, entry.sourceFile), { extension: "js" }))};`
|
|
1827
2191
|
).join("\n");
|
|
1828
2192
|
const ids = appCallableTools.map((entry, index) => ` ${methodNames[index]}: ${JSON.stringify(entry.id)}`).join(",\n");
|
|
1829
2193
|
const toolTypes = appCallableTools.map(
|
|
1830
|
-
(
|
|
2194
|
+
(_entry, index) => ` ${methodNames[index]}(params: ToolParams<typeof tool${index}>): Promise<ToolOutput<typeof tool${index}>>;`
|
|
1831
2195
|
).join("\n");
|
|
1832
2196
|
await writeFile2(
|
|
1833
|
-
|
|
2197
|
+
path8.join(generatedDir, "tools.ts"),
|
|
1834
2198
|
`/**
|
|
1835
2199
|
* Generated Sidecar widget tool client.
|
|
1836
2200
|
*
|
|
@@ -1878,19 +2242,15 @@ function uniqueMethodNames(names) {
|
|
|
1878
2242
|
});
|
|
1879
2243
|
}
|
|
1880
2244
|
|
|
1881
|
-
// packages/compiler/src/plugins.ts
|
|
1882
|
-
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
1883
|
-
import path14 from "path";
|
|
1884
|
-
|
|
1885
2245
|
// packages/compiler/src/identity.ts
|
|
1886
2246
|
import { readFile as readFile3 } from "fs/promises";
|
|
1887
|
-
import
|
|
2247
|
+
import path9 from "path";
|
|
1888
2248
|
async function loadProjectIdentity(rootDir) {
|
|
1889
|
-
const packageJson = await readOptionalJson(
|
|
2249
|
+
const packageJson = await readOptionalJson(path9.join(rootDir, "package.json"));
|
|
1890
2250
|
const configText = await readOptionalText(
|
|
1891
|
-
|
|
2251
|
+
path9.join(rootDir, "sidecar.config.ts")
|
|
1892
2252
|
);
|
|
1893
|
-
const name = readConfigString(configText, "name") ?? packageJson?.name ??
|
|
2253
|
+
const name = readConfigString(configText, "name") ?? packageJson?.name ?? path9.basename(rootDir);
|
|
1894
2254
|
const version = readConfigString(configText, "version") ?? packageJson?.version ?? "0.0.0-dev";
|
|
1895
2255
|
const description = readConfigString(configText, "description") ?? packageJson?.description ?? `${name} Sidecar app.`;
|
|
1896
2256
|
return {
|
|
@@ -1922,30 +2282,34 @@ function readConfigString(configText, key) {
|
|
|
1922
2282
|
return match?.[1];
|
|
1923
2283
|
}
|
|
1924
2284
|
|
|
2285
|
+
// packages/compiler/src/plugins.ts
|
|
2286
|
+
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
2287
|
+
import path15 from "path";
|
|
2288
|
+
|
|
1925
2289
|
// packages/compiler/src/plugin-components/agents.ts
|
|
1926
2290
|
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1927
|
-
import
|
|
2291
|
+
import path10 from "path";
|
|
1928
2292
|
async function emitClaudeAgents(rootDir, destination) {
|
|
1929
|
-
const source =
|
|
2293
|
+
const source = path10.join(rootDir, "agents");
|
|
1930
2294
|
if (!existsSyncSafe(source)) {
|
|
1931
2295
|
return;
|
|
1932
2296
|
}
|
|
1933
2297
|
const entries = await readdir2(source, { withFileTypes: true });
|
|
1934
2298
|
const agentDirs = entries.filter(
|
|
1935
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2299
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path10.join(source, entry.name, "agent.ts"))
|
|
1936
2300
|
);
|
|
1937
2301
|
if (!agentDirs.length) {
|
|
1938
2302
|
return;
|
|
1939
2303
|
}
|
|
1940
2304
|
await mkdir3(destination, { recursive: true });
|
|
1941
2305
|
for (const entry of agentDirs) {
|
|
1942
|
-
const sourceText = await readFile4(
|
|
2306
|
+
const sourceText = await readFile4(path10.join(source, entry.name, "agent.ts"), "utf8");
|
|
1943
2307
|
const markdown = parseClaudeAgent(
|
|
1944
2308
|
sourceText,
|
|
1945
2309
|
entry.name
|
|
1946
2310
|
);
|
|
1947
2311
|
const agentName = readObjectString(sourceText, "name") ?? entry.name;
|
|
1948
|
-
await writeFile3(
|
|
2312
|
+
await writeFile3(path10.join(destination, `${safeFileStem(agentName)}.md`), markdown);
|
|
1949
2313
|
}
|
|
1950
2314
|
}
|
|
1951
2315
|
function parseClaudeAgent(source, fallbackName) {
|
|
@@ -1974,41 +2338,41 @@ ${prompt.trim()}
|
|
|
1974
2338
|
|
|
1975
2339
|
// packages/compiler/src/plugin-components/commands.ts
|
|
1976
2340
|
import { cp, lstat, mkdir as mkdir4, readdir as readdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
1977
|
-
import
|
|
2341
|
+
import path11 from "path";
|
|
1978
2342
|
async function copyCommands(rootDir, destination) {
|
|
1979
|
-
const source =
|
|
2343
|
+
const source = path11.join(rootDir, "commands");
|
|
1980
2344
|
if (!existsSyncSafe(source)) {
|
|
1981
2345
|
return;
|
|
1982
2346
|
}
|
|
1983
2347
|
await mkdir4(destination, { recursive: true });
|
|
1984
2348
|
const entries = await readdir3(source, { withFileTypes: true });
|
|
1985
2349
|
for (const entry of entries) {
|
|
1986
|
-
const sourcePath =
|
|
2350
|
+
const sourcePath = path11.join(source, entry.name);
|
|
1987
2351
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1988
2352
|
if (await safeCommandCopyFilter(sourcePath)) {
|
|
1989
|
-
await cp(sourcePath,
|
|
2353
|
+
await cp(sourcePath, path11.join(destination, safeFileStem(entry.name.replace(/\.md$/, "")) + ".md"));
|
|
1990
2354
|
}
|
|
1991
2355
|
continue;
|
|
1992
2356
|
}
|
|
1993
2357
|
if (!entry.isDirectory()) {
|
|
1994
2358
|
continue;
|
|
1995
2359
|
}
|
|
1996
|
-
const dynamicCommand =
|
|
2360
|
+
const dynamicCommand = path11.join(sourcePath, "command.ts");
|
|
1997
2361
|
if (existsSyncSafe(dynamicCommand)) {
|
|
1998
2362
|
const sourceText = await readFile5(dynamicCommand, "utf8");
|
|
1999
2363
|
const markdown = parseDynamicCommand(sourceText, entry.name);
|
|
2000
2364
|
const commandName = readObjectString(sourceText, "name") ?? entry.name;
|
|
2001
|
-
await writeFile4(
|
|
2365
|
+
await writeFile4(path11.join(destination, `${safeFileStem(commandName)}.md`), markdown);
|
|
2002
2366
|
continue;
|
|
2003
2367
|
}
|
|
2004
|
-
await cp(sourcePath,
|
|
2368
|
+
await cp(sourcePath, path11.join(destination, entry.name), {
|
|
2005
2369
|
recursive: true,
|
|
2006
2370
|
filter: safeCommandCopyFilter
|
|
2007
2371
|
});
|
|
2008
2372
|
}
|
|
2009
2373
|
}
|
|
2010
2374
|
async function safeCommandCopyFilter(sourcePath) {
|
|
2011
|
-
const basename =
|
|
2375
|
+
const basename = path11.basename(sourcePath);
|
|
2012
2376
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2013
2377
|
return false;
|
|
2014
2378
|
}
|
|
@@ -2037,32 +2401,32 @@ ${Object.entries(frontmatter).map(([key, value]) => `${key}: ${yamlScalar(value)
|
|
|
2037
2401
|
|
|
2038
2402
|
// packages/compiler/src/plugin-components/hooks.ts
|
|
2039
2403
|
import { mkdir as mkdir5, readdir as readdir4, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
2040
|
-
import
|
|
2404
|
+
import path12 from "path";
|
|
2041
2405
|
import {
|
|
2042
2406
|
Node as Node6,
|
|
2043
2407
|
Project as Project2,
|
|
2044
2408
|
SyntaxKind as SyntaxKind4
|
|
2045
2409
|
} from "ts-morph";
|
|
2046
2410
|
async function copyHooks(rootDir, destination) {
|
|
2047
|
-
const source =
|
|
2411
|
+
const source = path12.join(rootDir, "hooks");
|
|
2048
2412
|
if (!existsSyncSafe(source)) {
|
|
2049
2413
|
return;
|
|
2050
2414
|
}
|
|
2051
2415
|
const entries = await readdir4(source, { withFileTypes: true });
|
|
2052
2416
|
const hookDirs = entries.filter(
|
|
2053
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2417
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path12.join(source, entry.name, "hook.ts"))
|
|
2054
2418
|
);
|
|
2055
2419
|
if (!hookDirs.length) {
|
|
2056
2420
|
return;
|
|
2057
2421
|
}
|
|
2058
2422
|
const config = {};
|
|
2059
2423
|
for (const entry of hookDirs) {
|
|
2060
|
-
const filePath =
|
|
2424
|
+
const filePath = path12.join(source, entry.name, "hook.ts");
|
|
2061
2425
|
mergeHooks(config, parseHookFile(await readFile6(filePath, "utf8"), entry.name));
|
|
2062
2426
|
}
|
|
2063
2427
|
await mkdir5(destination, { recursive: true });
|
|
2064
2428
|
await writeFile5(
|
|
2065
|
-
|
|
2429
|
+
path12.join(destination, "hooks.json"),
|
|
2066
2430
|
`${JSON.stringify(config, null, 2)}
|
|
2067
2431
|
`
|
|
2068
2432
|
);
|
|
@@ -2089,7 +2453,7 @@ function parseHookFile(source, fallbackName) {
|
|
|
2089
2453
|
throw new Error(`hooks/${fallbackName}/hook.ts must default-export hook({ ... }) or hooks({ ... }).`);
|
|
2090
2454
|
}
|
|
2091
2455
|
function parseHookDefinition(definition, fallbackName) {
|
|
2092
|
-
const event =
|
|
2456
|
+
const event = readStringProperty4(definition, "event");
|
|
2093
2457
|
if (!event) {
|
|
2094
2458
|
throw new Error(`hooks/${fallbackName}/hook.ts must declare an event string.`);
|
|
2095
2459
|
}
|
|
@@ -2099,7 +2463,7 @@ function parseHookDefinition(definition, fallbackName) {
|
|
|
2099
2463
|
}
|
|
2100
2464
|
return {
|
|
2101
2465
|
event,
|
|
2102
|
-
matcher:
|
|
2466
|
+
matcher: readStringProperty4(definition, "matcher"),
|
|
2103
2467
|
run: hooks
|
|
2104
2468
|
};
|
|
2105
2469
|
}
|
|
@@ -2119,7 +2483,7 @@ function parseHooksDefinition(definition, fallbackName) {
|
|
|
2119
2483
|
throw new Error(`hooks/${fallbackName}/hook.ts event "${event}" entries must be objects.`);
|
|
2120
2484
|
}
|
|
2121
2485
|
return stripUndefined2({
|
|
2122
|
-
matcher:
|
|
2486
|
+
matcher: readStringProperty4(entry, "matcher"),
|
|
2123
2487
|
hooks: readHookArray(entry, "run", fallbackName)
|
|
2124
2488
|
});
|
|
2125
2489
|
});
|
|
@@ -2142,7 +2506,7 @@ function parseHookHandlers(handlers, fallbackName) {
|
|
|
2142
2506
|
}
|
|
2143
2507
|
function parseHookHandler(handler, fallbackName) {
|
|
2144
2508
|
if (Node6.isObjectLiteralExpression(handler)) {
|
|
2145
|
-
const type =
|
|
2509
|
+
const type = readStringProperty4(handler, "type");
|
|
2146
2510
|
if (type !== "command" && type !== "http") {
|
|
2147
2511
|
throw new Error(`hooks/${fallbackName}/hook.ts hook handlers need type "command" or "http".`);
|
|
2148
2512
|
}
|
|
@@ -2192,7 +2556,7 @@ function objectLiteralToRecord(object) {
|
|
|
2192
2556
|
}
|
|
2193
2557
|
return stripUndefined2(record);
|
|
2194
2558
|
}
|
|
2195
|
-
function
|
|
2559
|
+
function readStringProperty4(object, propertyName) {
|
|
2196
2560
|
const property = object.getProperty(propertyName);
|
|
2197
2561
|
if (!property || !Node6.isPropertyAssignment(property)) {
|
|
2198
2562
|
return void 0;
|
|
@@ -2236,7 +2600,7 @@ function mergeHooks(target, source) {
|
|
|
2236
2600
|
|
|
2237
2601
|
// packages/compiler/src/plugin-components/passthrough.ts
|
|
2238
2602
|
import { cp as cp2, lstat as lstat2 } from "fs/promises";
|
|
2239
|
-
import
|
|
2603
|
+
import path13 from "path";
|
|
2240
2604
|
async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
2241
2605
|
for (const directory of [
|
|
2242
2606
|
"assets",
|
|
@@ -2245,18 +2609,18 @@ async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
|
2245
2609
|
"output-styles",
|
|
2246
2610
|
"themes"
|
|
2247
2611
|
]) {
|
|
2248
|
-
const source =
|
|
2612
|
+
const source = path13.join(rootDir, directory);
|
|
2249
2613
|
if (!existsSyncSafe(source)) {
|
|
2250
2614
|
continue;
|
|
2251
2615
|
}
|
|
2252
|
-
await cp2(source,
|
|
2616
|
+
await cp2(source, path13.join(pluginDir, directory), {
|
|
2253
2617
|
recursive: true,
|
|
2254
2618
|
filter: safePluginCopyFilter
|
|
2255
2619
|
});
|
|
2256
2620
|
}
|
|
2257
2621
|
}
|
|
2258
2622
|
async function safePluginCopyFilter(sourcePath) {
|
|
2259
|
-
const basename =
|
|
2623
|
+
const basename = path13.basename(sourcePath);
|
|
2260
2624
|
if (basename === "node_modules" || basename === ".git" || basename === ".env" || basename.startsWith(".env.")) {
|
|
2261
2625
|
return false;
|
|
2262
2626
|
}
|
|
@@ -2265,11 +2629,11 @@ async function safePluginCopyFilter(sourcePath) {
|
|
|
2265
2629
|
}
|
|
2266
2630
|
|
|
2267
2631
|
// packages/compiler/src/plugin-components/skills.ts
|
|
2268
|
-
import { existsSync as
|
|
2632
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2269
2633
|
import { cp as cp3, lstat as lstat3, mkdir as mkdir6, readdir as readdir5, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
2270
|
-
import
|
|
2634
|
+
import path14 from "path";
|
|
2271
2635
|
async function copySkills(rootDir, destination) {
|
|
2272
|
-
const source =
|
|
2636
|
+
const source = path14.join(rootDir, "skills");
|
|
2273
2637
|
if (!existsSyncSafe(source)) {
|
|
2274
2638
|
return;
|
|
2275
2639
|
}
|
|
@@ -2279,27 +2643,27 @@ async function copySkills(rootDir, destination) {
|
|
|
2279
2643
|
if (!entry.isDirectory()) {
|
|
2280
2644
|
continue;
|
|
2281
2645
|
}
|
|
2282
|
-
const sourceDir =
|
|
2283
|
-
const destinationDir =
|
|
2284
|
-
const staticSkill =
|
|
2285
|
-
const dynamicSkill =
|
|
2646
|
+
const sourceDir = path14.join(source, entry.name);
|
|
2647
|
+
const destinationDir = path14.join(destination, entry.name);
|
|
2648
|
+
const staticSkill = path14.join(sourceDir, "SKILL.md");
|
|
2649
|
+
const dynamicSkill = path14.join(sourceDir, "skill.ts");
|
|
2286
2650
|
await mkdir6(destinationDir, { recursive: true });
|
|
2287
|
-
if (
|
|
2651
|
+
if (existsSync5(staticSkill)) {
|
|
2288
2652
|
await cp3(sourceDir, destinationDir, {
|
|
2289
2653
|
recursive: true,
|
|
2290
|
-
filter: async (sourcePath) => !sourcePath.endsWith(`${
|
|
2654
|
+
filter: async (sourcePath) => !sourcePath.endsWith(`${path14.sep}skill.ts`) && await safeSkillCopyFilter(sourcePath)
|
|
2291
2655
|
});
|
|
2292
|
-
} else if (
|
|
2656
|
+
} else if (existsSync5(dynamicSkill)) {
|
|
2293
2657
|
const generated = parseDynamicSkill(
|
|
2294
2658
|
await readFile7(dynamicSkill, "utf8"),
|
|
2295
2659
|
entry.name
|
|
2296
2660
|
);
|
|
2297
|
-
await writeFile6(
|
|
2661
|
+
await writeFile6(path14.join(destinationDir, "SKILL.md"), generated);
|
|
2298
2662
|
}
|
|
2299
2663
|
}
|
|
2300
2664
|
}
|
|
2301
2665
|
async function safeSkillCopyFilter(sourcePath) {
|
|
2302
|
-
const basename =
|
|
2666
|
+
const basename = path14.basename(sourcePath);
|
|
2303
2667
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2304
2668
|
return false;
|
|
2305
2669
|
}
|
|
@@ -2325,25 +2689,25 @@ async function buildPluginPackages(rootDir, outRoot, manifest) {
|
|
|
2325
2689
|
await buildClaudePlugin(rootDir, outRoot, identity, manifest);
|
|
2326
2690
|
}
|
|
2327
2691
|
async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
2328
|
-
const pluginDir =
|
|
2329
|
-
await mkdir7(
|
|
2330
|
-
await copySkills(rootDir,
|
|
2331
|
-
await copyCommands(rootDir,
|
|
2332
|
-
await copyHooks(rootDir,
|
|
2333
|
-
await emitClaudeAgents(rootDir,
|
|
2692
|
+
const pluginDir = path15.join(outRoot, "claude-plugin");
|
|
2693
|
+
await mkdir7(path15.join(pluginDir, ".claude-plugin"), { recursive: true });
|
|
2694
|
+
await copySkills(rootDir, path15.join(pluginDir, "skills"));
|
|
2695
|
+
await copyCommands(rootDir, path15.join(pluginDir, "commands"));
|
|
2696
|
+
await copyHooks(rootDir, path15.join(pluginDir, "hooks"));
|
|
2697
|
+
await emitClaudeAgents(rootDir, path15.join(pluginDir, "agents"));
|
|
2334
2698
|
await copyClaudePassthroughDirectories(rootDir, pluginDir);
|
|
2335
|
-
await writeJson(
|
|
2699
|
+
await writeJson(path15.join(pluginDir, ".claude-plugin", "plugin.json"), {
|
|
2336
2700
|
name: identity.slug,
|
|
2337
2701
|
version: identity.version,
|
|
2338
2702
|
description: identity.description,
|
|
2339
2703
|
displayName: identity.name,
|
|
2340
2704
|
installationPreference: "available"
|
|
2341
2705
|
});
|
|
2342
|
-
await writeJson(
|
|
2706
|
+
await writeJson(path15.join(pluginDir, "version.json"), {
|
|
2343
2707
|
version: identity.version,
|
|
2344
2708
|
generatedBy: "sidecar"
|
|
2345
2709
|
});
|
|
2346
|
-
await writeJson(
|
|
2710
|
+
await writeJson(path15.join(pluginDir, ".mcp.json"), {
|
|
2347
2711
|
mcpServers: {
|
|
2348
2712
|
[identity.slug]: {
|
|
2349
2713
|
type: "http",
|
|
@@ -2351,7 +2715,7 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2351
2715
|
}
|
|
2352
2716
|
}
|
|
2353
2717
|
});
|
|
2354
|
-
await writeJson(
|
|
2718
|
+
await writeJson(path15.join(pluginDir, "settings.json"), {
|
|
2355
2719
|
sidecar: {
|
|
2356
2720
|
tools: manifest.tools.map((entry) => entry.id),
|
|
2357
2721
|
resources: manifest.resources.map((entry) => entry.uri),
|
|
@@ -2359,21 +2723,21 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2359
2723
|
}
|
|
2360
2724
|
});
|
|
2361
2725
|
await writeFile7(
|
|
2362
|
-
|
|
2726
|
+
path15.join(pluginDir, "README.md"),
|
|
2363
2727
|
`# ${identity.name} Claude Plugin
|
|
2364
2728
|
|
|
2365
2729
|
This package was generated by Sidecar.
|
|
2366
2730
|
|
|
2367
2731
|
Set \`SIDECAR_MCP_URL\` to the hosted MCP endpoint for this app.
|
|
2368
2732
|
|
|
2369
|
-
For local testing, run \`sidecar dev --tunnel\` and use the
|
|
2733
|
+
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.
|
|
2370
2734
|
|
|
2371
2735
|
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.
|
|
2372
2736
|
`
|
|
2373
2737
|
);
|
|
2374
2738
|
}
|
|
2375
2739
|
async function writeJson(filePath, value) {
|
|
2376
|
-
await mkdir7(
|
|
2740
|
+
await mkdir7(path15.dirname(filePath), { recursive: true });
|
|
2377
2741
|
await writeFile7(
|
|
2378
2742
|
filePath,
|
|
2379
2743
|
`${JSON.stringify(stripUndefined2(value), null, 2)}
|
|
@@ -2383,13 +2747,13 @@ async function writeJson(filePath, value) {
|
|
|
2383
2747
|
|
|
2384
2748
|
// packages/compiler/src/prompts.ts
|
|
2385
2749
|
import { readdir as readdir6 } from "fs/promises";
|
|
2386
|
-
import
|
|
2750
|
+
import path16 from "path";
|
|
2387
2751
|
import {
|
|
2388
2752
|
Node as Node7,
|
|
2389
2753
|
SyntaxKind as SyntaxKind5
|
|
2390
2754
|
} from "ts-morph";
|
|
2391
2755
|
async function analyzeProjectPrompts(rootDir) {
|
|
2392
|
-
const promptFiles = await findPromptFiles(
|
|
2756
|
+
const promptFiles = await findPromptFiles(path16.join(rootDir, "prompts"));
|
|
2393
2757
|
if (promptFiles.length === 0) {
|
|
2394
2758
|
return [];
|
|
2395
2759
|
}
|
|
@@ -2401,7 +2765,7 @@ async function analyzeProjectPrompts(rootDir) {
|
|
|
2401
2765
|
function analyzePromptFile(sourceFile, rootDir) {
|
|
2402
2766
|
const definition = findPromptDefinition(sourceFile);
|
|
2403
2767
|
const absoluteFile = sourceFile.getFilePath();
|
|
2404
|
-
const directory =
|
|
2768
|
+
const directory = path16.basename(path16.dirname(absoluteFile));
|
|
2405
2769
|
const name = getOptionalStringProperty2(definition, "name") ?? safePathSegment(directory);
|
|
2406
2770
|
const title = getRequiredStringProperty2(definition, "title", sourceFile);
|
|
2407
2771
|
const description = getOptionalStringProperty2(definition, "description");
|
|
@@ -2417,7 +2781,7 @@ function analyzePromptFile(sourceFile, rootDir) {
|
|
|
2417
2781
|
throw new CompilerError(sourceFile, "prompt({ ... }) must include a run method.");
|
|
2418
2782
|
}
|
|
2419
2783
|
return {
|
|
2420
|
-
sourceFile:
|
|
2784
|
+
sourceFile: path16.relative(rootDir, absoluteFile),
|
|
2421
2785
|
directory,
|
|
2422
2786
|
name,
|
|
2423
2787
|
title,
|
|
@@ -2436,7 +2800,7 @@ async function findPromptFiles(promptsDir) {
|
|
|
2436
2800
|
if (!entry.isDirectory()) {
|
|
2437
2801
|
continue;
|
|
2438
2802
|
}
|
|
2439
|
-
const filePath =
|
|
2803
|
+
const filePath = path16.join(promptsDir, entry.name, "prompt.ts");
|
|
2440
2804
|
if (existsSyncSafe(filePath)) {
|
|
2441
2805
|
files.push(filePath);
|
|
2442
2806
|
}
|
|
@@ -2558,7 +2922,7 @@ function readIcons(definition) {
|
|
|
2558
2922
|
return [{
|
|
2559
2923
|
src,
|
|
2560
2924
|
mimeType: getOptionalStringProperty2(object, "mimeType"),
|
|
2561
|
-
sizes:
|
|
2925
|
+
sizes: readStringArrayProperty4(object, "sizes")
|
|
2562
2926
|
}];
|
|
2563
2927
|
});
|
|
2564
2928
|
return icons.length ? icons : void 0;
|
|
@@ -2571,7 +2935,7 @@ function readObjectProperty4(definition, propertyName) {
|
|
|
2571
2935
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2572
2936
|
return initializer && Node7.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2573
2937
|
}
|
|
2574
|
-
function
|
|
2938
|
+
function readStringArrayProperty4(definition, propertyName) {
|
|
2575
2939
|
const property = definition.getProperty(propertyName);
|
|
2576
2940
|
if (!property || !Node7.isPropertyAssignment(property)) {
|
|
2577
2941
|
return void 0;
|
|
@@ -2585,13 +2949,13 @@ function readStringArrayProperty3(definition, propertyName) {
|
|
|
2585
2949
|
|
|
2586
2950
|
// packages/compiler/src/resources.ts
|
|
2587
2951
|
import { readdir as readdir7 } from "fs/promises";
|
|
2588
|
-
import
|
|
2952
|
+
import path17 from "path";
|
|
2589
2953
|
import {
|
|
2590
2954
|
Node as Node8,
|
|
2591
2955
|
SyntaxKind as SyntaxKind6
|
|
2592
2956
|
} from "ts-morph";
|
|
2593
2957
|
async function analyzeProjectResources(rootDir) {
|
|
2594
|
-
const resourceFiles = await findResourceFiles(
|
|
2958
|
+
const resourceFiles = await findResourceFiles(path17.join(rootDir, "resources"));
|
|
2595
2959
|
if (resourceFiles.length === 0) {
|
|
2596
2960
|
return [];
|
|
2597
2961
|
}
|
|
@@ -2603,7 +2967,7 @@ async function analyzeProjectResources(rootDir) {
|
|
|
2603
2967
|
function analyzeResourceFile(sourceFile, rootDir) {
|
|
2604
2968
|
const definition = findResourceDefinition(sourceFile);
|
|
2605
2969
|
const absoluteFile = sourceFile.getFilePath();
|
|
2606
|
-
const directory =
|
|
2970
|
+
const directory = path17.basename(path17.dirname(absoluteFile));
|
|
2607
2971
|
const defaultUri = `sidecar://resources/${safePathSegment(directory)}`;
|
|
2608
2972
|
const name = getRequiredStringProperty3(definition, "name", sourceFile);
|
|
2609
2973
|
const uri = getOptionalStringProperty3(definition, "uri") ?? defaultUri;
|
|
@@ -2621,7 +2985,7 @@ function analyzeResourceFile(sourceFile, rootDir) {
|
|
|
2621
2985
|
throw new CompilerError(sourceFile, "resource({ ... }) must include a read method.");
|
|
2622
2986
|
}
|
|
2623
2987
|
return {
|
|
2624
|
-
sourceFile:
|
|
2988
|
+
sourceFile: path17.relative(rootDir, absoluteFile),
|
|
2625
2989
|
directory,
|
|
2626
2990
|
uri,
|
|
2627
2991
|
name,
|
|
@@ -2644,7 +3008,7 @@ async function findResourceFiles(resourcesDir) {
|
|
|
2644
3008
|
if (!entry.isDirectory()) {
|
|
2645
3009
|
continue;
|
|
2646
3010
|
}
|
|
2647
|
-
const filePath =
|
|
3011
|
+
const filePath = path17.join(resourcesDir, entry.name, "resource.ts");
|
|
2648
3012
|
if (existsSyncSafe(filePath)) {
|
|
2649
3013
|
files.push(filePath);
|
|
2650
3014
|
}
|
|
@@ -2723,7 +3087,7 @@ function readAnnotations2(definition) {
|
|
|
2723
3087
|
return void 0;
|
|
2724
3088
|
}
|
|
2725
3089
|
const annotations = {};
|
|
2726
|
-
const audience =
|
|
3090
|
+
const audience = readStringArrayProperty5(initializer, "audience")?.filter((value) => value === "user" || value === "assistant");
|
|
2727
3091
|
const priority = getOptionalNumberProperty(initializer, "priority");
|
|
2728
3092
|
const lastModified = getOptionalStringProperty3(initializer, "lastModified");
|
|
2729
3093
|
if (audience?.length) annotations.audience = audience;
|
|
@@ -2752,7 +3116,7 @@ function readIcons2(definition) {
|
|
|
2752
3116
|
return [{
|
|
2753
3117
|
src,
|
|
2754
3118
|
mimeType: getOptionalStringProperty3(object, "mimeType"),
|
|
2755
|
-
sizes:
|
|
3119
|
+
sizes: readStringArrayProperty5(object, "sizes")
|
|
2756
3120
|
}];
|
|
2757
3121
|
});
|
|
2758
3122
|
return icons.length ? icons : void 0;
|
|
@@ -2765,7 +3129,7 @@ function readObjectProperty5(definition, propertyName) {
|
|
|
2765
3129
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2766
3130
|
return initializer && Node8.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2767
3131
|
}
|
|
2768
|
-
function
|
|
3132
|
+
function readStringArrayProperty5(definition, propertyName) {
|
|
2769
3133
|
const property = definition.getProperty(propertyName);
|
|
2770
3134
|
if (!property || !Node8.isPropertyAssignment(property)) {
|
|
2771
3135
|
return void 0;
|
|
@@ -2777,15 +3141,332 @@ function readStringArrayProperty4(definition, propertyName) {
|
|
|
2777
3141
|
return initializer.getElements().filter(Node8.isStringLiteral).map((element) => element.getLiteralText());
|
|
2778
3142
|
}
|
|
2779
3143
|
|
|
3144
|
+
// packages/compiler/src/server-output.ts
|
|
3145
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3146
|
+
import { mkdir as mkdir8, rm, writeFile as writeFile8 } from "fs/promises";
|
|
3147
|
+
import path18 from "path";
|
|
3148
|
+
import { build as esbuild2 } from "esbuild";
|
|
3149
|
+
var SERVER_ENTRYPOINT = "server/index.js";
|
|
3150
|
+
var VERCEL_FUNCTION_DIR = "functions/api/sidecar.func";
|
|
3151
|
+
var VERCEL_ENTRYPOINT = `${VERCEL_FUNCTION_DIR}/index.js`;
|
|
3152
|
+
var VERCEL_HANDLER_FILE = "index.js";
|
|
3153
|
+
async function buildServerOutput(rootDir, outDir, manifest, identity, host = "node", options = {}) {
|
|
3154
|
+
const cacheDir = path18.join(rootDir, ".sidecar", "cache", "server");
|
|
3155
|
+
await mkdir8(cacheDir, { recursive: true });
|
|
3156
|
+
const entryFile = path18.join(cacheDir, "index.ts");
|
|
3157
|
+
await writeFile8(entryFile, renderServerEntry(rootDir, entryFile, manifest, identity));
|
|
3158
|
+
const serverFile = path18.join(outDir, SERVER_ENTRYPOINT);
|
|
3159
|
+
await mkdir8(path18.dirname(serverFile), { recursive: true });
|
|
3160
|
+
await esbuild2({
|
|
3161
|
+
absWorkingDir: rootDir,
|
|
3162
|
+
alias: sidecarBundleAliases(rootDir),
|
|
3163
|
+
banner: {
|
|
3164
|
+
js: `import { createRequire as __sidecarCreateRequire } from "node:module";
|
|
3165
|
+
const require = __sidecarCreateRequire(import.meta.url);`
|
|
3166
|
+
},
|
|
3167
|
+
bundle: true,
|
|
3168
|
+
entryPoints: [entryFile],
|
|
3169
|
+
format: "esm",
|
|
3170
|
+
legalComments: "none",
|
|
3171
|
+
minify: false,
|
|
3172
|
+
nodePaths: [
|
|
3173
|
+
path18.join(rootDir, "node_modules"),
|
|
3174
|
+
path18.join(process.cwd(), "node_modules")
|
|
3175
|
+
],
|
|
3176
|
+
outfile: serverFile,
|
|
3177
|
+
platform: "node",
|
|
3178
|
+
sourcemap: false,
|
|
3179
|
+
target: "node20"
|
|
3180
|
+
});
|
|
3181
|
+
await writeFile8(path18.join(outDir, "package.json"), renderServerPackage(identity));
|
|
3182
|
+
if (host === "vercel") {
|
|
3183
|
+
const vercelOutputDir = options.vercelOutputDir ?? outDir;
|
|
3184
|
+
await rm(path18.join(vercelOutputDir, "api"), { recursive: true, force: true });
|
|
3185
|
+
await rm(path18.join(vercelOutputDir, "vercel.json"), { force: true });
|
|
3186
|
+
await writeFile8(path18.join(outDir, VERCEL_HANDLER_FILE), renderVercelEntrypoint());
|
|
3187
|
+
await writeFile8(path18.join(outDir, ".vc-config.json"), renderVercelFunctionConfig());
|
|
3188
|
+
await mkdir8(vercelOutputDir, { recursive: true });
|
|
3189
|
+
await writeFile8(path18.join(vercelOutputDir, "config.json"), renderVercelOutputConfig());
|
|
3190
|
+
} else {
|
|
3191
|
+
await rm(path18.join(outDir, "api"), { recursive: true, force: true });
|
|
3192
|
+
await rm(path18.join(outDir, "vercel.json"), { force: true });
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
function renderServerEntry(rootDir, entryFile, manifest, identity) {
|
|
3196
|
+
const entryDir = path18.dirname(entryFile);
|
|
3197
|
+
const tools = manifest.tools;
|
|
3198
|
+
const resources = manifest.resources;
|
|
3199
|
+
const prompts = manifest.prompts;
|
|
3200
|
+
const imports = [
|
|
3201
|
+
`import { readFileSync, realpathSync } from "node:fs";`,
|
|
3202
|
+
`import { createServer } from "node:http";`,
|
|
3203
|
+
`import { pathToFileURL } from "node:url";`,
|
|
3204
|
+
`import { createSidecarHttpHandler } from "@sidecar-ai/server";`,
|
|
3205
|
+
`import { isSidecarAuth } from "@sidecar-ai/auth";`,
|
|
3206
|
+
`import { isSidecarPrompt, isSidecarResource, isSidecarTool } from "@sidecar-ai/core";`,
|
|
3207
|
+
`import { isSidecarProxy } from "@sidecar-ai/server/proxy";`,
|
|
3208
|
+
...tools.map(
|
|
3209
|
+
(entry, index) => `import tool${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3210
|
+
),
|
|
3211
|
+
...resources.map(
|
|
3212
|
+
(entry, index) => `import resource${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3213
|
+
),
|
|
3214
|
+
...prompts.map(
|
|
3215
|
+
(entry, index) => `import prompt${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3216
|
+
),
|
|
3217
|
+
existsSync6(path18.join(rootDir, "auth.ts")) ? `import authExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "auth.ts")))};` : `const authExport = undefined;`,
|
|
3218
|
+
existsSync6(path18.join(rootDir, "proxy.ts")) ? `import proxyExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "proxy.ts")))};` : `const proxyExport = undefined;`,
|
|
3219
|
+
existsSync6(path18.join(rootDir, "sidecar.config.ts")) ? `import configExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "sidecar.config.ts")))};` : `const configExport = undefined;`
|
|
3220
|
+
].join("\n");
|
|
3221
|
+
return `${imports}
|
|
3222
|
+
|
|
3223
|
+
const manifest = ${JSON.stringify(manifest, null, 2)};
|
|
3224
|
+
const identity = ${JSON.stringify(identity, null, 2)};
|
|
3225
|
+
|
|
3226
|
+
const loadedAuth = authExport === undefined ? undefined : assertAuth(authExport);
|
|
3227
|
+
const auth = loadedAuth && process.env.SIDECAR_MCP_URL
|
|
3228
|
+
? loadedAuth.withResource(process.env.SIDECAR_MCP_URL)
|
|
3229
|
+
: loadedAuth;
|
|
3230
|
+
const proxy = proxyExport === undefined ? undefined : assertProxy(proxyExport);
|
|
3231
|
+
const runtimeConfig = configExport && typeof configExport === "object" ? configExport : undefined;
|
|
3232
|
+
|
|
3233
|
+
if (auth && !process.env.SIDECAR_MCP_URL) {
|
|
3234
|
+
console.warn("Sidecar auth is enabled. Set SIDECAR_MCP_URL to the public https://.../mcp URL before hosting.");
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
export const handler = createSidecarHttpHandler({
|
|
3238
|
+
name: identity.name,
|
|
3239
|
+
version: identity.version,
|
|
3240
|
+
path: process.env.SIDECAR_MCP_PATH ?? "/mcp",
|
|
3241
|
+
publicUrl: process.env.SIDECAR_PUBLIC_URL ?? process.env.SIDECAR_MCP_URL,
|
|
3242
|
+
auth,
|
|
3243
|
+
proxy,
|
|
3244
|
+
tools: [
|
|
3245
|
+
${tools.map((entry, index) => ` loadTool(${JSON.stringify(entry.sourceFile)}, tool${index}, manifest.tools[${index}].descriptor),`).join("\n")}
|
|
3246
|
+
],
|
|
3247
|
+
resources: [
|
|
3248
|
+
${renderWidgetResources(manifest)}
|
|
3249
|
+
${resources.map((entry, index) => ` loadResource(${JSON.stringify(entry.sourceFile)}, resource${index}, manifest.resources[${index}]),`).join("\n")}
|
|
3250
|
+
],
|
|
3251
|
+
resourceTemplates: manifest.resourceTemplates.map((entry) => ({ descriptor: entry.descriptor })),
|
|
3252
|
+
prompts: [
|
|
3253
|
+
${prompts.map((entry, index) => ` loadPrompt(${JSON.stringify(entry.sourceFile)}, prompt${index}, manifest.prompts[${index}].descriptor),`).join("\n")}
|
|
3254
|
+
],
|
|
3255
|
+
capabilities: {
|
|
3256
|
+
tools: runtimeConfig?.tools ?? manifest.config.tools,
|
|
3257
|
+
resources: runtimeConfig?.resources ?? manifest.config.resources,
|
|
3258
|
+
prompts: runtimeConfig?.prompts ?? manifest.config.prompts,
|
|
3259
|
+
},
|
|
3260
|
+
pagination: runtimeConfig?.pagination ?? {
|
|
3261
|
+
pageSize: manifest.config.pagination.pageSize,
|
|
3262
|
+
},
|
|
3263
|
+
});
|
|
3264
|
+
|
|
3265
|
+
export default handler;
|
|
3266
|
+
|
|
3267
|
+
export const server = createServer(handler);
|
|
3268
|
+
|
|
3269
|
+
if (isDirectRun()) {
|
|
3270
|
+
const port = readPort();
|
|
3271
|
+
const host = process.env.SIDECAR_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
3272
|
+
server.listen(port, host, () => {
|
|
3273
|
+
const localHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
3274
|
+
console.log(\`MCP running on Streamable HTTP at http://\${localHost}:\${port}\${process.env.SIDECAR_MCP_PATH ?? "/mcp"}\`);
|
|
3275
|
+
});
|
|
3276
|
+
|
|
3277
|
+
process.on("SIGTERM", () => shutdown());
|
|
3278
|
+
process.on("SIGINT", () => shutdown());
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
function loadTool(sourceFile, value, descriptor) {
|
|
3282
|
+
const tool = unwrapRuntimeDefault(value);
|
|
3283
|
+
if (!isSidecarTool(tool)) {
|
|
3284
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar tool.\`);
|
|
3285
|
+
}
|
|
3286
|
+
return { tool, descriptor };
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
function loadResource(sourceFile, value, entry) {
|
|
3290
|
+
const resource = unwrapRuntimeDefault(value);
|
|
3291
|
+
if (!isSidecarResource(resource)) {
|
|
3292
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar resource.\`);
|
|
3293
|
+
}
|
|
3294
|
+
return {
|
|
3295
|
+
uri: entry.uri,
|
|
3296
|
+
descriptor: entry.descriptor,
|
|
3297
|
+
resource,
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
function loadPrompt(sourceFile, value, descriptor) {
|
|
3302
|
+
const prompt = unwrapRuntimeDefault(value);
|
|
3303
|
+
if (!isSidecarPrompt(prompt)) {
|
|
3304
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar prompt.\`);
|
|
3305
|
+
}
|
|
3306
|
+
return { prompt, descriptor };
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
function assertAuth(value) {
|
|
3310
|
+
const authValue = unwrapRuntimeDefault(value);
|
|
3311
|
+
if (!isSidecarAuth(authValue)) {
|
|
3312
|
+
throw new Error("auth.ts must default-export auth({ ... }) from sidecar-ai.");
|
|
3313
|
+
}
|
|
3314
|
+
return authValue;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
function assertProxy(value) {
|
|
3318
|
+
const proxyValue = unwrapRuntimeDefault(value);
|
|
3319
|
+
if (!isSidecarProxy(proxyValue)) {
|
|
3320
|
+
throw new Error("proxy.ts must default-export proxy({ ... }) from @sidecar-ai/server/proxy.");
|
|
3321
|
+
}
|
|
3322
|
+
return proxyValue;
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
function unwrapRuntimeDefault(value) {
|
|
3326
|
+
if (
|
|
3327
|
+
value &&
|
|
3328
|
+
typeof value === "object" &&
|
|
3329
|
+
"default" in value &&
|
|
3330
|
+
Object.keys(value).every((key) => key === "default" || key === "__esModule")
|
|
3331
|
+
) {
|
|
3332
|
+
return unwrapRuntimeDefault(value.default);
|
|
3333
|
+
}
|
|
3334
|
+
return value;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
function readWidget(outputFile) {
|
|
3338
|
+
return readFileSync(new URL(\`../\${outputFile}\`, import.meta.url), "utf8");
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
function readPort() {
|
|
3342
|
+
const raw = process.env.PORT ?? process.env.SIDECAR_PORT ?? "3001";
|
|
3343
|
+
const port = Number(raw);
|
|
3344
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
3345
|
+
throw new Error(\`Invalid PORT/SIDECAR_PORT value: \${raw}\`);
|
|
3346
|
+
}
|
|
3347
|
+
return port;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
function isDirectRun() {
|
|
3351
|
+
const entry = process.argv[1];
|
|
3352
|
+
if (!entry) {
|
|
3353
|
+
return false;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
return import.meta.url === pathToFileURL(realpathSync(entry)).href;
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
function shutdown() {
|
|
3360
|
+
server.close(() => process.exit(0));
|
|
3361
|
+
}
|
|
3362
|
+
`;
|
|
3363
|
+
}
|
|
3364
|
+
function renderWidgetResources(manifest) {
|
|
3365
|
+
return manifest.tools.filter((entry) => entry.widget?.outputFile).map((entry) => ` {
|
|
3366
|
+
uri: ${JSON.stringify(entry.widget?.resourceUri)},
|
|
3367
|
+
name: ${JSON.stringify(entry.name)},
|
|
3368
|
+
description: ${JSON.stringify(entry.widget?.options?.description)},
|
|
3369
|
+
mimeType: "text/html;profile=mcp-app",
|
|
3370
|
+
text: readWidget(${JSON.stringify(entry.widget?.outputFile)}),
|
|
3371
|
+
_meta: ${JSON.stringify(entry.widget?.resourceMeta ?? void 0)},
|
|
3372
|
+
},`).join("\n");
|
|
3373
|
+
}
|
|
3374
|
+
function renderServerPackage(identity) {
|
|
3375
|
+
return `${JSON.stringify({
|
|
3376
|
+
name: `${identity.slug}-sidecar-server`,
|
|
3377
|
+
version: identity.version,
|
|
3378
|
+
private: true,
|
|
3379
|
+
type: "module",
|
|
3380
|
+
scripts: {
|
|
3381
|
+
start: "node server/index.js"
|
|
3382
|
+
},
|
|
3383
|
+
engines: {
|
|
3384
|
+
node: ">=20"
|
|
3385
|
+
}
|
|
3386
|
+
}, null, 2)}
|
|
3387
|
+
`;
|
|
3388
|
+
}
|
|
3389
|
+
function renderVercelEntrypoint() {
|
|
3390
|
+
return `export { default } from "./server/index.js";
|
|
3391
|
+
`;
|
|
3392
|
+
}
|
|
3393
|
+
function renderVercelFunctionConfig() {
|
|
3394
|
+
return `${JSON.stringify({
|
|
3395
|
+
runtime: "nodejs22.x",
|
|
3396
|
+
handler: VERCEL_HANDLER_FILE,
|
|
3397
|
+
launcherType: "Nodejs",
|
|
3398
|
+
shouldAddHelpers: true,
|
|
3399
|
+
supportsResponseStreaming: true,
|
|
3400
|
+
maxDuration: 300
|
|
3401
|
+
}, null, 2)}
|
|
3402
|
+
`;
|
|
3403
|
+
}
|
|
3404
|
+
function renderVercelOutputConfig() {
|
|
3405
|
+
return `${JSON.stringify({
|
|
3406
|
+
version: 3,
|
|
3407
|
+
routes: [
|
|
3408
|
+
{
|
|
3409
|
+
src: "/(.*)",
|
|
3410
|
+
dest: "/api/sidecar"
|
|
3411
|
+
}
|
|
3412
|
+
]
|
|
3413
|
+
}, null, 2)}
|
|
3414
|
+
`;
|
|
3415
|
+
}
|
|
3416
|
+
function sidecarBundleAliases(rootDir) {
|
|
3417
|
+
const repoRoot = findSidecarRepoRoot2(rootDir) ?? findSidecarRepoRoot2(process.cwd());
|
|
3418
|
+
if (!repoRoot) {
|
|
3419
|
+
return void 0;
|
|
3420
|
+
}
|
|
3421
|
+
return {
|
|
3422
|
+
"sidecar-ai": path18.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
|
|
3423
|
+
"@sidecar-ai/auth": path18.join(repoRoot, "packages", "auth", "src", "index.ts"),
|
|
3424
|
+
"@sidecar-ai/core": path18.join(repoRoot, "packages", "core", "src", "index.ts"),
|
|
3425
|
+
"@sidecar-ai/server": path18.join(repoRoot, "packages", "server", "src", "index.ts"),
|
|
3426
|
+
"@sidecar-ai/server/proxy": path18.join(repoRoot, "packages", "server", "src", "proxy.ts"),
|
|
3427
|
+
"@sidecar-ai/client": path18.join(repoRoot, "packages", "client", "src", "index.ts"),
|
|
3428
|
+
"@sidecar-ai/react": path18.join(repoRoot, "packages", "react", "src", "index.ts"),
|
|
3429
|
+
"@sidecar-ai/native": path18.join(repoRoot, "packages", "native", "src", "index.ts"),
|
|
3430
|
+
"@sidecar-ai/native/components": path18.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
|
|
3431
|
+
"@sidecar-ai/openai": path18.join(repoRoot, "packages", "openai", "src", "index.ts"),
|
|
3432
|
+
"@sidecar-ai/openai/components": path18.join(repoRoot, "packages", "openai", "src", "components.tsx"),
|
|
3433
|
+
"@sidecar-ai/openai/official": path18.join(repoRoot, "packages", "openai", "src", "official.ts"),
|
|
3434
|
+
"@sidecar-ai/anthropic": path18.join(repoRoot, "packages", "anthropic", "src", "index.ts"),
|
|
3435
|
+
"@sidecar-ai/anthropic/agent": path18.join(repoRoot, "packages", "anthropic", "src", "agent.ts"),
|
|
3436
|
+
"@sidecar-ai/anthropic/command": path18.join(repoRoot, "packages", "anthropic", "src", "command.ts"),
|
|
3437
|
+
"@sidecar-ai/anthropic/components": path18.join(repoRoot, "packages", "anthropic", "src", "components.tsx"),
|
|
3438
|
+
"@sidecar-ai/anthropic/hooks": path18.join(repoRoot, "packages", "anthropic", "src", "hooks.ts"),
|
|
3439
|
+
"@sidecar-ai/anthropic/mcp": path18.join(repoRoot, "packages", "anthropic", "src", "mcp.ts"),
|
|
3440
|
+
"@sidecar-ai/anthropic/plugin": path18.join(repoRoot, "packages", "anthropic", "src", "plugin.ts"),
|
|
3441
|
+
"@sidecar-ai/anthropic/skill": path18.join(repoRoot, "packages", "anthropic", "src", "skill.ts")
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3444
|
+
function findSidecarRepoRoot2(startDir) {
|
|
3445
|
+
let current = path18.resolve(startDir);
|
|
3446
|
+
while (true) {
|
|
3447
|
+
if (existsSync6(path18.join(current, "packages", "core", "src", "index.ts"))) {
|
|
3448
|
+
return current;
|
|
3449
|
+
}
|
|
3450
|
+
const parent = path18.dirname(current);
|
|
3451
|
+
if (parent === current) {
|
|
3452
|
+
return void 0;
|
|
3453
|
+
}
|
|
3454
|
+
current = parent;
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
|
|
2780
3458
|
// packages/compiler/src/build.ts
|
|
2781
3459
|
async function buildProject(options) {
|
|
2782
|
-
const rootDir =
|
|
2783
|
-
const target = options.target ?? "mcp";
|
|
3460
|
+
const rootDir = path19.resolve(options.rootDir);
|
|
2784
3461
|
const config = analyzeProjectConfig(rootDir);
|
|
3462
|
+
const target = options.target ?? config.build.target ?? "mcp";
|
|
3463
|
+
const host = options.host ?? config.build.host ?? "node";
|
|
3464
|
+
const plugins = options.plugins ?? config.build.plugins ?? false;
|
|
2785
3465
|
const tools = await analyzeProjectTools(rootDir, { target });
|
|
2786
3466
|
const resources = await analyzeProjectResources(rootDir);
|
|
2787
3467
|
const resourceTemplates = [];
|
|
2788
3468
|
const prompts = await analyzeProjectPrompts(rootDir);
|
|
3469
|
+
const identity = await loadProjectIdentity(rootDir);
|
|
2789
3470
|
const diagnostics = await collectProjectDiagnostics(rootDir, {
|
|
2790
3471
|
tools,
|
|
2791
3472
|
resources,
|
|
@@ -2796,11 +3477,13 @@ async function buildProject(options) {
|
|
|
2796
3477
|
if (errors.length) {
|
|
2797
3478
|
throw new Error(errors.map(formatDiagnostic).join("\n"));
|
|
2798
3479
|
}
|
|
2799
|
-
const outDir = resolveInsideRoot(rootDir, options.outDir ??
|
|
2800
|
-
|
|
3480
|
+
const outDir = resolveInsideRoot(rootDir, options.outDir ?? config.build.outDir ?? defaultBuildOutDir(host, target));
|
|
3481
|
+
const runtimeOutDir = resolveRuntimeOutputDir(outDir, host);
|
|
3482
|
+
await buildWidgets(rootDir, runtimeOutDir, tools, config.build.widgets);
|
|
2801
3483
|
const manifest = {
|
|
2802
3484
|
version: 1,
|
|
2803
3485
|
target,
|
|
3486
|
+
host,
|
|
2804
3487
|
rootDir: ".",
|
|
2805
3488
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2806
3489
|
config,
|
|
@@ -2810,23 +3493,47 @@ async function buildProject(options) {
|
|
|
2810
3493
|
prompts,
|
|
2811
3494
|
diagnostics
|
|
2812
3495
|
};
|
|
2813
|
-
await
|
|
2814
|
-
await
|
|
2815
|
-
|
|
3496
|
+
await mkdir9(runtimeOutDir, { recursive: true });
|
|
3497
|
+
await writeFile9(
|
|
3498
|
+
path19.join(runtimeOutDir, "manifest.sidecar.json"),
|
|
2816
3499
|
`${JSON.stringify(manifest, null, 2)}
|
|
2817
3500
|
`
|
|
2818
3501
|
);
|
|
2819
|
-
await
|
|
3502
|
+
await writeFile9(path19.join(runtimeOutDir, "README.md"), renderMcpReadme(manifest));
|
|
2820
3503
|
await writeGeneratedTypes(rootDir, tools);
|
|
2821
|
-
|
|
2822
|
-
|
|
3504
|
+
await buildServerOutput(rootDir, runtimeOutDir, manifest, identity, host, {
|
|
3505
|
+
vercelOutputDir: outDir
|
|
3506
|
+
});
|
|
3507
|
+
if (plugins) {
|
|
3508
|
+
await buildPluginPackages(rootDir, resolvePluginOutputBase(rootDir, outDir, host), manifest);
|
|
2823
3509
|
}
|
|
2824
3510
|
return manifest;
|
|
2825
3511
|
}
|
|
3512
|
+
function defaultBuildOutDir(host, target) {
|
|
3513
|
+
if (host === "vercel") {
|
|
3514
|
+
return ".vercel/output";
|
|
3515
|
+
}
|
|
3516
|
+
return `out/${target}`;
|
|
3517
|
+
}
|
|
3518
|
+
function resolveRuntimeOutputDir(outDir, host) {
|
|
3519
|
+
if (host === "vercel") {
|
|
3520
|
+
return path19.join(outDir, VERCEL_FUNCTION_DIR);
|
|
3521
|
+
}
|
|
3522
|
+
return outDir;
|
|
3523
|
+
}
|
|
3524
|
+
function resolvePluginOutputBase(rootDir, outDir, host) {
|
|
3525
|
+
if (host === "vercel" && isVercelBuildOutputDir(outDir)) {
|
|
3526
|
+
return path19.join(rootDir, "out");
|
|
3527
|
+
}
|
|
3528
|
+
return path19.dirname(outDir);
|
|
3529
|
+
}
|
|
3530
|
+
function isVercelBuildOutputDir(outDir) {
|
|
3531
|
+
return path19.basename(outDir) === "output" && path19.basename(path19.dirname(outDir)) === ".vercel";
|
|
3532
|
+
}
|
|
2826
3533
|
function resolveInsideRoot(rootDir, output) {
|
|
2827
|
-
const resolved =
|
|
2828
|
-
const relative =
|
|
2829
|
-
if (relative.startsWith("..") ||
|
|
3534
|
+
const resolved = path19.resolve(rootDir, output);
|
|
3535
|
+
const relative = path19.relative(rootDir, resolved);
|
|
3536
|
+
if (relative.startsWith("..") || path19.isAbsolute(relative)) {
|
|
2830
3537
|
throw new Error(`Build output must stay inside the project root: ${output}`);
|
|
2831
3538
|
}
|
|
2832
3539
|
return resolved;
|
|
@@ -2851,13 +3558,41 @@ ${resources || "No resources detected."}
|
|
|
2851
3558
|
|
|
2852
3559
|
${prompts || "No prompts detected."}
|
|
2853
3560
|
|
|
3561
|
+
${manifest.host === "vercel" ? renderVercelReadmeSection() : renderNodeReadmeSection()}
|
|
3562
|
+
|
|
2854
3563
|
## Local HTTPS Testing
|
|
2855
3564
|
|
|
2856
|
-
Run \`sidecar dev --tunnel\` from the project root to start the local MCP server on Streamable HTTP and print
|
|
3565
|
+
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.
|
|
3566
|
+
`;
|
|
3567
|
+
}
|
|
3568
|
+
function renderNodeReadmeSection() {
|
|
3569
|
+
return `## Run The Server
|
|
3570
|
+
|
|
3571
|
+
This output includes a standalone Node MCP server. Start it with:
|
|
3572
|
+
|
|
3573
|
+
\`\`\`sh
|
|
3574
|
+
npm start
|
|
3575
|
+
\`\`\`
|
|
3576
|
+
|
|
3577
|
+
or:
|
|
3578
|
+
|
|
3579
|
+
\`\`\`sh
|
|
3580
|
+
node server/index.js
|
|
3581
|
+
\`\`\`
|
|
3582
|
+
|
|
3583
|
+
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.
|
|
3584
|
+
`;
|
|
3585
|
+
}
|
|
3586
|
+
function renderVercelReadmeSection() {
|
|
3587
|
+
return `## Deploy To Vercel
|
|
3588
|
+
|
|
3589
|
+
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.
|
|
2857
3590
|
`;
|
|
2858
3591
|
}
|
|
2859
3592
|
export {
|
|
2860
3593
|
CompilerError,
|
|
3594
|
+
SERVER_ENTRYPOINT,
|
|
3595
|
+
VERCEL_ENTRYPOINT,
|
|
2861
3596
|
analyzeProjectConfig,
|
|
2862
3597
|
analyzeProjectPrompts,
|
|
2863
3598
|
analyzeProjectResources,
|