@sidecar-ai/compiler 0.1.0-alpha.0 → 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +22 -3
- package/dist/index.js +985 -270
- 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,40 +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
|
|
1564
|
-
import path6 from "path";
|
|
1927
|
+
import path7 from "path";
|
|
1565
1928
|
async function collectProjectDiagnostics(rootDir, input) {
|
|
1566
1929
|
const diagnostics = [];
|
|
1567
1930
|
const tools = Array.isArray(input) ? input : input.tools;
|
|
1568
1931
|
const resources = Array.isArray(input) ? [] : input.resources ?? [];
|
|
1569
1932
|
const prompts = Array.isArray(input) ? [] : input.prompts ?? [];
|
|
1570
1933
|
const config = Array.isArray(input) ? void 0 : input.config;
|
|
1571
|
-
const hasAuthConfig =
|
|
1572
|
-
const hasOpenAiAppsSdkUi = canResolveOpenAiAppsSdkUi(rootDir);
|
|
1934
|
+
const hasAuthConfig = existsSync4(path7.join(rootDir, "auth.ts"));
|
|
1573
1935
|
for (const entry of tools) {
|
|
1574
|
-
const toolPath =
|
|
1936
|
+
const toolPath = path7.join(rootDir, entry.sourceFile);
|
|
1575
1937
|
const toolSource = await readFile2(toolPath, "utf8");
|
|
1576
|
-
diagnostics.push(...diagnoseToolSource(
|
|
1938
|
+
diagnostics.push(...diagnoseToolSource(entry, toolSource, hasAuthConfig));
|
|
1577
1939
|
if (entry.widget) {
|
|
1578
|
-
const widgetPath =
|
|
1940
|
+
const widgetPath = path7.join(rootDir, entry.widget.sourceFile);
|
|
1579
1941
|
const widgetSource = await readFile2(widgetPath, "utf8");
|
|
1580
|
-
diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource
|
|
1942
|
+
diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource));
|
|
1581
1943
|
}
|
|
1582
1944
|
}
|
|
1583
1945
|
for (const entry of resources) {
|
|
1584
|
-
const resourcePath =
|
|
1946
|
+
const resourcePath = path7.join(rootDir, entry.sourceFile);
|
|
1585
1947
|
const resourceSource = await readFile2(resourcePath, "utf8");
|
|
1586
1948
|
diagnostics.push(...diagnoseResourceSource(entry, resourceSource));
|
|
1587
1949
|
}
|
|
1588
1950
|
for (const entry of prompts) {
|
|
1589
|
-
const promptPath =
|
|
1951
|
+
const promptPath = path7.join(rootDir, entry.sourceFile);
|
|
1590
1952
|
const promptSource = await readFile2(promptPath, "utf8");
|
|
1591
1953
|
diagnostics.push(...diagnosePromptSource(entry, promptSource));
|
|
1592
1954
|
}
|
|
@@ -1601,7 +1963,7 @@ function formatDiagnostic(diagnostic) {
|
|
|
1601
1963
|
hint: ${diagnostic.hint}` : "";
|
|
1602
1964
|
return `${location} - ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}${hint}`;
|
|
1603
1965
|
}
|
|
1604
|
-
function diagnoseToolSource(
|
|
1966
|
+
function diagnoseToolSource(entry, source, hasAuthConfig) {
|
|
1605
1967
|
const diagnostics = [];
|
|
1606
1968
|
const toolLocation = locate(source, "tool({");
|
|
1607
1969
|
if (!entry.description.trim().startsWith("Use this when")) {
|
|
@@ -1678,7 +2040,7 @@ function diagnoseToolSource(rootDir, entry, source, hasAuthConfig) {
|
|
|
1678
2040
|
}
|
|
1679
2041
|
return diagnostics.filter((diagnostic) => !isIgnored(source, diagnostic.code));
|
|
1680
2042
|
}
|
|
1681
|
-
function diagnoseWidgetSource(_rootDir, entry, source
|
|
2043
|
+
function diagnoseWidgetSource(_rootDir, entry, source) {
|
|
1682
2044
|
const diagnostics = [];
|
|
1683
2045
|
const widgetFile = entry.widget?.sourceFile;
|
|
1684
2046
|
if (!widgetFile) {
|
|
@@ -1714,16 +2076,6 @@ function diagnoseWidgetSource(_rootDir, entry, source, hasOpenAiAppsSdkUi) {
|
|
|
1714
2076
|
hint: "Use @sidecar-ai/native for portable primitives. Keep @sidecar-ai/openai/components for widgets intentionally targeted to ChatGPT."
|
|
1715
2077
|
});
|
|
1716
2078
|
}
|
|
1717
|
-
if (/@sidecar-ai\/openai\/components/.test(source) && !hasOpenAiAppsSdkUi && !isIgnored(source, "SIDECAR_OPENAI_UI_SDK_MISSING")) {
|
|
1718
|
-
diagnostics.push({
|
|
1719
|
-
severity: "error",
|
|
1720
|
-
code: "SIDECAR_OPENAI_UI_SDK_MISSING",
|
|
1721
|
-
message: `Widget for "${entry.id}" imports @sidecar-ai/openai/components but @openai/apps-sdk-ui is not installed.`,
|
|
1722
|
-
filePath: widgetFile,
|
|
1723
|
-
...locate(source, "@sidecar-ai/openai/components"),
|
|
1724
|
-
hint: "Install it with: npm install @openai/apps-sdk-ui"
|
|
1725
|
-
});
|
|
1726
|
-
}
|
|
1727
2079
|
if (entry.widget?.variant !== "anthropic" && /@sidecar-ai\/anthropic\/components/.test(source) && !isIgnored(source, "SIDECAR_ANTHROPIC_COMPONENT_CROSS_HOST")) {
|
|
1728
2080
|
diagnostics.push({
|
|
1729
2081
|
severity: "warning",
|
|
@@ -1813,14 +2165,6 @@ function hasCompanyKnowledgeShape(schema) {
|
|
|
1813
2165
|
const id = properties.id;
|
|
1814
2166
|
return query?.type === "string" || url?.type === "string" || id?.type === "string";
|
|
1815
2167
|
}
|
|
1816
|
-
function canResolveOpenAiAppsSdkUi(rootDir) {
|
|
1817
|
-
try {
|
|
1818
|
-
createRequire(path6.join(rootDir, "package.json")).resolve("@openai/apps-sdk-ui/css");
|
|
1819
|
-
return true;
|
|
1820
|
-
} catch {
|
|
1821
|
-
return false;
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
2168
|
function locate(source, needle) {
|
|
1825
2169
|
const index = Math.max(0, source.indexOf(needle));
|
|
1826
2170
|
const prefix = source.slice(0, index);
|
|
@@ -1836,21 +2180,21 @@ function isIgnored(source, code) {
|
|
|
1836
2180
|
|
|
1837
2181
|
// packages/compiler/src/generated.ts
|
|
1838
2182
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
1839
|
-
import
|
|
2183
|
+
import path8 from "path";
|
|
1840
2184
|
async function writeGeneratedTypes(rootDir, tools) {
|
|
1841
|
-
const generatedDir =
|
|
2185
|
+
const generatedDir = path8.join(rootDir, ".sidecar", "generated");
|
|
1842
2186
|
await mkdir2(generatedDir, { recursive: true });
|
|
1843
2187
|
const appCallableTools = tools.filter(isAppCallable);
|
|
1844
2188
|
const methodNames = uniqueMethodNames(appCallableTools.map((entry) => toIdentifier(entry.id)));
|
|
1845
2189
|
const imports = appCallableTools.map(
|
|
1846
|
-
(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" }))};`
|
|
1847
2191
|
).join("\n");
|
|
1848
2192
|
const ids = appCallableTools.map((entry, index) => ` ${methodNames[index]}: ${JSON.stringify(entry.id)}`).join(",\n");
|
|
1849
2193
|
const toolTypes = appCallableTools.map(
|
|
1850
|
-
(
|
|
2194
|
+
(_entry, index) => ` ${methodNames[index]}(params: ToolParams<typeof tool${index}>): Promise<ToolOutput<typeof tool${index}>>;`
|
|
1851
2195
|
).join("\n");
|
|
1852
2196
|
await writeFile2(
|
|
1853
|
-
|
|
2197
|
+
path8.join(generatedDir, "tools.ts"),
|
|
1854
2198
|
`/**
|
|
1855
2199
|
* Generated Sidecar widget tool client.
|
|
1856
2200
|
*
|
|
@@ -1898,19 +2242,15 @@ function uniqueMethodNames(names) {
|
|
|
1898
2242
|
});
|
|
1899
2243
|
}
|
|
1900
2244
|
|
|
1901
|
-
// packages/compiler/src/plugins.ts
|
|
1902
|
-
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
1903
|
-
import path14 from "path";
|
|
1904
|
-
|
|
1905
2245
|
// packages/compiler/src/identity.ts
|
|
1906
2246
|
import { readFile as readFile3 } from "fs/promises";
|
|
1907
|
-
import
|
|
2247
|
+
import path9 from "path";
|
|
1908
2248
|
async function loadProjectIdentity(rootDir) {
|
|
1909
|
-
const packageJson = await readOptionalJson(
|
|
2249
|
+
const packageJson = await readOptionalJson(path9.join(rootDir, "package.json"));
|
|
1910
2250
|
const configText = await readOptionalText(
|
|
1911
|
-
|
|
2251
|
+
path9.join(rootDir, "sidecar.config.ts")
|
|
1912
2252
|
);
|
|
1913
|
-
const name = readConfigString(configText, "name") ?? packageJson?.name ??
|
|
2253
|
+
const name = readConfigString(configText, "name") ?? packageJson?.name ?? path9.basename(rootDir);
|
|
1914
2254
|
const version = readConfigString(configText, "version") ?? packageJson?.version ?? "0.0.0-dev";
|
|
1915
2255
|
const description = readConfigString(configText, "description") ?? packageJson?.description ?? `${name} Sidecar app.`;
|
|
1916
2256
|
return {
|
|
@@ -1942,30 +2282,34 @@ function readConfigString(configText, key) {
|
|
|
1942
2282
|
return match?.[1];
|
|
1943
2283
|
}
|
|
1944
2284
|
|
|
2285
|
+
// packages/compiler/src/plugins.ts
|
|
2286
|
+
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
2287
|
+
import path15 from "path";
|
|
2288
|
+
|
|
1945
2289
|
// packages/compiler/src/plugin-components/agents.ts
|
|
1946
2290
|
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1947
|
-
import
|
|
2291
|
+
import path10 from "path";
|
|
1948
2292
|
async function emitClaudeAgents(rootDir, destination) {
|
|
1949
|
-
const source =
|
|
2293
|
+
const source = path10.join(rootDir, "agents");
|
|
1950
2294
|
if (!existsSyncSafe(source)) {
|
|
1951
2295
|
return;
|
|
1952
2296
|
}
|
|
1953
2297
|
const entries = await readdir2(source, { withFileTypes: true });
|
|
1954
2298
|
const agentDirs = entries.filter(
|
|
1955
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2299
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path10.join(source, entry.name, "agent.ts"))
|
|
1956
2300
|
);
|
|
1957
2301
|
if (!agentDirs.length) {
|
|
1958
2302
|
return;
|
|
1959
2303
|
}
|
|
1960
2304
|
await mkdir3(destination, { recursive: true });
|
|
1961
2305
|
for (const entry of agentDirs) {
|
|
1962
|
-
const sourceText = await readFile4(
|
|
2306
|
+
const sourceText = await readFile4(path10.join(source, entry.name, "agent.ts"), "utf8");
|
|
1963
2307
|
const markdown = parseClaudeAgent(
|
|
1964
2308
|
sourceText,
|
|
1965
2309
|
entry.name
|
|
1966
2310
|
);
|
|
1967
2311
|
const agentName = readObjectString(sourceText, "name") ?? entry.name;
|
|
1968
|
-
await writeFile3(
|
|
2312
|
+
await writeFile3(path10.join(destination, `${safeFileStem(agentName)}.md`), markdown);
|
|
1969
2313
|
}
|
|
1970
2314
|
}
|
|
1971
2315
|
function parseClaudeAgent(source, fallbackName) {
|
|
@@ -1994,41 +2338,41 @@ ${prompt.trim()}
|
|
|
1994
2338
|
|
|
1995
2339
|
// packages/compiler/src/plugin-components/commands.ts
|
|
1996
2340
|
import { cp, lstat, mkdir as mkdir4, readdir as readdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
1997
|
-
import
|
|
2341
|
+
import path11 from "path";
|
|
1998
2342
|
async function copyCommands(rootDir, destination) {
|
|
1999
|
-
const source =
|
|
2343
|
+
const source = path11.join(rootDir, "commands");
|
|
2000
2344
|
if (!existsSyncSafe(source)) {
|
|
2001
2345
|
return;
|
|
2002
2346
|
}
|
|
2003
2347
|
await mkdir4(destination, { recursive: true });
|
|
2004
2348
|
const entries = await readdir3(source, { withFileTypes: true });
|
|
2005
2349
|
for (const entry of entries) {
|
|
2006
|
-
const sourcePath =
|
|
2350
|
+
const sourcePath = path11.join(source, entry.name);
|
|
2007
2351
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
2008
2352
|
if (await safeCommandCopyFilter(sourcePath)) {
|
|
2009
|
-
await cp(sourcePath,
|
|
2353
|
+
await cp(sourcePath, path11.join(destination, safeFileStem(entry.name.replace(/\.md$/, "")) + ".md"));
|
|
2010
2354
|
}
|
|
2011
2355
|
continue;
|
|
2012
2356
|
}
|
|
2013
2357
|
if (!entry.isDirectory()) {
|
|
2014
2358
|
continue;
|
|
2015
2359
|
}
|
|
2016
|
-
const dynamicCommand =
|
|
2360
|
+
const dynamicCommand = path11.join(sourcePath, "command.ts");
|
|
2017
2361
|
if (existsSyncSafe(dynamicCommand)) {
|
|
2018
2362
|
const sourceText = await readFile5(dynamicCommand, "utf8");
|
|
2019
2363
|
const markdown = parseDynamicCommand(sourceText, entry.name);
|
|
2020
2364
|
const commandName = readObjectString(sourceText, "name") ?? entry.name;
|
|
2021
|
-
await writeFile4(
|
|
2365
|
+
await writeFile4(path11.join(destination, `${safeFileStem(commandName)}.md`), markdown);
|
|
2022
2366
|
continue;
|
|
2023
2367
|
}
|
|
2024
|
-
await cp(sourcePath,
|
|
2368
|
+
await cp(sourcePath, path11.join(destination, entry.name), {
|
|
2025
2369
|
recursive: true,
|
|
2026
2370
|
filter: safeCommandCopyFilter
|
|
2027
2371
|
});
|
|
2028
2372
|
}
|
|
2029
2373
|
}
|
|
2030
2374
|
async function safeCommandCopyFilter(sourcePath) {
|
|
2031
|
-
const basename =
|
|
2375
|
+
const basename = path11.basename(sourcePath);
|
|
2032
2376
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2033
2377
|
return false;
|
|
2034
2378
|
}
|
|
@@ -2057,32 +2401,32 @@ ${Object.entries(frontmatter).map(([key, value]) => `${key}: ${yamlScalar(value)
|
|
|
2057
2401
|
|
|
2058
2402
|
// packages/compiler/src/plugin-components/hooks.ts
|
|
2059
2403
|
import { mkdir as mkdir5, readdir as readdir4, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
2060
|
-
import
|
|
2404
|
+
import path12 from "path";
|
|
2061
2405
|
import {
|
|
2062
2406
|
Node as Node6,
|
|
2063
2407
|
Project as Project2,
|
|
2064
2408
|
SyntaxKind as SyntaxKind4
|
|
2065
2409
|
} from "ts-morph";
|
|
2066
2410
|
async function copyHooks(rootDir, destination) {
|
|
2067
|
-
const source =
|
|
2411
|
+
const source = path12.join(rootDir, "hooks");
|
|
2068
2412
|
if (!existsSyncSafe(source)) {
|
|
2069
2413
|
return;
|
|
2070
2414
|
}
|
|
2071
2415
|
const entries = await readdir4(source, { withFileTypes: true });
|
|
2072
2416
|
const hookDirs = entries.filter(
|
|
2073
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2417
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path12.join(source, entry.name, "hook.ts"))
|
|
2074
2418
|
);
|
|
2075
2419
|
if (!hookDirs.length) {
|
|
2076
2420
|
return;
|
|
2077
2421
|
}
|
|
2078
2422
|
const config = {};
|
|
2079
2423
|
for (const entry of hookDirs) {
|
|
2080
|
-
const filePath =
|
|
2424
|
+
const filePath = path12.join(source, entry.name, "hook.ts");
|
|
2081
2425
|
mergeHooks(config, parseHookFile(await readFile6(filePath, "utf8"), entry.name));
|
|
2082
2426
|
}
|
|
2083
2427
|
await mkdir5(destination, { recursive: true });
|
|
2084
2428
|
await writeFile5(
|
|
2085
|
-
|
|
2429
|
+
path12.join(destination, "hooks.json"),
|
|
2086
2430
|
`${JSON.stringify(config, null, 2)}
|
|
2087
2431
|
`
|
|
2088
2432
|
);
|
|
@@ -2109,7 +2453,7 @@ function parseHookFile(source, fallbackName) {
|
|
|
2109
2453
|
throw new Error(`hooks/${fallbackName}/hook.ts must default-export hook({ ... }) or hooks({ ... }).`);
|
|
2110
2454
|
}
|
|
2111
2455
|
function parseHookDefinition(definition, fallbackName) {
|
|
2112
|
-
const event =
|
|
2456
|
+
const event = readStringProperty4(definition, "event");
|
|
2113
2457
|
if (!event) {
|
|
2114
2458
|
throw new Error(`hooks/${fallbackName}/hook.ts must declare an event string.`);
|
|
2115
2459
|
}
|
|
@@ -2119,7 +2463,7 @@ function parseHookDefinition(definition, fallbackName) {
|
|
|
2119
2463
|
}
|
|
2120
2464
|
return {
|
|
2121
2465
|
event,
|
|
2122
|
-
matcher:
|
|
2466
|
+
matcher: readStringProperty4(definition, "matcher"),
|
|
2123
2467
|
run: hooks
|
|
2124
2468
|
};
|
|
2125
2469
|
}
|
|
@@ -2139,7 +2483,7 @@ function parseHooksDefinition(definition, fallbackName) {
|
|
|
2139
2483
|
throw new Error(`hooks/${fallbackName}/hook.ts event "${event}" entries must be objects.`);
|
|
2140
2484
|
}
|
|
2141
2485
|
return stripUndefined2({
|
|
2142
|
-
matcher:
|
|
2486
|
+
matcher: readStringProperty4(entry, "matcher"),
|
|
2143
2487
|
hooks: readHookArray(entry, "run", fallbackName)
|
|
2144
2488
|
});
|
|
2145
2489
|
});
|
|
@@ -2162,7 +2506,7 @@ function parseHookHandlers(handlers, fallbackName) {
|
|
|
2162
2506
|
}
|
|
2163
2507
|
function parseHookHandler(handler, fallbackName) {
|
|
2164
2508
|
if (Node6.isObjectLiteralExpression(handler)) {
|
|
2165
|
-
const type =
|
|
2509
|
+
const type = readStringProperty4(handler, "type");
|
|
2166
2510
|
if (type !== "command" && type !== "http") {
|
|
2167
2511
|
throw new Error(`hooks/${fallbackName}/hook.ts hook handlers need type "command" or "http".`);
|
|
2168
2512
|
}
|
|
@@ -2212,7 +2556,7 @@ function objectLiteralToRecord(object) {
|
|
|
2212
2556
|
}
|
|
2213
2557
|
return stripUndefined2(record);
|
|
2214
2558
|
}
|
|
2215
|
-
function
|
|
2559
|
+
function readStringProperty4(object, propertyName) {
|
|
2216
2560
|
const property = object.getProperty(propertyName);
|
|
2217
2561
|
if (!property || !Node6.isPropertyAssignment(property)) {
|
|
2218
2562
|
return void 0;
|
|
@@ -2256,7 +2600,7 @@ function mergeHooks(target, source) {
|
|
|
2256
2600
|
|
|
2257
2601
|
// packages/compiler/src/plugin-components/passthrough.ts
|
|
2258
2602
|
import { cp as cp2, lstat as lstat2 } from "fs/promises";
|
|
2259
|
-
import
|
|
2603
|
+
import path13 from "path";
|
|
2260
2604
|
async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
2261
2605
|
for (const directory of [
|
|
2262
2606
|
"assets",
|
|
@@ -2265,18 +2609,18 @@ async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
|
2265
2609
|
"output-styles",
|
|
2266
2610
|
"themes"
|
|
2267
2611
|
]) {
|
|
2268
|
-
const source =
|
|
2612
|
+
const source = path13.join(rootDir, directory);
|
|
2269
2613
|
if (!existsSyncSafe(source)) {
|
|
2270
2614
|
continue;
|
|
2271
2615
|
}
|
|
2272
|
-
await cp2(source,
|
|
2616
|
+
await cp2(source, path13.join(pluginDir, directory), {
|
|
2273
2617
|
recursive: true,
|
|
2274
2618
|
filter: safePluginCopyFilter
|
|
2275
2619
|
});
|
|
2276
2620
|
}
|
|
2277
2621
|
}
|
|
2278
2622
|
async function safePluginCopyFilter(sourcePath) {
|
|
2279
|
-
const basename =
|
|
2623
|
+
const basename = path13.basename(sourcePath);
|
|
2280
2624
|
if (basename === "node_modules" || basename === ".git" || basename === ".env" || basename.startsWith(".env.")) {
|
|
2281
2625
|
return false;
|
|
2282
2626
|
}
|
|
@@ -2285,11 +2629,11 @@ async function safePluginCopyFilter(sourcePath) {
|
|
|
2285
2629
|
}
|
|
2286
2630
|
|
|
2287
2631
|
// packages/compiler/src/plugin-components/skills.ts
|
|
2288
|
-
import { existsSync as
|
|
2632
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2289
2633
|
import { cp as cp3, lstat as lstat3, mkdir as mkdir6, readdir as readdir5, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
2290
|
-
import
|
|
2634
|
+
import path14 from "path";
|
|
2291
2635
|
async function copySkills(rootDir, destination) {
|
|
2292
|
-
const source =
|
|
2636
|
+
const source = path14.join(rootDir, "skills");
|
|
2293
2637
|
if (!existsSyncSafe(source)) {
|
|
2294
2638
|
return;
|
|
2295
2639
|
}
|
|
@@ -2299,27 +2643,27 @@ async function copySkills(rootDir, destination) {
|
|
|
2299
2643
|
if (!entry.isDirectory()) {
|
|
2300
2644
|
continue;
|
|
2301
2645
|
}
|
|
2302
|
-
const sourceDir =
|
|
2303
|
-
const destinationDir =
|
|
2304
|
-
const staticSkill =
|
|
2305
|
-
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");
|
|
2306
2650
|
await mkdir6(destinationDir, { recursive: true });
|
|
2307
|
-
if (
|
|
2651
|
+
if (existsSync5(staticSkill)) {
|
|
2308
2652
|
await cp3(sourceDir, destinationDir, {
|
|
2309
2653
|
recursive: true,
|
|
2310
|
-
filter: async (sourcePath) => !sourcePath.endsWith(`${
|
|
2654
|
+
filter: async (sourcePath) => !sourcePath.endsWith(`${path14.sep}skill.ts`) && await safeSkillCopyFilter(sourcePath)
|
|
2311
2655
|
});
|
|
2312
|
-
} else if (
|
|
2656
|
+
} else if (existsSync5(dynamicSkill)) {
|
|
2313
2657
|
const generated = parseDynamicSkill(
|
|
2314
2658
|
await readFile7(dynamicSkill, "utf8"),
|
|
2315
2659
|
entry.name
|
|
2316
2660
|
);
|
|
2317
|
-
await writeFile6(
|
|
2661
|
+
await writeFile6(path14.join(destinationDir, "SKILL.md"), generated);
|
|
2318
2662
|
}
|
|
2319
2663
|
}
|
|
2320
2664
|
}
|
|
2321
2665
|
async function safeSkillCopyFilter(sourcePath) {
|
|
2322
|
-
const basename =
|
|
2666
|
+
const basename = path14.basename(sourcePath);
|
|
2323
2667
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2324
2668
|
return false;
|
|
2325
2669
|
}
|
|
@@ -2345,25 +2689,25 @@ async function buildPluginPackages(rootDir, outRoot, manifest) {
|
|
|
2345
2689
|
await buildClaudePlugin(rootDir, outRoot, identity, manifest);
|
|
2346
2690
|
}
|
|
2347
2691
|
async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
2348
|
-
const pluginDir =
|
|
2349
|
-
await mkdir7(
|
|
2350
|
-
await copySkills(rootDir,
|
|
2351
|
-
await copyCommands(rootDir,
|
|
2352
|
-
await copyHooks(rootDir,
|
|
2353
|
-
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"));
|
|
2354
2698
|
await copyClaudePassthroughDirectories(rootDir, pluginDir);
|
|
2355
|
-
await writeJson(
|
|
2699
|
+
await writeJson(path15.join(pluginDir, ".claude-plugin", "plugin.json"), {
|
|
2356
2700
|
name: identity.slug,
|
|
2357
2701
|
version: identity.version,
|
|
2358
2702
|
description: identity.description,
|
|
2359
2703
|
displayName: identity.name,
|
|
2360
2704
|
installationPreference: "available"
|
|
2361
2705
|
});
|
|
2362
|
-
await writeJson(
|
|
2706
|
+
await writeJson(path15.join(pluginDir, "version.json"), {
|
|
2363
2707
|
version: identity.version,
|
|
2364
2708
|
generatedBy: "sidecar"
|
|
2365
2709
|
});
|
|
2366
|
-
await writeJson(
|
|
2710
|
+
await writeJson(path15.join(pluginDir, ".mcp.json"), {
|
|
2367
2711
|
mcpServers: {
|
|
2368
2712
|
[identity.slug]: {
|
|
2369
2713
|
type: "http",
|
|
@@ -2371,7 +2715,7 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2371
2715
|
}
|
|
2372
2716
|
}
|
|
2373
2717
|
});
|
|
2374
|
-
await writeJson(
|
|
2718
|
+
await writeJson(path15.join(pluginDir, "settings.json"), {
|
|
2375
2719
|
sidecar: {
|
|
2376
2720
|
tools: manifest.tools.map((entry) => entry.id),
|
|
2377
2721
|
resources: manifest.resources.map((entry) => entry.uri),
|
|
@@ -2379,21 +2723,21 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2379
2723
|
}
|
|
2380
2724
|
});
|
|
2381
2725
|
await writeFile7(
|
|
2382
|
-
|
|
2726
|
+
path15.join(pluginDir, "README.md"),
|
|
2383
2727
|
`# ${identity.name} Claude Plugin
|
|
2384
2728
|
|
|
2385
2729
|
This package was generated by Sidecar.
|
|
2386
2730
|
|
|
2387
2731
|
Set \`SIDECAR_MCP_URL\` to the hosted MCP endpoint for this app.
|
|
2388
2732
|
|
|
2389
|
-
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.
|
|
2390
2734
|
|
|
2391
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.
|
|
2392
2736
|
`
|
|
2393
2737
|
);
|
|
2394
2738
|
}
|
|
2395
2739
|
async function writeJson(filePath, value) {
|
|
2396
|
-
await mkdir7(
|
|
2740
|
+
await mkdir7(path15.dirname(filePath), { recursive: true });
|
|
2397
2741
|
await writeFile7(
|
|
2398
2742
|
filePath,
|
|
2399
2743
|
`${JSON.stringify(stripUndefined2(value), null, 2)}
|
|
@@ -2403,13 +2747,13 @@ async function writeJson(filePath, value) {
|
|
|
2403
2747
|
|
|
2404
2748
|
// packages/compiler/src/prompts.ts
|
|
2405
2749
|
import { readdir as readdir6 } from "fs/promises";
|
|
2406
|
-
import
|
|
2750
|
+
import path16 from "path";
|
|
2407
2751
|
import {
|
|
2408
2752
|
Node as Node7,
|
|
2409
2753
|
SyntaxKind as SyntaxKind5
|
|
2410
2754
|
} from "ts-morph";
|
|
2411
2755
|
async function analyzeProjectPrompts(rootDir) {
|
|
2412
|
-
const promptFiles = await findPromptFiles(
|
|
2756
|
+
const promptFiles = await findPromptFiles(path16.join(rootDir, "prompts"));
|
|
2413
2757
|
if (promptFiles.length === 0) {
|
|
2414
2758
|
return [];
|
|
2415
2759
|
}
|
|
@@ -2421,7 +2765,7 @@ async function analyzeProjectPrompts(rootDir) {
|
|
|
2421
2765
|
function analyzePromptFile(sourceFile, rootDir) {
|
|
2422
2766
|
const definition = findPromptDefinition(sourceFile);
|
|
2423
2767
|
const absoluteFile = sourceFile.getFilePath();
|
|
2424
|
-
const directory =
|
|
2768
|
+
const directory = path16.basename(path16.dirname(absoluteFile));
|
|
2425
2769
|
const name = getOptionalStringProperty2(definition, "name") ?? safePathSegment(directory);
|
|
2426
2770
|
const title = getRequiredStringProperty2(definition, "title", sourceFile);
|
|
2427
2771
|
const description = getOptionalStringProperty2(definition, "description");
|
|
@@ -2437,7 +2781,7 @@ function analyzePromptFile(sourceFile, rootDir) {
|
|
|
2437
2781
|
throw new CompilerError(sourceFile, "prompt({ ... }) must include a run method.");
|
|
2438
2782
|
}
|
|
2439
2783
|
return {
|
|
2440
|
-
sourceFile:
|
|
2784
|
+
sourceFile: path16.relative(rootDir, absoluteFile),
|
|
2441
2785
|
directory,
|
|
2442
2786
|
name,
|
|
2443
2787
|
title,
|
|
@@ -2456,7 +2800,7 @@ async function findPromptFiles(promptsDir) {
|
|
|
2456
2800
|
if (!entry.isDirectory()) {
|
|
2457
2801
|
continue;
|
|
2458
2802
|
}
|
|
2459
|
-
const filePath =
|
|
2803
|
+
const filePath = path16.join(promptsDir, entry.name, "prompt.ts");
|
|
2460
2804
|
if (existsSyncSafe(filePath)) {
|
|
2461
2805
|
files.push(filePath);
|
|
2462
2806
|
}
|
|
@@ -2578,7 +2922,7 @@ function readIcons(definition) {
|
|
|
2578
2922
|
return [{
|
|
2579
2923
|
src,
|
|
2580
2924
|
mimeType: getOptionalStringProperty2(object, "mimeType"),
|
|
2581
|
-
sizes:
|
|
2925
|
+
sizes: readStringArrayProperty4(object, "sizes")
|
|
2582
2926
|
}];
|
|
2583
2927
|
});
|
|
2584
2928
|
return icons.length ? icons : void 0;
|
|
@@ -2591,7 +2935,7 @@ function readObjectProperty4(definition, propertyName) {
|
|
|
2591
2935
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2592
2936
|
return initializer && Node7.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2593
2937
|
}
|
|
2594
|
-
function
|
|
2938
|
+
function readStringArrayProperty4(definition, propertyName) {
|
|
2595
2939
|
const property = definition.getProperty(propertyName);
|
|
2596
2940
|
if (!property || !Node7.isPropertyAssignment(property)) {
|
|
2597
2941
|
return void 0;
|
|
@@ -2605,13 +2949,13 @@ function readStringArrayProperty3(definition, propertyName) {
|
|
|
2605
2949
|
|
|
2606
2950
|
// packages/compiler/src/resources.ts
|
|
2607
2951
|
import { readdir as readdir7 } from "fs/promises";
|
|
2608
|
-
import
|
|
2952
|
+
import path17 from "path";
|
|
2609
2953
|
import {
|
|
2610
2954
|
Node as Node8,
|
|
2611
2955
|
SyntaxKind as SyntaxKind6
|
|
2612
2956
|
} from "ts-morph";
|
|
2613
2957
|
async function analyzeProjectResources(rootDir) {
|
|
2614
|
-
const resourceFiles = await findResourceFiles(
|
|
2958
|
+
const resourceFiles = await findResourceFiles(path17.join(rootDir, "resources"));
|
|
2615
2959
|
if (resourceFiles.length === 0) {
|
|
2616
2960
|
return [];
|
|
2617
2961
|
}
|
|
@@ -2623,7 +2967,7 @@ async function analyzeProjectResources(rootDir) {
|
|
|
2623
2967
|
function analyzeResourceFile(sourceFile, rootDir) {
|
|
2624
2968
|
const definition = findResourceDefinition(sourceFile);
|
|
2625
2969
|
const absoluteFile = sourceFile.getFilePath();
|
|
2626
|
-
const directory =
|
|
2970
|
+
const directory = path17.basename(path17.dirname(absoluteFile));
|
|
2627
2971
|
const defaultUri = `sidecar://resources/${safePathSegment(directory)}`;
|
|
2628
2972
|
const name = getRequiredStringProperty3(definition, "name", sourceFile);
|
|
2629
2973
|
const uri = getOptionalStringProperty3(definition, "uri") ?? defaultUri;
|
|
@@ -2641,7 +2985,7 @@ function analyzeResourceFile(sourceFile, rootDir) {
|
|
|
2641
2985
|
throw new CompilerError(sourceFile, "resource({ ... }) must include a read method.");
|
|
2642
2986
|
}
|
|
2643
2987
|
return {
|
|
2644
|
-
sourceFile:
|
|
2988
|
+
sourceFile: path17.relative(rootDir, absoluteFile),
|
|
2645
2989
|
directory,
|
|
2646
2990
|
uri,
|
|
2647
2991
|
name,
|
|
@@ -2664,7 +3008,7 @@ async function findResourceFiles(resourcesDir) {
|
|
|
2664
3008
|
if (!entry.isDirectory()) {
|
|
2665
3009
|
continue;
|
|
2666
3010
|
}
|
|
2667
|
-
const filePath =
|
|
3011
|
+
const filePath = path17.join(resourcesDir, entry.name, "resource.ts");
|
|
2668
3012
|
if (existsSyncSafe(filePath)) {
|
|
2669
3013
|
files.push(filePath);
|
|
2670
3014
|
}
|
|
@@ -2743,7 +3087,7 @@ function readAnnotations2(definition) {
|
|
|
2743
3087
|
return void 0;
|
|
2744
3088
|
}
|
|
2745
3089
|
const annotations = {};
|
|
2746
|
-
const audience =
|
|
3090
|
+
const audience = readStringArrayProperty5(initializer, "audience")?.filter((value) => value === "user" || value === "assistant");
|
|
2747
3091
|
const priority = getOptionalNumberProperty(initializer, "priority");
|
|
2748
3092
|
const lastModified = getOptionalStringProperty3(initializer, "lastModified");
|
|
2749
3093
|
if (audience?.length) annotations.audience = audience;
|
|
@@ -2772,7 +3116,7 @@ function readIcons2(definition) {
|
|
|
2772
3116
|
return [{
|
|
2773
3117
|
src,
|
|
2774
3118
|
mimeType: getOptionalStringProperty3(object, "mimeType"),
|
|
2775
|
-
sizes:
|
|
3119
|
+
sizes: readStringArrayProperty5(object, "sizes")
|
|
2776
3120
|
}];
|
|
2777
3121
|
});
|
|
2778
3122
|
return icons.length ? icons : void 0;
|
|
@@ -2785,7 +3129,7 @@ function readObjectProperty5(definition, propertyName) {
|
|
|
2785
3129
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2786
3130
|
return initializer && Node8.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2787
3131
|
}
|
|
2788
|
-
function
|
|
3132
|
+
function readStringArrayProperty5(definition, propertyName) {
|
|
2789
3133
|
const property = definition.getProperty(propertyName);
|
|
2790
3134
|
if (!property || !Node8.isPropertyAssignment(property)) {
|
|
2791
3135
|
return void 0;
|
|
@@ -2797,15 +3141,332 @@ function readStringArrayProperty4(definition, propertyName) {
|
|
|
2797
3141
|
return initializer.getElements().filter(Node8.isStringLiteral).map((element) => element.getLiteralText());
|
|
2798
3142
|
}
|
|
2799
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
|
+
|
|
2800
3458
|
// packages/compiler/src/build.ts
|
|
2801
3459
|
async function buildProject(options) {
|
|
2802
|
-
const rootDir =
|
|
2803
|
-
const target = options.target ?? "mcp";
|
|
3460
|
+
const rootDir = path19.resolve(options.rootDir);
|
|
2804
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;
|
|
2805
3465
|
const tools = await analyzeProjectTools(rootDir, { target });
|
|
2806
3466
|
const resources = await analyzeProjectResources(rootDir);
|
|
2807
3467
|
const resourceTemplates = [];
|
|
2808
3468
|
const prompts = await analyzeProjectPrompts(rootDir);
|
|
3469
|
+
const identity = await loadProjectIdentity(rootDir);
|
|
2809
3470
|
const diagnostics = await collectProjectDiagnostics(rootDir, {
|
|
2810
3471
|
tools,
|
|
2811
3472
|
resources,
|
|
@@ -2816,11 +3477,13 @@ async function buildProject(options) {
|
|
|
2816
3477
|
if (errors.length) {
|
|
2817
3478
|
throw new Error(errors.map(formatDiagnostic).join("\n"));
|
|
2818
3479
|
}
|
|
2819
|
-
const outDir = resolveInsideRoot(rootDir, options.outDir ??
|
|
2820
|
-
|
|
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);
|
|
2821
3483
|
const manifest = {
|
|
2822
3484
|
version: 1,
|
|
2823
3485
|
target,
|
|
3486
|
+
host,
|
|
2824
3487
|
rootDir: ".",
|
|
2825
3488
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2826
3489
|
config,
|
|
@@ -2830,23 +3493,47 @@ async function buildProject(options) {
|
|
|
2830
3493
|
prompts,
|
|
2831
3494
|
diagnostics
|
|
2832
3495
|
};
|
|
2833
|
-
await
|
|
2834
|
-
await
|
|
2835
|
-
|
|
3496
|
+
await mkdir9(runtimeOutDir, { recursive: true });
|
|
3497
|
+
await writeFile9(
|
|
3498
|
+
path19.join(runtimeOutDir, "manifest.sidecar.json"),
|
|
2836
3499
|
`${JSON.stringify(manifest, null, 2)}
|
|
2837
3500
|
`
|
|
2838
3501
|
);
|
|
2839
|
-
await
|
|
3502
|
+
await writeFile9(path19.join(runtimeOutDir, "README.md"), renderMcpReadme(manifest));
|
|
2840
3503
|
await writeGeneratedTypes(rootDir, tools);
|
|
2841
|
-
|
|
2842
|
-
|
|
3504
|
+
await buildServerOutput(rootDir, runtimeOutDir, manifest, identity, host, {
|
|
3505
|
+
vercelOutputDir: outDir
|
|
3506
|
+
});
|
|
3507
|
+
if (plugins) {
|
|
3508
|
+
await buildPluginPackages(rootDir, resolvePluginOutputBase(rootDir, outDir, host), manifest);
|
|
2843
3509
|
}
|
|
2844
3510
|
return manifest;
|
|
2845
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
|
+
}
|
|
2846
3533
|
function resolveInsideRoot(rootDir, output) {
|
|
2847
|
-
const resolved =
|
|
2848
|
-
const relative =
|
|
2849
|
-
if (relative.startsWith("..") ||
|
|
3534
|
+
const resolved = path19.resolve(rootDir, output);
|
|
3535
|
+
const relative = path19.relative(rootDir, resolved);
|
|
3536
|
+
if (relative.startsWith("..") || path19.isAbsolute(relative)) {
|
|
2850
3537
|
throw new Error(`Build output must stay inside the project root: ${output}`);
|
|
2851
3538
|
}
|
|
2852
3539
|
return resolved;
|
|
@@ -2871,13 +3558,41 @@ ${resources || "No resources detected."}
|
|
|
2871
3558
|
|
|
2872
3559
|
${prompts || "No prompts detected."}
|
|
2873
3560
|
|
|
3561
|
+
${manifest.host === "vercel" ? renderVercelReadmeSection() : renderNodeReadmeSection()}
|
|
3562
|
+
|
|
2874
3563
|
## Local HTTPS Testing
|
|
2875
3564
|
|
|
2876
|
-
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.
|
|
2877
3590
|
`;
|
|
2878
3591
|
}
|
|
2879
3592
|
export {
|
|
2880
3593
|
CompilerError,
|
|
3594
|
+
SERVER_ENTRYPOINT,
|
|
3595
|
+
VERCEL_ENTRYPOINT,
|
|
2881
3596
|
analyzeProjectConfig,
|
|
2882
3597
|
analyzeProjectPrompts,
|
|
2883
3598
|
analyzeProjectResources,
|