@sidecar-ai/compiler 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2892 @@
1
+ // packages/core/src/index.ts
2
+ var toolResultBrand = /* @__PURE__ */ Symbol.for("sidecar.toolResult");
3
+ var resourceResultBrand = /* @__PURE__ */ Symbol.for("sidecar.resourceResult");
4
+ var toolResult = Object.assign(
5
+ createToolResult,
6
+ {
7
+ text(text) {
8
+ return { type: "text", text };
9
+ },
10
+ error(message, options = {}) {
11
+ return createToolResult({
12
+ ...options,
13
+ structuredContent: options.structuredContent,
14
+ content: message,
15
+ isError: true
16
+ });
17
+ }
18
+ }
19
+ );
20
+ var resourceResult = Object.assign(
21
+ createResourceResult,
22
+ {
23
+ many(input) {
24
+ return createResourceResult(input);
25
+ }
26
+ }
27
+ );
28
+ function createToolResult(input) {
29
+ const resultEnvelope = stripUndefined({
30
+ structuredContent: input.structuredContent,
31
+ content: normalizeRequiredContent(input.content),
32
+ _meta: input.meta,
33
+ isError: input.isError
34
+ });
35
+ Object.defineProperty(resultEnvelope, toolResultBrand, {
36
+ enumerable: false,
37
+ value: true
38
+ });
39
+ return resultEnvelope;
40
+ }
41
+ function createResourceResult(input) {
42
+ const contents = Array.isArray(input) ? [...input] : [input];
43
+ if (!contents.length) {
44
+ throw new SidecarRuntimeError(
45
+ "resourceResult(...) must include at least one content item.",
46
+ "invalid_resource_result"
47
+ );
48
+ }
49
+ const resultEnvelope = {
50
+ contents
51
+ };
52
+ Object.defineProperty(resultEnvelope, resourceResultBrand, {
53
+ enumerable: false,
54
+ value: true
55
+ });
56
+ return resultEnvelope;
57
+ }
58
+ function createToolDescriptor(definition) {
59
+ const machineName = definition.id ?? toMachineName(definition.name);
60
+ validateToolId(machineName);
61
+ const target = definition.target ?? "mcp";
62
+ return stripUndefined({
63
+ name: machineName,
64
+ title: definition.name,
65
+ description: definition.description,
66
+ inputSchema: definition.inputSchema ?? emptyObjectSchema(),
67
+ outputSchema: definition.outputSchema,
68
+ securitySchemes: securitySchemes(definition.auth),
69
+ annotations: withAnnotationDefaults(definition.annotations),
70
+ _meta: mergeMeta(
71
+ securitySchemesMeta(definition.auth),
72
+ visibilityMeta(definition.visibility),
73
+ target === "chatgpt" ? chatGptToolMeta(definition.hosts?.chatgpt) : void 0,
74
+ definition.meta
75
+ )
76
+ });
77
+ }
78
+ function createResourceDescriptor(definition) {
79
+ validateResourceUri(definition.uri);
80
+ return stripUndefined({
81
+ uri: definition.uri,
82
+ name: definition.name,
83
+ title: definition.title,
84
+ description: definition.description,
85
+ mimeType: definition.mimeType,
86
+ size: definition.size,
87
+ icons: definition.icons ? [...definition.icons] : void 0,
88
+ annotations: normalizeAnnotations(definition.annotations),
89
+ _meta: definition.meta
90
+ });
91
+ }
92
+ function createPromptDescriptor(definition) {
93
+ validatePromptName(definition.name);
94
+ return stripUndefined({
95
+ name: definition.name,
96
+ title: definition.title,
97
+ description: definition.description,
98
+ arguments: promptArguments(definition.args),
99
+ icons: definition.icons ? [...definition.icons] : void 0
100
+ });
101
+ }
102
+ function securitySchemes(policy) {
103
+ if (!policy || policy.public === true) {
104
+ return [{ type: "noauth" }];
105
+ }
106
+ if ("scopes" in policy && policy.scopes?.length) {
107
+ return [{ type: "oauth2", scopes: policy.scopes.map((entry) => entry.id) }];
108
+ }
109
+ return [{ type: "oauth2", scopes: [] }];
110
+ }
111
+ function securitySchemesMeta(policy) {
112
+ return {
113
+ securitySchemes: securitySchemes(policy)
114
+ };
115
+ }
116
+ function visibilityMeta(visibility) {
117
+ if (!visibility) {
118
+ return void 0;
119
+ }
120
+ const uiVisibility = [
121
+ visibility.model !== false ? "model" : void 0,
122
+ visibility.widgets !== false ? "app" : void 0
123
+ ].filter(Boolean);
124
+ return {
125
+ ui: {
126
+ visibility: uiVisibility
127
+ }
128
+ };
129
+ }
130
+ function chatGptToolMeta(options) {
131
+ if (!options) {
132
+ return void 0;
133
+ }
134
+ return stripUndefined({
135
+ "openai/widgetAccessible": options.widgetAccessible,
136
+ "openai/visibility": options.visibility,
137
+ "openai/toolInvocation/invoking": options.invoking,
138
+ "openai/toolInvocation/invoked": options.invoked,
139
+ "openai/fileParams": options.fileParams ? [...options.fileParams] : void 0
140
+ });
141
+ }
142
+ function mergeMeta(...entries) {
143
+ const merged = {};
144
+ for (const entry of entries) {
145
+ if (!entry) {
146
+ continue;
147
+ }
148
+ for (const [key, value] of Object.entries(entry)) {
149
+ if (key === "ui" && isRecord(value) && isRecord(merged.ui)) {
150
+ merged.ui = { ...merged.ui, ...value };
151
+ } else {
152
+ merged[key] = value;
153
+ }
154
+ }
155
+ }
156
+ return Object.keys(merged).length ? stripUndefined(merged) : void 0;
157
+ }
158
+ function isRecord(value) {
159
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
160
+ }
161
+ function emptyObjectSchema() {
162
+ return {
163
+ type: "object",
164
+ properties: {},
165
+ required: [],
166
+ additionalProperties: false
167
+ };
168
+ }
169
+ function toMachineName(name) {
170
+ return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").replace(/_{2,}/g, "_").toLowerCase();
171
+ }
172
+ function validateToolId(id) {
173
+ if (!/^[A-Za-z0-9._-]{1,128}$/.test(id) || id === "." || id === ".." || id.includes("/..") || id.includes("../")) {
174
+ throw new SidecarDefinitionError(
175
+ `Invalid tool id "${id}". Tool ids must be 1-128 ASCII letters, digits, dots, hyphens, or underscores.`
176
+ );
177
+ }
178
+ }
179
+ function validateResourceUri(uri) {
180
+ try {
181
+ const parsed = new URL(uri);
182
+ if (!parsed.protocol || parsed.hash) {
183
+ throw new Error("invalid uri");
184
+ }
185
+ } catch {
186
+ throw new SidecarDefinitionError(
187
+ `Invalid resource uri "${uri}". Resource URIs must be absolute and must not include fragments.`
188
+ );
189
+ }
190
+ }
191
+ function validatePromptName(name) {
192
+ if (!/^[A-Za-z0-9._-]{1,128}$/.test(name) || name === "." || name === "..") {
193
+ throw new SidecarDefinitionError(
194
+ `Invalid prompt name "${name}". Prompt names must be 1-128 ASCII letters, digits, dots, hyphens, or underscores.`
195
+ );
196
+ }
197
+ }
198
+ function withAnnotationDefaults(annotations) {
199
+ const readOnlyHint = annotations?.readOnlyHint ?? false;
200
+ return {
201
+ title: annotations?.title,
202
+ readOnlyHint,
203
+ destructiveHint: annotations?.destructiveHint ?? !readOnlyHint,
204
+ idempotentHint: annotations?.idempotentHint ?? false,
205
+ openWorldHint: annotations?.openWorldHint ?? true
206
+ };
207
+ }
208
+ var SidecarDefinitionError = class extends Error {
209
+ constructor(message) {
210
+ super(message);
211
+ this.name = "SidecarDefinitionError";
212
+ }
213
+ };
214
+ var SidecarRuntimeError = class extends Error {
215
+ constructor(message, code) {
216
+ super(message);
217
+ this.code = code;
218
+ this.name = "SidecarRuntimeError";
219
+ }
220
+ code;
221
+ };
222
+ function normalizeRequiredContent(content) {
223
+ if (typeof content === "string") {
224
+ return [toolResult.text(content)];
225
+ }
226
+ const blocks = Array.isArray(content) ? content : [content];
227
+ if (blocks.length > 0) {
228
+ return blocks;
229
+ }
230
+ throw new SidecarRuntimeError(
231
+ "toolResult({ content }) must include at least one MCP content block.",
232
+ "invalid_tool_result"
233
+ );
234
+ }
235
+ function normalizeAnnotations(annotations) {
236
+ if (!annotations) {
237
+ return void 0;
238
+ }
239
+ return stripUndefined({
240
+ audience: annotations.audience ? [...annotations.audience] : void 0,
241
+ priority: annotations.priority,
242
+ lastModified: annotations.lastModified instanceof Date ? annotations.lastModified.toISOString() : annotations.lastModified
243
+ });
244
+ }
245
+ function promptArguments(args) {
246
+ if (!args) {
247
+ return void 0;
248
+ }
249
+ const entries = Object.entries(args).map(([name, value]) => {
250
+ if (typeof value === "string") {
251
+ return { name, description: value, required: true };
252
+ }
253
+ if (isReadonlyArray(value)) {
254
+ return {
255
+ name,
256
+ description: value.length ? `One of: ${value.map(String).join(", ")}.` : void 0,
257
+ required: true
258
+ };
259
+ }
260
+ return {
261
+ name,
262
+ description: value.description,
263
+ required: value.required ?? true
264
+ };
265
+ });
266
+ return entries.length ? entries : void 0;
267
+ }
268
+ function isReadonlyArray(value) {
269
+ return Array.isArray(value);
270
+ }
271
+ function stripUndefined(value) {
272
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
273
+ }
274
+
275
+ // packages/compiler/src/analyze.ts
276
+ import { readdir } from "fs/promises";
277
+ import path4 from "path";
278
+ import {
279
+ Node as Node4,
280
+ SyntaxKind as SyntaxKind2
281
+ } from "ts-morph";
282
+
283
+ // packages/compiler/src/ast.ts
284
+ import {
285
+ Node
286
+ } from "ts-morph";
287
+ function resolveDefaultExportCall(sourceFile, helperName) {
288
+ const exportAssignment = sourceFile.getExportAssignment(
289
+ (assignment) => !assignment.isExportEquals()
290
+ );
291
+ const expression = unwrapExpression(exportAssignment?.getExpression());
292
+ const direct = asHelperCall(expression, helperName);
293
+ if (direct) {
294
+ return direct;
295
+ }
296
+ if (!expression || !Node.isIdentifier(expression)) {
297
+ return void 0;
298
+ }
299
+ const declaration = findVariableDeclaration(sourceFile, expression.getText());
300
+ const initializer = unwrapExpression(declaration?.getInitializer());
301
+ return asHelperCall(initializer, helperName);
302
+ }
303
+ function unwrapExpression(expression) {
304
+ if (!expression) {
305
+ return void 0;
306
+ }
307
+ if (Node.isSatisfiesExpression(expression) || Node.isAsExpression(expression)) {
308
+ return expression.getExpression();
309
+ }
310
+ return expression;
311
+ }
312
+ function asHelperCall(expression, helperName) {
313
+ if (!expression || !Node.isCallExpression(expression)) {
314
+ return void 0;
315
+ }
316
+ const callee = expression.getExpression().getText();
317
+ return matchesHelperName(callee, helperName) ? expression : void 0;
318
+ }
319
+ function findVariableDeclaration(sourceFile, name) {
320
+ return sourceFile.getVariableDeclarations().find((declaration) => declaration.getName() === name);
321
+ }
322
+ function matchesHelperName(callee, helperName) {
323
+ return callee === helperName || callee.endsWith(`.${helperName}`);
324
+ }
325
+
326
+ // packages/compiler/src/errors.ts
327
+ var CompilerError = class extends Error {
328
+ constructor(sourceFile, message) {
329
+ super(`${sourceFile.getFilePath()}: ${message}`);
330
+ this.name = "CompilerError";
331
+ }
332
+ };
333
+
334
+ // packages/compiler/src/project.ts
335
+ import path2 from "path";
336
+ import {
337
+ ModuleKind,
338
+ ModuleResolutionKind,
339
+ Project,
340
+ ScriptTarget
341
+ } from "ts-morph";
342
+
343
+ // packages/compiler/src/utils.ts
344
+ import { existsSync } from "fs";
345
+ import path from "path";
346
+ function existsSyncSafe(filePath) {
347
+ return existsSync(filePath);
348
+ }
349
+ function toImportSpecifier(fromDir, toFile, options = {}) {
350
+ const relative = path.relative(fromDir, toFile).replaceAll(path.sep, "/");
351
+ const normalized = relative.startsWith(".") ? relative : `./${relative}`;
352
+ switch (options.extension ?? "none") {
353
+ case "js":
354
+ return normalized.replace(/\.(tsx|ts)$/, ".js");
355
+ case "preserve":
356
+ return normalized;
357
+ case "none":
358
+ return normalized.replace(/\.(tsx|ts)$/, "");
359
+ }
360
+ }
361
+ function toIdentifier(value) {
362
+ const identifier = value.replace(/[^A-Za-z0-9]+(.)/g, (_match, char) => char.toUpperCase()).replace(/^[^A-Za-z_$]+/, "").replace(/[^A-Za-z0-9_$]/g, "");
363
+ return identifier || "tool";
364
+ }
365
+ function safePathSegment(value) {
366
+ const segment = value.replace(/[^A-Za-z0-9._-]+/g, "_");
367
+ if (!segment || segment === "." || segment === "..") {
368
+ return "_";
369
+ }
370
+ return segment;
371
+ }
372
+ function safeFileStem(value) {
373
+ return safePathSegment(value).replace(/^\.+/, "_").replace(/^_+/, "_");
374
+ }
375
+ function yamlScalar(value) {
376
+ return JSON.stringify(String(value ?? ""));
377
+ }
378
+ function escapeHtml(value) {
379
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
380
+ }
381
+ function stripUndefined2(value) {
382
+ return Object.fromEntries(
383
+ Object.entries(value).filter(([, entry]) => entry !== void 0)
384
+ );
385
+ }
386
+ function readObjectString(source, key) {
387
+ const match = source.match(
388
+ new RegExp(`${key}\\s*:\\s*(["'\`])([\\s\\S]*?)\\1`)
389
+ );
390
+ return match?.[2]?.trim();
391
+ }
392
+ function readObjectStringArray(source, key) {
393
+ const match = source.match(new RegExp(`${key}\\s*:\\s*\\[([\\s\\S]*?)\\]`));
394
+ if (!match?.[1]) {
395
+ return void 0;
396
+ }
397
+ return [...match[1].matchAll(/["']([^"']+)["']/g)].map((item) => item[1]).filter(Boolean);
398
+ }
399
+
400
+ // packages/compiler/src/project.ts
401
+ function createProject(rootDir) {
402
+ const tsconfig = path2.join(rootDir, "tsconfig.json");
403
+ return new Project({
404
+ tsConfigFilePath: existsSyncSafe(tsconfig) ? tsconfig : void 0,
405
+ compilerOptions: existsSyncSafe(tsconfig) ? void 0 : {
406
+ allowJs: false,
407
+ baseUrl: process.cwd(),
408
+ esModuleInterop: true,
409
+ module: ModuleKind.NodeNext,
410
+ moduleResolution: ModuleResolutionKind.NodeNext,
411
+ paths: devSidecarTypePaths(),
412
+ strict: true,
413
+ target: ScriptTarget.ES2022
414
+ },
415
+ skipAddingFilesFromTsConfig: true
416
+ });
417
+ }
418
+ function devSidecarTypePaths() {
419
+ const repoRoot = process.cwd();
420
+ const corePath = path2.join(repoRoot, "packages", "core", "src", "index.ts");
421
+ if (!existsSyncSafe(corePath)) {
422
+ return void 0;
423
+ }
424
+ return {
425
+ "sidecar-ai": ["packages/sidecar-ai/src/index.ts"],
426
+ "@sidecar-ai/core": ["packages/core/src/index.ts"],
427
+ "@sidecar-ai/client": ["packages/client/src/index.ts"],
428
+ "@sidecar-ai/react": ["packages/react/src/index.ts"],
429
+ "@sidecar-ai/native": ["packages/native/src/index.ts"],
430
+ "@sidecar-ai/native/components": ["packages/native/src/components/index.tsx"],
431
+ "@sidecar-ai/openai": ["packages/openai/src/index.ts"],
432
+ "@sidecar-ai/openai/components": ["packages/openai/src/components.tsx"],
433
+ "@sidecar-ai/anthropic": ["packages/anthropic/src/index.ts"],
434
+ "@sidecar-ai/anthropic/components": ["packages/anthropic/src/components.tsx"],
435
+ "@sidecar-ai/anthropic/plugin": ["packages/anthropic/src/plugin.ts"],
436
+ "@sidecar-ai/anthropic/hooks": ["packages/anthropic/src/hooks.ts"]
437
+ };
438
+ }
439
+
440
+ // packages/compiler/src/schema.ts
441
+ import {
442
+ Node as Node2
443
+ } from "ts-morph";
444
+ function getParamsSchema(definition, execute) {
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
+ }
452
+ const [params] = execute.getParameters();
453
+ if (!params) {
454
+ return emptyObjectSchema();
455
+ }
456
+ return typeToJsonSchema(
457
+ params.getType(),
458
+ schemaDescription(params.getSymbol())
459
+ );
460
+ }
461
+ function getOutputSchema(definition, execute) {
462
+ const explicitOutput = definition.getProperty("output");
463
+ if (explicitOutput && Node2.isPropertyAssignment(explicitOutput)) {
464
+ return void 0;
465
+ }
466
+ const returnType = unwrapToolResultType(unwrapPromiseType(execute.getReturnType()));
467
+ if (returnType.isVoid() || returnType.isUndefined()) {
468
+ return void 0;
469
+ }
470
+ return typeToJsonSchema(returnType);
471
+ }
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
+ function typeToJsonSchema(type, description) {
541
+ const withoutUndefined = removeUndefined(type);
542
+ const schema = typeToJsonSchemaInner(withoutUndefined);
543
+ if (description) {
544
+ schema.description = description;
545
+ }
546
+ return schema;
547
+ }
548
+ function typeToJsonSchemaInner(type) {
549
+ if (type.isString() || type.isStringLiteral()) {
550
+ return literalOrPrimitive(type, "string");
551
+ }
552
+ if (type.isNumber() || type.isNumberLiteral()) {
553
+ return literalOrPrimitive(type, "number");
554
+ }
555
+ if (type.isBoolean() || type.isBooleanLiteral()) {
556
+ return literalOrPrimitive(type, "boolean");
557
+ }
558
+ if (type.isNull()) {
559
+ return { type: "null" };
560
+ }
561
+ if (type.isArray()) {
562
+ return {
563
+ type: "array",
564
+ items: typeToJsonSchema(type.getArrayElementTypeOrThrow())
565
+ };
566
+ }
567
+ if (type.isTuple()) {
568
+ const elements = type.getTupleElements();
569
+ return {
570
+ type: "array",
571
+ items: elements.length === 1 ? typeToJsonSchema(elements[0]) : { anyOf: elements.map((item) => typeToJsonSchema(item)) }
572
+ };
573
+ }
574
+ if (type.isUnion()) {
575
+ const parts = type.getUnionTypes().filter((part) => !part.isUndefined());
576
+ if (parts.every(isLiteralType)) {
577
+ return { enum: parts.map((part) => literalValue(part)) };
578
+ }
579
+ return { anyOf: parts.map((part) => typeToJsonSchema(part)) };
580
+ }
581
+ const properties = type.getProperties();
582
+ if (properties.length > 0) {
583
+ return objectTypeToSchema(properties);
584
+ }
585
+ return {};
586
+ }
587
+ function objectTypeToSchema(properties) {
588
+ const schemaProperties = {};
589
+ const required = [];
590
+ for (const property of properties) {
591
+ const declaration = property.getValueDeclaration() ?? property.getDeclarations()[0];
592
+ if (!declaration) {
593
+ continue;
594
+ }
595
+ const name = property.getName();
596
+ const rawType = property.getTypeAtLocation(declaration);
597
+ const propertyType = removeUndefined(rawType);
598
+ const propertySchema = typeToJsonSchema(
599
+ propertyType,
600
+ schemaDescription(property)
601
+ );
602
+ schemaProperties[name] = propertySchema;
603
+ if (!property.isOptional() && !containsUndefined(rawType)) {
604
+ required.push(name);
605
+ }
606
+ }
607
+ return {
608
+ type: "object",
609
+ properties: schemaProperties,
610
+ required,
611
+ additionalProperties: false
612
+ };
613
+ }
614
+ function literalOrPrimitive(type, primitive) {
615
+ if (isLiteralType(type)) {
616
+ return { const: literalValue(type) };
617
+ }
618
+ return { type: primitive };
619
+ }
620
+ function isLiteralType(type) {
621
+ return type.isStringLiteral() || type.isNumberLiteral() || type.isBooleanLiteral();
622
+ }
623
+ function literalValue(type) {
624
+ if (type.isStringLiteral()) return String(type.getLiteralValue());
625
+ if (type.isNumberLiteral()) return Number(type.getLiteralValue());
626
+ const text = type.getText();
627
+ return text === "true";
628
+ }
629
+ function unwrapPromiseType(type) {
630
+ if (!type.getText().startsWith("Promise<")) {
631
+ return type;
632
+ }
633
+ const args = type.getTypeArguments();
634
+ return args[0] ?? type;
635
+ }
636
+ function unwrapToolResultType(type) {
637
+ const aliasName = type.getAliasSymbol()?.getName();
638
+ if (aliasName === "ToolResult") {
639
+ const [structured2] = type.getAliasTypeArguments();
640
+ return structured2 ?? type;
641
+ }
642
+ const structured = type.getProperty("structuredContent");
643
+ const declaration = structured?.getValueDeclaration() ?? structured?.getDeclarations()[0];
644
+ if (!structured || !declaration) {
645
+ return type;
646
+ }
647
+ return removeUndefined(structured.getTypeAtLocation(declaration));
648
+ }
649
+ function removeUndefined(type) {
650
+ if (!type.isUnion()) {
651
+ return type;
652
+ }
653
+ const nonUndefined = type.getUnionTypes().filter((part) => !part.isUndefined());
654
+ return nonUndefined.length === 1 ? nonUndefined[0] : type;
655
+ }
656
+ function containsUndefined(type) {
657
+ return type.isUnion() && type.getUnionTypes().some((part) => part.isUndefined());
658
+ }
659
+ function schemaDescription(symbol) {
660
+ if (!symbol) {
661
+ return void 0;
662
+ }
663
+ return symbol.getDeclarations().flatMap((declaration) => {
664
+ if (Node2.isJSDocable(declaration)) {
665
+ return declaration.getJsDocs().map((doc) => doc.getCommentText() ?? "");
666
+ }
667
+ return [];
668
+ }).find((comment) => comment.trim().length > 0);
669
+ }
670
+
671
+ // packages/compiler/src/widgets.ts
672
+ import { createHash } from "crypto";
673
+ import { existsSync as existsSync2 } from "fs";
674
+ import { mkdir, readFile, writeFile } from "fs/promises";
675
+ import path3 from "path";
676
+ import { build as esbuild } from "esbuild";
677
+ import postcss from "postcss";
678
+ import {
679
+ Node as Node3,
680
+ SyntaxKind
681
+ } from "ts-morph";
682
+ async function buildWidgets(rootDir, outDir, tools) {
683
+ const widgets = tools.filter(
684
+ (entry) => Boolean(entry.widget)
685
+ );
686
+ if (!widgets.length) {
687
+ return;
688
+ }
689
+ const cacheDir = path3.join(rootDir, ".sidecar", "cache", "widgets");
690
+ await mkdir(cacheDir, { recursive: true });
691
+ const appStyle = await prepareAppStyle(rootDir, cacheDir);
692
+ for (const entry of widgets) {
693
+ const sourceFile = path3.join(rootDir, entry.widget.sourceFile);
694
+ const safeId = safePathSegment(entry.id);
695
+ const entryFile = path3.join(cacheDir, `${safeId}.entry.tsx`);
696
+ const importPath = toImportSpecifier(path3.dirname(entryFile), sourceFile);
697
+ await writeFile(
698
+ entryFile,
699
+ `import React from "react";
700
+ import { createRoot } from "react-dom/client";
701
+ import { SidecarWidgetRoot } from "@sidecar-ai/react";
702
+ import "@sidecar-ai/native/styles.css";
703
+ ${appStyle ? `import ${JSON.stringify(toImportSpecifier(path3.dirname(entryFile), appStyle))};` : ""}
704
+ import Component from ${JSON.stringify(importPath)};
705
+
706
+ createRoot(document.getElementById("root")!).render(
707
+ React.createElement(SidecarWidgetRoot, null, React.createElement(Component))
708
+ );
709
+ `
710
+ );
711
+ const bundled = await esbuild({
712
+ absWorkingDir: rootDir,
713
+ alias: devSidecarBundleAliases(rootDir),
714
+ bundle: true,
715
+ entryPoints: [entryFile],
716
+ format: "iife",
717
+ jsx: "automatic",
718
+ minify: false,
719
+ nodePaths: [
720
+ path3.join(rootDir, "node_modules"),
721
+ path3.join(process.cwd(), "node_modules")
722
+ ],
723
+ outfile: "widget.js",
724
+ platform: "browser",
725
+ sourcemap: false,
726
+ write: false
727
+ });
728
+ const javascript = bundled.outputFiles.find((file) => file.path.endsWith(".js"))?.text ?? "";
729
+ const css = bundled.outputFiles.filter((file) => file.path.endsWith(".css")).map((file) => file.text).join("\n");
730
+ const html = renderWidgetHtml(entry.name, javascript, css);
731
+ const hash = createHash("sha256").update(html).digest("hex").slice(0, 12);
732
+ const outputDir = path3.join(outDir, "public", "widgets", safeId);
733
+ const outputFile = path3.join(outputDir, `widget.${hash}.html`);
734
+ const resourceUri = `ui://${safeId}/widget.${hash}.html`;
735
+ await mkdir(outputDir, { recursive: true });
736
+ await writeFile(outputFile, html);
737
+ entry.widget.resourceUri = resourceUri;
738
+ entry.widget.resourceMeta = widgetResourceMeta(entry.widget.options, entry.target);
739
+ entry.widget.outputFile = path3.relative(outDir, outputFile);
740
+ entry.descriptor._meta = mergeWidgetMeta(entry.descriptor._meta, widgetMeta(resourceUri, entry.widget.options, entry.target));
741
+ }
742
+ }
743
+ function devSidecarBundleAliases(rootDir) {
744
+ const repoRoot = findSidecarRepoRoot(rootDir) ?? findSidecarRepoRoot(process.cwd());
745
+ if (!repoRoot) {
746
+ return void 0;
747
+ }
748
+ const reactEntry = path3.join(repoRoot, "packages", "react", "src", "index.ts");
749
+ if (!existsSync2(reactEntry)) {
750
+ return void 0;
751
+ }
752
+ return {
753
+ "sidecar-ai": path3.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
754
+ "@sidecar-ai/client": path3.join(repoRoot, "packages", "client", "src", "index.ts"),
755
+ "@sidecar-ai/core": path3.join(repoRoot, "packages", "core", "src", "index.ts"),
756
+ "@sidecar-ai/react": reactEntry,
757
+ "@sidecar-ai/native": path3.join(repoRoot, "packages", "native", "src", "index.ts"),
758
+ "@sidecar-ai/native/components": path3.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
759
+ "@sidecar-ai/native/styles.css": path3.join(repoRoot, "packages", "native", "src", "styles.css")
760
+ };
761
+ }
762
+ function findSidecarRepoRoot(startDir) {
763
+ let current = path3.resolve(startDir);
764
+ while (true) {
765
+ if (existsSync2(path3.join(current, "packages", "core", "src", "index.ts"))) {
766
+ return current;
767
+ }
768
+ const parent = path3.dirname(current);
769
+ if (parent === current) {
770
+ return void 0;
771
+ }
772
+ current = parent;
773
+ }
774
+ }
775
+ async function prepareAppStyle(rootDir, cacheDir) {
776
+ const sourceFile = path3.join(rootDir, "style.css");
777
+ if (!existsSync2(sourceFile)) {
778
+ return void 0;
779
+ }
780
+ const source = await readFile(sourceFile, "utf8");
781
+ const css = await processAppStyle(source, sourceFile);
782
+ const outputFile = path3.join(cacheDir, "style.css");
783
+ await writeFile(outputFile, css);
784
+ return outputFile;
785
+ }
786
+ async function processAppStyle(source, from) {
787
+ if (!needsPostcss(source)) {
788
+ return source;
789
+ }
790
+ const cssSource = source.replace(/@import\s+["']tailwindcss["'];?/g, "@tailwind utilities;").replace(/@import\s+["']@openai\/apps-sdk-ui\/css["'];?/g, "");
791
+ const plugins = [];
792
+ if (cssSource.includes("@tailwind")) {
793
+ const tailwind = await import("@tailwindcss/postcss");
794
+ plugins.push(tailwind.default({ base: path3.dirname(from) }));
795
+ }
796
+ const autoprefixer = await import("autoprefixer");
797
+ plugins.push(autoprefixer.default());
798
+ const result = await postcss(plugins).process(cssSource, {
799
+ from,
800
+ map: false
801
+ });
802
+ return result.css;
803
+ }
804
+ function needsPostcss(source) {
805
+ return /@import\s+["']tailwindcss["']|@tailwind|@source|@theme|@plugin/.test(source);
806
+ }
807
+ function findWidget(rootDir, toolFile, id, target = "mcp", toolVariant = "shared") {
808
+ const selected = selectWidgetFile(path3.dirname(toolFile), target, toolVariant);
809
+ if (!selected) {
810
+ return void 0;
811
+ }
812
+ const safeId = safePathSegment(id);
813
+ return {
814
+ sourceFile: path3.relative(rootDir, selected.filePath),
815
+ variant: selected.variant,
816
+ resourceUri: `ui://${safeId}/widget.html`
817
+ };
818
+ }
819
+ function readWidgetOptions(sourceFile) {
820
+ const definition = findWidgetDefinition(sourceFile);
821
+ if (!definition) {
822
+ return void 0;
823
+ }
824
+ const options = {};
825
+ const description = readStringProperty(definition, "description");
826
+ const prefersBorder = readBooleanProperty(definition, "prefersBorder");
827
+ const csp = readWidgetCsp(definition);
828
+ const permissions = readWidgetPermissions(definition);
829
+ const chatgpt = readChatGptWidgetOptions(definition);
830
+ const domain = readStringProperty(definition, "domain");
831
+ if (description) options.description = description;
832
+ if (prefersBorder !== void 0) options.prefersBorder = prefersBorder;
833
+ if (domain) options.domain = domain;
834
+ if (csp) options.csp = csp;
835
+ if (permissions) options.permissions = permissions;
836
+ if (chatgpt) options.hosts = { chatgpt };
837
+ return Object.keys(options).length ? options : void 0;
838
+ }
839
+ function widgetMeta(resourceUri, options = {}, target = "mcp") {
840
+ const chatgptCsp = {
841
+ connect_domains: options.csp?.connectDomains ? [...options.csp.connectDomains] : [],
842
+ resource_domains: options.csp?.resourceDomains ? [...options.csp.resourceDomains] : [],
843
+ frame_domains: options.csp?.frameDomains ? [...options.csp.frameDomains] : void 0,
844
+ redirect_domains: options.hosts?.chatgpt?.redirectDomains ? [...options.hosts.chatgpt.redirectDomains] : void 0
845
+ };
846
+ const standard = {
847
+ ui: {
848
+ resourceUri
849
+ }
850
+ };
851
+ if (target !== "chatgpt") {
852
+ return standard;
853
+ }
854
+ return {
855
+ ...standard,
856
+ "openai/outputTemplate": resourceUri,
857
+ "openai/widgetDescription": options.description,
858
+ "openai/widgetDomain": options.hosts?.chatgpt?.domain,
859
+ "openai/widgetCSP": stripUndefined3(chatgptCsp)
860
+ };
861
+ }
862
+ function widgetResourceMeta(options = {}, target = "mcp") {
863
+ const csp = stripUndefined3({
864
+ connectDomains: options.csp?.connectDomains ? [...options.csp.connectDomains] : [],
865
+ resourceDomains: options.csp?.resourceDomains ? [...options.csp.resourceDomains] : [],
866
+ frameDomains: options.csp?.frameDomains ? [...options.csp.frameDomains] : void 0,
867
+ baseUriDomains: options.csp?.baseUriDomains ? [...options.csp.baseUriDomains] : void 0
868
+ });
869
+ const permissions = widgetPermissionsMeta(options);
870
+ const ui = stripUndefined3({
871
+ csp,
872
+ permissions,
873
+ domain: options.domain ?? (target === "chatgpt" ? options.hosts?.chatgpt?.domain : void 0),
874
+ prefersBorder: options.prefersBorder
875
+ });
876
+ return Object.keys(ui).length ? { ui } : void 0;
877
+ }
878
+ function findWidgetDefinition(sourceFile) {
879
+ const call = resolveDefaultExportCall(sourceFile, "widget");
880
+ if (!call) {
881
+ return void 0;
882
+ }
883
+ const definition = unwrapExpression(call.getArguments()[0]);
884
+ if (!definition || !Node3.isObjectLiteralExpression(definition)) {
885
+ throw new CompilerError(
886
+ sourceFile,
887
+ "widget(...) must receive its metadata object as the first argument."
888
+ );
889
+ }
890
+ return definition;
891
+ }
892
+ function readWidgetCsp(definition) {
893
+ const initializer = readObjectProperty(definition, "csp");
894
+ if (!initializer) {
895
+ return void 0;
896
+ }
897
+ const csp = {
898
+ connectDomains: readStringArrayProperty(initializer, "connectDomains"),
899
+ resourceDomains: readStringArrayProperty(initializer, "resourceDomains"),
900
+ frameDomains: readStringArrayProperty(initializer, "frameDomains"),
901
+ baseUriDomains: readStringArrayProperty(initializer, "baseUriDomains")
902
+ };
903
+ return stripUndefined3(csp);
904
+ }
905
+ function readWidgetPermissions(definition) {
906
+ const initializer = readObjectProperty(definition, "permissions");
907
+ if (!initializer) {
908
+ return void 0;
909
+ }
910
+ const permissions = {
911
+ camera: readBooleanProperty(initializer, "camera"),
912
+ microphone: readBooleanProperty(initializer, "microphone"),
913
+ geolocation: readBooleanProperty(initializer, "geolocation"),
914
+ clipboardWrite: readBooleanProperty(initializer, "clipboardWrite")
915
+ };
916
+ return stripUndefined3(permissions);
917
+ }
918
+ function widgetPermissionsMeta(options) {
919
+ const permissions = options.permissions;
920
+ if (!permissions) {
921
+ return void 0;
922
+ }
923
+ const meta = stripUndefined3({
924
+ camera: permissions.camera ? {} : void 0,
925
+ microphone: permissions.microphone ? {} : void 0,
926
+ geolocation: permissions.geolocation ? {} : void 0,
927
+ clipboardWrite: permissions.clipboardWrite ? {} : void 0
928
+ });
929
+ return Object.keys(meta).length ? meta : void 0;
930
+ }
931
+ function readChatGptWidgetOptions(definition) {
932
+ const hosts = readObjectProperty(definition, "hosts");
933
+ const chatgpt = hosts ? readObjectProperty(hosts, "chatgpt") : void 0;
934
+ if (!chatgpt) {
935
+ return void 0;
936
+ }
937
+ const options = {
938
+ domain: readStringProperty(chatgpt, "domain"),
939
+ redirectDomains: readStringArrayProperty(chatgpt, "redirectDomains")
940
+ };
941
+ return stripUndefined3(options);
942
+ }
943
+ function readObjectProperty(definition, propertyName) {
944
+ const property = definition.getProperty(propertyName);
945
+ if (!property || !Node3.isPropertyAssignment(property)) {
946
+ return void 0;
947
+ }
948
+ const initializer = unwrapExpression(property.getInitializer());
949
+ return initializer && Node3.isObjectLiteralExpression(initializer) ? initializer : void 0;
950
+ }
951
+ function readStringProperty(definition, propertyName) {
952
+ const property = definition.getProperty(propertyName);
953
+ if (!property || !Node3.isPropertyAssignment(property)) {
954
+ return void 0;
955
+ }
956
+ const initializer = unwrapExpression(property.getInitializer());
957
+ return initializer && Node3.isStringLiteral(initializer) ? initializer.getLiteralText() : void 0;
958
+ }
959
+ function readBooleanProperty(definition, propertyName) {
960
+ const property = definition.getProperty(propertyName);
961
+ if (!property || !Node3.isPropertyAssignment(property)) {
962
+ return void 0;
963
+ }
964
+ const initializer = unwrapExpression(property.getInitializer());
965
+ if (!initializer) {
966
+ return void 0;
967
+ }
968
+ if (initializer.getKind() === SyntaxKind.TrueKeyword) {
969
+ return true;
970
+ }
971
+ if (initializer.getKind() === SyntaxKind.FalseKeyword) {
972
+ return false;
973
+ }
974
+ return void 0;
975
+ }
976
+ function readStringArrayProperty(definition, propertyName) {
977
+ const property = definition.getProperty(propertyName);
978
+ if (!property || !Node3.isPropertyAssignment(property)) {
979
+ return void 0;
980
+ }
981
+ const initializer = unwrapExpression(property.getInitializer());
982
+ if (!initializer || !Node3.isArrayLiteralExpression(initializer)) {
983
+ return void 0;
984
+ }
985
+ return initializer.getElements().filter(Node3.isStringLiteral).map((element) => element.getLiteralText());
986
+ }
987
+ function selectWidgetFile(directory, target, toolVariant) {
988
+ if (toolVariant === "openai") {
989
+ const filePath = path3.join(directory, "widget.openai.tsx");
990
+ return existsSync2(filePath) ? { filePath, variant: "openai" } : void 0;
991
+ }
992
+ if (toolVariant === "anthropic") {
993
+ const filePath = path3.join(directory, "widget.anthropic.tsx");
994
+ return existsSync2(filePath) ? { filePath, variant: "anthropic" } : void 0;
995
+ }
996
+ const candidates = target === "chatgpt" || target === "claude" || target === "mcp" ? [{ name: "widget.tsx", variant: "shared" }] : [];
997
+ for (const candidate of candidates) {
998
+ const filePath = path3.join(directory, candidate.name);
999
+ if (existsSync2(filePath)) {
1000
+ return { filePath, variant: candidate.variant };
1001
+ }
1002
+ }
1003
+ return void 0;
1004
+ }
1005
+ function mergeWidgetMeta(existing, widget) {
1006
+ const merged = { ...existing ?? {} };
1007
+ for (const [key, value] of Object.entries(widget)) {
1008
+ if (key === "ui" && isRecord2(value) && isRecord2(merged.ui)) {
1009
+ merged.ui = { ...merged.ui, ...value };
1010
+ } else {
1011
+ merged[key] = value;
1012
+ }
1013
+ }
1014
+ return merged;
1015
+ }
1016
+ function renderWidgetHtml(title, javascript, css = "") {
1017
+ return `<!doctype html>
1018
+ <html lang="en">
1019
+ <head>
1020
+ <meta charset="utf-8" />
1021
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1022
+ <title>${escapeHtml(title)}</title>
1023
+ <style>
1024
+ :root { color-scheme: light dark; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
1025
+ html, body, #root { min-height: 100%; margin: 0; background: transparent; }
1026
+ body { color: CanvasText; }
1027
+ * { box-sizing: border-box; }
1028
+ ${css}
1029
+ </style>
1030
+ </head>
1031
+ <body>
1032
+ <div id="root"></div>
1033
+ <script>${javascript}</script>
1034
+ </body>
1035
+ </html>
1036
+ `;
1037
+ }
1038
+ function isRecord2(value) {
1039
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1040
+ }
1041
+ function stripUndefined3(value) {
1042
+ return Object.fromEntries(
1043
+ Object.entries(value).filter(([, entry]) => entry !== void 0)
1044
+ );
1045
+ }
1046
+
1047
+ // packages/compiler/src/analyze.ts
1048
+ async function analyzeProjectTools(rootDir, options = {}) {
1049
+ const target = options.target ?? "mcp";
1050
+ const toolFiles = await findToolFiles(path4.join(rootDir, "server"), target);
1051
+ if (toolFiles.length === 0) {
1052
+ return [];
1053
+ }
1054
+ const project = createProject(rootDir);
1055
+ const authScopes = readAuthScopeCatalog(project, rootDir);
1056
+ return toolFiles.map(
1057
+ (candidate) => analyzeToolFile(project.addSourceFileAtPath(candidate.filePath), rootDir, {
1058
+ target,
1059
+ variant: candidate.variant,
1060
+ authScopes
1061
+ })
1062
+ );
1063
+ }
1064
+ function analyzeToolFile(sourceFile, rootDir, options = {}) {
1065
+ const target = options.target ?? "mcp";
1066
+ const variant = options.variant ?? "shared";
1067
+ const definition = findToolDefinition(sourceFile);
1068
+ const absoluteFile = sourceFile.getFilePath();
1069
+ const directory = path4.basename(path4.dirname(absoluteFile));
1070
+ const name = getRequiredStringProperty(definition, "name", sourceFile);
1071
+ const id = getOptionalStringProperty(definition, "id") ?? toMachineName(directory);
1072
+ const description = getRequiredStringProperty(
1073
+ definition,
1074
+ "description",
1075
+ sourceFile
1076
+ );
1077
+ const annotations = readAnnotations(definition);
1078
+ const visibility = readVisibility(definition);
1079
+ const hosts = readHosts(definition);
1080
+ const auth = readAuthPolicy(definition, options.authScopes ?? {});
1081
+ const execute = getExecuteFunction(definition, sourceFile);
1082
+ const inputSchema = getParamsSchema(definition, execute);
1083
+ const outputSchema = getOutputSchema(definition, execute);
1084
+ const descriptor = createToolDescriptor({
1085
+ name,
1086
+ id,
1087
+ description,
1088
+ target,
1089
+ inputSchema,
1090
+ outputSchema,
1091
+ annotations,
1092
+ visibility,
1093
+ hosts,
1094
+ auth,
1095
+ meta: void 0
1096
+ });
1097
+ validateWidgetHierarchy(sourceFile, absoluteFile, target, variant);
1098
+ const widget = findWidget(rootDir, absoluteFile, id, target, variant);
1099
+ if (widget) {
1100
+ const widgetFile = path4.join(rootDir, widget.sourceFile);
1101
+ const project = sourceFile.getProject();
1102
+ const widgetSourceFile = project.getSourceFile(widgetFile) ?? project.addSourceFileAtPath(widgetFile);
1103
+ widget.options = {
1104
+ description,
1105
+ ...readWidgetOptions(widgetSourceFile)
1106
+ };
1107
+ widget.resourceMeta = widgetResourceMeta(widget.options, target);
1108
+ descriptor._meta = mergeWidgetMeta(descriptor._meta, widgetMeta(widget.resourceUri, widget.options, target));
1109
+ }
1110
+ return {
1111
+ sourceFile: path4.relative(rootDir, absoluteFile),
1112
+ variant,
1113
+ target,
1114
+ directory,
1115
+ id,
1116
+ name,
1117
+ description,
1118
+ inputSchema,
1119
+ outputSchema,
1120
+ annotations,
1121
+ visibility,
1122
+ widget,
1123
+ descriptor
1124
+ };
1125
+ }
1126
+ function readAuthPolicy(definition, authScopes) {
1127
+ const initializer = readObjectProperty2(definition, "auth");
1128
+ if (!initializer) {
1129
+ return void 0;
1130
+ }
1131
+ const publicValue = readBooleanProperty2(initializer, "public");
1132
+ if (publicValue === true) {
1133
+ return { public: true };
1134
+ }
1135
+ const scopesProperty = initializer.getProperty("scopes");
1136
+ if (scopesProperty && Node4.isPropertyAssignment(scopesProperty)) {
1137
+ return { scopes: readScopeReferences(scopesProperty.getInitializer(), authScopes) };
1138
+ }
1139
+ return { authenticated: true };
1140
+ }
1141
+ function readAuthScopeCatalog(project, rootDir) {
1142
+ const authPath = path4.join(rootDir, "auth.ts");
1143
+ if (!existsSyncSafe(authPath)) {
1144
+ return {};
1145
+ }
1146
+ const sourceFile = project.addSourceFileAtPath(authPath);
1147
+ const catalog = {};
1148
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind2.CallExpression)) {
1149
+ if (!isNamedCall(call, "auth")) {
1150
+ continue;
1151
+ }
1152
+ const definition = unwrapExpression(call.getArguments()[0]);
1153
+ if (!definition || !Node4.isObjectLiteralExpression(definition)) {
1154
+ continue;
1155
+ }
1156
+ const scopes = readObjectProperty2(definition, "scopes");
1157
+ if (!scopes) {
1158
+ continue;
1159
+ }
1160
+ for (const property of scopes.getProperties()) {
1161
+ if (!Node4.isPropertyAssignment(property)) {
1162
+ continue;
1163
+ }
1164
+ const scope = readScopeCall(property.getInitializer());
1165
+ if (scope) {
1166
+ catalog[property.getName().replace(/^["']|["']$/g, "")] = scope;
1167
+ }
1168
+ }
1169
+ }
1170
+ return catalog;
1171
+ }
1172
+ function readScopeReferences(initializer, authScopes) {
1173
+ const expression = unwrapExpression(initializer);
1174
+ if (!expression || !Node4.isArrayLiteralExpression(expression)) {
1175
+ return [];
1176
+ }
1177
+ return expression.getElements().flatMap((element) => {
1178
+ const direct = readScopeCall(element);
1179
+ if (direct) {
1180
+ return [{ kind: "sidecar.scope", ...direct }];
1181
+ }
1182
+ const name = scopeReferenceName(element);
1183
+ const declared = name ? authScopes[name] : void 0;
1184
+ if (declared) {
1185
+ return [{ kind: "sidecar.scope", ...declared }];
1186
+ }
1187
+ const literal = unwrapExpression(element);
1188
+ if (literal && Node4.isStringLiteral(literal)) {
1189
+ return [{
1190
+ kind: "sidecar.scope",
1191
+ id: literal.getLiteralText(),
1192
+ description: ""
1193
+ }];
1194
+ }
1195
+ return [];
1196
+ });
1197
+ }
1198
+ function readScopeCall(node) {
1199
+ const expression = unwrapExpression(node);
1200
+ if (!expression || !Node4.isCallExpression(expression) || !isNamedCall(expression, "scope")) {
1201
+ return void 0;
1202
+ }
1203
+ const [idArg, descriptionArg] = expression.getArguments();
1204
+ if (!idArg || !Node4.isStringLiteral(idArg)) {
1205
+ return void 0;
1206
+ }
1207
+ return {
1208
+ id: idArg.getLiteralText(),
1209
+ description: Node4.isStringLiteral(descriptionArg) ? descriptionArg.getLiteralText() : ""
1210
+ };
1211
+ }
1212
+ function scopeReferenceName(node) {
1213
+ const expression = unwrapExpression(node);
1214
+ if (!expression || !Node4.isPropertyAccessExpression(expression)) {
1215
+ return void 0;
1216
+ }
1217
+ return expression.getName();
1218
+ }
1219
+ function isNamedCall(call, name) {
1220
+ const callee = call.getExpression().getText();
1221
+ return callee === name || callee.endsWith(`.${name}`);
1222
+ }
1223
+ function validateWidgetHierarchy(sourceFile, toolFile, target, variant) {
1224
+ const directory = path4.dirname(toolFile);
1225
+ const platformWidget = target === "chatgpt" ? "widget.openai.tsx" : target === "claude" ? "widget.anthropic.tsx" : void 0;
1226
+ if (!platformWidget || variant !== "shared") {
1227
+ return;
1228
+ }
1229
+ if (existsSyncSafe(path4.join(directory, platformWidget))) {
1230
+ const expectedTool = target === "chatgpt" ? "tool.openai.ts" : "tool.anthropic.ts";
1231
+ throw new CompilerError(
1232
+ sourceFile,
1233
+ `${platformWidget} requires a sibling ${expectedTool}; platform-specific widgets cannot attach to a shared tool.ts.`
1234
+ );
1235
+ }
1236
+ }
1237
+ async function findToolFiles(serverDir, target) {
1238
+ if (!existsSyncSafe(serverDir)) {
1239
+ return [];
1240
+ }
1241
+ const entries = await readdir(serverDir, { withFileTypes: true });
1242
+ const files = [];
1243
+ for (const entry of entries) {
1244
+ const entryPath = path4.join(serverDir, entry.name);
1245
+ if (entry.isDirectory()) {
1246
+ const candidate = selectToolFile(entryPath, target);
1247
+ if (candidate) {
1248
+ files.push(candidate);
1249
+ }
1250
+ }
1251
+ }
1252
+ return files.sort((left, right) => left.filePath.localeCompare(right.filePath));
1253
+ }
1254
+ function selectToolFile(directory, target) {
1255
+ const candidates = target === "chatgpt" ? [
1256
+ { name: "tool.openai.ts", variant: "openai" },
1257
+ { name: "tool.ts", variant: "shared" }
1258
+ ] : target === "claude" ? [
1259
+ { name: "tool.anthropic.ts", variant: "anthropic" },
1260
+ { name: "tool.ts", variant: "shared" }
1261
+ ] : [{ name: "tool.ts", variant: "shared" }];
1262
+ for (const candidate of candidates) {
1263
+ const filePath = path4.join(directory, candidate.name);
1264
+ if (existsSyncSafe(filePath)) {
1265
+ return { filePath, variant: candidate.variant };
1266
+ }
1267
+ }
1268
+ return void 0;
1269
+ }
1270
+ function findToolDefinition(sourceFile) {
1271
+ const call = resolveDefaultExportCall(sourceFile, "tool");
1272
+ if (!call) {
1273
+ throw new CompilerError(
1274
+ sourceFile,
1275
+ "tool.ts must default-export tool({ ... }) or an identifier initialized with tool({ ... })."
1276
+ );
1277
+ }
1278
+ const definition = unwrapExpression(call.getArguments()[0]);
1279
+ if (!definition || !Node4.isObjectLiteralExpression(definition)) {
1280
+ throw new CompilerError(
1281
+ sourceFile,
1282
+ "tool(...) must receive one object literal."
1283
+ );
1284
+ }
1285
+ return definition;
1286
+ }
1287
+ function getExecuteFunction(definition, sourceFile) {
1288
+ const property = definition.getProperty("execute");
1289
+ if (!property) {
1290
+ throw new CompilerError(
1291
+ sourceFile,
1292
+ "tool({ ... }) must include an execute method."
1293
+ );
1294
+ }
1295
+ if (Node4.isMethodDeclaration(property)) {
1296
+ return property;
1297
+ }
1298
+ if (Node4.isPropertyAssignment(property)) {
1299
+ const initializer = property.getInitializer();
1300
+ if (initializer && (Node4.isArrowFunction(initializer) || Node4.isFunctionExpression(initializer))) {
1301
+ return initializer;
1302
+ }
1303
+ }
1304
+ throw new CompilerError(
1305
+ sourceFile,
1306
+ "execute must be a method, function expression, or arrow function."
1307
+ );
1308
+ }
1309
+ function getRequiredStringProperty(definition, propertyName, sourceFile) {
1310
+ const value = getOptionalStringProperty(definition, propertyName);
1311
+ if (value === void 0 || !value.trim()) {
1312
+ throw new CompilerError(
1313
+ sourceFile,
1314
+ `tool({ ... }) must include a non-empty ${propertyName} string.`
1315
+ );
1316
+ }
1317
+ return value;
1318
+ }
1319
+ function getOptionalStringProperty(definition, propertyName) {
1320
+ const property = definition.getProperty(propertyName);
1321
+ if (!property || !Node4.isPropertyAssignment(property)) {
1322
+ return void 0;
1323
+ }
1324
+ const initializer = property.getInitializer();
1325
+ if (!initializer || !Node4.isStringLiteral(initializer)) {
1326
+ return void 0;
1327
+ }
1328
+ return initializer.getLiteralText();
1329
+ }
1330
+ function readAnnotations(definition) {
1331
+ const property = definition.getProperty("annotations");
1332
+ if (!property || !Node4.isPropertyAssignment(property)) {
1333
+ return void 0;
1334
+ }
1335
+ const initializer = property.getInitializer();
1336
+ if (!initializer || !Node4.isObjectLiteralExpression(initializer)) {
1337
+ return void 0;
1338
+ }
1339
+ const annotations = {};
1340
+ for (const key of [
1341
+ "title",
1342
+ "readOnlyHint",
1343
+ "destructiveHint",
1344
+ "idempotentHint",
1345
+ "openWorldHint"
1346
+ ]) {
1347
+ const value = initializer.getProperty(key);
1348
+ if (!value || !Node4.isPropertyAssignment(value)) {
1349
+ continue;
1350
+ }
1351
+ const expression = value.getInitializer();
1352
+ if (!expression) {
1353
+ continue;
1354
+ }
1355
+ if (key === "title" && Node4.isStringLiteral(expression)) {
1356
+ annotations.title = expression.getLiteralText();
1357
+ } else if (key !== "title" && (expression.getKind() === SyntaxKind2.TrueKeyword || expression.getKind() === SyntaxKind2.FalseKeyword)) {
1358
+ annotations[key] = expression.getKind() === SyntaxKind2.TrueKeyword;
1359
+ }
1360
+ }
1361
+ return Object.keys(annotations).length ? annotations : void 0;
1362
+ }
1363
+ function readVisibility(definition) {
1364
+ const initializer = readObjectProperty2(definition, "visibility");
1365
+ if (!initializer) {
1366
+ return void 0;
1367
+ }
1368
+ const visibility = {};
1369
+ for (const key of ["model", "widgets", "tools"]) {
1370
+ const property = initializer.getProperty(key);
1371
+ if (!property || !Node4.isPropertyAssignment(property)) {
1372
+ continue;
1373
+ }
1374
+ const value = property.getInitializer();
1375
+ if (!value) {
1376
+ continue;
1377
+ }
1378
+ if (value.getKind() === SyntaxKind2.TrueKeyword || value.getKind() === SyntaxKind2.FalseKeyword) {
1379
+ assignVisibility(visibility, key, value.getKind() === SyntaxKind2.TrueKeyword);
1380
+ } else if (Node4.isArrayLiteralExpression(value)) {
1381
+ assignVisibility(visibility, key, value.getElements().filter(Node4.isStringLiteral).map((element) => element.getLiteralText()));
1382
+ }
1383
+ }
1384
+ return Object.keys(visibility).length ? visibility : void 0;
1385
+ }
1386
+ function assignVisibility(visibility, key, value) {
1387
+ visibility[key] = value;
1388
+ }
1389
+ function readHosts(definition) {
1390
+ const initializer = readObjectProperty2(definition, "hosts");
1391
+ if (!initializer) {
1392
+ return void 0;
1393
+ }
1394
+ const chatgpt = readObjectProperty2(initializer, "chatgpt");
1395
+ if (!chatgpt) {
1396
+ return void 0;
1397
+ }
1398
+ const options = {};
1399
+ const invoking = readStringProperty2(chatgpt, "invoking");
1400
+ const invoked = readStringProperty2(chatgpt, "invoked");
1401
+ const visibility = readStringProperty2(chatgpt, "visibility");
1402
+ const widgetAccessible = readBooleanProperty2(chatgpt, "widgetAccessible");
1403
+ const fileParams = readStringArrayProperty2(chatgpt, "fileParams");
1404
+ if (invoking) options.invoking = invoking;
1405
+ if (invoked) options.invoked = invoked;
1406
+ if (visibility === "public" || visibility === "private") {
1407
+ options.visibility = visibility;
1408
+ }
1409
+ if (widgetAccessible !== void 0) {
1410
+ options.widgetAccessible = widgetAccessible;
1411
+ }
1412
+ if (fileParams) {
1413
+ options.fileParams = fileParams;
1414
+ }
1415
+ return Object.keys(options).length ? { chatgpt: options } : void 0;
1416
+ }
1417
+ function readObjectProperty2(definition, propertyName) {
1418
+ const property = definition.getProperty(propertyName);
1419
+ if (!property || !Node4.isPropertyAssignment(property)) {
1420
+ return void 0;
1421
+ }
1422
+ const initializer = unwrapExpression(property.getInitializer());
1423
+ return initializer && Node4.isObjectLiteralExpression(initializer) ? initializer : void 0;
1424
+ }
1425
+ function readStringProperty2(definition, propertyName) {
1426
+ const property = definition.getProperty(propertyName);
1427
+ if (!property || !Node4.isPropertyAssignment(property)) {
1428
+ return void 0;
1429
+ }
1430
+ const initializer = unwrapExpression(property.getInitializer());
1431
+ return initializer && Node4.isStringLiteral(initializer) ? initializer.getLiteralText() : void 0;
1432
+ }
1433
+ function readBooleanProperty2(definition, propertyName) {
1434
+ const property = definition.getProperty(propertyName);
1435
+ if (!property || !Node4.isPropertyAssignment(property)) {
1436
+ return void 0;
1437
+ }
1438
+ const initializer = unwrapExpression(property.getInitializer());
1439
+ if (!initializer) {
1440
+ return void 0;
1441
+ }
1442
+ if (initializer.getKind() === SyntaxKind2.TrueKeyword) {
1443
+ return true;
1444
+ }
1445
+ if (initializer.getKind() === SyntaxKind2.FalseKeyword) {
1446
+ return false;
1447
+ }
1448
+ return void 0;
1449
+ }
1450
+ function readStringArrayProperty2(definition, propertyName) {
1451
+ const property = definition.getProperty(propertyName);
1452
+ if (!property || !Node4.isPropertyAssignment(property)) {
1453
+ return void 0;
1454
+ }
1455
+ const initializer = unwrapExpression(property.getInitializer());
1456
+ if (!initializer || !Node4.isArrayLiteralExpression(initializer)) {
1457
+ return void 0;
1458
+ }
1459
+ return initializer.getElements().filter(Node4.isStringLiteral).map((element) => element.getLiteralText());
1460
+ }
1461
+
1462
+ // packages/compiler/src/build.ts
1463
+ import { mkdir as mkdir8, writeFile as writeFile8 } from "fs/promises";
1464
+ import path17 from "path";
1465
+
1466
+ // packages/compiler/src/config.ts
1467
+ import path5 from "path";
1468
+ import {
1469
+ Node as Node5,
1470
+ SyntaxKind as SyntaxKind3
1471
+ } from "ts-morph";
1472
+ function analyzeProjectConfig(rootDir) {
1473
+ const configPath = path5.join(rootDir, "sidecar.config.ts");
1474
+ if (!existsSyncSafe(configPath)) {
1475
+ return defaultCompilerConfig();
1476
+ }
1477
+ const project = createProject(rootDir);
1478
+ const sourceFile = project.addSourceFileAtPath(configPath);
1479
+ const call = resolveDefaultExportCall(sourceFile, "defineConfig");
1480
+ const definition = unwrapExpression(call?.getArguments()[0]);
1481
+ if (!definition || !Node5.isObjectLiteralExpression(definition)) {
1482
+ return defaultCompilerConfig();
1483
+ }
1484
+ return {
1485
+ resources: {
1486
+ subscribe: readBooleanNested(definition, "resources", "subscribe") ?? false,
1487
+ listChanged: readBooleanNested(definition, "resources", "listChanged") ?? false
1488
+ },
1489
+ prompts: {
1490
+ listChanged: readBooleanNested(definition, "prompts", "listChanged") ?? false
1491
+ },
1492
+ tools: {
1493
+ listChanged: readBooleanNested(definition, "tools", "listChanged") ?? false
1494
+ },
1495
+ pagination: {
1496
+ pageSize: readNumberNested(definition, "pagination", "pageSize") ?? 10,
1497
+ hasOverride: hasProperty(readObjectProperty3(definition, "pagination"), "override")
1498
+ }
1499
+ };
1500
+ }
1501
+ function defaultCompilerConfig() {
1502
+ return {
1503
+ resources: {
1504
+ subscribe: false,
1505
+ listChanged: false
1506
+ },
1507
+ prompts: {
1508
+ listChanged: false
1509
+ },
1510
+ tools: {
1511
+ listChanged: false
1512
+ },
1513
+ pagination: {
1514
+ pageSize: 10,
1515
+ hasOverride: false
1516
+ }
1517
+ };
1518
+ }
1519
+ function readBooleanNested(definition, section, propertyName) {
1520
+ const object = readObjectProperty3(definition, section);
1521
+ if (!object) {
1522
+ return void 0;
1523
+ }
1524
+ const property = object.getProperty(propertyName);
1525
+ if (!property || !Node5.isPropertyAssignment(property)) {
1526
+ return void 0;
1527
+ }
1528
+ const initializer = unwrapExpression(property.getInitializer());
1529
+ if (!initializer) {
1530
+ return void 0;
1531
+ }
1532
+ if (initializer.getKind() === SyntaxKind3.TrueKeyword) return true;
1533
+ if (initializer.getKind() === SyntaxKind3.FalseKeyword) return false;
1534
+ return void 0;
1535
+ }
1536
+ function readNumberNested(definition, section, propertyName) {
1537
+ const object = readObjectProperty3(definition, section);
1538
+ if (!object) {
1539
+ return void 0;
1540
+ }
1541
+ const property = object.getProperty(propertyName);
1542
+ if (!property || !Node5.isPropertyAssignment(property)) {
1543
+ return void 0;
1544
+ }
1545
+ const initializer = unwrapExpression(property.getInitializer());
1546
+ return initializer && Node5.isNumericLiteral(initializer) ? Number(initializer.getLiteralText()) : void 0;
1547
+ }
1548
+ function readObjectProperty3(definition, propertyName) {
1549
+ const property = definition.getProperty(propertyName);
1550
+ if (!property || !Node5.isPropertyAssignment(property)) {
1551
+ return void 0;
1552
+ }
1553
+ const initializer = unwrapExpression(property.getInitializer());
1554
+ return initializer && Node5.isObjectLiteralExpression(initializer) ? initializer : void 0;
1555
+ }
1556
+ function hasProperty(definition, propertyName) {
1557
+ return Boolean(definition?.getProperty(propertyName));
1558
+ }
1559
+
1560
+ // packages/compiler/src/diagnostics.ts
1561
+ import { existsSync as existsSync3 } from "fs";
1562
+ import { readFile as readFile2 } from "fs/promises";
1563
+ import { createRequire } from "module";
1564
+ import path6 from "path";
1565
+ async function collectProjectDiagnostics(rootDir, input) {
1566
+ const diagnostics = [];
1567
+ const tools = Array.isArray(input) ? input : input.tools;
1568
+ const resources = Array.isArray(input) ? [] : input.resources ?? [];
1569
+ const prompts = Array.isArray(input) ? [] : input.prompts ?? [];
1570
+ const config = Array.isArray(input) ? void 0 : input.config;
1571
+ const hasAuthConfig = existsSync3(path6.join(rootDir, "auth.ts"));
1572
+ const hasOpenAiAppsSdkUi = canResolveOpenAiAppsSdkUi(rootDir);
1573
+ for (const entry of tools) {
1574
+ const toolPath = path6.join(rootDir, entry.sourceFile);
1575
+ const toolSource = await readFile2(toolPath, "utf8");
1576
+ diagnostics.push(...diagnoseToolSource(rootDir, entry, toolSource, hasAuthConfig));
1577
+ if (entry.widget) {
1578
+ const widgetPath = path6.join(rootDir, entry.widget.sourceFile);
1579
+ const widgetSource = await readFile2(widgetPath, "utf8");
1580
+ diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource, hasOpenAiAppsSdkUi));
1581
+ }
1582
+ }
1583
+ for (const entry of resources) {
1584
+ const resourcePath = path6.join(rootDir, entry.sourceFile);
1585
+ const resourceSource = await readFile2(resourcePath, "utf8");
1586
+ diagnostics.push(...diagnoseResourceSource(entry, resourceSource));
1587
+ }
1588
+ for (const entry of prompts) {
1589
+ const promptPath = path6.join(rootDir, entry.sourceFile);
1590
+ const promptSource = await readFile2(promptPath, "utf8");
1591
+ diagnostics.push(...diagnosePromptSource(entry, promptSource));
1592
+ }
1593
+ if (config) {
1594
+ diagnostics.push(...diagnoseCapabilities(config, resources));
1595
+ }
1596
+ return diagnostics;
1597
+ }
1598
+ function formatDiagnostic(diagnostic) {
1599
+ const location = `${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}`;
1600
+ const hint = diagnostic.hint ? `
1601
+ hint: ${diagnostic.hint}` : "";
1602
+ return `${location} - ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}${hint}`;
1603
+ }
1604
+ function diagnoseToolSource(rootDir, entry, source, hasAuthConfig) {
1605
+ const diagnostics = [];
1606
+ const toolLocation = locate(source, "tool({");
1607
+ if (!entry.description.trim().startsWith("Use this when")) {
1608
+ diagnostics.push({
1609
+ severity: "warning",
1610
+ code: "SIDECAR_METADATA_DESCRIPTION",
1611
+ message: `Tool "${entry.id}" description should start with "Use this when..." for better model routing.`,
1612
+ filePath: entry.sourceFile,
1613
+ ...toolLocation,
1614
+ hint: "Keep the first sentence specific and include explicit disallowed cases when useful."
1615
+ });
1616
+ }
1617
+ for (const key of ["readOnlyHint", "destructiveHint", "openWorldHint"]) {
1618
+ if (!(key in (entry.annotations ?? {}))) {
1619
+ diagnostics.push({
1620
+ severity: "warning",
1621
+ code: "SIDECAR_TOOL_ANNOTATION",
1622
+ message: `Tool "${entry.id}" should explicitly declare annotations.${key}.`,
1623
+ filePath: entry.sourceFile,
1624
+ ...toolLocation,
1625
+ hint: "Hosts use these hints to frame approval and safety UX; Sidecar applies conservative defaults only as a fallback."
1626
+ });
1627
+ }
1628
+ }
1629
+ if (/auth\s*:/.test(source) && !hasAuthConfig && !isIgnored(source, "SIDECAR_AUTH_MISSING_CONFIG")) {
1630
+ diagnostics.push({
1631
+ severity: "warning",
1632
+ code: "SIDECAR_AUTH_MISSING_CONFIG",
1633
+ message: `Tool "${entry.id}" declares auth but the project has no reserved auth.ts file.`,
1634
+ filePath: entry.sourceFile,
1635
+ ...locate(source, "auth"),
1636
+ hint: "Add auth.ts at the project root or mark the tool auth policy as public."
1637
+ });
1638
+ }
1639
+ if (!returnsToolResult(source) && !isIgnored(source, "SIDECAR_TOOL_RESULT_REQUIRED")) {
1640
+ diagnostics.push({
1641
+ severity: "warning",
1642
+ code: "SIDECAR_TOOL_RESULT_REQUIRED",
1643
+ message: `Tool "${entry.id}" should return toolResult(...) from execute.`,
1644
+ filePath: entry.sourceFile,
1645
+ ...locate(source, "execute"),
1646
+ hint: "The runtime rejects plain objects so content, structuredContent, and meta always map cleanly to MCP result channels."
1647
+ });
1648
+ }
1649
+ if (/["']openai\//.test(source) && !/@sidecar-ai\/openai/.test(source) && !isIgnored(source, "SIDECAR_OPENAI_MAGIC_META")) {
1650
+ diagnostics.push({
1651
+ severity: "warning",
1652
+ code: "SIDECAR_OPENAI_MAGIC_META",
1653
+ message: `Tool "${entry.id}" uses raw ChatGPT metadata strings.`,
1654
+ filePath: entry.sourceFile,
1655
+ ...locate(source, "openai/"),
1656
+ hint: "Use @sidecar-ai/openai helpers or the typed hosts.chatgpt field so platform metadata stays typed."
1657
+ });
1658
+ }
1659
+ for (const parameterName of missingParameterDescriptions(entry.inputSchema)) {
1660
+ diagnostics.push({
1661
+ severity: "warning",
1662
+ code: "SIDECAR_PARAM_DESCRIPTION",
1663
+ message: `Parameter "${parameterName}" on tool "${entry.id}" is missing a JSDoc/schema description.`,
1664
+ filePath: entry.sourceFile,
1665
+ ...locate(source, parameterName),
1666
+ hint: "Add a JSDoc comment to the TypeScript field or provide a schema description."
1667
+ });
1668
+ }
1669
+ if (looksLikeKnowledgeTool(entry) && !hasCompanyKnowledgeShape(entry.inputSchema) && !isIgnored(source, "SIDECAR_COMPANY_KNOWLEDGE_SHAPE")) {
1670
+ diagnostics.push({
1671
+ severity: "warning",
1672
+ code: "SIDECAR_COMPANY_KNOWLEDGE_SHAPE",
1673
+ message: `Tool "${entry.id}" looks like a search/fetch knowledge tool but does not use the expected simple input shape.`,
1674
+ filePath: entry.sourceFile,
1675
+ ...toolLocation,
1676
+ hint: "Use a string query for search tools or a string id/url for fetch tools, or add // sidecar-ignore SIDECAR_COMPANY_KNOWLEDGE_SHAPE."
1677
+ });
1678
+ }
1679
+ return diagnostics.filter((diagnostic) => !isIgnored(source, diagnostic.code));
1680
+ }
1681
+ function diagnoseWidgetSource(_rootDir, entry, source, hasOpenAiAppsSdkUi) {
1682
+ const diagnostics = [];
1683
+ const widgetFile = entry.widget?.sourceFile;
1684
+ if (!widgetFile) {
1685
+ return diagnostics;
1686
+ }
1687
+ if (/window\s*\.\s*openai|\(\s*window\s+as\s+[^)]*\)\s*\.\s*openai/.test(source) && !isIgnored(source, "SIDECAR_OPENAI_RAW_BRIDGE")) {
1688
+ diagnostics.push({
1689
+ severity: "warning",
1690
+ code: "SIDECAR_OPENAI_RAW_BRIDGE",
1691
+ message: `Widget for "${entry.id}" reads window.openai directly.`,
1692
+ filePath: widgetFile,
1693
+ ...locate(source, "openai"),
1694
+ hint: "Use @sidecar-ai/openai runtime helpers in ChatGPT-only widgets, or @sidecar-ai/native/@sidecar-ai/client for portable behavior."
1695
+ });
1696
+ }
1697
+ if (entry.widget?.variant !== "openai" && /@sidecar-ai\/openai/.test(source) && !/@sidecar-ai\/native/.test(source) && !isIgnored(source, "SIDECAR_OPENAI_FALLBACK")) {
1698
+ diagnostics.push({
1699
+ severity: "warning",
1700
+ code: "SIDECAR_OPENAI_FALLBACK",
1701
+ message: `Widget for "${entry.id}" imports @sidecar-ai/openai without a portable fallback.`,
1702
+ filePath: widgetFile,
1703
+ ...locate(source, "@sidecar-ai/openai"),
1704
+ hint: "Prefer @sidecar-ai/native for user-facing behavior and reserve @sidecar-ai/openai for typed metadata or explicit ChatGPT-only code paths."
1705
+ });
1706
+ }
1707
+ if (entry.widget?.variant !== "openai" && /@sidecar-ai\/openai\/components/.test(source) && !isIgnored(source, "SIDECAR_OPENAI_COMPONENT_CROSS_HOST")) {
1708
+ diagnostics.push({
1709
+ severity: "warning",
1710
+ code: "SIDECAR_OPENAI_COMPONENT_CROSS_HOST",
1711
+ message: `Widget for "${entry.id}" imports ChatGPT-only components.`,
1712
+ filePath: widgetFile,
1713
+ ...locate(source, "@sidecar-ai/openai/components"),
1714
+ hint: "Use @sidecar-ai/native for portable primitives. Keep @sidecar-ai/openai/components for widgets intentionally targeted to ChatGPT."
1715
+ });
1716
+ }
1717
+ if (/@sidecar-ai\/openai\/components/.test(source) && !hasOpenAiAppsSdkUi && !isIgnored(source, "SIDECAR_OPENAI_UI_SDK_MISSING")) {
1718
+ diagnostics.push({
1719
+ severity: "error",
1720
+ code: "SIDECAR_OPENAI_UI_SDK_MISSING",
1721
+ message: `Widget for "${entry.id}" imports @sidecar-ai/openai/components but @openai/apps-sdk-ui is not installed.`,
1722
+ filePath: widgetFile,
1723
+ ...locate(source, "@sidecar-ai/openai/components"),
1724
+ hint: "Install it with: npm install @openai/apps-sdk-ui"
1725
+ });
1726
+ }
1727
+ if (entry.widget?.variant !== "anthropic" && /@sidecar-ai\/anthropic\/components/.test(source) && !isIgnored(source, "SIDECAR_ANTHROPIC_COMPONENT_CROSS_HOST")) {
1728
+ diagnostics.push({
1729
+ severity: "warning",
1730
+ code: "SIDECAR_ANTHROPIC_COMPONENT_CROSS_HOST",
1731
+ message: `Widget for "${entry.id}" imports Claude-only components.`,
1732
+ filePath: widgetFile,
1733
+ ...locate(source, "@sidecar-ai/anthropic/components"),
1734
+ hint: "Use @sidecar-ai/native for portable primitives. Keep @sidecar-ai/anthropic/components for widgets intentionally targeted to Claude."
1735
+ });
1736
+ }
1737
+ if (/\bPopover\b/.test(source) && /@sidecar-ai\/native/.test(source) && !isIgnored(source, "SIDECAR_NATIVE_NON_PORTABLE_COMPONENT")) {
1738
+ diagnostics.push({
1739
+ severity: "warning",
1740
+ code: "SIDECAR_NATIVE_NON_PORTABLE_COMPONENT",
1741
+ message: `Widget for "${entry.id}" appears to expect a native Popover.`,
1742
+ filePath: widgetFile,
1743
+ ...locate(source, "Popover"),
1744
+ hint: "Popover is host-specific because Claude inline apps discourage clipped overlay UI. Use @sidecar-ai/openai/components for ChatGPT-only popovers."
1745
+ });
1746
+ }
1747
+ return diagnostics.filter((diagnostic) => !isIgnored(source, diagnostic.code));
1748
+ }
1749
+ function diagnoseResourceSource(entry, source) {
1750
+ const diagnostics = [];
1751
+ if (!returnsResourceResult(source) && !isIgnored(source, "SIDECAR_RESOURCE_RESULT_REQUIRED")) {
1752
+ diagnostics.push({
1753
+ severity: "warning",
1754
+ code: "SIDECAR_RESOURCE_RESULT_REQUIRED",
1755
+ message: `Resource "${entry.uri}" should return resourceResult(...) from read.`,
1756
+ filePath: entry.sourceFile,
1757
+ ...locate(source, "read"),
1758
+ hint: "The runtime rejects plain objects so text/blob content maps cleanly to MCP resource contents."
1759
+ });
1760
+ }
1761
+ return diagnostics.filter((diagnostic) => !isIgnored(source, diagnostic.code));
1762
+ }
1763
+ function diagnosePromptSource(entry, source) {
1764
+ const diagnostics = [];
1765
+ if (!source.includes("run") && !isIgnored(source, "SIDECAR_PROMPT_RUN_REQUIRED")) {
1766
+ diagnostics.push({
1767
+ severity: "warning",
1768
+ code: "SIDECAR_PROMPT_RUN_REQUIRED",
1769
+ message: `Prompt "${entry.name}" should include a run method.`,
1770
+ filePath: entry.sourceFile,
1771
+ ...locate(source, "prompt({"),
1772
+ hint: "Prompt run methods return a string for the common case or MCP prompt messages for advanced cases."
1773
+ });
1774
+ }
1775
+ return diagnostics.filter((diagnostic) => !isIgnored(source, diagnostic.code));
1776
+ }
1777
+ function diagnoseCapabilities(config, resources) {
1778
+ const diagnostics = [];
1779
+ if (!config.resources.subscribe && resources.some((entry) => entry.subscribe)) {
1780
+ const entry = resources.find((resource) => resource.subscribe);
1781
+ if (entry) {
1782
+ diagnostics.push({
1783
+ severity: "error",
1784
+ code: "SIDECAR_RESOURCE_SUBSCRIBE_DISABLED",
1785
+ message: `Resource "${entry.uri}" sets subscribe: true but sidecar.config.ts does not enable resources.subscribe.`,
1786
+ filePath: entry.sourceFile,
1787
+ line: 1,
1788
+ column: 1,
1789
+ hint: "Add resources: { subscribe: true } to sidecar.config.ts or remove subscribe: true from this resource."
1790
+ });
1791
+ }
1792
+ }
1793
+ return diagnostics;
1794
+ }
1795
+ function returnsToolResult(source) {
1796
+ return /\breturn\s+toolResult(?:\.\w+)?\s*\(/.test(source);
1797
+ }
1798
+ function returnsResourceResult(source) {
1799
+ return /\breturn\s+resourceResult(?:\.\w+)?\s*\(/.test(source);
1800
+ }
1801
+ function missingParameterDescriptions(schema) {
1802
+ const properties = schema.properties ?? {};
1803
+ return Object.entries(properties).filter(([, value]) => !value.description?.trim()).map(([key]) => key);
1804
+ }
1805
+ function looksLikeKnowledgeTool(entry) {
1806
+ const text = `${entry.id} ${entry.name}`.toLowerCase();
1807
+ return /\b(search|fetch)\b/.test(text) || /(^|[._-])(search|fetch)([._-]|$)/.test(text);
1808
+ }
1809
+ function hasCompanyKnowledgeShape(schema) {
1810
+ const properties = schema.properties ?? {};
1811
+ const query = properties.query;
1812
+ const url = properties.url;
1813
+ const id = properties.id;
1814
+ return query?.type === "string" || url?.type === "string" || id?.type === "string";
1815
+ }
1816
+ function canResolveOpenAiAppsSdkUi(rootDir) {
1817
+ try {
1818
+ createRequire(path6.join(rootDir, "package.json")).resolve("@openai/apps-sdk-ui/css");
1819
+ return true;
1820
+ } catch {
1821
+ return false;
1822
+ }
1823
+ }
1824
+ function locate(source, needle) {
1825
+ const index = Math.max(0, source.indexOf(needle));
1826
+ const prefix = source.slice(0, index);
1827
+ const lines = prefix.split("\n");
1828
+ return {
1829
+ line: lines.length,
1830
+ column: (lines.at(-1)?.length ?? 0) + 1
1831
+ };
1832
+ }
1833
+ function isIgnored(source, code) {
1834
+ return source.includes(`sidecar-ignore ${code}`) || source.includes(`sidecar-ignore all`);
1835
+ }
1836
+
1837
+ // packages/compiler/src/generated.ts
1838
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
1839
+ import path7 from "path";
1840
+ async function writeGeneratedTypes(rootDir, tools) {
1841
+ const generatedDir = path7.join(rootDir, ".sidecar", "generated");
1842
+ await mkdir2(generatedDir, { recursive: true });
1843
+ const appCallableTools = tools.filter(isAppCallable);
1844
+ const methodNames = uniqueMethodNames(appCallableTools.map((entry) => toIdentifier(entry.id)));
1845
+ const imports = appCallableTools.map(
1846
+ (entry, index) => `import type tool${index} from ${JSON.stringify(toImportSpecifier(generatedDir, path7.join(rootDir, entry.sourceFile), { extension: "js" }))};`
1847
+ ).join("\n");
1848
+ const ids = appCallableTools.map((entry, index) => ` ${methodNames[index]}: ${JSON.stringify(entry.id)}`).join(",\n");
1849
+ const toolTypes = appCallableTools.map(
1850
+ (entry, index) => ` ${methodNames[index]}(params: ToolParams<typeof tool${index}>): Promise<ToolOutput<typeof tool${index}>>;`
1851
+ ).join("\n");
1852
+ await writeFile2(
1853
+ path7.join(generatedDir, "tools.ts"),
1854
+ `/**
1855
+ * Generated Sidecar widget tool client.
1856
+ *
1857
+ * Do not edit this file directly; it is regenerated by sidecar build and
1858
+ * sidecar dev.
1859
+ */
1860
+ ${imports}
1861
+ import { createToolClient } from "@sidecar-ai/client";
1862
+ import type { ToolResult } from "@sidecar-ai/core";
1863
+
1864
+ type ExecuteOf<T> = T extends { execute: infer Execute } ? Execute : never;
1865
+ type ToolParams<T> = ExecuteOf<T> extends (params: infer Params, ...args: any[]) => unknown ? Params : never;
1866
+ type RawToolOutput<T> = Awaited<ReturnType<Extract<ExecuteOf<T>, (...args: any[]) => unknown>>>;
1867
+ type ToolOutput<T> = RawToolOutput<T> extends ToolResult<infer Structured, any> ? Structured : never;
1868
+
1869
+ /** Machine names keyed by widget-friendly method names. */
1870
+ export const toolIds = {
1871
+ ${ids}
1872
+ } as const;
1873
+
1874
+ /** Typed callable tools available to widgets. */
1875
+ export type WidgetTools = {
1876
+ ${toolTypes}
1877
+ };
1878
+
1879
+ /** Default widget tool client backed by the host bridge. */
1880
+ export const tools = createToolClient<WidgetTools>(toolIds);
1881
+ `
1882
+ );
1883
+ }
1884
+ function isAppCallable(entry) {
1885
+ const ui = entry.descriptor._meta?.ui;
1886
+ if (!ui || typeof ui !== "object" || Array.isArray(ui)) {
1887
+ return true;
1888
+ }
1889
+ const visibility = ui.visibility;
1890
+ return !Array.isArray(visibility) || visibility.includes("app");
1891
+ }
1892
+ function uniqueMethodNames(names) {
1893
+ const seen = /* @__PURE__ */ new Map();
1894
+ return names.map((name) => {
1895
+ const count = seen.get(name) ?? 0;
1896
+ seen.set(name, count + 1);
1897
+ return count === 0 ? name : `${name}${count + 1}`;
1898
+ });
1899
+ }
1900
+
1901
+ // packages/compiler/src/plugins.ts
1902
+ import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
1903
+ import path14 from "path";
1904
+
1905
+ // packages/compiler/src/identity.ts
1906
+ import { readFile as readFile3 } from "fs/promises";
1907
+ import path8 from "path";
1908
+ async function loadProjectIdentity(rootDir) {
1909
+ const packageJson = await readOptionalJson(path8.join(rootDir, "package.json"));
1910
+ const configText = await readOptionalText(
1911
+ path8.join(rootDir, "sidecar.config.ts")
1912
+ );
1913
+ const name = readConfigString(configText, "name") ?? packageJson?.name ?? path8.basename(rootDir);
1914
+ const version = readConfigString(configText, "version") ?? packageJson?.version ?? "0.0.0-dev";
1915
+ const description = readConfigString(configText, "description") ?? packageJson?.description ?? `${name} Sidecar app.`;
1916
+ return {
1917
+ name,
1918
+ slug: toMachineName(name).replaceAll("_", "-"),
1919
+ version,
1920
+ description
1921
+ };
1922
+ }
1923
+ async function readOptionalJson(filePath) {
1924
+ if (!existsSyncSafe(filePath)) {
1925
+ return void 0;
1926
+ }
1927
+ return JSON.parse(await readFile3(filePath, "utf8"));
1928
+ }
1929
+ async function readOptionalText(filePath) {
1930
+ if (!existsSyncSafe(filePath)) {
1931
+ return void 0;
1932
+ }
1933
+ return readFile3(filePath, "utf8");
1934
+ }
1935
+ function readConfigString(configText, key) {
1936
+ if (!configText) {
1937
+ return void 0;
1938
+ }
1939
+ const match = configText.match(
1940
+ new RegExp(`${key}\\s*:\\s*["'\`]([^"'\`]+)["'\`]`)
1941
+ );
1942
+ return match?.[1];
1943
+ }
1944
+
1945
+ // packages/compiler/src/plugin-components/agents.ts
1946
+ import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
1947
+ import path9 from "path";
1948
+ async function emitClaudeAgents(rootDir, destination) {
1949
+ const source = path9.join(rootDir, "agents");
1950
+ if (!existsSyncSafe(source)) {
1951
+ return;
1952
+ }
1953
+ const entries = await readdir2(source, { withFileTypes: true });
1954
+ const agentDirs = entries.filter(
1955
+ (entry) => entry.isDirectory() && existsSyncSafe(path9.join(source, entry.name, "agent.ts"))
1956
+ );
1957
+ if (!agentDirs.length) {
1958
+ return;
1959
+ }
1960
+ await mkdir3(destination, { recursive: true });
1961
+ for (const entry of agentDirs) {
1962
+ const sourceText = await readFile4(path9.join(source, entry.name, "agent.ts"), "utf8");
1963
+ const markdown = parseClaudeAgent(
1964
+ sourceText,
1965
+ entry.name
1966
+ );
1967
+ const agentName = readObjectString(sourceText, "name") ?? entry.name;
1968
+ await writeFile3(path9.join(destination, `${safeFileStem(agentName)}.md`), markdown);
1969
+ }
1970
+ }
1971
+ function parseClaudeAgent(source, fallbackName) {
1972
+ const name = readObjectString(source, "name") ?? fallbackName;
1973
+ const description = readObjectString(source, "description") ?? `${name} agent.`;
1974
+ const prompt = readObjectString(source, "prompt") ?? "";
1975
+ const tools = readObjectStringArray(source, "tools");
1976
+ const disallowedTools = readObjectStringArray(source, "disallowedTools");
1977
+ const model = readObjectString(source, "model");
1978
+ const color = readObjectString(source, "color");
1979
+ const frontmatter = stripUndefined2({
1980
+ name,
1981
+ description,
1982
+ model,
1983
+ color,
1984
+ tools: tools?.join(", "),
1985
+ "disallowed-tools": disallowedTools?.join(", ")
1986
+ });
1987
+ return `---
1988
+ ${Object.entries(frontmatter).map(([key, value]) => `${key}: ${yamlScalar(value)}`).join("\n")}
1989
+ ---
1990
+
1991
+ ${prompt.trim()}
1992
+ `;
1993
+ }
1994
+
1995
+ // packages/compiler/src/plugin-components/commands.ts
1996
+ import { cp, lstat, mkdir as mkdir4, readdir as readdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
1997
+ import path10 from "path";
1998
+ async function copyCommands(rootDir, destination) {
1999
+ const source = path10.join(rootDir, "commands");
2000
+ if (!existsSyncSafe(source)) {
2001
+ return;
2002
+ }
2003
+ await mkdir4(destination, { recursive: true });
2004
+ const entries = await readdir3(source, { withFileTypes: true });
2005
+ for (const entry of entries) {
2006
+ const sourcePath = path10.join(source, entry.name);
2007
+ if (entry.isFile() && entry.name.endsWith(".md")) {
2008
+ if (await safeCommandCopyFilter(sourcePath)) {
2009
+ await cp(sourcePath, path10.join(destination, safeFileStem(entry.name.replace(/\.md$/, "")) + ".md"));
2010
+ }
2011
+ continue;
2012
+ }
2013
+ if (!entry.isDirectory()) {
2014
+ continue;
2015
+ }
2016
+ const dynamicCommand = path10.join(sourcePath, "command.ts");
2017
+ if (existsSyncSafe(dynamicCommand)) {
2018
+ const sourceText = await readFile5(dynamicCommand, "utf8");
2019
+ const markdown = parseDynamicCommand(sourceText, entry.name);
2020
+ const commandName = readObjectString(sourceText, "name") ?? entry.name;
2021
+ await writeFile4(path10.join(destination, `${safeFileStem(commandName)}.md`), markdown);
2022
+ continue;
2023
+ }
2024
+ await cp(sourcePath, path10.join(destination, entry.name), {
2025
+ recursive: true,
2026
+ filter: safeCommandCopyFilter
2027
+ });
2028
+ }
2029
+ }
2030
+ async function safeCommandCopyFilter(sourcePath) {
2031
+ const basename = path10.basename(sourcePath);
2032
+ if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
2033
+ return false;
2034
+ }
2035
+ const stat = await lstat(sourcePath);
2036
+ return !stat.isSymbolicLink();
2037
+ }
2038
+ function parseDynamicCommand(source, fallbackName) {
2039
+ const name = readObjectString(source, "name") ?? fallbackName;
2040
+ const description = readObjectString(source, "description");
2041
+ const prompt = readObjectString(source, "prompt") ?? "";
2042
+ const argumentHint = readObjectString(source, "argumentHint");
2043
+ const allowedTools = readObjectStringArray(source, "allowedTools");
2044
+ const frontmatter = stripUndefined2({
2045
+ description,
2046
+ "argument-hint": argumentHint,
2047
+ "allowed-tools": allowedTools?.join(", ")
2048
+ });
2049
+ const header = Object.keys(frontmatter).length ? `---
2050
+ ${Object.entries(frontmatter).map(([key, value]) => `${key}: ${yamlScalar(value)}`).join("\n")}
2051
+ ---
2052
+
2053
+ ` : "";
2054
+ return `${header}${prompt.trim() || `# ${name}`}
2055
+ `;
2056
+ }
2057
+
2058
+ // packages/compiler/src/plugin-components/hooks.ts
2059
+ import { mkdir as mkdir5, readdir as readdir4, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
2060
+ import path11 from "path";
2061
+ import {
2062
+ Node as Node6,
2063
+ Project as Project2,
2064
+ SyntaxKind as SyntaxKind4
2065
+ } from "ts-morph";
2066
+ async function copyHooks(rootDir, destination) {
2067
+ const source = path11.join(rootDir, "hooks");
2068
+ if (!existsSyncSafe(source)) {
2069
+ return;
2070
+ }
2071
+ const entries = await readdir4(source, { withFileTypes: true });
2072
+ const hookDirs = entries.filter(
2073
+ (entry) => entry.isDirectory() && existsSyncSafe(path11.join(source, entry.name, "hook.ts"))
2074
+ );
2075
+ if (!hookDirs.length) {
2076
+ return;
2077
+ }
2078
+ const config = {};
2079
+ for (const entry of hookDirs) {
2080
+ const filePath = path11.join(source, entry.name, "hook.ts");
2081
+ mergeHooks(config, parseHookFile(await readFile6(filePath, "utf8"), entry.name));
2082
+ }
2083
+ await mkdir5(destination, { recursive: true });
2084
+ await writeFile5(
2085
+ path11.join(destination, "hooks.json"),
2086
+ `${JSON.stringify(config, null, 2)}
2087
+ `
2088
+ );
2089
+ }
2090
+ function parseHookFile(source, fallbackName) {
2091
+ const project = new Project2({ useInMemoryFileSystem: true });
2092
+ const sourceFile = project.createSourceFile(`${fallbackName}.ts`, source);
2093
+ const call = resolveDefaultExportCall(sourceFile, "hook") ?? resolveDefaultExportCall(sourceFile, "hooks");
2094
+ if (!call) {
2095
+ throw new Error(`hooks/${fallbackName}/hook.ts must default-export hook({ ... }) or hooks({ ... }).`);
2096
+ }
2097
+ const definition = unwrapExpression(call.getArguments()[0]);
2098
+ if (!definition || !Node6.isObjectLiteralExpression(definition)) {
2099
+ throw new Error(`hooks/${fallbackName}/hook.ts must default-export hook({ ... }) or hooks({ ... }).`);
2100
+ }
2101
+ const callee = call.getExpression().getText();
2102
+ if (callee === "hook" || callee.endsWith(".hook")) {
2103
+ const entry = parseHookDefinition(definition, fallbackName);
2104
+ return { [entry.event]: [stripUndefined2({ matcher: entry.matcher, hooks: entry.run })] };
2105
+ }
2106
+ if (callee === "hooks" || callee.endsWith(".hooks")) {
2107
+ return parseHooksDefinition(definition, fallbackName);
2108
+ }
2109
+ throw new Error(`hooks/${fallbackName}/hook.ts must default-export hook({ ... }) or hooks({ ... }).`);
2110
+ }
2111
+ function parseHookDefinition(definition, fallbackName) {
2112
+ const event = readStringProperty3(definition, "event");
2113
+ if (!event) {
2114
+ throw new Error(`hooks/${fallbackName}/hook.ts must declare an event string.`);
2115
+ }
2116
+ const hooks = readHookArray(definition, "run", fallbackName);
2117
+ if (!hooks.length) {
2118
+ throw new Error(`hooks/${fallbackName}/hook.ts must declare at least one run handler.`);
2119
+ }
2120
+ return {
2121
+ event,
2122
+ matcher: readStringProperty3(definition, "matcher"),
2123
+ run: hooks
2124
+ };
2125
+ }
2126
+ function parseHooksDefinition(definition, fallbackName) {
2127
+ const config = {};
2128
+ for (const property of definition.getProperties()) {
2129
+ if (!Node6.isPropertyAssignment(property)) {
2130
+ continue;
2131
+ }
2132
+ const event = property.getName().replace(/^["']|["']$/g, "");
2133
+ const initializer = property.getInitializer();
2134
+ if (!initializer || !Node6.isArrayLiteralExpression(initializer)) {
2135
+ throw new Error(`hooks/${fallbackName}/hook.ts event "${event}" must be an array.`);
2136
+ }
2137
+ config[event] = initializer.getElements().map((entry) => {
2138
+ if (!Node6.isObjectLiteralExpression(entry)) {
2139
+ throw new Error(`hooks/${fallbackName}/hook.ts event "${event}" entries must be objects.`);
2140
+ }
2141
+ return stripUndefined2({
2142
+ matcher: readStringProperty3(entry, "matcher"),
2143
+ hooks: readHookArray(entry, "run", fallbackName)
2144
+ });
2145
+ });
2146
+ }
2147
+ return config;
2148
+ }
2149
+ function readHookArray(definition, propertyName, fallbackName) {
2150
+ const property = definition.getProperty(propertyName);
2151
+ if (!property || !Node6.isPropertyAssignment(property)) {
2152
+ return [];
2153
+ }
2154
+ const initializer = property.getInitializer();
2155
+ if (!initializer || !Node6.isArrayLiteralExpression(initializer)) {
2156
+ throw new Error(`hooks/${fallbackName}/hook.ts ${propertyName} must be an array.`);
2157
+ }
2158
+ return parseHookHandlers(initializer, fallbackName);
2159
+ }
2160
+ function parseHookHandlers(handlers, fallbackName) {
2161
+ return handlers.getElements().map((handler) => parseHookHandler(handler, fallbackName));
2162
+ }
2163
+ function parseHookHandler(handler, fallbackName) {
2164
+ if (Node6.isObjectLiteralExpression(handler)) {
2165
+ const type = readStringProperty3(handler, "type");
2166
+ if (type !== "command" && type !== "http") {
2167
+ throw new Error(`hooks/${fallbackName}/hook.ts hook handlers need type "command" or "http".`);
2168
+ }
2169
+ return objectLiteralToRecord(handler);
2170
+ }
2171
+ if (Node6.isCallExpression(handler)) {
2172
+ return parseHookHandlerCall(handler, fallbackName);
2173
+ }
2174
+ throw new Error(`hooks/${fallbackName}/hook.ts hook handlers must be objects or helper calls.`);
2175
+ }
2176
+ function parseHookHandlerCall(handler, fallbackName) {
2177
+ const callee = handler.getExpression().getText();
2178
+ const [firstArg, optionsArg] = handler.getArguments();
2179
+ const first = firstArg ? stringFromExpression(firstArg) : void 0;
2180
+ if (!first) {
2181
+ throw new Error(`hooks/${fallbackName}/hook.ts hook helper calls require a string first argument.`);
2182
+ }
2183
+ const options = optionsArg && Node6.isObjectLiteralExpression(optionsArg) ? objectLiteralToRecord(optionsArg) : {};
2184
+ if (callee.endsWith("commandHook")) {
2185
+ return stripUndefined2({
2186
+ type: "command",
2187
+ command: first,
2188
+ ...options
2189
+ });
2190
+ }
2191
+ if (callee.endsWith("httpHook")) {
2192
+ return stripUndefined2({
2193
+ type: "http",
2194
+ url: first,
2195
+ ...options
2196
+ });
2197
+ }
2198
+ throw new Error(`hooks/${fallbackName}/hook.ts uses an unsupported hook helper "${callee}".`);
2199
+ }
2200
+ function objectLiteralToRecord(object) {
2201
+ const record = {};
2202
+ for (const property of object.getProperties()) {
2203
+ if (!Node6.isPropertyAssignment(property)) {
2204
+ continue;
2205
+ }
2206
+ const key = property.getName().replace(/^["']|["']$/g, "");
2207
+ const initializer = property.getInitializer();
2208
+ if (!initializer) {
2209
+ continue;
2210
+ }
2211
+ record[key] = valueFromExpression(initializer);
2212
+ }
2213
+ return stripUndefined2(record);
2214
+ }
2215
+ function readStringProperty3(object, propertyName) {
2216
+ const property = object.getProperty(propertyName);
2217
+ if (!property || !Node6.isPropertyAssignment(property)) {
2218
+ return void 0;
2219
+ }
2220
+ const initializer = property.getInitializer();
2221
+ return initializer ? stringFromExpression(initializer) : void 0;
2222
+ }
2223
+ function valueFromExpression(expression) {
2224
+ const string = stringFromExpression(expression);
2225
+ if (string !== void 0) {
2226
+ return string;
2227
+ }
2228
+ if (expression.getKind() === SyntaxKind4.TrueKeyword) {
2229
+ return true;
2230
+ }
2231
+ if (expression.getKind() === SyntaxKind4.FalseKeyword) {
2232
+ return false;
2233
+ }
2234
+ if (Node6.isNumericLiteral(expression)) {
2235
+ return Number(expression.getLiteralText());
2236
+ }
2237
+ if (Node6.isArrayLiteralExpression(expression)) {
2238
+ return expression.getElements().map(valueFromExpression);
2239
+ }
2240
+ if (Node6.isObjectLiteralExpression(expression)) {
2241
+ return objectLiteralToRecord(expression);
2242
+ }
2243
+ return void 0;
2244
+ }
2245
+ function stringFromExpression(expression) {
2246
+ if (Node6.isStringLiteral(expression) || Node6.isNoSubstitutionTemplateLiteral(expression)) {
2247
+ return expression.getLiteralText();
2248
+ }
2249
+ return void 0;
2250
+ }
2251
+ function mergeHooks(target, source) {
2252
+ for (const [event, matchers] of Object.entries(source)) {
2253
+ target[event] = [...target[event] ?? [], ...matchers];
2254
+ }
2255
+ }
2256
+
2257
+ // packages/compiler/src/plugin-components/passthrough.ts
2258
+ import { cp as cp2, lstat as lstat2 } from "fs/promises";
2259
+ import path12 from "path";
2260
+ async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
2261
+ for (const directory of [
2262
+ "assets",
2263
+ "bin",
2264
+ "monitors",
2265
+ "output-styles",
2266
+ "themes"
2267
+ ]) {
2268
+ const source = path12.join(rootDir, directory);
2269
+ if (!existsSyncSafe(source)) {
2270
+ continue;
2271
+ }
2272
+ await cp2(source, path12.join(pluginDir, directory), {
2273
+ recursive: true,
2274
+ filter: safePluginCopyFilter
2275
+ });
2276
+ }
2277
+ }
2278
+ async function safePluginCopyFilter(sourcePath) {
2279
+ const basename = path12.basename(sourcePath);
2280
+ if (basename === "node_modules" || basename === ".git" || basename === ".env" || basename.startsWith(".env.")) {
2281
+ return false;
2282
+ }
2283
+ const stat = await lstat2(sourcePath);
2284
+ return !stat.isSymbolicLink();
2285
+ }
2286
+
2287
+ // packages/compiler/src/plugin-components/skills.ts
2288
+ import { existsSync as existsSync4 } from "fs";
2289
+ import { cp as cp3, lstat as lstat3, mkdir as mkdir6, readdir as readdir5, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
2290
+ import path13 from "path";
2291
+ async function copySkills(rootDir, destination) {
2292
+ const source = path13.join(rootDir, "skills");
2293
+ if (!existsSyncSafe(source)) {
2294
+ return;
2295
+ }
2296
+ await mkdir6(destination, { recursive: true });
2297
+ const entries = await readdir5(source, { withFileTypes: true });
2298
+ for (const entry of entries) {
2299
+ if (!entry.isDirectory()) {
2300
+ continue;
2301
+ }
2302
+ const sourceDir = path13.join(source, entry.name);
2303
+ const destinationDir = path13.join(destination, entry.name);
2304
+ const staticSkill = path13.join(sourceDir, "SKILL.md");
2305
+ const dynamicSkill = path13.join(sourceDir, "skill.ts");
2306
+ await mkdir6(destinationDir, { recursive: true });
2307
+ if (existsSync4(staticSkill)) {
2308
+ await cp3(sourceDir, destinationDir, {
2309
+ recursive: true,
2310
+ filter: async (sourcePath) => !sourcePath.endsWith(`${path13.sep}skill.ts`) && await safeSkillCopyFilter(sourcePath)
2311
+ });
2312
+ } else if (existsSync4(dynamicSkill)) {
2313
+ const generated = parseDynamicSkill(
2314
+ await readFile7(dynamicSkill, "utf8"),
2315
+ entry.name
2316
+ );
2317
+ await writeFile6(path13.join(destinationDir, "SKILL.md"), generated);
2318
+ }
2319
+ }
2320
+ }
2321
+ async function safeSkillCopyFilter(sourcePath) {
2322
+ const basename = path13.basename(sourcePath);
2323
+ if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
2324
+ return false;
2325
+ }
2326
+ const stat = await lstat3(sourcePath);
2327
+ return !stat.isSymbolicLink();
2328
+ }
2329
+ function parseDynamicSkill(source, fallbackName) {
2330
+ const name = readObjectString(source, "name") ?? fallbackName;
2331
+ const description = readObjectString(source, "description") ?? `${name} skill.`;
2332
+ const body = readObjectString(source, "body") ?? "";
2333
+ return `---
2334
+ name: ${yamlScalar(name)}
2335
+ description: ${yamlScalar(description)}
2336
+ ---
2337
+
2338
+ ${body.trim()}
2339
+ `;
2340
+ }
2341
+
2342
+ // packages/compiler/src/plugins.ts
2343
+ async function buildPluginPackages(rootDir, outRoot, manifest) {
2344
+ const identity = await loadProjectIdentity(rootDir);
2345
+ await buildClaudePlugin(rootDir, outRoot, identity, manifest);
2346
+ }
2347
+ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
2348
+ const pluginDir = path14.join(outRoot, "claude-plugin");
2349
+ await mkdir7(path14.join(pluginDir, ".claude-plugin"), { recursive: true });
2350
+ await copySkills(rootDir, path14.join(pluginDir, "skills"));
2351
+ await copyCommands(rootDir, path14.join(pluginDir, "commands"));
2352
+ await copyHooks(rootDir, path14.join(pluginDir, "hooks"));
2353
+ await emitClaudeAgents(rootDir, path14.join(pluginDir, "agents"));
2354
+ await copyClaudePassthroughDirectories(rootDir, pluginDir);
2355
+ await writeJson(path14.join(pluginDir, ".claude-plugin", "plugin.json"), {
2356
+ name: identity.slug,
2357
+ version: identity.version,
2358
+ description: identity.description,
2359
+ displayName: identity.name,
2360
+ installationPreference: "available"
2361
+ });
2362
+ await writeJson(path14.join(pluginDir, "version.json"), {
2363
+ version: identity.version,
2364
+ generatedBy: "sidecar"
2365
+ });
2366
+ await writeJson(path14.join(pluginDir, ".mcp.json"), {
2367
+ mcpServers: {
2368
+ [identity.slug]: {
2369
+ type: "http",
2370
+ url: "${SIDECAR_MCP_URL}"
2371
+ }
2372
+ }
2373
+ });
2374
+ await writeJson(path14.join(pluginDir, "settings.json"), {
2375
+ sidecar: {
2376
+ tools: manifest.tools.map((entry) => entry.id),
2377
+ resources: manifest.resources.map((entry) => entry.uri),
2378
+ prompts: manifest.prompts.map((entry) => entry.name)
2379
+ }
2380
+ });
2381
+ await writeFile7(
2382
+ path14.join(pluginDir, "README.md"),
2383
+ `# ${identity.name} Claude Plugin
2384
+
2385
+ This package was generated by Sidecar.
2386
+
2387
+ Set \`SIDECAR_MCP_URL\` to the hosted MCP endpoint for this app.
2388
+
2389
+ For local testing, run \`sidecar dev --tunnel\` and use the printed HTTPS MCP URL.
2390
+
2391
+ Claude/Cowork plugin-specific components such as skills, slash commands, agents, hooks, output styles, themes, monitors, assets, and bin scripts will be emitted here when the source project defines them.
2392
+ `
2393
+ );
2394
+ }
2395
+ async function writeJson(filePath, value) {
2396
+ await mkdir7(path14.dirname(filePath), { recursive: true });
2397
+ await writeFile7(
2398
+ filePath,
2399
+ `${JSON.stringify(stripUndefined2(value), null, 2)}
2400
+ `
2401
+ );
2402
+ }
2403
+
2404
+ // packages/compiler/src/prompts.ts
2405
+ import { readdir as readdir6 } from "fs/promises";
2406
+ import path15 from "path";
2407
+ import {
2408
+ Node as Node7,
2409
+ SyntaxKind as SyntaxKind5
2410
+ } from "ts-morph";
2411
+ async function analyzeProjectPrompts(rootDir) {
2412
+ const promptFiles = await findPromptFiles(path15.join(rootDir, "prompts"));
2413
+ if (promptFiles.length === 0) {
2414
+ return [];
2415
+ }
2416
+ const project = createProject(rootDir);
2417
+ return promptFiles.map(
2418
+ (filePath) => analyzePromptFile(project.addSourceFileAtPath(filePath), rootDir)
2419
+ );
2420
+ }
2421
+ function analyzePromptFile(sourceFile, rootDir) {
2422
+ const definition = findPromptDefinition(sourceFile);
2423
+ const absoluteFile = sourceFile.getFilePath();
2424
+ const directory = path15.basename(path15.dirname(absoluteFile));
2425
+ const name = getOptionalStringProperty2(definition, "name") ?? safePathSegment(directory);
2426
+ const title = getRequiredStringProperty2(definition, "title", sourceFile);
2427
+ const description = getOptionalStringProperty2(definition, "description");
2428
+ const args = readArgs(definition);
2429
+ const descriptor = createPromptDescriptor({
2430
+ name,
2431
+ title,
2432
+ description,
2433
+ args,
2434
+ icons: readIcons(definition)
2435
+ });
2436
+ if (!getMethodOrFunctionProperty(definition, "run")) {
2437
+ throw new CompilerError(sourceFile, "prompt({ ... }) must include a run method.");
2438
+ }
2439
+ return {
2440
+ sourceFile: path15.relative(rootDir, absoluteFile),
2441
+ directory,
2442
+ name,
2443
+ title,
2444
+ description,
2445
+ args,
2446
+ descriptor
2447
+ };
2448
+ }
2449
+ async function findPromptFiles(promptsDir) {
2450
+ if (!existsSyncSafe(promptsDir)) {
2451
+ return [];
2452
+ }
2453
+ const entries = await readdir6(promptsDir, { withFileTypes: true });
2454
+ const files = [];
2455
+ for (const entry of entries) {
2456
+ if (!entry.isDirectory()) {
2457
+ continue;
2458
+ }
2459
+ const filePath = path15.join(promptsDir, entry.name, "prompt.ts");
2460
+ if (existsSyncSafe(filePath)) {
2461
+ files.push(filePath);
2462
+ }
2463
+ }
2464
+ return files.sort((left, right) => left.localeCompare(right));
2465
+ }
2466
+ function findPromptDefinition(sourceFile) {
2467
+ const call = resolveDefaultExportCall(sourceFile, "prompt");
2468
+ if (!call) {
2469
+ throw new CompilerError(
2470
+ sourceFile,
2471
+ "prompt.ts must default-export prompt({ ... }) or an identifier initialized with prompt({ ... })."
2472
+ );
2473
+ }
2474
+ const definition = unwrapExpression(call.getArguments()[0]);
2475
+ if (!definition || !Node7.isObjectLiteralExpression(definition)) {
2476
+ throw new CompilerError(sourceFile, "prompt(...) must receive one object literal.");
2477
+ }
2478
+ return definition;
2479
+ }
2480
+ function getRequiredStringProperty2(definition, propertyName, sourceFile) {
2481
+ const value = getOptionalStringProperty2(definition, propertyName);
2482
+ if (value === void 0 || !value.trim()) {
2483
+ throw new CompilerError(
2484
+ sourceFile,
2485
+ `prompt({ ... }) must include a non-empty ${propertyName} string.`
2486
+ );
2487
+ }
2488
+ return value;
2489
+ }
2490
+ function getOptionalStringProperty2(definition, propertyName) {
2491
+ const property = definition.getProperty(propertyName);
2492
+ if (!property || !Node7.isPropertyAssignment(property)) {
2493
+ return void 0;
2494
+ }
2495
+ const initializer = unwrapExpression(property.getInitializer());
2496
+ return initializer && Node7.isStringLiteral(initializer) ? initializer.getLiteralText() : void 0;
2497
+ }
2498
+ function readArgs(definition) {
2499
+ const initializer = readObjectProperty4(definition, "args");
2500
+ if (!initializer) {
2501
+ return void 0;
2502
+ }
2503
+ const args = {};
2504
+ for (const property of initializer.getProperties()) {
2505
+ if (!Node7.isPropertyAssignment(property)) {
2506
+ continue;
2507
+ }
2508
+ const value = readArgInput(property.getInitializer());
2509
+ if (value !== void 0) {
2510
+ args[property.getName().replace(/^["']|["']$/g, "")] = value;
2511
+ }
2512
+ }
2513
+ return Object.keys(args).length ? args : void 0;
2514
+ }
2515
+ function readArgInput(node) {
2516
+ const initializer = unwrapExpression(node);
2517
+ if (!initializer) {
2518
+ return void 0;
2519
+ }
2520
+ if (Node7.isStringLiteral(initializer)) {
2521
+ return initializer.getLiteralText();
2522
+ }
2523
+ if (Node7.isArrayLiteralExpression(initializer)) {
2524
+ return initializer.getElements().filter(Node7.isLiteralExpression).map((element) => element.getLiteralText());
2525
+ }
2526
+ if (Node7.isObjectLiteralExpression(initializer)) {
2527
+ return {
2528
+ description: getOptionalStringProperty2(initializer, "description"),
2529
+ required: readBooleanProperty3(initializer, "required")
2530
+ };
2531
+ }
2532
+ return void 0;
2533
+ }
2534
+ function readBooleanProperty3(definition, propertyName) {
2535
+ const property = definition.getProperty(propertyName);
2536
+ if (!property || !Node7.isPropertyAssignment(property)) {
2537
+ return void 0;
2538
+ }
2539
+ const initializer = unwrapExpression(property.getInitializer());
2540
+ if (!initializer) {
2541
+ return void 0;
2542
+ }
2543
+ if (initializer.getKind() === SyntaxKind5.TrueKeyword) return true;
2544
+ if (initializer.getKind() === SyntaxKind5.FalseKeyword) return false;
2545
+ return void 0;
2546
+ }
2547
+ function getMethodOrFunctionProperty(definition, propertyName) {
2548
+ const property = definition.getProperty(propertyName);
2549
+ if (Node7.isMethodDeclaration(property)) {
2550
+ return property;
2551
+ }
2552
+ if (property && Node7.isPropertyAssignment(property)) {
2553
+ const initializer = property.getInitializer();
2554
+ if (initializer && (Node7.isArrowFunction(initializer) || Node7.isFunctionExpression(initializer))) {
2555
+ return initializer;
2556
+ }
2557
+ }
2558
+ return void 0;
2559
+ }
2560
+ function readIcons(definition) {
2561
+ const property = definition.getProperty("icons");
2562
+ if (!property || !Node7.isPropertyAssignment(property)) {
2563
+ return void 0;
2564
+ }
2565
+ const initializer = unwrapExpression(property.getInitializer());
2566
+ if (!initializer || !Node7.isArrayLiteralExpression(initializer)) {
2567
+ return void 0;
2568
+ }
2569
+ const icons = initializer.getElements().flatMap((element) => {
2570
+ const object = unwrapExpression(element);
2571
+ if (!object || !Node7.isObjectLiteralExpression(object)) {
2572
+ return [];
2573
+ }
2574
+ const src = getOptionalStringProperty2(object, "src");
2575
+ if (!src) {
2576
+ return [];
2577
+ }
2578
+ return [{
2579
+ src,
2580
+ mimeType: getOptionalStringProperty2(object, "mimeType"),
2581
+ sizes: readStringArrayProperty3(object, "sizes")
2582
+ }];
2583
+ });
2584
+ return icons.length ? icons : void 0;
2585
+ }
2586
+ function readObjectProperty4(definition, propertyName) {
2587
+ const property = definition.getProperty(propertyName);
2588
+ if (!property || !Node7.isPropertyAssignment(property)) {
2589
+ return void 0;
2590
+ }
2591
+ const initializer = unwrapExpression(property.getInitializer());
2592
+ return initializer && Node7.isObjectLiteralExpression(initializer) ? initializer : void 0;
2593
+ }
2594
+ function readStringArrayProperty3(definition, propertyName) {
2595
+ const property = definition.getProperty(propertyName);
2596
+ if (!property || !Node7.isPropertyAssignment(property)) {
2597
+ return void 0;
2598
+ }
2599
+ const initializer = unwrapExpression(property.getInitializer());
2600
+ if (!initializer || !Node7.isArrayLiteralExpression(initializer)) {
2601
+ return void 0;
2602
+ }
2603
+ return initializer.getElements().filter(Node7.isStringLiteral).map((element) => element.getLiteralText());
2604
+ }
2605
+
2606
+ // packages/compiler/src/resources.ts
2607
+ import { readdir as readdir7 } from "fs/promises";
2608
+ import path16 from "path";
2609
+ import {
2610
+ Node as Node8,
2611
+ SyntaxKind as SyntaxKind6
2612
+ } from "ts-morph";
2613
+ async function analyzeProjectResources(rootDir) {
2614
+ const resourceFiles = await findResourceFiles(path16.join(rootDir, "resources"));
2615
+ if (resourceFiles.length === 0) {
2616
+ return [];
2617
+ }
2618
+ const project = createProject(rootDir);
2619
+ return resourceFiles.map(
2620
+ (filePath) => analyzeResourceFile(project.addSourceFileAtPath(filePath), rootDir)
2621
+ );
2622
+ }
2623
+ function analyzeResourceFile(sourceFile, rootDir) {
2624
+ const definition = findResourceDefinition(sourceFile);
2625
+ const absoluteFile = sourceFile.getFilePath();
2626
+ const directory = path16.basename(path16.dirname(absoluteFile));
2627
+ const defaultUri = `sidecar://resources/${safePathSegment(directory)}`;
2628
+ const name = getRequiredStringProperty3(definition, "name", sourceFile);
2629
+ const uri = getOptionalStringProperty3(definition, "uri") ?? defaultUri;
2630
+ const descriptor = createResourceDescriptor({
2631
+ uri,
2632
+ name,
2633
+ title: getOptionalStringProperty3(definition, "title"),
2634
+ description: getOptionalStringProperty3(definition, "description"),
2635
+ mimeType: getOptionalStringProperty3(definition, "mimeType"),
2636
+ size: getOptionalNumberProperty(definition, "size"),
2637
+ icons: readIcons2(definition),
2638
+ annotations: readAnnotations2(definition)
2639
+ });
2640
+ if (!getMethodOrFunctionProperty2(definition, "read")) {
2641
+ throw new CompilerError(sourceFile, "resource({ ... }) must include a read method.");
2642
+ }
2643
+ return {
2644
+ sourceFile: path16.relative(rootDir, absoluteFile),
2645
+ directory,
2646
+ uri,
2647
+ name,
2648
+ title: descriptor.title,
2649
+ description: descriptor.description,
2650
+ mimeType: descriptor.mimeType,
2651
+ size: descriptor.size,
2652
+ annotations: descriptor.annotations,
2653
+ subscribe: readBooleanProperty4(definition, "subscribe"),
2654
+ descriptor
2655
+ };
2656
+ }
2657
+ async function findResourceFiles(resourcesDir) {
2658
+ if (!existsSyncSafe(resourcesDir)) {
2659
+ return [];
2660
+ }
2661
+ const entries = await readdir7(resourcesDir, { withFileTypes: true });
2662
+ const files = [];
2663
+ for (const entry of entries) {
2664
+ if (!entry.isDirectory()) {
2665
+ continue;
2666
+ }
2667
+ const filePath = path16.join(resourcesDir, entry.name, "resource.ts");
2668
+ if (existsSyncSafe(filePath)) {
2669
+ files.push(filePath);
2670
+ }
2671
+ }
2672
+ return files.sort((left, right) => left.localeCompare(right));
2673
+ }
2674
+ function findResourceDefinition(sourceFile) {
2675
+ const call = resolveDefaultExportCall(sourceFile, "resource");
2676
+ if (!call) {
2677
+ throw new CompilerError(
2678
+ sourceFile,
2679
+ "resource.ts must default-export resource({ ... }) or an identifier initialized with resource({ ... })."
2680
+ );
2681
+ }
2682
+ const definition = unwrapExpression(call.getArguments()[0]);
2683
+ if (!definition || !Node8.isObjectLiteralExpression(definition)) {
2684
+ throw new CompilerError(sourceFile, "resource(...) must receive one object literal.");
2685
+ }
2686
+ return definition;
2687
+ }
2688
+ function getRequiredStringProperty3(definition, propertyName, sourceFile) {
2689
+ const value = getOptionalStringProperty3(definition, propertyName);
2690
+ if (value === void 0 || !value.trim()) {
2691
+ throw new CompilerError(
2692
+ sourceFile,
2693
+ `resource({ ... }) must include a non-empty ${propertyName} string.`
2694
+ );
2695
+ }
2696
+ return value;
2697
+ }
2698
+ function getOptionalStringProperty3(definition, propertyName) {
2699
+ const property = definition.getProperty(propertyName);
2700
+ if (!property || !Node8.isPropertyAssignment(property)) {
2701
+ return void 0;
2702
+ }
2703
+ const initializer = unwrapExpression(property.getInitializer());
2704
+ return initializer && Node8.isStringLiteral(initializer) ? initializer.getLiteralText() : void 0;
2705
+ }
2706
+ function getOptionalNumberProperty(definition, propertyName) {
2707
+ const property = definition.getProperty(propertyName);
2708
+ if (!property || !Node8.isPropertyAssignment(property)) {
2709
+ return void 0;
2710
+ }
2711
+ const initializer = unwrapExpression(property.getInitializer());
2712
+ return initializer && Node8.isNumericLiteral(initializer) ? Number(initializer.getLiteralText()) : void 0;
2713
+ }
2714
+ function readBooleanProperty4(definition, propertyName) {
2715
+ const property = definition.getProperty(propertyName);
2716
+ if (!property || !Node8.isPropertyAssignment(property)) {
2717
+ return void 0;
2718
+ }
2719
+ const initializer = unwrapExpression(property.getInitializer());
2720
+ if (!initializer) {
2721
+ return void 0;
2722
+ }
2723
+ if (initializer.getKind() === SyntaxKind6.TrueKeyword) return true;
2724
+ if (initializer.getKind() === SyntaxKind6.FalseKeyword) return false;
2725
+ return void 0;
2726
+ }
2727
+ function getMethodOrFunctionProperty2(definition, propertyName) {
2728
+ const property = definition.getProperty(propertyName);
2729
+ if (Node8.isMethodDeclaration(property)) {
2730
+ return property;
2731
+ }
2732
+ if (property && Node8.isPropertyAssignment(property)) {
2733
+ const initializer = property.getInitializer();
2734
+ if (initializer && (Node8.isArrowFunction(initializer) || Node8.isFunctionExpression(initializer))) {
2735
+ return initializer;
2736
+ }
2737
+ }
2738
+ return void 0;
2739
+ }
2740
+ function readAnnotations2(definition) {
2741
+ const initializer = readObjectProperty5(definition, "annotations");
2742
+ if (!initializer) {
2743
+ return void 0;
2744
+ }
2745
+ const annotations = {};
2746
+ const audience = readStringArrayProperty4(initializer, "audience")?.filter((value) => value === "user" || value === "assistant");
2747
+ const priority = getOptionalNumberProperty(initializer, "priority");
2748
+ const lastModified = getOptionalStringProperty3(initializer, "lastModified");
2749
+ if (audience?.length) annotations.audience = audience;
2750
+ if (priority !== void 0) annotations.priority = priority;
2751
+ if (lastModified) annotations.lastModified = lastModified;
2752
+ return Object.keys(annotations).length ? annotations : void 0;
2753
+ }
2754
+ function readIcons2(definition) {
2755
+ const property = definition.getProperty("icons");
2756
+ if (!property || !Node8.isPropertyAssignment(property)) {
2757
+ return void 0;
2758
+ }
2759
+ const initializer = unwrapExpression(property.getInitializer());
2760
+ if (!initializer || !Node8.isArrayLiteralExpression(initializer)) {
2761
+ return void 0;
2762
+ }
2763
+ const icons = initializer.getElements().flatMap((element) => {
2764
+ const object = unwrapExpression(element);
2765
+ if (!object || !Node8.isObjectLiteralExpression(object)) {
2766
+ return [];
2767
+ }
2768
+ const src = getOptionalStringProperty3(object, "src");
2769
+ if (!src) {
2770
+ return [];
2771
+ }
2772
+ return [{
2773
+ src,
2774
+ mimeType: getOptionalStringProperty3(object, "mimeType"),
2775
+ sizes: readStringArrayProperty4(object, "sizes")
2776
+ }];
2777
+ });
2778
+ return icons.length ? icons : void 0;
2779
+ }
2780
+ function readObjectProperty5(definition, propertyName) {
2781
+ const property = definition.getProperty(propertyName);
2782
+ if (!property || !Node8.isPropertyAssignment(property)) {
2783
+ return void 0;
2784
+ }
2785
+ const initializer = unwrapExpression(property.getInitializer());
2786
+ return initializer && Node8.isObjectLiteralExpression(initializer) ? initializer : void 0;
2787
+ }
2788
+ function readStringArrayProperty4(definition, propertyName) {
2789
+ const property = definition.getProperty(propertyName);
2790
+ if (!property || !Node8.isPropertyAssignment(property)) {
2791
+ return void 0;
2792
+ }
2793
+ const initializer = unwrapExpression(property.getInitializer());
2794
+ if (!initializer || !Node8.isArrayLiteralExpression(initializer)) {
2795
+ return void 0;
2796
+ }
2797
+ return initializer.getElements().filter(Node8.isStringLiteral).map((element) => element.getLiteralText());
2798
+ }
2799
+
2800
+ // packages/compiler/src/build.ts
2801
+ async function buildProject(options) {
2802
+ const rootDir = path17.resolve(options.rootDir);
2803
+ const target = options.target ?? "mcp";
2804
+ const config = analyzeProjectConfig(rootDir);
2805
+ const tools = await analyzeProjectTools(rootDir, { target });
2806
+ const resources = await analyzeProjectResources(rootDir);
2807
+ const resourceTemplates = [];
2808
+ const prompts = await analyzeProjectPrompts(rootDir);
2809
+ const diagnostics = await collectProjectDiagnostics(rootDir, {
2810
+ tools,
2811
+ resources,
2812
+ prompts,
2813
+ config
2814
+ });
2815
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
2816
+ if (errors.length) {
2817
+ throw new Error(errors.map(formatDiagnostic).join("\n"));
2818
+ }
2819
+ const outDir = resolveInsideRoot(rootDir, options.outDir ?? "out/mcp");
2820
+ await buildWidgets(rootDir, outDir, tools);
2821
+ const manifest = {
2822
+ version: 1,
2823
+ target,
2824
+ rootDir: ".",
2825
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2826
+ config,
2827
+ tools,
2828
+ resources,
2829
+ resourceTemplates,
2830
+ prompts,
2831
+ diagnostics
2832
+ };
2833
+ await mkdir8(outDir, { recursive: true });
2834
+ await writeFile8(
2835
+ path17.join(outDir, "manifest.sidecar.json"),
2836
+ `${JSON.stringify(manifest, null, 2)}
2837
+ `
2838
+ );
2839
+ await writeFile8(path17.join(outDir, "README.md"), renderMcpReadme(manifest));
2840
+ await writeGeneratedTypes(rootDir, tools);
2841
+ if (options.plugins) {
2842
+ await buildPluginPackages(rootDir, path17.dirname(outDir), manifest);
2843
+ }
2844
+ return manifest;
2845
+ }
2846
+ function resolveInsideRoot(rootDir, output) {
2847
+ const resolved = path17.resolve(rootDir, output);
2848
+ const relative = path17.relative(rootDir, resolved);
2849
+ if (relative.startsWith("..") || path17.isAbsolute(relative)) {
2850
+ throw new Error(`Build output must stay inside the project root: ${output}`);
2851
+ }
2852
+ return resolved;
2853
+ }
2854
+ function renderMcpReadme(manifest) {
2855
+ const tools = manifest.tools.map((toolEntry) => `- \`${toolEntry.id}\`: ${toolEntry.description}`).join("\n");
2856
+ const resources = manifest.resources.map((resourceEntry) => `- \`${resourceEntry.uri}\`: ${resourceEntry.description ?? resourceEntry.name}`).join("\n");
2857
+ const prompts = manifest.prompts.map((promptEntry) => `- \`${promptEntry.name}\`: ${promptEntry.description ?? promptEntry.title}`).join("\n");
2858
+ return `# Sidecar MCP Build
2859
+
2860
+ Generated by Sidecar.
2861
+
2862
+ ## Tools
2863
+
2864
+ ${tools || "No tools detected."}
2865
+
2866
+ ## Resources
2867
+
2868
+ ${resources || "No resources detected."}
2869
+
2870
+ ## Prompts
2871
+
2872
+ ${prompts || "No prompts detected."}
2873
+
2874
+ ## Local HTTPS Testing
2875
+
2876
+ Run \`sidecar dev --tunnel\` from the project root to start the local MCP server on Streamable HTTP and print an HTTPS MCP URL that can be added to ChatGPT, Claude, or a Claude plugin install.
2877
+ `;
2878
+ }
2879
+ export {
2880
+ CompilerError,
2881
+ analyzeProjectConfig,
2882
+ analyzeProjectPrompts,
2883
+ analyzeProjectResources,
2884
+ analyzeProjectTools,
2885
+ analyzePromptFile,
2886
+ analyzeResourceFile,
2887
+ analyzeToolFile,
2888
+ buildProject,
2889
+ collectProjectDiagnostics,
2890
+ formatDiagnostic
2891
+ };
2892
+ //# sourceMappingURL=index.js.map