@sidecar-ai/compiler 0.1.0-alpha.1 → 0.1.0-alpha.11
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 +11 -134
- package/dist/index.js +1301 -253
- 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,396 @@ 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
|
+
async function buildCodeModeWidget(rootDir, outDir, tools, target, config = void 0) {
|
|
819
|
+
const renderable = tools.filter(
|
|
820
|
+
(entry) => Boolean(entry.widget)
|
|
821
|
+
);
|
|
822
|
+
if (!renderable.length) {
|
|
823
|
+
return void 0;
|
|
824
|
+
}
|
|
825
|
+
const cacheDir = path4.join(rootDir, ".sidecar", "cache", "widgets");
|
|
826
|
+
await mkdir(cacheDir, { recursive: true });
|
|
827
|
+
const appStyle = await prepareAppStyle(rootDir, cacheDir);
|
|
828
|
+
const configure = await loadWidgetBundlerHook(rootDir, cacheDir, config?.configure);
|
|
829
|
+
const safeId = "code-mode";
|
|
830
|
+
const entryFile = path4.join(cacheDir, `${safeId}.entry.tsx`);
|
|
831
|
+
await writeFile(entryFile, renderCodeModeWidgetEntry(rootDir, entryFile, renderable, appStyle));
|
|
832
|
+
const baseOptions = enforceWidgetBuildInvariants({
|
|
833
|
+
absWorkingDir: rootDir,
|
|
834
|
+
alias: sidecarWidgetAliases(rootDir),
|
|
835
|
+
bundle: true,
|
|
836
|
+
define: {
|
|
837
|
+
"process.env.NODE_ENV": JSON.stringify("production")
|
|
838
|
+
},
|
|
839
|
+
entryPoints: [entryFile],
|
|
840
|
+
format: "iife",
|
|
841
|
+
jsx: "automatic",
|
|
842
|
+
minify: true,
|
|
843
|
+
nodePaths: [
|
|
844
|
+
path4.join(rootDir, "node_modules"),
|
|
845
|
+
path4.join(process.cwd(), "node_modules")
|
|
846
|
+
],
|
|
847
|
+
outfile: "widget.js",
|
|
848
|
+
platform: "browser",
|
|
849
|
+
sourcemap: false,
|
|
850
|
+
write: false
|
|
851
|
+
});
|
|
852
|
+
const staticOptions = widgetBuildOptionsFromConfig(rootDir, config);
|
|
853
|
+
const configuredOptions = configure ? await configureWidgetBuild(configure, {
|
|
854
|
+
rootDir,
|
|
855
|
+
outDir,
|
|
856
|
+
entryFile,
|
|
857
|
+
widget: {
|
|
858
|
+
toolId: "execute_code",
|
|
859
|
+
toolName: "Execute Code",
|
|
860
|
+
target,
|
|
861
|
+
sourceFile: path4.relative(rootDir, entryFile)
|
|
862
|
+
},
|
|
863
|
+
esbuildOptions: mergeWidgetBuildOptions(baseOptions, staticOptions)
|
|
864
|
+
}) : void 0;
|
|
865
|
+
const bundled = await esbuild(enforceWidgetBuildInvariants(
|
|
866
|
+
mergeWidgetBuildOptions(baseOptions, staticOptions, configuredOptions),
|
|
867
|
+
{ rootDir, entryFile }
|
|
868
|
+
));
|
|
869
|
+
const outputFiles = bundled.outputFiles ?? [];
|
|
870
|
+
const javascript = outputFiles.find((file) => file.path.endsWith(".js"))?.text ?? "";
|
|
871
|
+
const css = outputFiles.filter((file) => file.path.endsWith(".css")).map((file) => file.text).join("\n");
|
|
872
|
+
const html = renderWidgetHtml("Execute Code", javascript, css);
|
|
873
|
+
const hash = createHash("sha256").update(html).digest("hex").slice(0, 12);
|
|
874
|
+
const outputDir = path4.join(outDir, "public", "widgets", safeId);
|
|
875
|
+
const outputFile = path4.join(outputDir, `widget.${hash}.html`);
|
|
876
|
+
const resourceUri = `ui://${safeId}/widget.${hash}.html`;
|
|
877
|
+
const options = {
|
|
878
|
+
description: "Renders the selected result from a Sidecar code-mode execution.",
|
|
879
|
+
csp: {
|
|
880
|
+
connectDomains: [],
|
|
881
|
+
resourceDomains: []
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
await mkdir(outputDir, { recursive: true });
|
|
885
|
+
await writeFile(outputFile, html);
|
|
886
|
+
return {
|
|
887
|
+
sourceFile: path4.relative(rootDir, entryFile),
|
|
888
|
+
variant: "shared",
|
|
889
|
+
resourceUri,
|
|
890
|
+
resourceMeta: widgetResourceMeta(options, target),
|
|
891
|
+
outputFile: path4.relative(outDir, outputFile),
|
|
892
|
+
options
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function widgetBuildOptionsFromConfig(rootDir, config) {
|
|
896
|
+
const esbuildConfig = config?.esbuild;
|
|
897
|
+
if (!esbuildConfig) {
|
|
898
|
+
return void 0;
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
alias: normalizeAliases(rootDir, esbuildConfig.alias),
|
|
902
|
+
conditions: esbuildConfig.conditions,
|
|
903
|
+
define: esbuildConfig.define,
|
|
904
|
+
external: esbuildConfig.external,
|
|
905
|
+
jsx: esbuildConfig.jsx,
|
|
906
|
+
jsxImportSource: esbuildConfig.jsxImportSource,
|
|
907
|
+
loader: esbuildConfig.loader,
|
|
908
|
+
mainFields: esbuildConfig.mainFields
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
async function loadWidgetBundlerHook(rootDir, cacheDir, hookPath) {
|
|
912
|
+
if (!hookPath) {
|
|
913
|
+
return void 0;
|
|
914
|
+
}
|
|
915
|
+
const sourcePath = path4.resolve(rootDir, hookPath);
|
|
916
|
+
const relative = path4.relative(rootDir, sourcePath);
|
|
917
|
+
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
918
|
+
throw new Error(`Widget bundler hook must stay inside the project root: ${hookPath}`);
|
|
919
|
+
}
|
|
920
|
+
if (!existsSync3(sourcePath)) {
|
|
921
|
+
throw new Error(`Widget bundler hook not found: ${hookPath}`);
|
|
922
|
+
}
|
|
923
|
+
const outputFile = path4.join(cacheDir, `widget-bundler.${contentHash(hookPath)}.mjs`);
|
|
924
|
+
await esbuild({
|
|
925
|
+
absWorkingDir: rootDir,
|
|
926
|
+
alias: devSidecarBundleAliases(rootDir),
|
|
927
|
+
bundle: true,
|
|
928
|
+
entryPoints: [sourcePath],
|
|
929
|
+
format: "esm",
|
|
930
|
+
nodePaths: [
|
|
931
|
+
path4.join(rootDir, "node_modules"),
|
|
932
|
+
path4.join(process.cwd(), "node_modules")
|
|
933
|
+
],
|
|
934
|
+
outfile: outputFile,
|
|
935
|
+
packages: "external",
|
|
936
|
+
platform: "node",
|
|
937
|
+
target: "node20"
|
|
938
|
+
});
|
|
939
|
+
const module = await import(`${pathToFileURL2(outputFile).href}?t=${Date.now()}`);
|
|
940
|
+
if (typeof module.default !== "function") {
|
|
941
|
+
throw new Error(`Widget bundler hook must default-export a function: ${hookPath}`);
|
|
942
|
+
}
|
|
943
|
+
return module.default;
|
|
944
|
+
}
|
|
945
|
+
async function configureWidgetBuild(hook, input) {
|
|
946
|
+
const result = await hook(input);
|
|
947
|
+
if (!result) {
|
|
948
|
+
return void 0;
|
|
949
|
+
}
|
|
950
|
+
if (typeof result === "object" && "esbuildOptions" in result) {
|
|
951
|
+
return result.esbuildOptions;
|
|
952
|
+
}
|
|
953
|
+
return result;
|
|
954
|
+
}
|
|
955
|
+
function mergeWidgetBuildOptions(...options) {
|
|
956
|
+
const merged = {};
|
|
957
|
+
for (const option of options) {
|
|
958
|
+
if (!option) {
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
const previousAlias = merged.alias;
|
|
962
|
+
const previousDefine = merged.define;
|
|
963
|
+
const previousLoader = merged.loader;
|
|
964
|
+
const previousExternal = merged.external;
|
|
965
|
+
const previousNodePaths = merged.nodePaths;
|
|
966
|
+
const previousPlugins = merged.plugins;
|
|
967
|
+
Object.assign(merged, option);
|
|
968
|
+
merged.alias = { ...previousAlias ?? {}, ...option.alias ?? {} };
|
|
969
|
+
merged.define = { ...previousDefine ?? {}, ...option.define ?? {} };
|
|
970
|
+
merged.loader = { ...previousLoader ?? {}, ...option.loader ?? {} };
|
|
971
|
+
merged.external = uniqueStrings([...previousExternal ?? [], ...option.external ?? []]);
|
|
972
|
+
merged.nodePaths = uniqueStrings([...previousNodePaths ?? [], ...option.nodePaths ?? []]);
|
|
973
|
+
merged.plugins = [...previousPlugins ?? [], ...option.plugins ?? []];
|
|
974
|
+
}
|
|
975
|
+
return merged;
|
|
976
|
+
}
|
|
977
|
+
function enforceWidgetBuildInvariants(options, required) {
|
|
978
|
+
const aliases = required ? { ...options.alias ?? {}, ...reactSingletonAliases(required.rootDir) } : options.alias;
|
|
979
|
+
return {
|
|
980
|
+
...options,
|
|
981
|
+
absWorkingDir: required?.rootDir ?? options.absWorkingDir,
|
|
982
|
+
alias: aliases,
|
|
983
|
+
bundle: true,
|
|
984
|
+
entryPoints: required ? [required.entryFile] : options.entryPoints,
|
|
985
|
+
format: "iife",
|
|
986
|
+
outfile: "widget.js",
|
|
987
|
+
platform: "browser",
|
|
988
|
+
write: false
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function renderCodeModeWidgetEntry(rootDir, entryFile, entries, appStyle) {
|
|
992
|
+
const entryDir = path4.dirname(entryFile);
|
|
993
|
+
const imports = entries.map((entry, index) => {
|
|
994
|
+
const sourceFile = path4.join(rootDir, entry.widget.sourceFile);
|
|
995
|
+
return `import Widget${index} from ${JSON.stringify(toImportSpecifier(entryDir, sourceFile))};`;
|
|
996
|
+
}).join("\n");
|
|
997
|
+
const registry = entries.map((entry, index) => `${JSON.stringify(entry.id)}: Widget${index}`).join(",\n ");
|
|
998
|
+
return `import React, { useMemo } from "react";
|
|
999
|
+
import { createRoot } from "react-dom/client";
|
|
1000
|
+
import {
|
|
1001
|
+
SidecarWidgetProvider,
|
|
1002
|
+
SidecarWidgetRoot,
|
|
1003
|
+
browserBridge,
|
|
1004
|
+
useToolResult
|
|
1005
|
+
} from "@sidecar-ai/react";
|
|
1006
|
+
import { Heading, Stack, Surface, Text } from "@sidecar-ai/native/components";
|
|
1007
|
+
import "@sidecar-ai/native/styles.css";
|
|
1008
|
+
${appStyle ? `import ${JSON.stringify(toImportSpecifier(entryDir, appStyle))};` : ""}
|
|
1009
|
+
${imports}
|
|
1010
|
+
|
|
1011
|
+
const registry = {
|
|
1012
|
+
${registry}
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
function nestedResult(result, payload) {
|
|
1016
|
+
return {
|
|
1017
|
+
...result,
|
|
1018
|
+
structuredContent: payload?.result ?? payload?.value,
|
|
1019
|
+
structured: payload?.result ?? payload?.value,
|
|
1020
|
+
content: payload?.content ?? result.content ?? [],
|
|
1021
|
+
meta: payload?.meta ?? result.meta ?? {},
|
|
1022
|
+
_meta: payload?.meta ?? result._meta ?? {},
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
function createNestedBridge(result, payload) {
|
|
1027
|
+
return {
|
|
1028
|
+
...browserBridge,
|
|
1029
|
+
getToolResult() {
|
|
1030
|
+
return nestedResult(result, payload);
|
|
1031
|
+
},
|
|
1032
|
+
subscribeToolResult(listener) {
|
|
1033
|
+
return browserBridge.subscribeToolResult((next) => {
|
|
1034
|
+
const nextPayload = next?.structuredContent?.codeMode;
|
|
1035
|
+
listener(nestedResult(next, nextPayload));
|
|
1036
|
+
});
|
|
1037
|
+
},
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function GenericCodeModeResult({ payload }) {
|
|
1042
|
+
return React.createElement("main", { className: "sidecar-code-mode" },
|
|
1043
|
+
React.createElement(Stack, { gap: "md" },
|
|
1044
|
+
React.createElement(Heading, { level: 1 }, "Code result"),
|
|
1045
|
+
React.createElement(Surface, { variant: "card" },
|
|
1046
|
+
React.createElement("pre", {
|
|
1047
|
+
style: {
|
|
1048
|
+
margin: 0,
|
|
1049
|
+
overflowWrap: "anywhere",
|
|
1050
|
+
whiteSpace: "pre-wrap"
|
|
1051
|
+
}
|
|
1052
|
+
}, JSON.stringify(payload?.value ?? payload ?? {}, null, 2))
|
|
1053
|
+
)
|
|
1054
|
+
)
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function CodeModeWidget() {
|
|
1059
|
+
const result = useToolResult();
|
|
1060
|
+
const payload = result.structuredContent?.codeMode;
|
|
1061
|
+
const renderer = payload?.renderer;
|
|
1062
|
+
const Component = renderer ? registry[renderer] : undefined;
|
|
1063
|
+
const bridge = useMemo(() => createNestedBridge(result, payload), [result, payload]);
|
|
1064
|
+
|
|
1065
|
+
if (Component) {
|
|
1066
|
+
return React.createElement(
|
|
1067
|
+
SidecarWidgetProvider,
|
|
1068
|
+
{ bridge },
|
|
1069
|
+
React.createElement(Component)
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (payload?.ok === false) {
|
|
1074
|
+
return React.createElement("main", { className: "sidecar-code-mode" },
|
|
1075
|
+
React.createElement(Stack, { gap: "sm" },
|
|
1076
|
+
React.createElement(Heading, { level: 1 }, "Code failed"),
|
|
1077
|
+
React.createElement(Text, { tone: "muted" }, "The generated code did not complete successfully.")
|
|
1078
|
+
)
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
return React.createElement(GenericCodeModeResult, { payload });
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
createRoot(document.getElementById("root")).render(
|
|
1086
|
+
React.createElement(SidecarWidgetRoot, null, React.createElement(CodeModeWidget))
|
|
1087
|
+
);
|
|
1088
|
+
`;
|
|
1089
|
+
}
|
|
1090
|
+
function sidecarWidgetAliases(rootDir) {
|
|
1091
|
+
return {
|
|
1092
|
+
...devSidecarBundleAliases(rootDir) ?? {},
|
|
1093
|
+
...reactSingletonAliases(rootDir)
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function reactSingletonAliases(rootDir) {
|
|
1097
|
+
return {
|
|
1098
|
+
react: resolveProjectModule(rootDir, "react"),
|
|
1099
|
+
"react/jsx-runtime": resolveProjectModule(rootDir, "react/jsx-runtime"),
|
|
1100
|
+
"react/jsx-dev-runtime": resolveProjectModule(rootDir, "react/jsx-dev-runtime"),
|
|
1101
|
+
"react-dom": resolveProjectModule(rootDir, "react-dom"),
|
|
1102
|
+
"react-dom/client": resolveProjectModule(rootDir, "react-dom/client"),
|
|
1103
|
+
"react-dom/server": resolveProjectModule(rootDir, "react-dom/server")
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function resolveProjectModule(rootDir, specifier) {
|
|
1107
|
+
return requireFromCompiler.resolve(specifier, {
|
|
1108
|
+
paths: [rootDir, process.cwd()]
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
function normalizeAliases(rootDir, aliases) {
|
|
1112
|
+
if (!aliases) {
|
|
1113
|
+
return void 0;
|
|
1114
|
+
}
|
|
1115
|
+
return Object.fromEntries(
|
|
1116
|
+
Object.entries(aliases).map(([key, value]) => [
|
|
1117
|
+
key,
|
|
1118
|
+
value.startsWith(".") ? path4.resolve(rootDir, value) : value
|
|
1119
|
+
])
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
function uniqueStrings(values) {
|
|
1123
|
+
return [...new Set(values)];
|
|
1124
|
+
}
|
|
1125
|
+
function contentHash(value) {
|
|
1126
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
1127
|
+
}
|
|
743
1128
|
function devSidecarBundleAliases(rootDir) {
|
|
744
1129
|
const repoRoot = findSidecarRepoRoot(rootDir) ?? findSidecarRepoRoot(process.cwd());
|
|
745
1130
|
if (!repoRoot) {
|
|
746
1131
|
return void 0;
|
|
747
1132
|
}
|
|
748
|
-
const reactEntry =
|
|
749
|
-
if (!
|
|
1133
|
+
const reactEntry = path4.join(repoRoot, "packages", "react", "src", "index.ts");
|
|
1134
|
+
if (!existsSync3(reactEntry)) {
|
|
750
1135
|
return void 0;
|
|
751
1136
|
}
|
|
752
1137
|
return {
|
|
753
|
-
"sidecar-ai":
|
|
754
|
-
"
|
|
755
|
-
"@sidecar-ai/
|
|
1138
|
+
"sidecar-ai/remote": path4.join(repoRoot, "packages", "sidecar-ai", "src", "remote.ts"),
|
|
1139
|
+
"sidecar-ai": path4.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
|
|
1140
|
+
"@sidecar-ai/client": path4.join(repoRoot, "packages", "client", "src", "index.ts"),
|
|
1141
|
+
"@sidecar-ai/core": path4.join(repoRoot, "packages", "core", "src", "index.ts"),
|
|
756
1142
|
"@sidecar-ai/react": reactEntry,
|
|
757
|
-
"@sidecar-ai/native":
|
|
758
|
-
"@sidecar-ai/native/components":
|
|
759
|
-
"@sidecar-ai/native/styles.css":
|
|
1143
|
+
"@sidecar-ai/native": path4.join(repoRoot, "packages", "native", "src", "index.ts"),
|
|
1144
|
+
"@sidecar-ai/native/components": path4.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
|
|
1145
|
+
"@sidecar-ai/native/styles.css": path4.join(repoRoot, "packages", "native", "src", "styles.css")
|
|
760
1146
|
};
|
|
761
1147
|
}
|
|
762
1148
|
function findSidecarRepoRoot(startDir) {
|
|
763
|
-
let current =
|
|
1149
|
+
let current = path4.resolve(startDir);
|
|
764
1150
|
while (true) {
|
|
765
|
-
if (
|
|
1151
|
+
if (existsSync3(path4.join(current, "packages", "core", "src", "index.ts"))) {
|
|
766
1152
|
return current;
|
|
767
1153
|
}
|
|
768
|
-
const parent =
|
|
1154
|
+
const parent = path4.dirname(current);
|
|
769
1155
|
if (parent === current) {
|
|
770
1156
|
return void 0;
|
|
771
1157
|
}
|
|
@@ -773,13 +1159,13 @@ function findSidecarRepoRoot(startDir) {
|
|
|
773
1159
|
}
|
|
774
1160
|
}
|
|
775
1161
|
async function prepareAppStyle(rootDir, cacheDir) {
|
|
776
|
-
const sourceFile =
|
|
777
|
-
if (!
|
|
1162
|
+
const sourceFile = path4.join(rootDir, "style.css");
|
|
1163
|
+
if (!existsSync3(sourceFile)) {
|
|
778
1164
|
return void 0;
|
|
779
1165
|
}
|
|
780
1166
|
const source = await readFile(sourceFile, "utf8");
|
|
781
1167
|
const css = await processAppStyle(source, sourceFile);
|
|
782
|
-
const outputFile =
|
|
1168
|
+
const outputFile = path4.join(cacheDir, "style.css");
|
|
783
1169
|
await writeFile(outputFile, css);
|
|
784
1170
|
return outputFile;
|
|
785
1171
|
}
|
|
@@ -791,7 +1177,7 @@ async function processAppStyle(source, from) {
|
|
|
791
1177
|
const plugins = [];
|
|
792
1178
|
if (cssSource.includes("@tailwind")) {
|
|
793
1179
|
const tailwind = await import("@tailwindcss/postcss");
|
|
794
|
-
plugins.push(tailwind.default({ base:
|
|
1180
|
+
plugins.push(tailwind.default({ base: path4.dirname(from) }));
|
|
795
1181
|
}
|
|
796
1182
|
const autoprefixer = await import("autoprefixer");
|
|
797
1183
|
plugins.push(autoprefixer.default());
|
|
@@ -805,13 +1191,13 @@ function needsPostcss(source) {
|
|
|
805
1191
|
return /@import\s+["']tailwindcss["']|@tailwind|@source|@theme|@plugin/.test(source);
|
|
806
1192
|
}
|
|
807
1193
|
function findWidget(rootDir, toolFile, id, target = "mcp", toolVariant = "shared") {
|
|
808
|
-
const selected = selectWidgetFile(
|
|
1194
|
+
const selected = selectWidgetFile(path4.dirname(toolFile), target, toolVariant);
|
|
809
1195
|
if (!selected) {
|
|
810
1196
|
return void 0;
|
|
811
1197
|
}
|
|
812
1198
|
const safeId = safePathSegment(id);
|
|
813
1199
|
return {
|
|
814
|
-
sourceFile:
|
|
1200
|
+
sourceFile: path4.relative(rootDir, selected.filePath),
|
|
815
1201
|
variant: selected.variant,
|
|
816
1202
|
resourceUri: `ui://${safeId}/widget.html`
|
|
817
1203
|
};
|
|
@@ -846,7 +1232,8 @@ function widgetMeta(resourceUri, options = {}, target = "mcp") {
|
|
|
846
1232
|
const standard = {
|
|
847
1233
|
ui: {
|
|
848
1234
|
resourceUri
|
|
849
|
-
}
|
|
1235
|
+
},
|
|
1236
|
+
"ui/resourceUri": resourceUri
|
|
850
1237
|
};
|
|
851
1238
|
if (target !== "chatgpt") {
|
|
852
1239
|
return standard;
|
|
@@ -862,7 +1249,7 @@ function widgetMeta(resourceUri, options = {}, target = "mcp") {
|
|
|
862
1249
|
function widgetResourceMeta(options = {}, target = "mcp") {
|
|
863
1250
|
const csp = stripUndefined3({
|
|
864
1251
|
connectDomains: options.csp?.connectDomains ? [...options.csp.connectDomains] : [],
|
|
865
|
-
resourceDomains: options
|
|
1252
|
+
resourceDomains: widgetResourceDomains(options, target),
|
|
866
1253
|
frameDomains: options.csp?.frameDomains ? [...options.csp.frameDomains] : void 0,
|
|
867
1254
|
baseUriDomains: options.csp?.baseUriDomains ? [...options.csp.baseUriDomains] : void 0
|
|
868
1255
|
});
|
|
@@ -875,6 +1262,13 @@ function widgetResourceMeta(options = {}, target = "mcp") {
|
|
|
875
1262
|
});
|
|
876
1263
|
return Object.keys(ui).length ? { ui } : void 0;
|
|
877
1264
|
}
|
|
1265
|
+
function widgetResourceDomains(options, target) {
|
|
1266
|
+
const domains = [...options.csp?.resourceDomains ?? []];
|
|
1267
|
+
if (target === "claude" && !domains.includes(CLAUDE_FONT_RESOURCE_DOMAIN)) {
|
|
1268
|
+
domains.push(CLAUDE_FONT_RESOURCE_DOMAIN);
|
|
1269
|
+
}
|
|
1270
|
+
return domains;
|
|
1271
|
+
}
|
|
878
1272
|
function findWidgetDefinition(sourceFile) {
|
|
879
1273
|
const call = resolveDefaultExportCall(sourceFile, "widget");
|
|
880
1274
|
if (!call) {
|
|
@@ -986,17 +1380,17 @@ function readStringArrayProperty(definition, propertyName) {
|
|
|
986
1380
|
}
|
|
987
1381
|
function selectWidgetFile(directory, target, toolVariant) {
|
|
988
1382
|
if (toolVariant === "openai") {
|
|
989
|
-
const filePath =
|
|
990
|
-
return
|
|
1383
|
+
const filePath = path4.join(directory, "widget.openai.tsx");
|
|
1384
|
+
return existsSync3(filePath) ? { filePath, variant: "openai" } : void 0;
|
|
991
1385
|
}
|
|
992
1386
|
if (toolVariant === "anthropic") {
|
|
993
|
-
const filePath =
|
|
994
|
-
return
|
|
1387
|
+
const filePath = path4.join(directory, "widget.anthropic.tsx");
|
|
1388
|
+
return existsSync3(filePath) ? { filePath, variant: "anthropic" } : void 0;
|
|
995
1389
|
}
|
|
996
1390
|
const candidates = target === "chatgpt" || target === "claude" || target === "mcp" ? [{ name: "widget.tsx", variant: "shared" }] : [];
|
|
997
1391
|
for (const candidate of candidates) {
|
|
998
|
-
const filePath =
|
|
999
|
-
if (
|
|
1392
|
+
const filePath = path4.join(directory, candidate.name);
|
|
1393
|
+
if (existsSync3(filePath)) {
|
|
1000
1394
|
return { filePath, variant: candidate.variant };
|
|
1001
1395
|
}
|
|
1002
1396
|
}
|
|
@@ -1047,17 +1441,23 @@ function stripUndefined3(value) {
|
|
|
1047
1441
|
// packages/compiler/src/analyze.ts
|
|
1048
1442
|
async function analyzeProjectTools(rootDir, options = {}) {
|
|
1049
1443
|
const target = options.target ?? "mcp";
|
|
1050
|
-
const toolFiles = await findToolFiles(
|
|
1444
|
+
const toolFiles = await findToolFiles(path5.join(rootDir, "server"), target);
|
|
1051
1445
|
if (toolFiles.length === 0) {
|
|
1052
1446
|
return [];
|
|
1053
1447
|
}
|
|
1054
1448
|
const project = createProject(rootDir);
|
|
1055
1449
|
const authScopes = readAuthScopeCatalog(project, rootDir);
|
|
1056
|
-
|
|
1057
|
-
|
|
1450
|
+
const sources = toolFiles.map((candidate) => ({
|
|
1451
|
+
candidate,
|
|
1452
|
+
sourceFile: project.addSourceFileAtPath(candidate.filePath)
|
|
1453
|
+
}));
|
|
1454
|
+
const runtimeInputSchemas = await readRuntimeInputSchemas(rootDir, sources);
|
|
1455
|
+
return sources.map(
|
|
1456
|
+
({ candidate, sourceFile }) => analyzeToolFile(sourceFile, rootDir, {
|
|
1058
1457
|
target,
|
|
1059
1458
|
variant: candidate.variant,
|
|
1060
|
-
authScopes
|
|
1459
|
+
authScopes,
|
|
1460
|
+
inputSchema: runtimeInputSchemas.get(candidate.filePath)
|
|
1061
1461
|
})
|
|
1062
1462
|
);
|
|
1063
1463
|
}
|
|
@@ -1066,7 +1466,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1066
1466
|
const variant = options.variant ?? "shared";
|
|
1067
1467
|
const definition = findToolDefinition(sourceFile);
|
|
1068
1468
|
const absoluteFile = sourceFile.getFilePath();
|
|
1069
|
-
const directory =
|
|
1469
|
+
const directory = path5.basename(path5.dirname(absoluteFile));
|
|
1070
1470
|
const name = getRequiredStringProperty(definition, "name", sourceFile);
|
|
1071
1471
|
const id = getOptionalStringProperty(definition, "id") ?? toMachineName(directory);
|
|
1072
1472
|
const description = getRequiredStringProperty(
|
|
@@ -1079,7 +1479,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1079
1479
|
const hosts = readHosts(definition);
|
|
1080
1480
|
const auth = readAuthPolicy(definition, options.authScopes ?? {});
|
|
1081
1481
|
const execute = getExecuteFunction(definition, sourceFile);
|
|
1082
|
-
const inputSchema = getParamsSchema(definition, execute);
|
|
1482
|
+
const inputSchema = options.inputSchema ?? getParamsSchema(definition, execute);
|
|
1083
1483
|
const outputSchema = getOutputSchema(definition, execute);
|
|
1084
1484
|
const descriptor = createToolDescriptor({
|
|
1085
1485
|
name,
|
|
@@ -1097,7 +1497,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1097
1497
|
validateWidgetHierarchy(sourceFile, absoluteFile, target, variant);
|
|
1098
1498
|
const widget = findWidget(rootDir, absoluteFile, id, target, variant);
|
|
1099
1499
|
if (widget) {
|
|
1100
|
-
const widgetFile =
|
|
1500
|
+
const widgetFile = path5.join(rootDir, widget.sourceFile);
|
|
1101
1501
|
const project = sourceFile.getProject();
|
|
1102
1502
|
const widgetSourceFile = project.getSourceFile(widgetFile) ?? project.addSourceFileAtPath(widgetFile);
|
|
1103
1503
|
widget.options = {
|
|
@@ -1108,7 +1508,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1108
1508
|
descriptor._meta = mergeWidgetMeta(descriptor._meta, widgetMeta(widget.resourceUri, widget.options, target));
|
|
1109
1509
|
}
|
|
1110
1510
|
return {
|
|
1111
|
-
sourceFile:
|
|
1511
|
+
sourceFile: path5.relative(rootDir, absoluteFile),
|
|
1112
1512
|
variant,
|
|
1113
1513
|
target,
|
|
1114
1514
|
directory,
|
|
@@ -1123,6 +1523,26 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1123
1523
|
descriptor
|
|
1124
1524
|
};
|
|
1125
1525
|
}
|
|
1526
|
+
async function readRuntimeInputSchemas(rootDir, sources) {
|
|
1527
|
+
const schemas = /* @__PURE__ */ new Map();
|
|
1528
|
+
await Promise.all(sources.map(async ({ candidate, sourceFile }) => {
|
|
1529
|
+
const definition = findToolDefinition(sourceFile);
|
|
1530
|
+
if (!definitionHasRuntimeParams(definition)) {
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
const schema = await readRuntimeToolInputSchema(rootDir, candidate.filePath);
|
|
1534
|
+
if (schema) {
|
|
1535
|
+
schemas.set(candidate.filePath, schema);
|
|
1536
|
+
}
|
|
1537
|
+
}));
|
|
1538
|
+
return schemas;
|
|
1539
|
+
}
|
|
1540
|
+
function definitionHasRuntimeParams(definition) {
|
|
1541
|
+
if (definition.getProperty("params")) {
|
|
1542
|
+
return true;
|
|
1543
|
+
}
|
|
1544
|
+
return Boolean(getWithParamsCall(definition));
|
|
1545
|
+
}
|
|
1126
1546
|
function readAuthPolicy(definition, authScopes) {
|
|
1127
1547
|
const initializer = readObjectProperty2(definition, "auth");
|
|
1128
1548
|
if (!initializer) {
|
|
@@ -1139,7 +1559,7 @@ function readAuthPolicy(definition, authScopes) {
|
|
|
1139
1559
|
return { authenticated: true };
|
|
1140
1560
|
}
|
|
1141
1561
|
function readAuthScopeCatalog(project, rootDir) {
|
|
1142
|
-
const authPath =
|
|
1562
|
+
const authPath = path5.join(rootDir, "auth.ts");
|
|
1143
1563
|
if (!existsSyncSafe(authPath)) {
|
|
1144
1564
|
return {};
|
|
1145
1565
|
}
|
|
@@ -1221,12 +1641,12 @@ function isNamedCall(call, name) {
|
|
|
1221
1641
|
return callee === name || callee.endsWith(`.${name}`);
|
|
1222
1642
|
}
|
|
1223
1643
|
function validateWidgetHierarchy(sourceFile, toolFile, target, variant) {
|
|
1224
|
-
const directory =
|
|
1644
|
+
const directory = path5.dirname(toolFile);
|
|
1225
1645
|
const platformWidget = target === "chatgpt" ? "widget.openai.tsx" : target === "claude" ? "widget.anthropic.tsx" : void 0;
|
|
1226
1646
|
if (!platformWidget || variant !== "shared") {
|
|
1227
1647
|
return;
|
|
1228
1648
|
}
|
|
1229
|
-
if (existsSyncSafe(
|
|
1649
|
+
if (existsSyncSafe(path5.join(directory, platformWidget))) {
|
|
1230
1650
|
const expectedTool = target === "chatgpt" ? "tool.openai.ts" : "tool.anthropic.ts";
|
|
1231
1651
|
throw new CompilerError(
|
|
1232
1652
|
sourceFile,
|
|
@@ -1241,7 +1661,7 @@ async function findToolFiles(serverDir, target) {
|
|
|
1241
1661
|
const entries = await readdir(serverDir, { withFileTypes: true });
|
|
1242
1662
|
const files = [];
|
|
1243
1663
|
for (const entry of entries) {
|
|
1244
|
-
const entryPath =
|
|
1664
|
+
const entryPath = path5.join(serverDir, entry.name);
|
|
1245
1665
|
if (entry.isDirectory()) {
|
|
1246
1666
|
const candidate = selectToolFile(entryPath, target);
|
|
1247
1667
|
if (candidate) {
|
|
@@ -1260,7 +1680,7 @@ function selectToolFile(directory, target) {
|
|
|
1260
1680
|
{ name: "tool.ts", variant: "shared" }
|
|
1261
1681
|
] : [{ name: "tool.ts", variant: "shared" }];
|
|
1262
1682
|
for (const candidate of candidates) {
|
|
1263
|
-
const filePath =
|
|
1683
|
+
const filePath = path5.join(directory, candidate.name);
|
|
1264
1684
|
if (existsSyncSafe(filePath)) {
|
|
1265
1685
|
return { filePath, variant: candidate.variant };
|
|
1266
1686
|
}
|
|
@@ -1296,16 +1716,32 @@ function getExecuteFunction(definition, sourceFile) {
|
|
|
1296
1716
|
return property;
|
|
1297
1717
|
}
|
|
1298
1718
|
if (Node4.isPropertyAssignment(property)) {
|
|
1299
|
-
const initializer = property.getInitializer();
|
|
1719
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1300
1720
|
if (initializer && (Node4.isArrowFunction(initializer) || Node4.isFunctionExpression(initializer))) {
|
|
1301
1721
|
return initializer;
|
|
1302
1722
|
}
|
|
1723
|
+
const withParamsCall = getWithParamsCall(definition);
|
|
1724
|
+
const wrappedExecute = unwrapExpression(withParamsCall?.getArguments()[1]);
|
|
1725
|
+
if (wrappedExecute && (Node4.isArrowFunction(wrappedExecute) || Node4.isFunctionExpression(wrappedExecute))) {
|
|
1726
|
+
return wrappedExecute;
|
|
1727
|
+
}
|
|
1303
1728
|
}
|
|
1304
1729
|
throw new CompilerError(
|
|
1305
1730
|
sourceFile,
|
|
1306
1731
|
"execute must be a method, function expression, or arrow function."
|
|
1307
1732
|
);
|
|
1308
1733
|
}
|
|
1734
|
+
function getWithParamsCall(definition) {
|
|
1735
|
+
const property = definition.getProperty("execute");
|
|
1736
|
+
if (!property || !Node4.isPropertyAssignment(property)) {
|
|
1737
|
+
return void 0;
|
|
1738
|
+
}
|
|
1739
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1740
|
+
if (!initializer || !Node4.isCallExpression(initializer)) {
|
|
1741
|
+
return void 0;
|
|
1742
|
+
}
|
|
1743
|
+
return isNamedCall(initializer, "withParams") ? initializer : void 0;
|
|
1744
|
+
}
|
|
1309
1745
|
function getRequiredStringProperty(definition, propertyName, sourceFile) {
|
|
1310
1746
|
const value = getOptionalStringProperty(definition, propertyName);
|
|
1311
1747
|
if (value === void 0 || !value.trim()) {
|
|
@@ -1460,17 +1896,18 @@ function readStringArrayProperty2(definition, propertyName) {
|
|
|
1460
1896
|
}
|
|
1461
1897
|
|
|
1462
1898
|
// packages/compiler/src/build.ts
|
|
1463
|
-
import { mkdir as
|
|
1464
|
-
import
|
|
1899
|
+
import { mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
|
|
1900
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1901
|
+
import path19 from "path";
|
|
1465
1902
|
|
|
1466
1903
|
// packages/compiler/src/config.ts
|
|
1467
|
-
import
|
|
1904
|
+
import path6 from "path";
|
|
1468
1905
|
import {
|
|
1469
1906
|
Node as Node5,
|
|
1470
1907
|
SyntaxKind as SyntaxKind3
|
|
1471
1908
|
} from "ts-morph";
|
|
1472
1909
|
function analyzeProjectConfig(rootDir) {
|
|
1473
|
-
const configPath =
|
|
1910
|
+
const configPath = path6.join(rootDir, "sidecar.config.ts");
|
|
1474
1911
|
if (!existsSyncSafe(configPath)) {
|
|
1475
1912
|
return defaultCompilerConfig();
|
|
1476
1913
|
}
|
|
@@ -1482,6 +1919,13 @@ function analyzeProjectConfig(rootDir) {
|
|
|
1482
1919
|
return defaultCompilerConfig();
|
|
1483
1920
|
}
|
|
1484
1921
|
return {
|
|
1922
|
+
build: {
|
|
1923
|
+
target: readTargetNested(definition, "build", "target"),
|
|
1924
|
+
host: readHostNested(definition, "build", "host"),
|
|
1925
|
+
outDir: readStringNested(definition, "build", "outDir"),
|
|
1926
|
+
plugins: readBooleanNested(definition, "build", "plugins"),
|
|
1927
|
+
widgets: readWidgetBuildConfig(definition)
|
|
1928
|
+
},
|
|
1485
1929
|
resources: {
|
|
1486
1930
|
subscribe: readBooleanNested(definition, "resources", "subscribe") ?? false,
|
|
1487
1931
|
listChanged: readBooleanNested(definition, "resources", "listChanged") ?? false
|
|
@@ -1493,13 +1937,56 @@ function analyzeProjectConfig(rootDir) {
|
|
|
1493
1937
|
listChanged: readBooleanNested(definition, "tools", "listChanged") ?? false
|
|
1494
1938
|
},
|
|
1495
1939
|
pagination: {
|
|
1496
|
-
pageSize: readNumberNested(definition, "pagination", "pageSize") ??
|
|
1940
|
+
pageSize: readNumberNested(definition, "pagination", "pageSize") ?? 50,
|
|
1497
1941
|
hasOverride: hasProperty(readObjectProperty3(definition, "pagination"), "override")
|
|
1942
|
+
},
|
|
1943
|
+
codeMode: readCodeModeConfig(definition),
|
|
1944
|
+
remoteExecution: {
|
|
1945
|
+
enabled: readBooleanProperty3(definition, "remoteExecution") ?? false
|
|
1498
1946
|
}
|
|
1499
1947
|
};
|
|
1500
1948
|
}
|
|
1949
|
+
function readWidgetBuildConfig(definition) {
|
|
1950
|
+
const widgets = readObjectProperty3(readObjectProperty3(definition, "build"), "widgets");
|
|
1951
|
+
if (!widgets) {
|
|
1952
|
+
return void 0;
|
|
1953
|
+
}
|
|
1954
|
+
const config = {};
|
|
1955
|
+
const configure = readStringProperty3(widgets, "configure");
|
|
1956
|
+
const esbuild3 = readWidgetEsbuildConfig(widgets);
|
|
1957
|
+
if (configure) config.configure = configure;
|
|
1958
|
+
if (esbuild3) config.esbuild = esbuild3;
|
|
1959
|
+
return Object.keys(config).length ? config : void 0;
|
|
1960
|
+
}
|
|
1961
|
+
function readWidgetEsbuildConfig(widgets) {
|
|
1962
|
+
const esbuild3 = readObjectProperty3(widgets, "esbuild");
|
|
1963
|
+
if (!esbuild3) {
|
|
1964
|
+
return void 0;
|
|
1965
|
+
}
|
|
1966
|
+
const config = {};
|
|
1967
|
+
const alias = readStringRecordProperty(esbuild3, "alias");
|
|
1968
|
+
const define = readStringRecordProperty(esbuild3, "define");
|
|
1969
|
+
const external = readStringArrayProperty3(esbuild3, "external");
|
|
1970
|
+
const loader = readStringRecordProperty(esbuild3, "loader");
|
|
1971
|
+
const conditions = readStringArrayProperty3(esbuild3, "conditions");
|
|
1972
|
+
const mainFields = readStringArrayProperty3(esbuild3, "mainFields");
|
|
1973
|
+
const jsx = readStringProperty3(esbuild3, "jsx");
|
|
1974
|
+
const jsxImportSource = readStringProperty3(esbuild3, "jsxImportSource");
|
|
1975
|
+
if (alias) config.alias = alias;
|
|
1976
|
+
if (define) config.define = define;
|
|
1977
|
+
if (external) config.external = external;
|
|
1978
|
+
if (loader) config.loader = loader;
|
|
1979
|
+
if (conditions) config.conditions = conditions;
|
|
1980
|
+
if (mainFields) config.mainFields = mainFields;
|
|
1981
|
+
if (jsx === "automatic" || jsx === "transform" || jsx === "preserve") {
|
|
1982
|
+
config.jsx = jsx;
|
|
1983
|
+
}
|
|
1984
|
+
if (jsxImportSource) config.jsxImportSource = jsxImportSource;
|
|
1985
|
+
return Object.keys(config).length ? config : void 0;
|
|
1986
|
+
}
|
|
1501
1987
|
function defaultCompilerConfig() {
|
|
1502
1988
|
return {
|
|
1989
|
+
build: {},
|
|
1503
1990
|
resources: {
|
|
1504
1991
|
subscribe: false,
|
|
1505
1992
|
listChanged: false
|
|
@@ -1511,17 +1998,101 @@ function defaultCompilerConfig() {
|
|
|
1511
1998
|
listChanged: false
|
|
1512
1999
|
},
|
|
1513
2000
|
pagination: {
|
|
1514
|
-
pageSize:
|
|
2001
|
+
pageSize: 50,
|
|
1515
2002
|
hasOverride: false
|
|
2003
|
+
},
|
|
2004
|
+
codeMode: {
|
|
2005
|
+
enabled: false,
|
|
2006
|
+
unsafe: false,
|
|
2007
|
+
render: {
|
|
2008
|
+
enabled: true,
|
|
2009
|
+
strategy: "last-renderable"
|
|
2010
|
+
}
|
|
2011
|
+
},
|
|
2012
|
+
remoteExecution: {
|
|
2013
|
+
enabled: false
|
|
1516
2014
|
}
|
|
1517
2015
|
};
|
|
1518
2016
|
}
|
|
2017
|
+
function readCodeModeConfig(definition) {
|
|
2018
|
+
const defaults = defaultCompilerConfig().codeMode;
|
|
2019
|
+
const property = definition.getProperty("codeMode");
|
|
2020
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
2021
|
+
return defaults;
|
|
2022
|
+
}
|
|
2023
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2024
|
+
if (!initializer) {
|
|
2025
|
+
return defaults;
|
|
2026
|
+
}
|
|
2027
|
+
if (initializer.getKind() === SyntaxKind3.TrueKeyword) {
|
|
2028
|
+
return { ...defaults, enabled: true };
|
|
2029
|
+
}
|
|
2030
|
+
if (initializer.getKind() === SyntaxKind3.FalseKeyword) {
|
|
2031
|
+
return { ...defaults, enabled: false };
|
|
2032
|
+
}
|
|
2033
|
+
if (!Node5.isObjectLiteralExpression(initializer)) {
|
|
2034
|
+
return defaults;
|
|
2035
|
+
}
|
|
2036
|
+
return {
|
|
2037
|
+
enabled: true,
|
|
2038
|
+
unsafe: readBooleanProperty3(initializer, "unsafe") ?? false,
|
|
2039
|
+
render: readCodeModeRenderConfig(initializer) ?? defaults.render
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
function readCodeModeRenderConfig(definition) {
|
|
2043
|
+
const property = definition.getProperty("render");
|
|
2044
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
2045
|
+
return void 0;
|
|
2046
|
+
}
|
|
2047
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2048
|
+
if (!initializer) {
|
|
2049
|
+
return void 0;
|
|
2050
|
+
}
|
|
2051
|
+
if (initializer.getKind() === SyntaxKind3.TrueKeyword) {
|
|
2052
|
+
return { enabled: true, strategy: "last-renderable" };
|
|
2053
|
+
}
|
|
2054
|
+
if (initializer.getKind() === SyntaxKind3.FalseKeyword) {
|
|
2055
|
+
return { enabled: false, strategy: "last-renderable" };
|
|
2056
|
+
}
|
|
2057
|
+
if (!Node5.isObjectLiteralExpression(initializer)) {
|
|
2058
|
+
return void 0;
|
|
2059
|
+
}
|
|
2060
|
+
return {
|
|
2061
|
+
enabled: readBooleanProperty3(initializer, "enabled") ?? true,
|
|
2062
|
+
strategy: readRenderStrategy(initializer) ?? "last-renderable"
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
function readRenderStrategy(definition) {
|
|
2066
|
+
const value = readStringProperty3(definition, "strategy");
|
|
2067
|
+
if (value === "last-renderable" || value === "first-renderable" || value === "explicit") {
|
|
2068
|
+
return value;
|
|
2069
|
+
}
|
|
2070
|
+
return void 0;
|
|
2071
|
+
}
|
|
2072
|
+
function readTargetNested(definition, section, propertyName) {
|
|
2073
|
+
const value = readStringNested(definition, section, propertyName);
|
|
2074
|
+
return value === "mcp" || value === "chatgpt" || value === "claude" ? value : void 0;
|
|
2075
|
+
}
|
|
2076
|
+
function readHostNested(definition, section, propertyName) {
|
|
2077
|
+
const value = readStringNested(definition, section, propertyName);
|
|
2078
|
+
return value === "node" || value === "vercel" ? value : void 0;
|
|
2079
|
+
}
|
|
2080
|
+
function readStringNested(definition, section, propertyName) {
|
|
2081
|
+
const object = readObjectProperty3(definition, section);
|
|
2082
|
+
if (!object) {
|
|
2083
|
+
return void 0;
|
|
2084
|
+
}
|
|
2085
|
+
return readStringProperty3(object, propertyName);
|
|
2086
|
+
}
|
|
1519
2087
|
function readBooleanNested(definition, section, propertyName) {
|
|
1520
2088
|
const object = readObjectProperty3(definition, section);
|
|
1521
2089
|
if (!object) {
|
|
1522
2090
|
return void 0;
|
|
1523
2091
|
}
|
|
1524
|
-
|
|
2092
|
+
return readBooleanProperty3(object, propertyName);
|
|
2093
|
+
}
|
|
2094
|
+
function readBooleanProperty3(definition, propertyName) {
|
|
2095
|
+
const property = definition.getProperty(propertyName);
|
|
1525
2096
|
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
1526
2097
|
return void 0;
|
|
1527
2098
|
}
|
|
@@ -1546,6 +2117,9 @@ function readNumberNested(definition, section, propertyName) {
|
|
|
1546
2117
|
return initializer && Node5.isNumericLiteral(initializer) ? Number(initializer.getLiteralText()) : void 0;
|
|
1547
2118
|
}
|
|
1548
2119
|
function readObjectProperty3(definition, propertyName) {
|
|
2120
|
+
if (!definition) {
|
|
2121
|
+
return void 0;
|
|
2122
|
+
}
|
|
1549
2123
|
const property = definition.getProperty(propertyName);
|
|
1550
2124
|
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
1551
2125
|
return void 0;
|
|
@@ -1553,38 +2127,79 @@ function readObjectProperty3(definition, propertyName) {
|
|
|
1553
2127
|
const initializer = unwrapExpression(property.getInitializer());
|
|
1554
2128
|
return initializer && Node5.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
1555
2129
|
}
|
|
2130
|
+
function readStringProperty3(definition, propertyName) {
|
|
2131
|
+
const property = definition.getProperty(propertyName);
|
|
2132
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
2133
|
+
return void 0;
|
|
2134
|
+
}
|
|
2135
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2136
|
+
return initializer && Node5.isStringLiteral(initializer) ? initializer.getLiteralText() : void 0;
|
|
2137
|
+
}
|
|
2138
|
+
function readStringArrayProperty3(definition, propertyName) {
|
|
2139
|
+
const property = definition.getProperty(propertyName);
|
|
2140
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
2141
|
+
return void 0;
|
|
2142
|
+
}
|
|
2143
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2144
|
+
if (!initializer || !Node5.isArrayLiteralExpression(initializer)) {
|
|
2145
|
+
return void 0;
|
|
2146
|
+
}
|
|
2147
|
+
const values = initializer.getElements().map((element) => {
|
|
2148
|
+
const unwrapped = unwrapExpression(element);
|
|
2149
|
+
return unwrapped && Node5.isStringLiteral(unwrapped) ? unwrapped.getLiteralText() : void 0;
|
|
2150
|
+
});
|
|
2151
|
+
return values.every((value) => value !== void 0) ? values : void 0;
|
|
2152
|
+
}
|
|
2153
|
+
function readStringRecordProperty(definition, propertyName) {
|
|
2154
|
+
const object = readObjectProperty3(definition, propertyName);
|
|
2155
|
+
if (!object) {
|
|
2156
|
+
return void 0;
|
|
2157
|
+
}
|
|
2158
|
+
const record = {};
|
|
2159
|
+
for (const property of object.getProperties()) {
|
|
2160
|
+
if (!Node5.isPropertyAssignment(property)) {
|
|
2161
|
+
return void 0;
|
|
2162
|
+
}
|
|
2163
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2164
|
+
if (!initializer || !Node5.isStringLiteral(initializer)) {
|
|
2165
|
+
return void 0;
|
|
2166
|
+
}
|
|
2167
|
+
record[property.getName().replace(/^["']|["']$/g, "")] = initializer.getLiteralText();
|
|
2168
|
+
}
|
|
2169
|
+
return record;
|
|
2170
|
+
}
|
|
1556
2171
|
function hasProperty(definition, propertyName) {
|
|
1557
2172
|
return Boolean(definition?.getProperty(propertyName));
|
|
1558
2173
|
}
|
|
1559
2174
|
|
|
1560
2175
|
// packages/compiler/src/diagnostics.ts
|
|
1561
|
-
import { existsSync as
|
|
2176
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1562
2177
|
import { readFile as readFile2 } from "fs/promises";
|
|
1563
|
-
import
|
|
2178
|
+
import path7 from "path";
|
|
1564
2179
|
async function collectProjectDiagnostics(rootDir, input) {
|
|
1565
2180
|
const diagnostics = [];
|
|
1566
2181
|
const tools = Array.isArray(input) ? input : input.tools;
|
|
1567
2182
|
const resources = Array.isArray(input) ? [] : input.resources ?? [];
|
|
1568
2183
|
const prompts = Array.isArray(input) ? [] : input.prompts ?? [];
|
|
1569
2184
|
const config = Array.isArray(input) ? void 0 : input.config;
|
|
1570
|
-
const hasAuthConfig =
|
|
2185
|
+
const hasAuthConfig = existsSync4(path7.join(rootDir, "auth.ts"));
|
|
1571
2186
|
for (const entry of tools) {
|
|
1572
|
-
const toolPath =
|
|
2187
|
+
const toolPath = path7.join(rootDir, entry.sourceFile);
|
|
1573
2188
|
const toolSource = await readFile2(toolPath, "utf8");
|
|
1574
|
-
diagnostics.push(...diagnoseToolSource(
|
|
2189
|
+
diagnostics.push(...diagnoseToolSource(entry, toolSource, hasAuthConfig));
|
|
1575
2190
|
if (entry.widget) {
|
|
1576
|
-
const widgetPath =
|
|
2191
|
+
const widgetPath = path7.join(rootDir, entry.widget.sourceFile);
|
|
1577
2192
|
const widgetSource = await readFile2(widgetPath, "utf8");
|
|
1578
2193
|
diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource));
|
|
1579
2194
|
}
|
|
1580
2195
|
}
|
|
1581
2196
|
for (const entry of resources) {
|
|
1582
|
-
const resourcePath =
|
|
2197
|
+
const resourcePath = path7.join(rootDir, entry.sourceFile);
|
|
1583
2198
|
const resourceSource = await readFile2(resourcePath, "utf8");
|
|
1584
2199
|
diagnostics.push(...diagnoseResourceSource(entry, resourceSource));
|
|
1585
2200
|
}
|
|
1586
2201
|
for (const entry of prompts) {
|
|
1587
|
-
const promptPath =
|
|
2202
|
+
const promptPath = path7.join(rootDir, entry.sourceFile);
|
|
1588
2203
|
const promptSource = await readFile2(promptPath, "utf8");
|
|
1589
2204
|
diagnostics.push(...diagnosePromptSource(entry, promptSource));
|
|
1590
2205
|
}
|
|
@@ -1599,7 +2214,7 @@ function formatDiagnostic(diagnostic) {
|
|
|
1599
2214
|
hint: ${diagnostic.hint}` : "";
|
|
1600
2215
|
return `${location} - ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}${hint}`;
|
|
1601
2216
|
}
|
|
1602
|
-
function diagnoseToolSource(
|
|
2217
|
+
function diagnoseToolSource(entry, source, hasAuthConfig) {
|
|
1603
2218
|
const diagnostics = [];
|
|
1604
2219
|
const toolLocation = locate(source, "tool({");
|
|
1605
2220
|
if (!entry.description.trim().startsWith("Use this when")) {
|
|
@@ -1816,21 +2431,21 @@ function isIgnored(source, code) {
|
|
|
1816
2431
|
|
|
1817
2432
|
// packages/compiler/src/generated.ts
|
|
1818
2433
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
1819
|
-
import
|
|
2434
|
+
import path8 from "path";
|
|
1820
2435
|
async function writeGeneratedTypes(rootDir, tools) {
|
|
1821
|
-
const generatedDir =
|
|
2436
|
+
const generatedDir = path8.join(rootDir, ".sidecar", "generated");
|
|
1822
2437
|
await mkdir2(generatedDir, { recursive: true });
|
|
1823
2438
|
const appCallableTools = tools.filter(isAppCallable);
|
|
1824
2439
|
const methodNames = uniqueMethodNames(appCallableTools.map((entry) => toIdentifier(entry.id)));
|
|
1825
2440
|
const imports = appCallableTools.map(
|
|
1826
|
-
(entry, index) => `import type tool${index} from ${JSON.stringify(toImportSpecifier(generatedDir,
|
|
2441
|
+
(entry, index) => `import type tool${index} from ${JSON.stringify(toImportSpecifier(generatedDir, path8.join(rootDir, entry.sourceFile), { extension: "js" }))};`
|
|
1827
2442
|
).join("\n");
|
|
1828
2443
|
const ids = appCallableTools.map((entry, index) => ` ${methodNames[index]}: ${JSON.stringify(entry.id)}`).join(",\n");
|
|
1829
2444
|
const toolTypes = appCallableTools.map(
|
|
1830
|
-
(
|
|
2445
|
+
(_entry, index) => ` ${methodNames[index]}(params: ToolParams<typeof tool${index}>): Promise<ToolOutput<typeof tool${index}>>;`
|
|
1831
2446
|
).join("\n");
|
|
1832
2447
|
await writeFile2(
|
|
1833
|
-
|
|
2448
|
+
path8.join(generatedDir, "tools.ts"),
|
|
1834
2449
|
`/**
|
|
1835
2450
|
* Generated Sidecar widget tool client.
|
|
1836
2451
|
*
|
|
@@ -1878,19 +2493,15 @@ function uniqueMethodNames(names) {
|
|
|
1878
2493
|
});
|
|
1879
2494
|
}
|
|
1880
2495
|
|
|
1881
|
-
// packages/compiler/src/plugins.ts
|
|
1882
|
-
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
1883
|
-
import path14 from "path";
|
|
1884
|
-
|
|
1885
2496
|
// packages/compiler/src/identity.ts
|
|
1886
2497
|
import { readFile as readFile3 } from "fs/promises";
|
|
1887
|
-
import
|
|
2498
|
+
import path9 from "path";
|
|
1888
2499
|
async function loadProjectIdentity(rootDir) {
|
|
1889
|
-
const packageJson = await readOptionalJson(
|
|
2500
|
+
const packageJson = await readOptionalJson(path9.join(rootDir, "package.json"));
|
|
1890
2501
|
const configText = await readOptionalText(
|
|
1891
|
-
|
|
2502
|
+
path9.join(rootDir, "sidecar.config.ts")
|
|
1892
2503
|
);
|
|
1893
|
-
const name = readConfigString(configText, "name") ?? packageJson?.name ??
|
|
2504
|
+
const name = readConfigString(configText, "name") ?? packageJson?.name ?? path9.basename(rootDir);
|
|
1894
2505
|
const version = readConfigString(configText, "version") ?? packageJson?.version ?? "0.0.0-dev";
|
|
1895
2506
|
const description = readConfigString(configText, "description") ?? packageJson?.description ?? `${name} Sidecar app.`;
|
|
1896
2507
|
return {
|
|
@@ -1922,30 +2533,34 @@ function readConfigString(configText, key) {
|
|
|
1922
2533
|
return match?.[1];
|
|
1923
2534
|
}
|
|
1924
2535
|
|
|
2536
|
+
// packages/compiler/src/plugins.ts
|
|
2537
|
+
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
2538
|
+
import path15 from "path";
|
|
2539
|
+
|
|
1925
2540
|
// packages/compiler/src/plugin-components/agents.ts
|
|
1926
2541
|
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1927
|
-
import
|
|
2542
|
+
import path10 from "path";
|
|
1928
2543
|
async function emitClaudeAgents(rootDir, destination) {
|
|
1929
|
-
const source =
|
|
2544
|
+
const source = path10.join(rootDir, "agents");
|
|
1930
2545
|
if (!existsSyncSafe(source)) {
|
|
1931
2546
|
return;
|
|
1932
2547
|
}
|
|
1933
2548
|
const entries = await readdir2(source, { withFileTypes: true });
|
|
1934
2549
|
const agentDirs = entries.filter(
|
|
1935
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2550
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path10.join(source, entry.name, "agent.ts"))
|
|
1936
2551
|
);
|
|
1937
2552
|
if (!agentDirs.length) {
|
|
1938
2553
|
return;
|
|
1939
2554
|
}
|
|
1940
2555
|
await mkdir3(destination, { recursive: true });
|
|
1941
2556
|
for (const entry of agentDirs) {
|
|
1942
|
-
const sourceText = await readFile4(
|
|
2557
|
+
const sourceText = await readFile4(path10.join(source, entry.name, "agent.ts"), "utf8");
|
|
1943
2558
|
const markdown = parseClaudeAgent(
|
|
1944
2559
|
sourceText,
|
|
1945
2560
|
entry.name
|
|
1946
2561
|
);
|
|
1947
2562
|
const agentName = readObjectString(sourceText, "name") ?? entry.name;
|
|
1948
|
-
await writeFile3(
|
|
2563
|
+
await writeFile3(path10.join(destination, `${safeFileStem(agentName)}.md`), markdown);
|
|
1949
2564
|
}
|
|
1950
2565
|
}
|
|
1951
2566
|
function parseClaudeAgent(source, fallbackName) {
|
|
@@ -1974,41 +2589,41 @@ ${prompt.trim()}
|
|
|
1974
2589
|
|
|
1975
2590
|
// packages/compiler/src/plugin-components/commands.ts
|
|
1976
2591
|
import { cp, lstat, mkdir as mkdir4, readdir as readdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
1977
|
-
import
|
|
2592
|
+
import path11 from "path";
|
|
1978
2593
|
async function copyCommands(rootDir, destination) {
|
|
1979
|
-
const source =
|
|
2594
|
+
const source = path11.join(rootDir, "commands");
|
|
1980
2595
|
if (!existsSyncSafe(source)) {
|
|
1981
2596
|
return;
|
|
1982
2597
|
}
|
|
1983
2598
|
await mkdir4(destination, { recursive: true });
|
|
1984
2599
|
const entries = await readdir3(source, { withFileTypes: true });
|
|
1985
2600
|
for (const entry of entries) {
|
|
1986
|
-
const sourcePath =
|
|
2601
|
+
const sourcePath = path11.join(source, entry.name);
|
|
1987
2602
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1988
2603
|
if (await safeCommandCopyFilter(sourcePath)) {
|
|
1989
|
-
await cp(sourcePath,
|
|
2604
|
+
await cp(sourcePath, path11.join(destination, safeFileStem(entry.name.replace(/\.md$/, "")) + ".md"));
|
|
1990
2605
|
}
|
|
1991
2606
|
continue;
|
|
1992
2607
|
}
|
|
1993
2608
|
if (!entry.isDirectory()) {
|
|
1994
2609
|
continue;
|
|
1995
2610
|
}
|
|
1996
|
-
const dynamicCommand =
|
|
2611
|
+
const dynamicCommand = path11.join(sourcePath, "command.ts");
|
|
1997
2612
|
if (existsSyncSafe(dynamicCommand)) {
|
|
1998
2613
|
const sourceText = await readFile5(dynamicCommand, "utf8");
|
|
1999
2614
|
const markdown = parseDynamicCommand(sourceText, entry.name);
|
|
2000
2615
|
const commandName = readObjectString(sourceText, "name") ?? entry.name;
|
|
2001
|
-
await writeFile4(
|
|
2616
|
+
await writeFile4(path11.join(destination, `${safeFileStem(commandName)}.md`), markdown);
|
|
2002
2617
|
continue;
|
|
2003
2618
|
}
|
|
2004
|
-
await cp(sourcePath,
|
|
2619
|
+
await cp(sourcePath, path11.join(destination, entry.name), {
|
|
2005
2620
|
recursive: true,
|
|
2006
2621
|
filter: safeCommandCopyFilter
|
|
2007
2622
|
});
|
|
2008
2623
|
}
|
|
2009
2624
|
}
|
|
2010
2625
|
async function safeCommandCopyFilter(sourcePath) {
|
|
2011
|
-
const basename =
|
|
2626
|
+
const basename = path11.basename(sourcePath);
|
|
2012
2627
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2013
2628
|
return false;
|
|
2014
2629
|
}
|
|
@@ -2037,32 +2652,32 @@ ${Object.entries(frontmatter).map(([key, value]) => `${key}: ${yamlScalar(value)
|
|
|
2037
2652
|
|
|
2038
2653
|
// packages/compiler/src/plugin-components/hooks.ts
|
|
2039
2654
|
import { mkdir as mkdir5, readdir as readdir4, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
2040
|
-
import
|
|
2655
|
+
import path12 from "path";
|
|
2041
2656
|
import {
|
|
2042
2657
|
Node as Node6,
|
|
2043
2658
|
Project as Project2,
|
|
2044
2659
|
SyntaxKind as SyntaxKind4
|
|
2045
2660
|
} from "ts-morph";
|
|
2046
2661
|
async function copyHooks(rootDir, destination) {
|
|
2047
|
-
const source =
|
|
2662
|
+
const source = path12.join(rootDir, "hooks");
|
|
2048
2663
|
if (!existsSyncSafe(source)) {
|
|
2049
2664
|
return;
|
|
2050
2665
|
}
|
|
2051
2666
|
const entries = await readdir4(source, { withFileTypes: true });
|
|
2052
2667
|
const hookDirs = entries.filter(
|
|
2053
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2668
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path12.join(source, entry.name, "hook.ts"))
|
|
2054
2669
|
);
|
|
2055
2670
|
if (!hookDirs.length) {
|
|
2056
2671
|
return;
|
|
2057
2672
|
}
|
|
2058
2673
|
const config = {};
|
|
2059
2674
|
for (const entry of hookDirs) {
|
|
2060
|
-
const filePath =
|
|
2675
|
+
const filePath = path12.join(source, entry.name, "hook.ts");
|
|
2061
2676
|
mergeHooks(config, parseHookFile(await readFile6(filePath, "utf8"), entry.name));
|
|
2062
2677
|
}
|
|
2063
2678
|
await mkdir5(destination, { recursive: true });
|
|
2064
2679
|
await writeFile5(
|
|
2065
|
-
|
|
2680
|
+
path12.join(destination, "hooks.json"),
|
|
2066
2681
|
`${JSON.stringify(config, null, 2)}
|
|
2067
2682
|
`
|
|
2068
2683
|
);
|
|
@@ -2089,7 +2704,7 @@ function parseHookFile(source, fallbackName) {
|
|
|
2089
2704
|
throw new Error(`hooks/${fallbackName}/hook.ts must default-export hook({ ... }) or hooks({ ... }).`);
|
|
2090
2705
|
}
|
|
2091
2706
|
function parseHookDefinition(definition, fallbackName) {
|
|
2092
|
-
const event =
|
|
2707
|
+
const event = readStringProperty4(definition, "event");
|
|
2093
2708
|
if (!event) {
|
|
2094
2709
|
throw new Error(`hooks/${fallbackName}/hook.ts must declare an event string.`);
|
|
2095
2710
|
}
|
|
@@ -2099,7 +2714,7 @@ function parseHookDefinition(definition, fallbackName) {
|
|
|
2099
2714
|
}
|
|
2100
2715
|
return {
|
|
2101
2716
|
event,
|
|
2102
|
-
matcher:
|
|
2717
|
+
matcher: readStringProperty4(definition, "matcher"),
|
|
2103
2718
|
run: hooks
|
|
2104
2719
|
};
|
|
2105
2720
|
}
|
|
@@ -2119,7 +2734,7 @@ function parseHooksDefinition(definition, fallbackName) {
|
|
|
2119
2734
|
throw new Error(`hooks/${fallbackName}/hook.ts event "${event}" entries must be objects.`);
|
|
2120
2735
|
}
|
|
2121
2736
|
return stripUndefined2({
|
|
2122
|
-
matcher:
|
|
2737
|
+
matcher: readStringProperty4(entry, "matcher"),
|
|
2123
2738
|
hooks: readHookArray(entry, "run", fallbackName)
|
|
2124
2739
|
});
|
|
2125
2740
|
});
|
|
@@ -2142,7 +2757,7 @@ function parseHookHandlers(handlers, fallbackName) {
|
|
|
2142
2757
|
}
|
|
2143
2758
|
function parseHookHandler(handler, fallbackName) {
|
|
2144
2759
|
if (Node6.isObjectLiteralExpression(handler)) {
|
|
2145
|
-
const type =
|
|
2760
|
+
const type = readStringProperty4(handler, "type");
|
|
2146
2761
|
if (type !== "command" && type !== "http") {
|
|
2147
2762
|
throw new Error(`hooks/${fallbackName}/hook.ts hook handlers need type "command" or "http".`);
|
|
2148
2763
|
}
|
|
@@ -2192,7 +2807,7 @@ function objectLiteralToRecord(object) {
|
|
|
2192
2807
|
}
|
|
2193
2808
|
return stripUndefined2(record);
|
|
2194
2809
|
}
|
|
2195
|
-
function
|
|
2810
|
+
function readStringProperty4(object, propertyName) {
|
|
2196
2811
|
const property = object.getProperty(propertyName);
|
|
2197
2812
|
if (!property || !Node6.isPropertyAssignment(property)) {
|
|
2198
2813
|
return void 0;
|
|
@@ -2236,7 +2851,7 @@ function mergeHooks(target, source) {
|
|
|
2236
2851
|
|
|
2237
2852
|
// packages/compiler/src/plugin-components/passthrough.ts
|
|
2238
2853
|
import { cp as cp2, lstat as lstat2 } from "fs/promises";
|
|
2239
|
-
import
|
|
2854
|
+
import path13 from "path";
|
|
2240
2855
|
async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
2241
2856
|
for (const directory of [
|
|
2242
2857
|
"assets",
|
|
@@ -2245,18 +2860,18 @@ async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
|
2245
2860
|
"output-styles",
|
|
2246
2861
|
"themes"
|
|
2247
2862
|
]) {
|
|
2248
|
-
const source =
|
|
2863
|
+
const source = path13.join(rootDir, directory);
|
|
2249
2864
|
if (!existsSyncSafe(source)) {
|
|
2250
2865
|
continue;
|
|
2251
2866
|
}
|
|
2252
|
-
await cp2(source,
|
|
2867
|
+
await cp2(source, path13.join(pluginDir, directory), {
|
|
2253
2868
|
recursive: true,
|
|
2254
2869
|
filter: safePluginCopyFilter
|
|
2255
2870
|
});
|
|
2256
2871
|
}
|
|
2257
2872
|
}
|
|
2258
2873
|
async function safePluginCopyFilter(sourcePath) {
|
|
2259
|
-
const basename =
|
|
2874
|
+
const basename = path13.basename(sourcePath);
|
|
2260
2875
|
if (basename === "node_modules" || basename === ".git" || basename === ".env" || basename.startsWith(".env.")) {
|
|
2261
2876
|
return false;
|
|
2262
2877
|
}
|
|
@@ -2265,11 +2880,11 @@ async function safePluginCopyFilter(sourcePath) {
|
|
|
2265
2880
|
}
|
|
2266
2881
|
|
|
2267
2882
|
// packages/compiler/src/plugin-components/skills.ts
|
|
2268
|
-
import { existsSync as
|
|
2883
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2269
2884
|
import { cp as cp3, lstat as lstat3, mkdir as mkdir6, readdir as readdir5, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
2270
|
-
import
|
|
2885
|
+
import path14 from "path";
|
|
2271
2886
|
async function copySkills(rootDir, destination) {
|
|
2272
|
-
const source =
|
|
2887
|
+
const source = path14.join(rootDir, "skills");
|
|
2273
2888
|
if (!existsSyncSafe(source)) {
|
|
2274
2889
|
return;
|
|
2275
2890
|
}
|
|
@@ -2279,27 +2894,27 @@ async function copySkills(rootDir, destination) {
|
|
|
2279
2894
|
if (!entry.isDirectory()) {
|
|
2280
2895
|
continue;
|
|
2281
2896
|
}
|
|
2282
|
-
const sourceDir =
|
|
2283
|
-
const destinationDir =
|
|
2284
|
-
const staticSkill =
|
|
2285
|
-
const dynamicSkill =
|
|
2897
|
+
const sourceDir = path14.join(source, entry.name);
|
|
2898
|
+
const destinationDir = path14.join(destination, entry.name);
|
|
2899
|
+
const staticSkill = path14.join(sourceDir, "SKILL.md");
|
|
2900
|
+
const dynamicSkill = path14.join(sourceDir, "skill.ts");
|
|
2286
2901
|
await mkdir6(destinationDir, { recursive: true });
|
|
2287
|
-
if (
|
|
2902
|
+
if (existsSync5(staticSkill)) {
|
|
2288
2903
|
await cp3(sourceDir, destinationDir, {
|
|
2289
2904
|
recursive: true,
|
|
2290
|
-
filter: async (sourcePath) => !sourcePath.endsWith(`${
|
|
2905
|
+
filter: async (sourcePath) => !sourcePath.endsWith(`${path14.sep}skill.ts`) && await safeSkillCopyFilter(sourcePath)
|
|
2291
2906
|
});
|
|
2292
|
-
} else if (
|
|
2907
|
+
} else if (existsSync5(dynamicSkill)) {
|
|
2293
2908
|
const generated = parseDynamicSkill(
|
|
2294
2909
|
await readFile7(dynamicSkill, "utf8"),
|
|
2295
2910
|
entry.name
|
|
2296
2911
|
);
|
|
2297
|
-
await writeFile6(
|
|
2912
|
+
await writeFile6(path14.join(destinationDir, "SKILL.md"), generated);
|
|
2298
2913
|
}
|
|
2299
2914
|
}
|
|
2300
2915
|
}
|
|
2301
2916
|
async function safeSkillCopyFilter(sourcePath) {
|
|
2302
|
-
const basename =
|
|
2917
|
+
const basename = path14.basename(sourcePath);
|
|
2303
2918
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2304
2919
|
return false;
|
|
2305
2920
|
}
|
|
@@ -2325,25 +2940,25 @@ async function buildPluginPackages(rootDir, outRoot, manifest) {
|
|
|
2325
2940
|
await buildClaudePlugin(rootDir, outRoot, identity, manifest);
|
|
2326
2941
|
}
|
|
2327
2942
|
async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
2328
|
-
const pluginDir =
|
|
2329
|
-
await mkdir7(
|
|
2330
|
-
await copySkills(rootDir,
|
|
2331
|
-
await copyCommands(rootDir,
|
|
2332
|
-
await copyHooks(rootDir,
|
|
2333
|
-
await emitClaudeAgents(rootDir,
|
|
2943
|
+
const pluginDir = path15.join(outRoot, "claude-plugin");
|
|
2944
|
+
await mkdir7(path15.join(pluginDir, ".claude-plugin"), { recursive: true });
|
|
2945
|
+
await copySkills(rootDir, path15.join(pluginDir, "skills"));
|
|
2946
|
+
await copyCommands(rootDir, path15.join(pluginDir, "commands"));
|
|
2947
|
+
await copyHooks(rootDir, path15.join(pluginDir, "hooks"));
|
|
2948
|
+
await emitClaudeAgents(rootDir, path15.join(pluginDir, "agents"));
|
|
2334
2949
|
await copyClaudePassthroughDirectories(rootDir, pluginDir);
|
|
2335
|
-
await writeJson(
|
|
2950
|
+
await writeJson(path15.join(pluginDir, ".claude-plugin", "plugin.json"), {
|
|
2336
2951
|
name: identity.slug,
|
|
2337
2952
|
version: identity.version,
|
|
2338
2953
|
description: identity.description,
|
|
2339
2954
|
displayName: identity.name,
|
|
2340
2955
|
installationPreference: "available"
|
|
2341
2956
|
});
|
|
2342
|
-
await writeJson(
|
|
2957
|
+
await writeJson(path15.join(pluginDir, "version.json"), {
|
|
2343
2958
|
version: identity.version,
|
|
2344
2959
|
generatedBy: "sidecar"
|
|
2345
2960
|
});
|
|
2346
|
-
await writeJson(
|
|
2961
|
+
await writeJson(path15.join(pluginDir, ".mcp.json"), {
|
|
2347
2962
|
mcpServers: {
|
|
2348
2963
|
[identity.slug]: {
|
|
2349
2964
|
type: "http",
|
|
@@ -2351,7 +2966,7 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2351
2966
|
}
|
|
2352
2967
|
}
|
|
2353
2968
|
});
|
|
2354
|
-
await writeJson(
|
|
2969
|
+
await writeJson(path15.join(pluginDir, "settings.json"), {
|
|
2355
2970
|
sidecar: {
|
|
2356
2971
|
tools: manifest.tools.map((entry) => entry.id),
|
|
2357
2972
|
resources: manifest.resources.map((entry) => entry.uri),
|
|
@@ -2359,21 +2974,21 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2359
2974
|
}
|
|
2360
2975
|
});
|
|
2361
2976
|
await writeFile7(
|
|
2362
|
-
|
|
2977
|
+
path15.join(pluginDir, "README.md"),
|
|
2363
2978
|
`# ${identity.name} Claude Plugin
|
|
2364
2979
|
|
|
2365
2980
|
This package was generated by Sidecar.
|
|
2366
2981
|
|
|
2367
2982
|
Set \`SIDECAR_MCP_URL\` to the hosted MCP endpoint for this app.
|
|
2368
2983
|
|
|
2369
|
-
For local testing, run \`sidecar dev --tunnel\` and use the
|
|
2984
|
+
For local testing, run \`sidecar dev --tunnel\` and use the validated HTTPS MCP URL it prints. Temporary quick tunnels are public and best-effort; use a configured tunnel/domain or deployed preview for repeatable testing.
|
|
2370
2985
|
|
|
2371
2986
|
Claude/Cowork plugin-specific components such as skills, slash commands, agents, hooks, output styles, themes, monitors, assets, and bin scripts will be emitted here when the source project defines them.
|
|
2372
2987
|
`
|
|
2373
2988
|
);
|
|
2374
2989
|
}
|
|
2375
2990
|
async function writeJson(filePath, value) {
|
|
2376
|
-
await mkdir7(
|
|
2991
|
+
await mkdir7(path15.dirname(filePath), { recursive: true });
|
|
2377
2992
|
await writeFile7(
|
|
2378
2993
|
filePath,
|
|
2379
2994
|
`${JSON.stringify(stripUndefined2(value), null, 2)}
|
|
@@ -2383,13 +2998,13 @@ async function writeJson(filePath, value) {
|
|
|
2383
2998
|
|
|
2384
2999
|
// packages/compiler/src/prompts.ts
|
|
2385
3000
|
import { readdir as readdir6 } from "fs/promises";
|
|
2386
|
-
import
|
|
3001
|
+
import path16 from "path";
|
|
2387
3002
|
import {
|
|
2388
3003
|
Node as Node7,
|
|
2389
3004
|
SyntaxKind as SyntaxKind5
|
|
2390
3005
|
} from "ts-morph";
|
|
2391
3006
|
async function analyzeProjectPrompts(rootDir) {
|
|
2392
|
-
const promptFiles = await findPromptFiles(
|
|
3007
|
+
const promptFiles = await findPromptFiles(path16.join(rootDir, "prompts"));
|
|
2393
3008
|
if (promptFiles.length === 0) {
|
|
2394
3009
|
return [];
|
|
2395
3010
|
}
|
|
@@ -2401,7 +3016,7 @@ async function analyzeProjectPrompts(rootDir) {
|
|
|
2401
3016
|
function analyzePromptFile(sourceFile, rootDir) {
|
|
2402
3017
|
const definition = findPromptDefinition(sourceFile);
|
|
2403
3018
|
const absoluteFile = sourceFile.getFilePath();
|
|
2404
|
-
const directory =
|
|
3019
|
+
const directory = path16.basename(path16.dirname(absoluteFile));
|
|
2405
3020
|
const name = getOptionalStringProperty2(definition, "name") ?? safePathSegment(directory);
|
|
2406
3021
|
const title = getRequiredStringProperty2(definition, "title", sourceFile);
|
|
2407
3022
|
const description = getOptionalStringProperty2(definition, "description");
|
|
@@ -2417,7 +3032,7 @@ function analyzePromptFile(sourceFile, rootDir) {
|
|
|
2417
3032
|
throw new CompilerError(sourceFile, "prompt({ ... }) must include a run method.");
|
|
2418
3033
|
}
|
|
2419
3034
|
return {
|
|
2420
|
-
sourceFile:
|
|
3035
|
+
sourceFile: path16.relative(rootDir, absoluteFile),
|
|
2421
3036
|
directory,
|
|
2422
3037
|
name,
|
|
2423
3038
|
title,
|
|
@@ -2436,7 +3051,7 @@ async function findPromptFiles(promptsDir) {
|
|
|
2436
3051
|
if (!entry.isDirectory()) {
|
|
2437
3052
|
continue;
|
|
2438
3053
|
}
|
|
2439
|
-
const filePath =
|
|
3054
|
+
const filePath = path16.join(promptsDir, entry.name, "prompt.ts");
|
|
2440
3055
|
if (existsSyncSafe(filePath)) {
|
|
2441
3056
|
files.push(filePath);
|
|
2442
3057
|
}
|
|
@@ -2506,12 +3121,12 @@ function readArgInput(node) {
|
|
|
2506
3121
|
if (Node7.isObjectLiteralExpression(initializer)) {
|
|
2507
3122
|
return {
|
|
2508
3123
|
description: getOptionalStringProperty2(initializer, "description"),
|
|
2509
|
-
required:
|
|
3124
|
+
required: readBooleanProperty4(initializer, "required")
|
|
2510
3125
|
};
|
|
2511
3126
|
}
|
|
2512
3127
|
return void 0;
|
|
2513
3128
|
}
|
|
2514
|
-
function
|
|
3129
|
+
function readBooleanProperty4(definition, propertyName) {
|
|
2515
3130
|
const property = definition.getProperty(propertyName);
|
|
2516
3131
|
if (!property || !Node7.isPropertyAssignment(property)) {
|
|
2517
3132
|
return void 0;
|
|
@@ -2558,7 +3173,7 @@ function readIcons(definition) {
|
|
|
2558
3173
|
return [{
|
|
2559
3174
|
src,
|
|
2560
3175
|
mimeType: getOptionalStringProperty2(object, "mimeType"),
|
|
2561
|
-
sizes:
|
|
3176
|
+
sizes: readStringArrayProperty4(object, "sizes")
|
|
2562
3177
|
}];
|
|
2563
3178
|
});
|
|
2564
3179
|
return icons.length ? icons : void 0;
|
|
@@ -2571,7 +3186,7 @@ function readObjectProperty4(definition, propertyName) {
|
|
|
2571
3186
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2572
3187
|
return initializer && Node7.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2573
3188
|
}
|
|
2574
|
-
function
|
|
3189
|
+
function readStringArrayProperty4(definition, propertyName) {
|
|
2575
3190
|
const property = definition.getProperty(propertyName);
|
|
2576
3191
|
if (!property || !Node7.isPropertyAssignment(property)) {
|
|
2577
3192
|
return void 0;
|
|
@@ -2585,13 +3200,13 @@ function readStringArrayProperty3(definition, propertyName) {
|
|
|
2585
3200
|
|
|
2586
3201
|
// packages/compiler/src/resources.ts
|
|
2587
3202
|
import { readdir as readdir7 } from "fs/promises";
|
|
2588
|
-
import
|
|
3203
|
+
import path17 from "path";
|
|
2589
3204
|
import {
|
|
2590
3205
|
Node as Node8,
|
|
2591
3206
|
SyntaxKind as SyntaxKind6
|
|
2592
3207
|
} from "ts-morph";
|
|
2593
3208
|
async function analyzeProjectResources(rootDir) {
|
|
2594
|
-
const resourceFiles = await findResourceFiles(
|
|
3209
|
+
const resourceFiles = await findResourceFiles(path17.join(rootDir, "resources"));
|
|
2595
3210
|
if (resourceFiles.length === 0) {
|
|
2596
3211
|
return [];
|
|
2597
3212
|
}
|
|
@@ -2603,7 +3218,7 @@ async function analyzeProjectResources(rootDir) {
|
|
|
2603
3218
|
function analyzeResourceFile(sourceFile, rootDir) {
|
|
2604
3219
|
const definition = findResourceDefinition(sourceFile);
|
|
2605
3220
|
const absoluteFile = sourceFile.getFilePath();
|
|
2606
|
-
const directory =
|
|
3221
|
+
const directory = path17.basename(path17.dirname(absoluteFile));
|
|
2607
3222
|
const defaultUri = `sidecar://resources/${safePathSegment(directory)}`;
|
|
2608
3223
|
const name = getRequiredStringProperty3(definition, "name", sourceFile);
|
|
2609
3224
|
const uri = getOptionalStringProperty3(definition, "uri") ?? defaultUri;
|
|
@@ -2621,7 +3236,7 @@ function analyzeResourceFile(sourceFile, rootDir) {
|
|
|
2621
3236
|
throw new CompilerError(sourceFile, "resource({ ... }) must include a read method.");
|
|
2622
3237
|
}
|
|
2623
3238
|
return {
|
|
2624
|
-
sourceFile:
|
|
3239
|
+
sourceFile: path17.relative(rootDir, absoluteFile),
|
|
2625
3240
|
directory,
|
|
2626
3241
|
uri,
|
|
2627
3242
|
name,
|
|
@@ -2630,7 +3245,7 @@ function analyzeResourceFile(sourceFile, rootDir) {
|
|
|
2630
3245
|
mimeType: descriptor.mimeType,
|
|
2631
3246
|
size: descriptor.size,
|
|
2632
3247
|
annotations: descriptor.annotations,
|
|
2633
|
-
subscribe:
|
|
3248
|
+
subscribe: readBooleanProperty5(definition, "subscribe"),
|
|
2634
3249
|
descriptor
|
|
2635
3250
|
};
|
|
2636
3251
|
}
|
|
@@ -2644,7 +3259,7 @@ async function findResourceFiles(resourcesDir) {
|
|
|
2644
3259
|
if (!entry.isDirectory()) {
|
|
2645
3260
|
continue;
|
|
2646
3261
|
}
|
|
2647
|
-
const filePath =
|
|
3262
|
+
const filePath = path17.join(resourcesDir, entry.name, "resource.ts");
|
|
2648
3263
|
if (existsSyncSafe(filePath)) {
|
|
2649
3264
|
files.push(filePath);
|
|
2650
3265
|
}
|
|
@@ -2691,7 +3306,7 @@ function getOptionalNumberProperty(definition, propertyName) {
|
|
|
2691
3306
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2692
3307
|
return initializer && Node8.isNumericLiteral(initializer) ? Number(initializer.getLiteralText()) : void 0;
|
|
2693
3308
|
}
|
|
2694
|
-
function
|
|
3309
|
+
function readBooleanProperty5(definition, propertyName) {
|
|
2695
3310
|
const property = definition.getProperty(propertyName);
|
|
2696
3311
|
if (!property || !Node8.isPropertyAssignment(property)) {
|
|
2697
3312
|
return void 0;
|
|
@@ -2723,7 +3338,7 @@ function readAnnotations2(definition) {
|
|
|
2723
3338
|
return void 0;
|
|
2724
3339
|
}
|
|
2725
3340
|
const annotations = {};
|
|
2726
|
-
const audience =
|
|
3341
|
+
const audience = readStringArrayProperty5(initializer, "audience")?.filter((value) => value === "user" || value === "assistant");
|
|
2727
3342
|
const priority = getOptionalNumberProperty(initializer, "priority");
|
|
2728
3343
|
const lastModified = getOptionalStringProperty3(initializer, "lastModified");
|
|
2729
3344
|
if (audience?.length) annotations.audience = audience;
|
|
@@ -2752,7 +3367,7 @@ function readIcons2(definition) {
|
|
|
2752
3367
|
return [{
|
|
2753
3368
|
src,
|
|
2754
3369
|
mimeType: getOptionalStringProperty3(object, "mimeType"),
|
|
2755
|
-
sizes:
|
|
3370
|
+
sizes: readStringArrayProperty5(object, "sizes")
|
|
2756
3371
|
}];
|
|
2757
3372
|
});
|
|
2758
3373
|
return icons.length ? icons : void 0;
|
|
@@ -2765,7 +3380,7 @@ function readObjectProperty5(definition, propertyName) {
|
|
|
2765
3380
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2766
3381
|
return initializer && Node8.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2767
3382
|
}
|
|
2768
|
-
function
|
|
3383
|
+
function readStringArrayProperty5(definition, propertyName) {
|
|
2769
3384
|
const property = definition.getProperty(propertyName);
|
|
2770
3385
|
if (!property || !Node8.isPropertyAssignment(property)) {
|
|
2771
3386
|
return void 0;
|
|
@@ -2777,15 +3392,386 @@ function readStringArrayProperty4(definition, propertyName) {
|
|
|
2777
3392
|
return initializer.getElements().filter(Node8.isStringLiteral).map((element) => element.getLiteralText());
|
|
2778
3393
|
}
|
|
2779
3394
|
|
|
3395
|
+
// packages/compiler/src/server-output.ts
|
|
3396
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3397
|
+
import { mkdir as mkdir8, rm, writeFile as writeFile8 } from "fs/promises";
|
|
3398
|
+
import path18 from "path";
|
|
3399
|
+
import { build as esbuild2 } from "esbuild";
|
|
3400
|
+
var SERVER_ENTRYPOINT = "server/index.js";
|
|
3401
|
+
var VERCEL_FUNCTION_DIR = "functions/api/sidecar.func";
|
|
3402
|
+
var VERCEL_ENTRYPOINT = `${VERCEL_FUNCTION_DIR}/index.js`;
|
|
3403
|
+
var VERCEL_HANDLER_FILE = "index.js";
|
|
3404
|
+
async function buildServerOutput(rootDir, outDir, manifest, identity, host = "node", options = {}) {
|
|
3405
|
+
const cacheDir = path18.join(rootDir, ".sidecar", "cache", "server");
|
|
3406
|
+
await mkdir8(cacheDir, { recursive: true });
|
|
3407
|
+
const entryFile = path18.join(cacheDir, "index.ts");
|
|
3408
|
+
await writeFile8(entryFile, renderServerEntry(rootDir, entryFile, manifest, identity));
|
|
3409
|
+
const serverFile = path18.join(outDir, SERVER_ENTRYPOINT);
|
|
3410
|
+
await mkdir8(path18.dirname(serverFile), { recursive: true });
|
|
3411
|
+
await esbuild2({
|
|
3412
|
+
absWorkingDir: rootDir,
|
|
3413
|
+
alias: sidecarBundleAliases(rootDir),
|
|
3414
|
+
banner: {
|
|
3415
|
+
js: `import { createRequire as __sidecarCreateRequire } from "node:module";
|
|
3416
|
+
const require = __sidecarCreateRequire(import.meta.url);`
|
|
3417
|
+
},
|
|
3418
|
+
bundle: true,
|
|
3419
|
+
entryPoints: [entryFile],
|
|
3420
|
+
format: "esm",
|
|
3421
|
+
legalComments: "none",
|
|
3422
|
+
minify: false,
|
|
3423
|
+
nodePaths: [
|
|
3424
|
+
path18.join(rootDir, "node_modules"),
|
|
3425
|
+
path18.join(process.cwd(), "node_modules")
|
|
3426
|
+
],
|
|
3427
|
+
outfile: serverFile,
|
|
3428
|
+
platform: "node",
|
|
3429
|
+
sourcemap: false,
|
|
3430
|
+
target: "node20"
|
|
3431
|
+
});
|
|
3432
|
+
await writeFile8(path18.join(outDir, "package.json"), renderServerPackage(identity));
|
|
3433
|
+
if (host === "vercel") {
|
|
3434
|
+
const vercelOutputDir = options.vercelOutputDir ?? outDir;
|
|
3435
|
+
await rm(path18.join(vercelOutputDir, "api"), { recursive: true, force: true });
|
|
3436
|
+
await rm(path18.join(vercelOutputDir, "vercel.json"), { force: true });
|
|
3437
|
+
await writeFile8(path18.join(outDir, VERCEL_HANDLER_FILE), renderVercelEntrypoint());
|
|
3438
|
+
await writeFile8(path18.join(outDir, ".vc-config.json"), renderVercelFunctionConfig());
|
|
3439
|
+
await mkdir8(vercelOutputDir, { recursive: true });
|
|
3440
|
+
await writeFile8(path18.join(vercelOutputDir, "config.json"), renderVercelOutputConfig());
|
|
3441
|
+
} else {
|
|
3442
|
+
await rm(path18.join(outDir, "api"), { recursive: true, force: true });
|
|
3443
|
+
await rm(path18.join(outDir, "vercel.json"), { force: true });
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
function renderServerEntry(rootDir, entryFile, manifest, identity) {
|
|
3447
|
+
const entryDir = path18.dirname(entryFile);
|
|
3448
|
+
const tools = manifest.tools;
|
|
3449
|
+
const resources = manifest.resources;
|
|
3450
|
+
const prompts = manifest.prompts;
|
|
3451
|
+
const imports = [
|
|
3452
|
+
`import { readFileSync, realpathSync } from "node:fs";`,
|
|
3453
|
+
`import { createServer } from "node:http";`,
|
|
3454
|
+
`import { pathToFileURL } from "node:url";`,
|
|
3455
|
+
`import { createSidecarHttpHandler } from "@sidecar-ai/server";`,
|
|
3456
|
+
`import { isSidecarAuth } from "@sidecar-ai/auth";`,
|
|
3457
|
+
`import { isSidecarPrompt, isSidecarRemote, isSidecarResource, isSidecarTool } from "@sidecar-ai/core";`,
|
|
3458
|
+
`import { isSidecarProxy } from "@sidecar-ai/server/proxy";`,
|
|
3459
|
+
...tools.map(
|
|
3460
|
+
(entry, index) => `import tool${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3461
|
+
),
|
|
3462
|
+
...resources.map(
|
|
3463
|
+
(entry, index) => `import resource${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3464
|
+
),
|
|
3465
|
+
...prompts.map(
|
|
3466
|
+
(entry, index) => `import prompt${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3467
|
+
),
|
|
3468
|
+
existsSync6(path18.join(rootDir, "auth.ts")) ? `import authExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "auth.ts")))};` : `const authExport = undefined;`,
|
|
3469
|
+
existsSync6(path18.join(rootDir, "proxy.ts")) ? `import proxyExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "proxy.ts")))};` : `const proxyExport = undefined;`,
|
|
3470
|
+
existsSync6(path18.join(rootDir, "remote.ts")) ? `import remoteExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "remote.ts")))};` : `const remoteExport = undefined;`,
|
|
3471
|
+
existsSync6(path18.join(rootDir, "sidecar.config.ts")) ? `import configExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "sidecar.config.ts")))};` : `const configExport = undefined;`
|
|
3472
|
+
].join("\n");
|
|
3473
|
+
return `${imports}
|
|
3474
|
+
|
|
3475
|
+
const manifest = ${JSON.stringify(manifest, null, 2)};
|
|
3476
|
+
const identity = ${JSON.stringify(identity, null, 2)};
|
|
3477
|
+
|
|
3478
|
+
const loadedAuth = authExport === undefined ? undefined : assertAuth(authExport);
|
|
3479
|
+
const auth = loadedAuth && process.env.SIDECAR_MCP_URL
|
|
3480
|
+
? loadedAuth.withResource(process.env.SIDECAR_MCP_URL)
|
|
3481
|
+
: loadedAuth;
|
|
3482
|
+
const proxy = proxyExport === undefined ? undefined : assertProxy(proxyExport);
|
|
3483
|
+
const remoteExecution = manifest.codeMode?.remoteExecution
|
|
3484
|
+
? assertRemote(remoteExport)
|
|
3485
|
+
: undefined;
|
|
3486
|
+
const runtimeConfig = configExport && typeof configExport === "object" ? configExport : undefined;
|
|
3487
|
+
|
|
3488
|
+
if (auth && !process.env.SIDECAR_MCP_URL) {
|
|
3489
|
+
console.warn("Sidecar auth is enabled. Set SIDECAR_MCP_URL to the public https://.../mcp URL before hosting.");
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
export const handler = createSidecarHttpHandler({
|
|
3493
|
+
name: identity.name,
|
|
3494
|
+
version: identity.version,
|
|
3495
|
+
path: process.env.SIDECAR_MCP_PATH ?? "/mcp",
|
|
3496
|
+
publicUrl: process.env.SIDECAR_PUBLIC_URL ?? process.env.SIDECAR_MCP_URL,
|
|
3497
|
+
auth,
|
|
3498
|
+
proxy,
|
|
3499
|
+
codeMode: manifest.codeMode
|
|
3500
|
+
? {
|
|
3501
|
+
enabled: true,
|
|
3502
|
+
unsafe: manifest.codeMode.unsafe,
|
|
3503
|
+
render: manifest.codeMode.render,
|
|
3504
|
+
widgetMeta: manifest.codeMode.widget?.resourceUri
|
|
3505
|
+
? {
|
|
3506
|
+
ui: { resourceUri: manifest.codeMode.widget.resourceUri },
|
|
3507
|
+
"ui/resourceUri": manifest.codeMode.widget.resourceUri,
|
|
3508
|
+
...(manifest.target === "chatgpt" ? { "openai/outputTemplate": manifest.codeMode.widget.resourceUri } : {}),
|
|
3509
|
+
}
|
|
3510
|
+
: undefined,
|
|
3511
|
+
}
|
|
3512
|
+
: undefined,
|
|
3513
|
+
remoteExecution,
|
|
3514
|
+
tools: [
|
|
3515
|
+
${tools.map((entry, index) => ` loadTool(${JSON.stringify(entry.sourceFile)}, tool${index}, manifest.tools[${index}].descriptor),`).join("\n")}
|
|
3516
|
+
],
|
|
3517
|
+
resources: [
|
|
3518
|
+
${renderWidgetResources(manifest)}
|
|
3519
|
+
${resources.map((entry, index) => ` loadResource(${JSON.stringify(entry.sourceFile)}, resource${index}, manifest.resources[${index}]),`).join("\n")}
|
|
3520
|
+
],
|
|
3521
|
+
resourceTemplates: manifest.resourceTemplates.map((entry) => ({ descriptor: entry.descriptor })),
|
|
3522
|
+
prompts: [
|
|
3523
|
+
${prompts.map((entry, index) => ` loadPrompt(${JSON.stringify(entry.sourceFile)}, prompt${index}, manifest.prompts[${index}].descriptor),`).join("\n")}
|
|
3524
|
+
],
|
|
3525
|
+
capabilities: {
|
|
3526
|
+
tools: runtimeConfig?.tools ?? manifest.config.tools,
|
|
3527
|
+
resources: runtimeConfig?.resources ?? manifest.config.resources,
|
|
3528
|
+
prompts: runtimeConfig?.prompts ?? manifest.config.prompts,
|
|
3529
|
+
},
|
|
3530
|
+
pagination: runtimeConfig?.pagination ?? {
|
|
3531
|
+
pageSize: manifest.config.pagination.pageSize,
|
|
3532
|
+
},
|
|
3533
|
+
});
|
|
3534
|
+
|
|
3535
|
+
export default handler;
|
|
3536
|
+
|
|
3537
|
+
export const server = createServer(handler);
|
|
3538
|
+
|
|
3539
|
+
if (isDirectRun()) {
|
|
3540
|
+
const port = readPort();
|
|
3541
|
+
const host = process.env.SIDECAR_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
3542
|
+
server.listen(port, host, () => {
|
|
3543
|
+
const localHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
3544
|
+
console.log(\`MCP running on Streamable HTTP at http://\${localHost}:\${port}\${process.env.SIDECAR_MCP_PATH ?? "/mcp"}\`);
|
|
3545
|
+
printRuntimeUrls(localHost, port);
|
|
3546
|
+
});
|
|
3547
|
+
|
|
3548
|
+
process.on("SIGTERM", () => shutdown());
|
|
3549
|
+
process.on("SIGINT", () => shutdown());
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
function printRuntimeUrls(localHost, port) {
|
|
3553
|
+
const mcpPath = process.env.SIDECAR_MCP_PATH ?? "/mcp";
|
|
3554
|
+
console.log("Sidecar URLs:");
|
|
3555
|
+
console.log(\` Local MCP: http://\${localHost}:\${port}\${mcpPath}\`);
|
|
3556
|
+
console.log(\` Public MCP: \${process.env.SIDECAR_MCP_URL ?? "set SIDECAR_MCP_URL=https://your-host.example.com" + mcpPath}\`);
|
|
3557
|
+
if (process.env.SIDECAR_PUBLIC_URL) {
|
|
3558
|
+
console.log(\` Public base: \${process.env.SIDECAR_PUBLIC_URL}\`);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
function loadTool(sourceFile, value, descriptor) {
|
|
3563
|
+
const tool = unwrapRuntimeDefault(value);
|
|
3564
|
+
if (!isSidecarTool(tool)) {
|
|
3565
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar tool.\`);
|
|
3566
|
+
}
|
|
3567
|
+
return { tool, descriptor };
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
function loadResource(sourceFile, value, entry) {
|
|
3571
|
+
const resource = unwrapRuntimeDefault(value);
|
|
3572
|
+
if (!isSidecarResource(resource)) {
|
|
3573
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar resource.\`);
|
|
3574
|
+
}
|
|
3575
|
+
return {
|
|
3576
|
+
uri: entry.uri,
|
|
3577
|
+
descriptor: entry.descriptor,
|
|
3578
|
+
resource,
|
|
3579
|
+
};
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
function loadPrompt(sourceFile, value, descriptor) {
|
|
3583
|
+
const prompt = unwrapRuntimeDefault(value);
|
|
3584
|
+
if (!isSidecarPrompt(prompt)) {
|
|
3585
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar prompt.\`);
|
|
3586
|
+
}
|
|
3587
|
+
return { prompt, descriptor };
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
function assertAuth(value) {
|
|
3591
|
+
const authValue = unwrapRuntimeDefault(value);
|
|
3592
|
+
if (!isSidecarAuth(authValue)) {
|
|
3593
|
+
throw new Error("auth.ts must default-export auth({ ... }) from sidecar-ai.");
|
|
3594
|
+
}
|
|
3595
|
+
return authValue;
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
function assertProxy(value) {
|
|
3599
|
+
const proxyValue = unwrapRuntimeDefault(value);
|
|
3600
|
+
if (!isSidecarProxy(proxyValue)) {
|
|
3601
|
+
throw new Error("proxy.ts must default-export proxy({ ... }) from @sidecar-ai/server/proxy.");
|
|
3602
|
+
}
|
|
3603
|
+
return proxyValue;
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
function assertRemote(value) {
|
|
3607
|
+
const remoteValue = unwrapRuntimeDefault(value);
|
|
3608
|
+
if (!isSidecarRemote(remoteValue)) {
|
|
3609
|
+
throw new Error("remote.ts must default-export remote({ execute }) from sidecar-ai/remote.");
|
|
3610
|
+
}
|
|
3611
|
+
return remoteValue;
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
function unwrapRuntimeDefault(value) {
|
|
3615
|
+
if (
|
|
3616
|
+
value &&
|
|
3617
|
+
typeof value === "object" &&
|
|
3618
|
+
"default" in value &&
|
|
3619
|
+
Object.keys(value).every((key) => key === "default" || key === "__esModule")
|
|
3620
|
+
) {
|
|
3621
|
+
return unwrapRuntimeDefault(value.default);
|
|
3622
|
+
}
|
|
3623
|
+
return value;
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
function readWidget(outputFile) {
|
|
3627
|
+
return readFileSync(new URL(\`../\${outputFile}\`, import.meta.url), "utf8");
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
function readPort() {
|
|
3631
|
+
const raw = process.env.PORT ?? process.env.SIDECAR_PORT ?? "3001";
|
|
3632
|
+
const port = Number(raw);
|
|
3633
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
3634
|
+
throw new Error(\`Invalid PORT/SIDECAR_PORT value: \${raw}\`);
|
|
3635
|
+
}
|
|
3636
|
+
return port;
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
function isDirectRun() {
|
|
3640
|
+
const entry = process.argv[1];
|
|
3641
|
+
if (!entry) {
|
|
3642
|
+
return false;
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
return import.meta.url === pathToFileURL(realpathSync(entry)).href;
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
function shutdown() {
|
|
3649
|
+
server.close(() => process.exit(0));
|
|
3650
|
+
}
|
|
3651
|
+
`;
|
|
3652
|
+
}
|
|
3653
|
+
function renderWidgetResources(manifest) {
|
|
3654
|
+
const toolWidgets = manifest.tools.filter((entry) => entry.widget?.outputFile).map((entry) => ` {
|
|
3655
|
+
uri: ${JSON.stringify(entry.widget?.resourceUri)},
|
|
3656
|
+
name: ${JSON.stringify(entry.name)},
|
|
3657
|
+
description: ${JSON.stringify(entry.widget?.options?.description)},
|
|
3658
|
+
mimeType: "text/html;profile=mcp-app",
|
|
3659
|
+
text: readWidget(${JSON.stringify(entry.widget?.outputFile)}),
|
|
3660
|
+
_meta: ${JSON.stringify(entry.widget?.resourceMeta ?? void 0)},
|
|
3661
|
+
},`).join("\n");
|
|
3662
|
+
const codeModeWidget = manifest.codeMode?.widget?.outputFile ? ` {
|
|
3663
|
+
uri: ${JSON.stringify(manifest.codeMode.widget.resourceUri)},
|
|
3664
|
+
name: "Execute Code",
|
|
3665
|
+
description: ${JSON.stringify(manifest.codeMode.widget.options?.description)},
|
|
3666
|
+
mimeType: "text/html;profile=mcp-app",
|
|
3667
|
+
text: readWidget(${JSON.stringify(manifest.codeMode.widget.outputFile)}),
|
|
3668
|
+
_meta: ${JSON.stringify(manifest.codeMode.widget.resourceMeta ?? void 0)},
|
|
3669
|
+
},` : "";
|
|
3670
|
+
return [codeModeWidget, toolWidgets].filter(Boolean).join("\n");
|
|
3671
|
+
}
|
|
3672
|
+
function renderServerPackage(identity) {
|
|
3673
|
+
return `${JSON.stringify({
|
|
3674
|
+
name: `${identity.slug}-sidecar-server`,
|
|
3675
|
+
version: identity.version,
|
|
3676
|
+
private: true,
|
|
3677
|
+
type: "module",
|
|
3678
|
+
scripts: {
|
|
3679
|
+
start: "node server/index.js"
|
|
3680
|
+
},
|
|
3681
|
+
engines: {
|
|
3682
|
+
node: ">=20"
|
|
3683
|
+
}
|
|
3684
|
+
}, null, 2)}
|
|
3685
|
+
`;
|
|
3686
|
+
}
|
|
3687
|
+
function renderVercelEntrypoint() {
|
|
3688
|
+
return `export { default } from "./server/index.js";
|
|
3689
|
+
`;
|
|
3690
|
+
}
|
|
3691
|
+
function renderVercelFunctionConfig() {
|
|
3692
|
+
return `${JSON.stringify({
|
|
3693
|
+
runtime: "nodejs22.x",
|
|
3694
|
+
handler: VERCEL_HANDLER_FILE,
|
|
3695
|
+
launcherType: "Nodejs",
|
|
3696
|
+
shouldAddHelpers: true,
|
|
3697
|
+
supportsResponseStreaming: true,
|
|
3698
|
+
maxDuration: 300
|
|
3699
|
+
}, null, 2)}
|
|
3700
|
+
`;
|
|
3701
|
+
}
|
|
3702
|
+
function renderVercelOutputConfig() {
|
|
3703
|
+
return `${JSON.stringify({
|
|
3704
|
+
version: 3,
|
|
3705
|
+
routes: [
|
|
3706
|
+
{
|
|
3707
|
+
src: "/(.*)",
|
|
3708
|
+
dest: "/api/sidecar"
|
|
3709
|
+
}
|
|
3710
|
+
]
|
|
3711
|
+
}, null, 2)}
|
|
3712
|
+
`;
|
|
3713
|
+
}
|
|
3714
|
+
function sidecarBundleAliases(rootDir) {
|
|
3715
|
+
const repoRoot = findSidecarRepoRoot2(rootDir) ?? findSidecarRepoRoot2(process.cwd());
|
|
3716
|
+
if (!repoRoot) {
|
|
3717
|
+
return void 0;
|
|
3718
|
+
}
|
|
3719
|
+
return {
|
|
3720
|
+
"sidecar-ai/remote": path18.join(repoRoot, "packages", "sidecar-ai", "src", "remote.ts"),
|
|
3721
|
+
"sidecar-ai": path18.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
|
|
3722
|
+
"@sidecar-ai/auth": path18.join(repoRoot, "packages", "auth", "src", "index.ts"),
|
|
3723
|
+
"@sidecar-ai/core": path18.join(repoRoot, "packages", "core", "src", "index.ts"),
|
|
3724
|
+
"@sidecar-ai/server": path18.join(repoRoot, "packages", "server", "src", "index.ts"),
|
|
3725
|
+
"@sidecar-ai/server/proxy": path18.join(repoRoot, "packages", "server", "src", "proxy.ts"),
|
|
3726
|
+
"@sidecar-ai/client": path18.join(repoRoot, "packages", "client", "src", "index.ts"),
|
|
3727
|
+
"@sidecar-ai/react": path18.join(repoRoot, "packages", "react", "src", "index.ts"),
|
|
3728
|
+
"@sidecar-ai/native": path18.join(repoRoot, "packages", "native", "src", "index.ts"),
|
|
3729
|
+
"@sidecar-ai/native/components": path18.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
|
|
3730
|
+
"@sidecar-ai/openai": path18.join(repoRoot, "packages", "openai", "src", "index.ts"),
|
|
3731
|
+
"@sidecar-ai/openai/components": path18.join(repoRoot, "packages", "openai", "src", "components.tsx"),
|
|
3732
|
+
"@sidecar-ai/openai/official": path18.join(repoRoot, "packages", "openai", "src", "official.ts"),
|
|
3733
|
+
"@sidecar-ai/anthropic": path18.join(repoRoot, "packages", "anthropic", "src", "index.ts"),
|
|
3734
|
+
"@sidecar-ai/anthropic/agent": path18.join(repoRoot, "packages", "anthropic", "src", "agent.ts"),
|
|
3735
|
+
"@sidecar-ai/anthropic/command": path18.join(repoRoot, "packages", "anthropic", "src", "command.ts"),
|
|
3736
|
+
"@sidecar-ai/anthropic/components": path18.join(repoRoot, "packages", "anthropic", "src", "components.tsx"),
|
|
3737
|
+
"@sidecar-ai/anthropic/hooks": path18.join(repoRoot, "packages", "anthropic", "src", "hooks.ts"),
|
|
3738
|
+
"@sidecar-ai/anthropic/mcp": path18.join(repoRoot, "packages", "anthropic", "src", "mcp.ts"),
|
|
3739
|
+
"@sidecar-ai/anthropic/plugin": path18.join(repoRoot, "packages", "anthropic", "src", "plugin.ts"),
|
|
3740
|
+
"@sidecar-ai/anthropic/skill": path18.join(repoRoot, "packages", "anthropic", "src", "skill.ts")
|
|
3741
|
+
};
|
|
3742
|
+
}
|
|
3743
|
+
function findSidecarRepoRoot2(startDir) {
|
|
3744
|
+
let current = path18.resolve(startDir);
|
|
3745
|
+
while (true) {
|
|
3746
|
+
if (existsSync6(path18.join(current, "packages", "core", "src", "index.ts"))) {
|
|
3747
|
+
return current;
|
|
3748
|
+
}
|
|
3749
|
+
const parent = path18.dirname(current);
|
|
3750
|
+
if (parent === current) {
|
|
3751
|
+
return void 0;
|
|
3752
|
+
}
|
|
3753
|
+
current = parent;
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
|
|
2780
3757
|
// packages/compiler/src/build.ts
|
|
2781
3758
|
async function buildProject(options) {
|
|
2782
|
-
const rootDir =
|
|
2783
|
-
const target = options.target ?? "mcp";
|
|
3759
|
+
const rootDir = path19.resolve(options.rootDir);
|
|
2784
3760
|
const config = analyzeProjectConfig(rootDir);
|
|
3761
|
+
const target = options.target ?? config.build.target ?? "mcp";
|
|
3762
|
+
const host = options.host ?? config.build.host ?? "node";
|
|
3763
|
+
const plugins = options.plugins ?? config.build.plugins ?? false;
|
|
3764
|
+
if (config.codeMode.enabled && !config.codeMode.unsafe && !config.remoteExecution.enabled) {
|
|
3765
|
+
throw new Error("Code mode requires remoteExecution: true, or codeMode: { unsafe: true } for trusted local use.");
|
|
3766
|
+
}
|
|
3767
|
+
if (config.codeMode.enabled && config.remoteExecution.enabled && !existsSync7(path19.join(rootDir, "remote.ts"))) {
|
|
3768
|
+
throw new Error("remoteExecution: true requires a reserved remote.ts file at the project root.");
|
|
3769
|
+
}
|
|
2785
3770
|
const tools = await analyzeProjectTools(rootDir, { target });
|
|
2786
3771
|
const resources = await analyzeProjectResources(rootDir);
|
|
2787
3772
|
const resourceTemplates = [];
|
|
2788
3773
|
const prompts = await analyzeProjectPrompts(rootDir);
|
|
3774
|
+
const identity = await loadProjectIdentity(rootDir);
|
|
2789
3775
|
const diagnostics = await collectProjectDiagnostics(rootDir, {
|
|
2790
3776
|
tools,
|
|
2791
3777
|
resources,
|
|
@@ -2796,37 +3782,71 @@ async function buildProject(options) {
|
|
|
2796
3782
|
if (errors.length) {
|
|
2797
3783
|
throw new Error(errors.map(formatDiagnostic).join("\n"));
|
|
2798
3784
|
}
|
|
2799
|
-
const outDir = resolveInsideRoot(rootDir, options.outDir ??
|
|
2800
|
-
|
|
3785
|
+
const outDir = resolveInsideRoot(rootDir, options.outDir ?? config.build.outDir ?? defaultBuildOutDir(host, target));
|
|
3786
|
+
const runtimeOutDir = resolveRuntimeOutputDir(outDir, host);
|
|
3787
|
+
await buildWidgets(rootDir, runtimeOutDir, tools, config.build.widgets);
|
|
3788
|
+
const codeModeWidget = config.codeMode.enabled && config.codeMode.render.enabled ? await buildCodeModeWidget(rootDir, runtimeOutDir, tools, target, config.build.widgets) : void 0;
|
|
2801
3789
|
const manifest = {
|
|
2802
3790
|
version: 1,
|
|
2803
3791
|
target,
|
|
3792
|
+
host,
|
|
2804
3793
|
rootDir: ".",
|
|
2805
3794
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2806
3795
|
config,
|
|
2807
3796
|
tools,
|
|
3797
|
+
codeMode: config.codeMode.enabled ? {
|
|
3798
|
+
enabled: true,
|
|
3799
|
+
unsafe: config.codeMode.unsafe,
|
|
3800
|
+
remoteExecution: config.remoteExecution.enabled,
|
|
3801
|
+
render: config.codeMode.render,
|
|
3802
|
+
widget: codeModeWidget
|
|
3803
|
+
} : void 0,
|
|
2808
3804
|
resources,
|
|
2809
3805
|
resourceTemplates,
|
|
2810
3806
|
prompts,
|
|
2811
3807
|
diagnostics
|
|
2812
3808
|
};
|
|
2813
|
-
await
|
|
2814
|
-
await
|
|
2815
|
-
|
|
3809
|
+
await mkdir9(runtimeOutDir, { recursive: true });
|
|
3810
|
+
await writeFile9(
|
|
3811
|
+
path19.join(runtimeOutDir, "manifest.sidecar.json"),
|
|
2816
3812
|
`${JSON.stringify(manifest, null, 2)}
|
|
2817
3813
|
`
|
|
2818
3814
|
);
|
|
2819
|
-
await
|
|
3815
|
+
await writeFile9(path19.join(runtimeOutDir, "README.md"), renderMcpReadme(manifest));
|
|
2820
3816
|
await writeGeneratedTypes(rootDir, tools);
|
|
2821
|
-
|
|
2822
|
-
|
|
3817
|
+
await buildServerOutput(rootDir, runtimeOutDir, manifest, identity, host, {
|
|
3818
|
+
vercelOutputDir: outDir
|
|
3819
|
+
});
|
|
3820
|
+
if (plugins) {
|
|
3821
|
+
await buildPluginPackages(rootDir, resolvePluginOutputBase(rootDir, outDir, host), manifest);
|
|
2823
3822
|
}
|
|
2824
3823
|
return manifest;
|
|
2825
3824
|
}
|
|
3825
|
+
function defaultBuildOutDir(host, target) {
|
|
3826
|
+
if (host === "vercel") {
|
|
3827
|
+
return ".vercel/output";
|
|
3828
|
+
}
|
|
3829
|
+
return `out/${target}`;
|
|
3830
|
+
}
|
|
3831
|
+
function resolveRuntimeOutputDir(outDir, host) {
|
|
3832
|
+
if (host === "vercel") {
|
|
3833
|
+
return path19.join(outDir, VERCEL_FUNCTION_DIR);
|
|
3834
|
+
}
|
|
3835
|
+
return outDir;
|
|
3836
|
+
}
|
|
3837
|
+
function resolvePluginOutputBase(rootDir, outDir, host) {
|
|
3838
|
+
if (host === "vercel" && isVercelBuildOutputDir(outDir)) {
|
|
3839
|
+
return path19.join(rootDir, "out");
|
|
3840
|
+
}
|
|
3841
|
+
return path19.dirname(outDir);
|
|
3842
|
+
}
|
|
3843
|
+
function isVercelBuildOutputDir(outDir) {
|
|
3844
|
+
return path19.basename(outDir) === "output" && path19.basename(path19.dirname(outDir)) === ".vercel";
|
|
3845
|
+
}
|
|
2826
3846
|
function resolveInsideRoot(rootDir, output) {
|
|
2827
|
-
const resolved =
|
|
2828
|
-
const relative =
|
|
2829
|
-
if (relative.startsWith("..") ||
|
|
3847
|
+
const resolved = path19.resolve(rootDir, output);
|
|
3848
|
+
const relative = path19.relative(rootDir, resolved);
|
|
3849
|
+
if (relative.startsWith("..") || path19.isAbsolute(relative)) {
|
|
2830
3850
|
throw new Error(`Build output must stay inside the project root: ${output}`);
|
|
2831
3851
|
}
|
|
2832
3852
|
return resolved;
|
|
@@ -2851,13 +3871,41 @@ ${resources || "No resources detected."}
|
|
|
2851
3871
|
|
|
2852
3872
|
${prompts || "No prompts detected."}
|
|
2853
3873
|
|
|
3874
|
+
${manifest.host === "vercel" ? renderVercelReadmeSection() : renderNodeReadmeSection()}
|
|
3875
|
+
|
|
2854
3876
|
## Local HTTPS Testing
|
|
2855
3877
|
|
|
2856
|
-
Run \`sidecar dev --tunnel\` from the project root to start the local MCP server on Streamable HTTP and print
|
|
3878
|
+
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.
|
|
3879
|
+
`;
|
|
3880
|
+
}
|
|
3881
|
+
function renderNodeReadmeSection() {
|
|
3882
|
+
return `## Run The Server
|
|
3883
|
+
|
|
3884
|
+
This output includes a standalone Node MCP server. Start it with:
|
|
3885
|
+
|
|
3886
|
+
\`\`\`sh
|
|
3887
|
+
npm start
|
|
3888
|
+
\`\`\`
|
|
3889
|
+
|
|
3890
|
+
or:
|
|
3891
|
+
|
|
3892
|
+
\`\`\`sh
|
|
3893
|
+
node server/index.js
|
|
3894
|
+
\`\`\`
|
|
3895
|
+
|
|
3896
|
+
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.
|
|
3897
|
+
`;
|
|
3898
|
+
}
|
|
3899
|
+
function renderVercelReadmeSection() {
|
|
3900
|
+
return `## Deploy To Vercel
|
|
3901
|
+
|
|
3902
|
+
This output uses Vercel's Build Output API. The MCP function is emitted at \`${VERCEL_FUNCTION_DIR}\`, and \`config.json\` routes Streamable HTTP traffic to it. Set \`SIDECAR_MCP_URL\` to the public \`https://.../mcp\` URL in Vercel.
|
|
2857
3903
|
`;
|
|
2858
3904
|
}
|
|
2859
3905
|
export {
|
|
2860
3906
|
CompilerError,
|
|
3907
|
+
SERVER_ENTRYPOINT,
|
|
3908
|
+
VERCEL_ENTRYPOINT,
|
|
2861
3909
|
analyzeProjectConfig,
|
|
2862
3910
|
analyzeProjectPrompts,
|
|
2863
3911
|
analyzeProjectResources,
|