@axintai/compiler 0.2.1 → 0.3.1

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/cli/index.js CHANGED
@@ -1,41 +1,15 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/cli/index.ts
4
- import { Command } from "commander";
5
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
6
- import { resolve, dirname } from "path";
7
- import { fileURLToPath } from "url";
8
-
9
- // src/core/compiler.ts
10
- import { readFileSync } from "fs";
11
-
12
- // src/core/parser.ts
13
- import ts from "typescript";
14
-
15
- // src/core/types.ts
16
- var PARAM_TYPES = /* @__PURE__ */ new Set([
17
- "string",
18
- "int",
19
- "double",
20
- "float",
21
- "boolean",
22
- "date",
23
- "duration",
24
- "url"
25
- ]);
26
- var LEGACY_PARAM_ALIASES = {
27
- number: "int"
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
28
6
  };
29
- var SWIFT_TYPE_MAP = {
30
- string: "String",
31
- int: "Int",
32
- double: "Double",
33
- float: "Float",
34
- boolean: "Bool",
35
- date: "Date",
36
- duration: "Measurement<UnitDuration>",
37
- url: "URL"
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
38
10
  };
11
+
12
+ // src/core/types.ts
39
13
  function irTypeToSwift(type) {
40
14
  switch (type.kind) {
41
15
  case "primitive":
@@ -46,12 +20,46 @@ function irTypeToSwift(type) {
46
20
  return `${irTypeToSwift(type.innerType)}?`;
47
21
  case "entity":
48
22
  return type.entityName;
23
+ case "entityQuery":
24
+ return `${type.entityName}Query`;
25
+ case "dynamicOptions":
26
+ return `[DynamicOptionsResult<${irTypeToSwift(type.valueType)}>]`;
49
27
  case "enum":
50
28
  return type.name;
51
29
  }
52
30
  }
31
+ var PARAM_TYPES, LEGACY_PARAM_ALIASES, SWIFT_TYPE_MAP;
32
+ var init_types = __esm({
33
+ "src/core/types.ts"() {
34
+ "use strict";
35
+ PARAM_TYPES = /* @__PURE__ */ new Set([
36
+ "string",
37
+ "int",
38
+ "double",
39
+ "float",
40
+ "boolean",
41
+ "date",
42
+ "duration",
43
+ "url"
44
+ ]);
45
+ LEGACY_PARAM_ALIASES = {
46
+ number: "int"
47
+ };
48
+ SWIFT_TYPE_MAP = {
49
+ string: "String",
50
+ int: "Int",
51
+ double: "Double",
52
+ float: "Float",
53
+ boolean: "Bool",
54
+ date: "Date",
55
+ duration: "Measurement<UnitDuration>",
56
+ url: "URL"
57
+ };
58
+ }
59
+ });
53
60
 
54
61
  // src/core/parser.ts
62
+ import ts from "typescript";
55
63
  function parseIntentSource(source, filePath = "<stdin>") {
56
64
  const sourceFile = ts.createSourceFile(
57
65
  filePath,
@@ -61,6 +69,9 @@ function parseIntentSource(source, filePath = "<stdin>") {
61
69
  // setParentNodes
62
70
  ts.ScriptKind.TS
63
71
  );
72
+ const entities = findDefineEntityCalls(sourceFile).map(
73
+ (call) => parseEntityDefinition(call, filePath, sourceFile)
74
+ );
64
75
  const defineIntentCall = findDefineIntentCall(sourceFile);
65
76
  if (!defineIntentCall) {
66
77
  throw new ParserError(
@@ -123,6 +134,10 @@ function parseIntentSource(source, filePath = "<stdin>") {
123
134
  const entitlements = readStringArray(entitlementsNode);
124
135
  const infoPlistNode = props.get("infoPlistKeys");
125
136
  const infoPlistKeys = readStringRecord(infoPlistNode);
137
+ const donateOnPerformNode = props.get("donateOnPerform");
138
+ const donateOnPerform = readBooleanLiteral(donateOnPerformNode);
139
+ const customResultTypeNode = props.get("customResultType");
140
+ const customResultType = readStringLiteral(customResultTypeNode);
126
141
  return {
127
142
  name,
128
143
  title,
@@ -134,7 +149,10 @@ function parseIntentSource(source, filePath = "<stdin>") {
134
149
  sourceFile: filePath,
135
150
  entitlements: entitlements.length > 0 ? entitlements : void 0,
136
151
  infoPlistKeys: Object.keys(infoPlistKeys).length > 0 ? infoPlistKeys : void 0,
137
- isDiscoverable: isDiscoverable ?? void 0
152
+ isDiscoverable: isDiscoverable ?? void 0,
153
+ entities: entities.length > 0 ? entities : void 0,
154
+ donateOnPerform: donateOnPerform ?? void 0,
155
+ customResultType: customResultType ?? void 0
138
156
  };
139
157
  }
140
158
  function findDefineIntentCall(node) {
@@ -150,6 +168,18 @@ function findDefineIntentCall(node) {
150
168
  visit(node);
151
169
  return found;
152
170
  }
171
+ function findDefineEntityCalls(node) {
172
+ const found = [];
173
+ const visit = (n) => {
174
+ if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === "defineEntity") {
175
+ found.push(n);
176
+ return;
177
+ }
178
+ ts.forEachChild(n, visit);
179
+ };
180
+ visit(node);
181
+ return found;
182
+ }
153
183
  function propertyMap(obj) {
154
184
  const map = /* @__PURE__ */ new Map();
155
185
  for (const prop of obj.properties) {
@@ -203,6 +233,77 @@ function readStringRecord(node) {
203
233
  }
204
234
  return rec;
205
235
  }
236
+ function parseEntityDefinition(call, filePath, sourceFile) {
237
+ const arg = call.arguments[0];
238
+ if (!arg || !ts.isObjectLiteralExpression(arg)) {
239
+ throw new ParserError(
240
+ "AX015",
241
+ "defineEntity() must be called with an object literal",
242
+ filePath,
243
+ posOf(sourceFile, call),
244
+ "Pass an object: defineEntity({ name, display, properties, query })"
245
+ );
246
+ }
247
+ const props = propertyMap(arg);
248
+ const name = readStringLiteral(props.get("name"));
249
+ if (!name) {
250
+ throw new ParserError(
251
+ "AX016",
252
+ "Entity definition missing required field: name",
253
+ filePath,
254
+ posOf(sourceFile, arg),
255
+ 'Add a name field: name: "Task"'
256
+ );
257
+ }
258
+ const displayNode = props.get("display");
259
+ if (!displayNode || !ts.isObjectLiteralExpression(displayNode)) {
260
+ throw new ParserError(
261
+ "AX017",
262
+ "Entity definition missing required field: display",
263
+ filePath,
264
+ posOf(sourceFile, arg),
265
+ 'Add display field: display: { title: "name", subtitle: "status" }'
266
+ );
267
+ }
268
+ const displayProps = propertyMap(displayNode);
269
+ const displayRepresentation = {
270
+ title: readStringLiteral(displayProps.get("title")) || "name",
271
+ subtitle: readStringLiteral(displayProps.get("subtitle")) || void 0,
272
+ image: readStringLiteral(displayProps.get("image")) || void 0
273
+ };
274
+ const propertiesNode = props.get("properties");
275
+ const properties = propertiesNode ? extractParameters(propertiesNode, filePath, sourceFile) : [];
276
+ const queryTypeNode = props.get("query");
277
+ const queryTypeStr = readStringLiteral(queryTypeNode);
278
+ const queryType = validateQueryType(queryTypeStr, filePath, sourceFile, queryTypeNode);
279
+ return {
280
+ name,
281
+ displayRepresentation,
282
+ properties,
283
+ queryType
284
+ };
285
+ }
286
+ function validateQueryType(value, filePath, sourceFile, node) {
287
+ if (!value) {
288
+ throw new ParserError(
289
+ "AX018",
290
+ "Entity definition missing required field: query",
291
+ filePath,
292
+ node ? posOf(sourceFile, node) : void 0,
293
+ 'Add query field: query: "string" (or "all", "id", "property")'
294
+ );
295
+ }
296
+ const valid = ["all", "id", "string", "property"];
297
+ if (!valid.includes(value)) {
298
+ throw new ParserError(
299
+ "AX019",
300
+ `Invalid query type: "${value}". Must be one of: all, id, string, property`,
301
+ filePath,
302
+ node ? posOf(sourceFile, node) : void 0
303
+ );
304
+ }
305
+ return value;
306
+ }
206
307
  function extractParameters(node, filePath, sourceFile) {
207
308
  if (!ts.isObjectLiteralExpression(node)) {
208
309
  throw new ParserError(
@@ -218,20 +319,20 @@ function extractParameters(node, filePath, sourceFile) {
218
319
  if (!ts.isPropertyAssignment(prop)) continue;
219
320
  const paramName = propertyKeyName(prop.name);
220
321
  if (!paramName) continue;
221
- const { typeName, description, configObject } = extractParamCall(
322
+ const { typeName, description, configObject, callExpr } = extractParamCall(
222
323
  prop.initializer,
223
324
  filePath,
224
325
  sourceFile
225
326
  );
226
- const resolvedType = resolveParamType(typeName, filePath, sourceFile, prop);
327
+ const resolvedType = resolveParamType(typeName, filePath, sourceFile, prop, callExpr);
227
328
  const isOptional = configObject ? readBooleanLiteral(configObject.get("required")) === false : false;
228
329
  const defaultExpr = configObject?.get("default");
229
330
  const defaultValue = defaultExpr ? evaluateLiteral(defaultExpr) : void 0;
230
331
  const titleFromConfig = configObject ? readStringLiteral(configObject.get("title")) : null;
231
332
  const irType = isOptional ? {
232
333
  kind: "optional",
233
- innerType: { kind: "primitive", value: resolvedType }
234
- } : { kind: "primitive", value: resolvedType };
334
+ innerType: resolvedType
335
+ } : resolvedType;
235
336
  params.push({
236
337
  name: paramName,
237
338
  type: irType,
@@ -263,34 +364,98 @@ function extractParamCall(expr, filePath, sourceFile) {
263
364
  );
264
365
  }
265
366
  const typeName = expr.expression.name.text;
266
- const descriptionArg = expr.arguments[0];
267
- const configArg = expr.arguments[1];
367
+ let descriptionArg;
368
+ let configArg;
369
+ if (typeName === "entity" && expr.arguments.length >= 2) {
370
+ descriptionArg = expr.arguments[1];
371
+ configArg = expr.arguments[2];
372
+ } else if (typeName === "dynamicOptions" && expr.arguments.length >= 3) {
373
+ descriptionArg = expr.arguments[2];
374
+ configArg = expr.arguments[3];
375
+ } else {
376
+ descriptionArg = expr.arguments[0];
377
+ configArg = expr.arguments[1];
378
+ }
268
379
  const description = descriptionArg ? readStringLiteral(descriptionArg) : null;
269
380
  if (description === null) {
270
381
  throw new ParserError(
271
382
  "AX008",
272
- `param.${typeName}() requires a string description as the first argument`,
383
+ `param.${typeName}() requires a string description`,
273
384
  filePath,
274
385
  posOf(sourceFile, expr),
275
386
  `Example: param.${typeName}("Human-readable description")`
276
387
  );
277
388
  }
278
389
  const configObject = configArg && ts.isObjectLiteralExpression(configArg) ? propertyMap(configArg) : null;
279
- return { typeName, description, configObject };
390
+ return { typeName, description, configObject, callExpr: expr };
280
391
  }
281
- function resolveParamType(typeName, filePath, sourceFile, node) {
392
+ function resolveParamType(typeName, filePath, sourceFile, node, callExpr) {
282
393
  if (PARAM_TYPES.has(typeName)) {
283
- return typeName;
394
+ return { kind: "primitive", value: typeName };
284
395
  }
285
396
  if (typeName in LEGACY_PARAM_ALIASES) {
286
- return LEGACY_PARAM_ALIASES[typeName];
397
+ return {
398
+ kind: "primitive",
399
+ value: LEGACY_PARAM_ALIASES[typeName]
400
+ };
401
+ }
402
+ if (typeName === "entity") {
403
+ if (!callExpr || callExpr.arguments.length === 0) {
404
+ throw new ParserError(
405
+ "AX020",
406
+ "param.entity() requires the entity name as the first argument",
407
+ filePath,
408
+ posOf(sourceFile, node),
409
+ 'Example: param.entity("Task", "Reference an entity")'
410
+ );
411
+ }
412
+ const entityName = readStringLiteral(callExpr.arguments[0]);
413
+ if (!entityName) {
414
+ throw new ParserError(
415
+ "AX021",
416
+ "param.entity() requires a string entity name",
417
+ filePath,
418
+ posOf(sourceFile, node)
419
+ );
420
+ }
421
+ return {
422
+ kind: "entity",
423
+ entityName,
424
+ properties: []
425
+ };
426
+ }
427
+ if (typeName === "dynamicOptions") {
428
+ if (!callExpr || callExpr.arguments.length < 2) {
429
+ throw new ParserError(
430
+ "AX022",
431
+ "param.dynamicOptions() requires (providerName, paramType)",
432
+ filePath,
433
+ posOf(sourceFile, node),
434
+ 'Example: param.dynamicOptions("PlaylistProvider", param.string(...))'
435
+ );
436
+ }
437
+ const providerName = readStringLiteral(callExpr.arguments[0]);
438
+ if (!providerName) {
439
+ throw new ParserError(
440
+ "AX023",
441
+ "param.dynamicOptions() provider name must be a string",
442
+ filePath,
443
+ posOf(sourceFile, node)
444
+ );
445
+ }
446
+ const valueType = { kind: "primitive", value: "string" };
447
+ return {
448
+ kind: "dynamicOptions",
449
+ valueType,
450
+ providerName
451
+ };
287
452
  }
288
453
  throw new ParserError(
289
454
  "AX005",
290
455
  `Unknown param type: param.${typeName}`,
291
456
  filePath,
292
457
  posOf(sourceFile, node),
293
- `Supported types: ${[...PARAM_TYPES].join(", ")}`
458
+ `Supported types: ${[...PARAM_TYPES].join(", ")}, entity, dynamicOptions`
294
459
  );
295
460
  }
296
461
  function evaluateLiteral(node) {
@@ -364,33 +529,40 @@ function posOf(sourceFile, node) {
364
529
  return void 0;
365
530
  }
366
531
  }
367
- var ParserError = class extends Error {
368
- constructor(code, message, file, line, suggestion) {
369
- super(message);
370
- this.code = code;
371
- this.file = file;
372
- this.line = line;
373
- this.suggestion = suggestion;
374
- this.name = "ParserError";
375
- }
376
- code;
377
- file;
378
- line;
379
- suggestion;
380
- format() {
381
- let output = `
532
+ var ParserError;
533
+ var init_parser = __esm({
534
+ "src/core/parser.ts"() {
535
+ "use strict";
536
+ init_types();
537
+ ParserError = class extends Error {
538
+ constructor(code, message, file, line, suggestion) {
539
+ super(message);
540
+ this.code = code;
541
+ this.file = file;
542
+ this.line = line;
543
+ this.suggestion = suggestion;
544
+ this.name = "ParserError";
545
+ }
546
+ code;
547
+ file;
548
+ line;
549
+ suggestion;
550
+ format() {
551
+ let output = `
382
552
  error[${this.code}]: ${this.message}
383
553
  `;
384
- if (this.file) output += ` --> ${this.file}`;
385
- if (this.line) output += `:${this.line}`;
386
- output += "\n";
387
- if (this.suggestion) {
388
- output += ` = help: ${this.suggestion}
554
+ if (this.file) output += ` --> ${this.file}`;
555
+ if (this.line) output += `:${this.line}`;
556
+ output += "\n";
557
+ if (this.suggestion) {
558
+ output += ` = help: ${this.suggestion}
389
559
  `;
390
- }
391
- return output;
560
+ }
561
+ return output;
562
+ }
563
+ };
392
564
  }
393
- };
565
+ });
394
566
 
395
567
  // src/core/generator.ts
396
568
  function escapeSwiftString(s) {
@@ -408,6 +580,14 @@ function generateSwift(intent) {
408
580
  lines.push(`import AppIntents`);
409
581
  lines.push(`import Foundation`);
410
582
  lines.push(``);
583
+ if (intent.entities && intent.entities.length > 0) {
584
+ for (const entity of intent.entities) {
585
+ lines.push(generateEntity(entity));
586
+ lines.push(``);
587
+ lines.push(generateEntityQuery(entity));
588
+ lines.push(``);
589
+ }
590
+ }
411
591
  lines.push(`struct ${intent.name}Intent: AppIntent {`);
412
592
  lines.push(
413
593
  ` static let title: LocalizedStringResource = "${escapeSwiftString(intent.title)}"`
@@ -425,17 +605,101 @@ function generateSwift(intent) {
425
605
  if (intent.parameters.length > 0) {
426
606
  lines.push(``);
427
607
  }
428
- const returnTypeSignature = generateReturnSignature(intent.returnType);
608
+ const returnTypeSignature = generateReturnSignature(
609
+ intent.returnType,
610
+ intent.customResultType
611
+ );
429
612
  lines.push(` func perform() async throws -> ${returnTypeSignature} {`);
430
613
  lines.push(` // TODO: Implement your intent logic here.`);
431
614
  if (intent.parameters.length > 0) {
432
615
  const paramList = intent.parameters.map((p) => `\\(${p.name})`).join(", ");
433
616
  lines.push(` // Parameters available: ${paramList}`);
434
617
  }
435
- lines.push(generatePerformReturn(intent.returnType));
618
+ if (intent.donateOnPerform) {
619
+ lines.push(` `);
620
+ lines.push(` // Donate this intent to Siri and Spotlight`);
621
+ lines.push(` try? await IntentDonationManager.shared.donate(intent: self)`);
622
+ }
623
+ lines.push(generatePerformReturn(intent.returnType, intent.customResultType));
624
+ lines.push(` }`);
625
+ lines.push(`}`);
626
+ lines.push(``);
627
+ return lines.join("\n");
628
+ }
629
+ function generateEntity(entity) {
630
+ const lines = [];
631
+ const propertyNames = new Set(entity.properties.map((p) => p.name));
632
+ lines.push(`struct ${entity.name}: AppEntity {`);
633
+ lines.push(` static var defaultQuery = ${entity.name}Query()`);
634
+ lines.push(``);
635
+ const hasId = propertyNames.has("id");
636
+ if (!hasId) {
637
+ lines.push(` var id: String`);
638
+ }
639
+ for (const prop of entity.properties) {
640
+ const swiftType = irTypeToSwift(prop.type);
641
+ lines.push(` var ${prop.name}: ${swiftType}`);
642
+ }
643
+ lines.push(``);
644
+ lines.push(
645
+ ` static let typeDisplayRepresentation: TypeDisplayRepresentation = TypeDisplayRepresentation(`
646
+ );
647
+ lines.push(
648
+ ` name: LocalizedStringResource("${escapeSwiftString(entity.name)}")`
649
+ );
650
+ lines.push(` )`);
651
+ lines.push(``);
652
+ lines.push(` var displayRepresentation: DisplayRepresentation {`);
653
+ lines.push(` DisplayRepresentation(`);
654
+ lines.push(
655
+ ` title: "\\(${entity.displayRepresentation.title})"${entity.displayRepresentation.subtitle || entity.displayRepresentation.image ? "," : ""}`
656
+ );
657
+ if (entity.displayRepresentation.subtitle) {
658
+ const hasImage = !!entity.displayRepresentation.image;
659
+ lines.push(
660
+ ` subtitle: "\\(${entity.displayRepresentation.subtitle})"${hasImage ? "," : ""}`
661
+ );
662
+ }
663
+ if (entity.displayRepresentation.image) {
664
+ lines.push(
665
+ ` image: .init(systemName: "${escapeSwiftString(entity.displayRepresentation.image)}")`
666
+ );
667
+ }
668
+ lines.push(` )`);
436
669
  lines.push(` }`);
437
670
  lines.push(`}`);
671
+ return lines.join("\n");
672
+ }
673
+ function generateEntityQuery(entity) {
674
+ const lines = [];
675
+ const queryType = entity.queryType;
676
+ lines.push(`struct ${entity.name}Query: EntityQuery {`);
677
+ lines.push(
678
+ ` func entities(for identifiers: [${entity.name}.ID]) async throws -> [${entity.name}] {`
679
+ );
680
+ lines.push(` // TODO: Fetch entities by IDs`);
681
+ lines.push(` return []`);
682
+ lines.push(` }`);
438
683
  lines.push(``);
684
+ if (queryType === "all") {
685
+ lines.push(` func allEntities() async throws -> [${entity.name}] {`);
686
+ lines.push(` // TODO: Return all entities`);
687
+ lines.push(` return []`);
688
+ lines.push(` }`);
689
+ } else if (queryType === "id") {
690
+ lines.push(` // ID-based query is provided by the entities(for:) method above`);
691
+ } else if (queryType === "string") {
692
+ lines.push(
693
+ ` func entities(matching string: String) async throws -> [${entity.name}] {`
694
+ );
695
+ lines.push(` // TODO: Search entities by string`);
696
+ lines.push(` return []`);
697
+ lines.push(` }`);
698
+ } else if (queryType === "property") {
699
+ lines.push(` // Property-based query: implement using EntityPropertyQuery`);
700
+ lines.push(` // Example: property.where { \\$0.status == "active" }`);
701
+ }
702
+ lines.push(`}`);
439
703
  return lines.join("\n");
440
704
  }
441
705
  function generateInfoPlistFragment(intent) {
@@ -443,9 +707,7 @@ function generateInfoPlistFragment(intent) {
443
707
  if (!keys || Object.keys(keys).length === 0) return void 0;
444
708
  const lines = [];
445
709
  lines.push(`<?xml version="1.0" encoding="UTF-8"?>`);
446
- lines.push(
447
- `<!-- Info.plist fragment generated by Axint for ${intent.name}Intent -->`
448
- );
710
+ lines.push(`<!-- Info.plist fragment generated by Axint for ${intent.name}Intent -->`);
449
711
  lines.push(`<!-- Merge these keys into your app's Info.plist. -->`);
450
712
  lines.push(`<plist version="1.0">`);
451
713
  lines.push(`<dict>`);
@@ -502,7 +764,10 @@ function generateParameter(param) {
502
764
  lines.push(``);
503
765
  return lines.join("\n");
504
766
  }
505
- function generateReturnSignature(type) {
767
+ function generateReturnSignature(type, customResultType) {
768
+ if (customResultType) {
769
+ return customResultType;
770
+ }
506
771
  if (type.kind === "primitive") {
507
772
  const swift = irTypeToSwift(type);
508
773
  return `some IntentResult & ReturnsValue<${swift}>`;
@@ -513,8 +778,11 @@ function generateReturnSignature(type) {
513
778
  }
514
779
  return `some IntentResult`;
515
780
  }
516
- function generatePerformReturn(type) {
781
+ function generatePerformReturn(type, customResultType) {
517
782
  const indent = " ";
783
+ if (customResultType) {
784
+ return `${indent}// TODO: Return a ${customResultType} instance`;
785
+ }
518
786
  if (type.kind === "primitive") {
519
787
  return `${indent}return .result(value: ${defaultLiteralFor(type.value)})`;
520
788
  }
@@ -554,10 +822,14 @@ function formatSwiftDefault(value, _type) {
554
822
  if (typeof value === "boolean") return value ? "true" : "false";
555
823
  return `"${escapeSwiftString(String(value))}"`;
556
824
  }
825
+ var init_generator = __esm({
826
+ "src/core/generator.ts"() {
827
+ "use strict";
828
+ init_types();
829
+ }
830
+ });
557
831
 
558
832
  // src/core/validator.ts
559
- var MAX_PARAMETERS = 10;
560
- var MAX_TITLE_LENGTH = 60;
561
833
  function validateIntent(intent) {
562
834
  const diagnostics = [];
563
835
  if (!intent.name || !/^[A-Z][a-zA-Z0-9]*$/.test(intent.name)) {
@@ -659,6 +931,54 @@ function validateIntent(intent) {
659
931
  });
660
932
  }
661
933
  }
934
+ if (intent.entities) {
935
+ for (const entity of intent.entities) {
936
+ diagnostics.push(...validateEntity(entity, intent.sourceFile));
937
+ }
938
+ }
939
+ return diagnostics;
940
+ }
941
+ function validateEntity(entity, sourceFile) {
942
+ const diagnostics = [];
943
+ if (!entity.name || !/^[A-Z][a-zA-Z0-9]*$/.test(entity.name)) {
944
+ diagnostics.push({
945
+ code: "AX110",
946
+ severity: "error",
947
+ message: `Entity name "${entity.name}" must be PascalCase (e.g., "Task", "Playlist")`,
948
+ file: sourceFile,
949
+ suggestion: `Rename to "${toPascalCase(entity.name)}"`
950
+ });
951
+ }
952
+ if (entity.properties.length === 0) {
953
+ diagnostics.push({
954
+ code: "AX111",
955
+ severity: "error",
956
+ message: `Entity "${entity.name}" must have at least one property`,
957
+ file: sourceFile,
958
+ suggestion: "Add properties to define the entity's structure"
959
+ });
960
+ }
961
+ const titleProp = entity.displayRepresentation.title;
962
+ const propertyNames = new Set(entity.properties.map((p) => p.name));
963
+ if (titleProp && !propertyNames.has(titleProp)) {
964
+ diagnostics.push({
965
+ code: "AX112",
966
+ severity: "warning",
967
+ message: `Display title "${titleProp}" does not reference an existing property`,
968
+ file: sourceFile,
969
+ suggestion: `Available properties: ${[...propertyNames].join(", ")}`
970
+ });
971
+ }
972
+ const validQueryTypes = ["all", "id", "string", "property"];
973
+ if (!validQueryTypes.includes(entity.queryType)) {
974
+ diagnostics.push({
975
+ code: "AX113",
976
+ severity: "error",
977
+ message: `Entity query type "${entity.queryType}" is not valid`,
978
+ file: sourceFile,
979
+ suggestion: `Use one of: ${validQueryTypes.join(", ")}`
980
+ });
981
+ }
662
982
  return diagnostics;
663
983
  }
664
984
  function validateSwiftSource(swift) {
@@ -690,8 +1010,17 @@ function toPascalCase(s) {
690
1010
  if (!s) return "UnnamedIntent";
691
1011
  return s.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
692
1012
  }
1013
+ var MAX_PARAMETERS, MAX_TITLE_LENGTH;
1014
+ var init_validator = __esm({
1015
+ "src/core/validator.ts"() {
1016
+ "use strict";
1017
+ MAX_PARAMETERS = 10;
1018
+ MAX_TITLE_LENGTH = 60;
1019
+ }
1020
+ });
693
1021
 
694
1022
  // src/core/compiler.ts
1023
+ import { readFileSync } from "fs";
695
1024
  function compileFile(filePath, options = {}) {
696
1025
  let source;
697
1026
  try {
@@ -712,7 +1041,6 @@ function compileFile(filePath, options = {}) {
712
1041
  return compileSource(source, filePath, options);
713
1042
  }
714
1043
  function compileSource(source, fileName = "<stdin>", options = {}) {
715
- const diagnostics = [];
716
1044
  let ir;
717
1045
  try {
718
1046
  ir = parseIntentSource(source, fileName);
@@ -734,6 +1062,10 @@ function compileSource(source, fileName = "<stdin>", options = {}) {
734
1062
  }
735
1063
  throw err;
736
1064
  }
1065
+ return compileFromIR(ir, options);
1066
+ }
1067
+ function compileFromIR(ir, options = {}) {
1068
+ const diagnostics = [];
737
1069
  const irDiagnostics = validateIntent(ir);
738
1070
  diagnostics.push(...irDiagnostics);
739
1071
  if (irDiagnostics.some((d) => d.severity === "error")) {
@@ -764,105 +1096,2233 @@ function compileSource(source, fileName = "<stdin>", options = {}) {
764
1096
  diagnostics
765
1097
  };
766
1098
  }
767
-
768
- // src/cli/index.ts
769
- var __dirname = dirname(fileURLToPath(import.meta.url));
770
- var pkg = JSON.parse(readFileSync2(resolve(__dirname, "../../package.json"), "utf-8"));
771
- var VERSION = pkg.version;
772
- var program = new Command();
773
- program.name("axint").description(
774
- "The open-source compiler that transforms AI agent definitions into native Apple App Intents."
775
- ).version(VERSION);
776
- program.command("compile").description("Compile a TypeScript intent definition into Swift").argument("<file>", "Path to the TypeScript intent definition").option("-o, --out <dir>", "Output directory for generated Swift", ".").option("--no-validate", "Skip validation of generated Swift").option("--stdout", "Print generated Swift to stdout instead of writing a file").option("--json", "Output result as JSON (machine-readable)").action(
777
- (file, options) => {
778
- const filePath = resolve(file);
779
- try {
780
- const result = compileFile(filePath, {
781
- outDir: options.out,
782
- validate: options.validate
783
- });
784
- if (options.json) {
785
- console.log(
786
- JSON.stringify(
787
- {
788
- success: result.success,
789
- swift: result.output?.swiftCode ?? null,
790
- outputPath: result.output?.outputPath ?? null,
791
- diagnostics: result.diagnostics.map((d) => ({
792
- code: d.code,
793
- severity: d.severity,
794
- message: d.message,
795
- file: d.file,
796
- line: d.line,
797
- suggestion: d.suggestion
798
- }))
799
- },
800
- null,
801
- 2
802
- )
803
- );
804
- if (!result.success) process.exit(1);
805
- return;
806
- }
807
- for (const d of result.diagnostics) {
808
- const prefix = d.severity === "error" ? "\x1B[31merror\x1B[0m" : d.severity === "warning" ? "\x1B[33mwarning\x1B[0m" : "\x1B[36minfo\x1B[0m";
809
- console.error(` ${prefix}[${d.code}]: ${d.message}`);
810
- if (d.file) console.error(` --> ${d.file}${d.line ? `:${d.line}` : ""}`);
811
- if (d.suggestion) console.error(` = help: ${d.suggestion}`);
812
- console.error();
813
- }
814
- if (!result.success || !result.output) {
815
- console.error(
816
- `\x1B[31mCompilation failed with ${result.diagnostics.filter((d) => d.severity === "error").length} error(s)\x1B[0m`
817
- );
818
- process.exit(1);
819
- }
820
- if (options.stdout) {
821
- console.log(result.output.swiftCode);
822
- } else {
823
- const outPath = resolve(result.output.outputPath);
824
- mkdirSync(dirname(outPath), { recursive: true });
825
- writeFileSync(outPath, result.output.swiftCode, "utf-8");
826
- console.log(`\x1B[32m\u2713\x1B[0m Compiled ${result.output.ir.name} \u2192 ${outPath}`);
827
- }
828
- const warnings = result.diagnostics.filter(
829
- (d) => d.severity === "warning"
830
- ).length;
831
- if (warnings > 0) {
832
- console.log(` ${warnings} warning(s)`);
833
- }
834
- } catch (err) {
835
- if (err && typeof err === "object" && "format" in err && typeof err.format === "function") {
836
- console.error(err.format());
837
- } else {
838
- console.error(`\x1B[31merror:\x1B[0m ${err}`);
839
- }
840
- process.exit(1);
841
- }
842
- }
843
- );
844
- program.command("validate").description("Validate a TypeScript intent definition without generating output").argument("<file>", "Path to the TypeScript intent definition").action((file) => {
845
- const filePath = resolve(file);
846
- try {
847
- const result = compileFile(filePath, { validate: true });
848
- for (const d of result.diagnostics) {
849
- const prefix = d.severity === "error" ? "\x1B[31merror\x1B[0m" : d.severity === "warning" ? "\x1B[33mwarning\x1B[0m" : "\x1B[36minfo\x1B[0m";
850
- console.error(` ${prefix}[${d.code}]: ${d.message}`);
851
- if (d.suggestion) console.error(` = help: ${d.suggestion}`);
852
- }
853
- if (result.success) {
854
- console.log(`\x1B[32m\u2713\x1B[0m Valid intent definition`);
855
- } else {
856
- process.exit(1);
1099
+ function irFromJSON(data) {
1100
+ const parameters = (data.parameters ?? []).map(
1101
+ (p) => {
1102
+ const param = p;
1103
+ return {
1104
+ name: param.name,
1105
+ type: normalizeIRType(param.type),
1106
+ title: param.title ?? param.description ?? "",
1107
+ description: param.description ?? "",
1108
+ isOptional: param.optional ?? param.isOptional ?? false,
1109
+ defaultValue: param.default ?? param.defaultValue
1110
+ };
857
1111
  }
858
- } catch (err) {
859
- if (err && typeof err === "object" && "format" in err && typeof err.format === "function") {
860
- console.error(err.format());
861
- } else {
862
- console.error(`\x1B[31merror:\x1B[0m ${err}`);
1112
+ );
1113
+ return {
1114
+ name: data.name,
1115
+ title: data.title,
1116
+ description: data.description,
1117
+ domain: data.domain,
1118
+ parameters,
1119
+ returnType: data.returnType ? normalizeIRType(data.returnType) : { kind: "primitive", value: "string" },
1120
+ sourceFile: data.sourceFile ?? void 0,
1121
+ entitlements: data.entitlements ?? void 0,
1122
+ infoPlistKeys: data.infoPlistKeys ?? void 0,
1123
+ isDiscoverable: data.isDiscoverable ?? true
1124
+ };
1125
+ }
1126
+ function normalizeIRType(type) {
1127
+ if (typeof type === "string") {
1128
+ const normalized = type === "number" ? "int" : type;
1129
+ if (VALID_PRIMITIVES.has(normalized)) {
1130
+ return { kind: "primitive", value: normalized };
863
1131
  }
864
- process.exit(1);
1132
+ return { kind: "primitive", value: "string" };
1133
+ }
1134
+ if (type && typeof type === "object") {
1135
+ const t = type;
1136
+ if (t.kind === "primitive") return type;
1137
+ if (t.kind === "array")
1138
+ return { kind: "array", elementType: normalizeIRType(t.elementType) };
1139
+ if (t.kind === "optional")
1140
+ return { kind: "optional", innerType: normalizeIRType(t.innerType) };
1141
+ if (t.kind === "entity") return type;
1142
+ if (t.kind === "enum") return type;
1143
+ }
1144
+ return { kind: "primitive", value: "string" };
1145
+ }
1146
+ var VALID_PRIMITIVES;
1147
+ var init_compiler = __esm({
1148
+ "src/core/compiler.ts"() {
1149
+ "use strict";
1150
+ init_parser();
1151
+ init_generator();
1152
+ init_validator();
1153
+ VALID_PRIMITIVES = /* @__PURE__ */ new Set([
1154
+ "string",
1155
+ "int",
1156
+ "double",
1157
+ "float",
1158
+ "boolean",
1159
+ "date",
1160
+ "duration",
1161
+ "url"
1162
+ ]);
865
1163
  }
866
1164
  });
867
- program.parse();
1165
+
1166
+ // src/templates/index.ts
1167
+ function getTemplate(id) {
1168
+ return TEMPLATES.find((t) => t.id === id);
1169
+ }
1170
+ function listTemplates(category) {
1171
+ if (category) {
1172
+ return TEMPLATES.filter((t) => t.category === category);
1173
+ }
1174
+ return TEMPLATES;
1175
+ }
1176
+ var sendMessage, createEvent, bookRide, getDirections, playTrack, createNote, logExpense, logWorkout, setThermostat, placeOrder, searchTasks, dynamicPlaylist, TEMPLATES;
1177
+ var init_templates = __esm({
1178
+ "src/templates/index.ts"() {
1179
+ "use strict";
1180
+ sendMessage = {
1181
+ id: "send-message",
1182
+ name: "send-message",
1183
+ title: "Send Message",
1184
+ domain: "messaging",
1185
+ category: "messaging",
1186
+ description: "Send a text message to a contact.",
1187
+ source: `import { defineIntent, param } from "@axintai/compiler";
1188
+
1189
+ export default defineIntent({
1190
+ name: "SendMessage",
1191
+ title: "Send Message",
1192
+ description: "Sends a message to a specified contact.",
1193
+ domain: "messaging",
1194
+ params: {
1195
+ recipient: param.string("Who to send the message to"),
1196
+ body: param.string("The message content"),
1197
+ },
1198
+ perform: async ({ recipient, body }) => {
1199
+ // TODO: Integrate with your messaging backend
1200
+ return { sent: true };
1201
+ },
1202
+ });
1203
+ `
1204
+ };
1205
+ createEvent = {
1206
+ id: "create-event",
1207
+ name: "create-event",
1208
+ title: "Create Calendar Event",
1209
+ domain: "productivity",
1210
+ category: "productivity",
1211
+ description: "Create a calendar event with a title, date, and duration.",
1212
+ source: `import { defineIntent, param } from "@axintai/compiler";
1213
+
1214
+ export default defineIntent({
1215
+ name: "CreateEvent",
1216
+ title: "Create Calendar Event",
1217
+ description: "Creates a new event in the user's calendar.",
1218
+ domain: "productivity",
1219
+ entitlements: ["com.apple.developer.siri"],
1220
+ infoPlistKeys: {
1221
+ NSCalendarsUsageDescription: "Access to your calendar to create events.",
1222
+ },
1223
+ params: {
1224
+ title: param.string("Event title"),
1225
+ date: param.date("Event date"),
1226
+ durationMinutes: param.int("Duration in minutes", { default: 30 }),
1227
+ allDay: param.boolean("All-day event", { required: false }),
1228
+ },
1229
+ perform: async ({ title, date }) => {
1230
+ return { eventId: "evt_placeholder" };
1231
+ },
1232
+ });
1233
+ `
1234
+ };
1235
+ bookRide = {
1236
+ id: "book-ride",
1237
+ name: "book-ride",
1238
+ title: "Book a Ride",
1239
+ domain: "navigation",
1240
+ category: "navigation",
1241
+ description: "Request a ride from a pickup location to a destination.",
1242
+ source: `import { defineIntent, param } from "@axintai/compiler";
1243
+
1244
+ export default defineIntent({
1245
+ name: "BookRide",
1246
+ title: "Book a Ride",
1247
+ description: "Requests a ride from a pickup location to a destination.",
1248
+ domain: "navigation",
1249
+ params: {
1250
+ pickup: param.string("Pickup location"),
1251
+ destination: param.string("Destination address"),
1252
+ passengers: param.int("Number of passengers", { default: 1 }),
1253
+ },
1254
+ perform: async ({ pickup, destination }) => {
1255
+ return { rideId: "ride_placeholder", eta: 300 };
1256
+ },
1257
+ });
1258
+ `
1259
+ };
1260
+ getDirections = {
1261
+ id: "get-directions",
1262
+ name: "get-directions",
1263
+ title: "Get Directions",
1264
+ domain: "navigation",
1265
+ category: "navigation",
1266
+ description: "Get turn-by-turn directions to a destination.",
1267
+ source: `import { defineIntent, param } from "@axintai/compiler";
1268
+
1269
+ export default defineIntent({
1270
+ name: "GetDirections",
1271
+ title: "Get Directions",
1272
+ description: "Returns turn-by-turn directions to a destination.",
1273
+ domain: "navigation",
1274
+ params: {
1275
+ destination: param.string("Where to navigate to"),
1276
+ mode: param.string("Travel mode (driving, walking, transit)", {
1277
+ default: "driving",
1278
+ }),
1279
+ },
1280
+ perform: async ({ destination }) => {
1281
+ return { routeId: "route_placeholder" };
1282
+ },
1283
+ });
1284
+ `
1285
+ };
1286
+ playTrack = {
1287
+ id: "play-track",
1288
+ name: "play-track",
1289
+ title: "Play Track",
1290
+ domain: "media",
1291
+ category: "media",
1292
+ description: "Play a specific track or song.",
1293
+ source: `import { defineIntent, param } from "@axintai/compiler";
1294
+
1295
+ export default defineIntent({
1296
+ name: "PlayTrack",
1297
+ title: "Play Track",
1298
+ description: "Plays a specific track by title and artist.",
1299
+ domain: "media",
1300
+ params: {
1301
+ track: param.string("Track title"),
1302
+ artist: param.string("Artist name", { required: false }),
1303
+ shuffle: param.boolean("Shuffle mode", { required: false }),
1304
+ },
1305
+ perform: async ({ track }) => {
1306
+ return { playing: true };
1307
+ },
1308
+ });
1309
+ `
1310
+ };
1311
+ createNote = {
1312
+ id: "create-note",
1313
+ name: "create-note",
1314
+ title: "Create Note",
1315
+ domain: "productivity",
1316
+ category: "productivity",
1317
+ description: "Create a new note with a title and body.",
1318
+ source: `import { defineIntent, param } from "@axintai/compiler";
1319
+
1320
+ export default defineIntent({
1321
+ name: "CreateNote",
1322
+ title: "Create Note",
1323
+ description: "Creates a new note with a title and body.",
1324
+ domain: "productivity",
1325
+ params: {
1326
+ title: param.string("Note title"),
1327
+ body: param.string("Note body"),
1328
+ pinned: param.boolean("Pin the note", { required: false }),
1329
+ },
1330
+ perform: async ({ title, body }) => {
1331
+ return { noteId: "note_placeholder" };
1332
+ },
1333
+ });
1334
+ `
1335
+ };
1336
+ logExpense = {
1337
+ id: "log-expense",
1338
+ name: "log-expense",
1339
+ title: "Log Expense",
1340
+ domain: "finance",
1341
+ category: "finance",
1342
+ description: "Log a financial expense with amount, category, and note.",
1343
+ source: `import { defineIntent, param } from "@axintai/compiler";
1344
+
1345
+ export default defineIntent({
1346
+ name: "LogExpense",
1347
+ title: "Log Expense",
1348
+ description: "Logs a financial expense with amount, category, and note.",
1349
+ domain: "finance",
1350
+ params: {
1351
+ amount: param.double("Expense amount"),
1352
+ currency: param.string("ISO currency code (e.g., USD)", {
1353
+ default: "USD",
1354
+ }),
1355
+ category: param.string("Expense category"),
1356
+ note: param.string("Optional note", { required: false }),
1357
+ },
1358
+ perform: async ({ amount, category }) => {
1359
+ return { expenseId: "exp_placeholder" };
1360
+ },
1361
+ });
1362
+ `
1363
+ };
1364
+ logWorkout = {
1365
+ id: "log-workout",
1366
+ name: "log-workout",
1367
+ title: "Log Workout",
1368
+ domain: "health",
1369
+ category: "health",
1370
+ description: "Log a workout with duration, type, and calories burned.",
1371
+ source: `import { defineIntent, param } from "@axintai/compiler";
1372
+
1373
+ export default defineIntent({
1374
+ name: "LogWorkout",
1375
+ title: "Log Workout",
1376
+ description: "Logs a workout with duration, type, and calories burned.",
1377
+ domain: "health",
1378
+ entitlements: ["com.apple.developer.healthkit"],
1379
+ infoPlistKeys: {
1380
+ NSHealthShareUsageDescription: "Read workout history to track progress.",
1381
+ NSHealthUpdateUsageDescription: "Save new workouts you log.",
1382
+ },
1383
+ params: {
1384
+ type: param.string("Workout type (e.g., running, cycling)"),
1385
+ duration: param.duration("Workout duration"),
1386
+ calories: param.int("Calories burned", { required: false }),
1387
+ },
1388
+ perform: async ({ type, duration }) => {
1389
+ return { workoutId: "wo_placeholder" };
1390
+ },
1391
+ });
1392
+ `
1393
+ };
1394
+ setThermostat = {
1395
+ id: "set-thermostat",
1396
+ name: "set-thermostat",
1397
+ title: "Set Thermostat",
1398
+ domain: "smart-home",
1399
+ category: "smart-home",
1400
+ description: "Set a smart-home thermostat to a target temperature.",
1401
+ source: `import { defineIntent, param } from "@axintai/compiler";
1402
+
1403
+ export default defineIntent({
1404
+ name: "SetThermostat",
1405
+ title: "Set Thermostat",
1406
+ description: "Sets a smart-home thermostat to a target temperature.",
1407
+ domain: "smart-home",
1408
+ params: {
1409
+ room: param.string("Which room"),
1410
+ temperature: param.double("Target temperature"),
1411
+ unit: param.string("Temperature unit (F or C)", { default: "F" }),
1412
+ },
1413
+ perform: async ({ room, temperature }) => {
1414
+ return { set: true };
1415
+ },
1416
+ });
1417
+ `
1418
+ };
1419
+ placeOrder = {
1420
+ id: "place-order",
1421
+ name: "place-order",
1422
+ title: "Place Order",
1423
+ domain: "commerce",
1424
+ category: "commerce",
1425
+ description: "Place a commerce order for a product.",
1426
+ source: `import { defineIntent, param } from "@axintai/compiler";
1427
+
1428
+ export default defineIntent({
1429
+ name: "PlaceOrder",
1430
+ title: "Place Order",
1431
+ description: "Places an order for a product.",
1432
+ domain: "commerce",
1433
+ params: {
1434
+ productId: param.string("Product identifier"),
1435
+ quantity: param.int("Quantity", { default: 1 }),
1436
+ shippingAddress: param.string("Shipping address", { required: false }),
1437
+ },
1438
+ perform: async ({ productId, quantity }) => {
1439
+ return { orderId: "ord_placeholder", total: 0 };
1440
+ },
1441
+ });
1442
+ `
1443
+ };
1444
+ searchTasks = {
1445
+ id: "search-tasks",
1446
+ name: "search-tasks",
1447
+ title: "Search Tasks",
1448
+ domain: "productivity",
1449
+ category: "productivity",
1450
+ description: "Search for tasks using EntityQuery with string-based search.",
1451
+ source: `import { defineIntent, defineEntity, param } from "@axintai/compiler";
1452
+
1453
+ defineEntity({
1454
+ name: "Task",
1455
+ display: {
1456
+ title: "name",
1457
+ subtitle: "status",
1458
+ },
1459
+ properties: {
1460
+ id: param.string("Unique task identifier"),
1461
+ name: param.string("Task name"),
1462
+ status: param.string("Task status (todo, in-progress, done)"),
1463
+ dueDate: param.date("Due date"),
1464
+ },
1465
+ query: "string",
1466
+ });
1467
+
1468
+ export default defineIntent({
1469
+ name: "SearchTasks",
1470
+ title: "Search Tasks",
1471
+ description: "Search for tasks by name or status.",
1472
+ domain: "productivity",
1473
+ params: {
1474
+ query: param.string("Search query"),
1475
+ status: param.string("Filter by status (optional)", { required: false }),
1476
+ },
1477
+ donateOnPerform: true,
1478
+ perform: async ({ query, status }) => {
1479
+ // TODO: Search your task database with the query
1480
+ // Use status filter if provided
1481
+ return { found: true, results: 0 };
1482
+ },
1483
+ });
1484
+ `
1485
+ };
1486
+ dynamicPlaylist = {
1487
+ id: "dynamic-playlist",
1488
+ name: "dynamic-playlist",
1489
+ title: "Dynamic Playlist",
1490
+ domain: "media",
1491
+ category: "media",
1492
+ description: "Create a playlist by name, mood, and track count.",
1493
+ source: `import { defineIntent, param } from "@axintai/compiler";
1494
+
1495
+ export default defineIntent({
1496
+ name: "DynamicPlaylist",
1497
+ title: "Create Dynamic Playlist",
1498
+ description: "Create a playlist with a given mood or genre.",
1499
+ domain: "media",
1500
+ params: {
1501
+ name: param.string("Playlist name"),
1502
+ mood: param.string("Mood or genre (e.g., chill, workout, focus)"),
1503
+ trackCount: param.int("Number of tracks", { default: 20 }),
1504
+ },
1505
+ perform: async ({ name, mood }) => {
1506
+ return { playlistId: "playlist_placeholder" };
1507
+ },
1508
+ });
1509
+ `
1510
+ };
1511
+ TEMPLATES = [
1512
+ sendMessage,
1513
+ createEvent,
1514
+ bookRide,
1515
+ getDirections,
1516
+ playTrack,
1517
+ createNote,
1518
+ logExpense,
1519
+ logWorkout,
1520
+ setThermostat,
1521
+ placeOrder,
1522
+ searchTasks,
1523
+ dynamicPlaylist
1524
+ ];
1525
+ }
1526
+ });
1527
+
1528
+ // src/core/format.ts
1529
+ var format_exports = {};
1530
+ __export(format_exports, {
1531
+ SWIFT_FORMAT_CONFIG: () => SWIFT_FORMAT_CONFIG,
1532
+ formatSwift: () => formatSwift
1533
+ });
1534
+ import { spawn as spawn2 } from "child_process";
1535
+ import { writeFile as writeFile2, unlink } from "fs/promises";
1536
+ import { join as join2 } from "path";
1537
+ import { tmpdir } from "os";
1538
+ async function formatSwift(source, options = {}) {
1539
+ const available = await hasSwiftFormat();
1540
+ if (!available) {
1541
+ if (options.strict) {
1542
+ throw new Error(
1543
+ "swift-format not found on $PATH. Install Xcode + Command Line Tools, or drop --format."
1544
+ );
1545
+ }
1546
+ return {
1547
+ formatted: source,
1548
+ ran: false,
1549
+ reason: "swift-format not found on $PATH"
1550
+ };
1551
+ }
1552
+ const configPath = join2(
1553
+ tmpdir(),
1554
+ `axint-swift-format-${Date.now()}-${Math.random().toString(36).slice(2)}.json`
1555
+ );
1556
+ await writeFile2(configPath, JSON.stringify(SWIFT_FORMAT_CONFIG, null, 2));
1557
+ try {
1558
+ const result = await runSwiftFormat(source, configPath, options.timeoutMs ?? 8e3);
1559
+ if (result.code === 0) {
1560
+ return { formatted: result.stdout, ran: true };
1561
+ }
1562
+ if (options.strict) {
1563
+ throw new Error(`swift-format failed: ${result.stderr}`);
1564
+ }
1565
+ return {
1566
+ formatted: source,
1567
+ ran: false,
1568
+ reason: `swift-format exited ${result.code}: ${result.stderr}`
1569
+ };
1570
+ } finally {
1571
+ await unlink(configPath).catch(() => void 0);
1572
+ }
1573
+ }
1574
+ function hasSwiftFormat() {
1575
+ return new Promise((resolve3) => {
1576
+ const child = spawn2("swift-format", ["--version"], { stdio: "pipe" });
1577
+ child.on("error", () => resolve3(false));
1578
+ child.on("exit", (code) => resolve3(code === 0));
1579
+ });
1580
+ }
1581
+ function runSwiftFormat(source, configPath, timeoutMs) {
1582
+ return new Promise((resolve3) => {
1583
+ const child = spawn2("swift-format", ["format", "--configuration", configPath], {
1584
+ stdio: ["pipe", "pipe", "pipe"]
1585
+ });
1586
+ let stdout = "";
1587
+ let stderr = "";
1588
+ child.stdout?.on("data", (d) => stdout += d.toString());
1589
+ child.stderr?.on("data", (d) => stderr += d.toString());
1590
+ const timer = setTimeout(() => {
1591
+ child.kill("SIGKILL");
1592
+ resolve3({
1593
+ stdout,
1594
+ stderr: stderr + `
1595
+ [format] killed after ${timeoutMs}ms`,
1596
+ code: 124
1597
+ });
1598
+ }, timeoutMs);
1599
+ child.on("exit", (code) => {
1600
+ clearTimeout(timer);
1601
+ resolve3({ stdout, stderr, code: code ?? 1 });
1602
+ });
1603
+ child.stdin?.write(source);
1604
+ child.stdin?.end();
1605
+ });
1606
+ }
1607
+ var SWIFT_FORMAT_CONFIG;
1608
+ var init_format = __esm({
1609
+ "src/core/format.ts"() {
1610
+ "use strict";
1611
+ SWIFT_FORMAT_CONFIG = {
1612
+ version: 1,
1613
+ lineLength: 100,
1614
+ indentation: { spaces: 4 },
1615
+ tabWidth: 4,
1616
+ maximumBlankLines: 1,
1617
+ respectsExistingLineBreaks: true,
1618
+ lineBreakBeforeControlFlowKeywords: false,
1619
+ lineBreakBeforeEachArgument: false,
1620
+ lineBreakBeforeEachGenericRequirement: false,
1621
+ prioritizeKeepingFunctionOutputTogether: false,
1622
+ indentConditionalCompilationBlocks: true,
1623
+ lineBreakAroundMultilineExpressionChainComponents: false,
1624
+ fileScopedDeclarationPrivacy: { accessLevel: "private" },
1625
+ rules: {
1626
+ AllPublicDeclarationsHaveDocumentation: false,
1627
+ AlwaysUseLowerCamelCase: true,
1628
+ AmbiguousTrailingClosureOverload: true,
1629
+ BeginDocumentationCommentWithOneLineSummary: false,
1630
+ DoNotUseSemicolons: true,
1631
+ DontRepeatTypeInStaticProperties: true,
1632
+ FileScopedDeclarationPrivacy: true,
1633
+ FullyIndirectEnum: true,
1634
+ GroupNumericLiterals: true,
1635
+ IdentifiersMustBeASCII: true,
1636
+ NeverForceUnwrap: false,
1637
+ NeverUseForceTry: false,
1638
+ NeverUseImplicitlyUnwrappedOptionals: false,
1639
+ NoBlockComments: false,
1640
+ NoCasesWithOnlyFallthrough: true,
1641
+ NoEmptyTrailingClosureParentheses: true,
1642
+ NoLabelsInCasePatterns: true,
1643
+ NoLeadingUnderscores: false,
1644
+ NoParensAroundConditions: true,
1645
+ NoVoidReturnOnFunctionSignature: true,
1646
+ OneCasePerLine: true,
1647
+ OneVariableDeclarationPerLine: true,
1648
+ OnlyOneTrailingClosureArgument: true,
1649
+ OrderedImports: true,
1650
+ ReturnVoidInsteadOfEmptyTuple: true,
1651
+ UseEarlyExits: false,
1652
+ UseLetInEveryBoundCaseVariable: true,
1653
+ UseShorthandTypeNames: true,
1654
+ UseSingleLinePropertyGetter: true,
1655
+ UseSynthesizedInitializer: true,
1656
+ UseTripleSlashForDocumentationComments: true,
1657
+ UseWhereClausesInForLoops: false,
1658
+ ValidateDocumentationComments: false
1659
+ }
1660
+ };
1661
+ }
1662
+ });
1663
+
1664
+ // src/core/sandbox.ts
1665
+ var sandbox_exports = {};
1666
+ __export(sandbox_exports, {
1667
+ sandboxCompile: () => sandboxCompile,
1668
+ sandboxExists: () => sandboxExists
1669
+ });
1670
+ import { mkdir as mkdir2, writeFile as writeFile3, rm } from "fs/promises";
1671
+ import { existsSync as existsSync2 } from "fs";
1672
+ import { join as join3 } from "path";
1673
+ import { tmpdir as tmpdir2 } from "os";
1674
+ import { spawn as spawn3 } from "child_process";
1675
+ async function sandboxCompile(swiftSource, options) {
1676
+ const start = Date.now();
1677
+ const available = await hasSwiftToolchain();
1678
+ if (!available) {
1679
+ throw new Error(
1680
+ "Swift toolchain not found. Install Xcode + Command Line Tools, or run without `--sandbox`."
1681
+ );
1682
+ }
1683
+ const root = options.rootDir ?? join3(tmpdir2(), "axint-sandbox", options.intentName);
1684
+ const srcDir = join3(root, "Sources", `${options.intentName}Sandbox`);
1685
+ try {
1686
+ await mkdir2(srcDir, { recursive: true });
1687
+ await writeFile3(join3(root, "Package.swift"), PACKAGE_SWIFT(options.intentName));
1688
+ await writeFile3(join3(srcDir, `${options.intentName}Intent.swift`), swiftSource);
1689
+ } catch (err) {
1690
+ return {
1691
+ ok: false,
1692
+ stdout: "",
1693
+ stderr: `Failed to stage sandbox: ${err.message}`,
1694
+ durationMs: Date.now() - start,
1695
+ sandboxPath: root
1696
+ };
1697
+ }
1698
+ const { stdout, stderr, code } = await runSwiftBuild(root, options.timeoutMs ?? 6e4);
1699
+ if (!options.keep && code === 0) {
1700
+ await rm(root, { recursive: true, force: true }).catch(() => void 0);
1701
+ }
1702
+ return {
1703
+ ok: code === 0,
1704
+ stdout,
1705
+ stderr,
1706
+ durationMs: Date.now() - start,
1707
+ sandboxPath: root
1708
+ };
1709
+ }
1710
+ async function hasSwiftToolchain() {
1711
+ return new Promise((resolve3) => {
1712
+ const child = spawn3("swift", ["--version"], { stdio: "pipe" });
1713
+ child.on("error", () => resolve3(false));
1714
+ child.on("exit", (code) => resolve3(code === 0));
1715
+ });
1716
+ }
1717
+ function runSwiftBuild(cwd, timeoutMs) {
1718
+ return new Promise((resolve3) => {
1719
+ const child = spawn3("swift", ["build", "-c", "debug"], {
1720
+ cwd,
1721
+ stdio: ["ignore", "pipe", "pipe"]
1722
+ });
1723
+ let stdout = "";
1724
+ let stderr = "";
1725
+ child.stdout?.on("data", (d) => stdout += d.toString());
1726
+ child.stderr?.on("data", (d) => stderr += d.toString());
1727
+ const timer = setTimeout(() => {
1728
+ child.kill("SIGKILL");
1729
+ resolve3({
1730
+ stdout,
1731
+ stderr: stderr + `
1732
+ [sandbox] killed after ${timeoutMs}ms`,
1733
+ code: 124
1734
+ });
1735
+ }, timeoutMs);
1736
+ child.on("exit", (code) => {
1737
+ clearTimeout(timer);
1738
+ resolve3({ stdout, stderr, code: code ?? 1 });
1739
+ });
1740
+ });
1741
+ }
1742
+ function sandboxExists(intentName) {
1743
+ return existsSync2(join3(tmpdir2(), "axint-sandbox", intentName));
1744
+ }
1745
+ var PACKAGE_SWIFT;
1746
+ var init_sandbox = __esm({
1747
+ "src/core/sandbox.ts"() {
1748
+ "use strict";
1749
+ PACKAGE_SWIFT = (name) => `// swift-tools-version:5.9
1750
+ import PackageDescription
1751
+
1752
+ let package = Package(
1753
+ name: "${name}Sandbox",
1754
+ platforms: [
1755
+ .iOS(.v16),
1756
+ .macOS(.v13),
1757
+ ],
1758
+ products: [
1759
+ .library(name: "${name}Sandbox", targets: ["${name}Sandbox"]),
1760
+ ],
1761
+ targets: [
1762
+ .target(
1763
+ name: "${name}Sandbox",
1764
+ path: "Sources/${name}Sandbox"
1765
+ ),
1766
+ ]
1767
+ )
1768
+ `;
1769
+ }
1770
+ });
1771
+
1772
+ // src/mcp/scaffold.ts
1773
+ function scaffoldIntent(input) {
1774
+ const name = toPascalCase2(input.name);
1775
+ const title = humanize(name);
1776
+ const desc = sanitize(input.description);
1777
+ const domain = input.domain ? sanitize(input.domain) : void 0;
1778
+ const paramLines = [];
1779
+ const destructureNames = [];
1780
+ for (const p of input.params ?? []) {
1781
+ const type = resolveType(p.type);
1782
+ const safeName = toCamelCase(p.name);
1783
+ destructureNames.push(safeName);
1784
+ paramLines.push(
1785
+ ` ${safeName}: param.${type}(${JSON.stringify(sanitize(p.description))}),`
1786
+ );
1787
+ }
1788
+ const paramsBlock = paramLines.length > 0 ? `
1789
+ ${paramLines.join("\n")}
1790
+ ` : "";
1791
+ const destructure = destructureNames.length > 0 ? `{ ${destructureNames.join(", ")} }` : "_";
1792
+ const lines = [];
1793
+ lines.push(`/**`);
1794
+ lines.push(` * ${name}Intent`);
1795
+ lines.push(` *`);
1796
+ lines.push(` * ${desc}`);
1797
+ lines.push(` *`);
1798
+ lines.push(` * Generated by axint_scaffold. Edit freely, then run:`);
1799
+ lines.push(` * npx axint compile src/intents/${kebab(name)}.ts`);
1800
+ lines.push(` */`);
1801
+ lines.push(`import { defineIntent, param } from "@axintai/compiler";`);
1802
+ lines.push(``);
1803
+ lines.push(`export default defineIntent({`);
1804
+ lines.push(` name: ${JSON.stringify(name)},`);
1805
+ lines.push(` title: ${JSON.stringify(title)},`);
1806
+ lines.push(` description: ${JSON.stringify(desc)},`);
1807
+ if (domain) lines.push(` domain: ${JSON.stringify(domain)},`);
1808
+ lines.push(` params: {${paramsBlock}},`);
1809
+ lines.push(` perform: async (${destructure}) => {`);
1810
+ lines.push(` // TODO: Implement ${name}`);
1811
+ lines.push(` return { success: true };`);
1812
+ lines.push(` },`);
1813
+ lines.push(`});`);
1814
+ lines.push(``);
1815
+ return lines.join("\n");
1816
+ }
1817
+ function resolveType(raw) {
1818
+ const lc = raw.toLowerCase();
1819
+ if (PARAM_TYPES.has(lc)) return lc;
1820
+ if (lc in LEGACY_PARAM_ALIASES) return LEGACY_PARAM_ALIASES[lc];
1821
+ return "string";
1822
+ }
1823
+ function toPascalCase2(s) {
1824
+ if (!s) return "UnnamedIntent";
1825
+ return s.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
1826
+ }
1827
+ function toCamelCase(s) {
1828
+ const pascal = toPascalCase2(s);
1829
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
1830
+ }
1831
+ function humanize(pascal) {
1832
+ return pascal.replace(/([A-Z])/g, " $1").trim();
1833
+ }
1834
+ function kebab(pascal) {
1835
+ return pascal.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/\s+/g, "-").toLowerCase();
1836
+ }
1837
+ function sanitize(s) {
1838
+ return s.replace(/[\x00-\x1f\x7f]+/g, " ").replace(/\s+/g, " ").trim();
1839
+ }
1840
+ var init_scaffold = __esm({
1841
+ "src/mcp/scaffold.ts"() {
1842
+ "use strict";
1843
+ init_types();
1844
+ }
1845
+ });
1846
+
1847
+ // src/mcp/server.ts
1848
+ var server_exports = {};
1849
+ __export(server_exports, {
1850
+ startMCPServer: () => startMCPServer
1851
+ });
1852
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1853
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1854
+ import {
1855
+ CallToolRequestSchema,
1856
+ ListToolsRequestSchema
1857
+ } from "@modelcontextprotocol/sdk/types.js";
1858
+ import { readFileSync as readFileSync2 } from "fs";
1859
+ import { resolve, dirname } from "path";
1860
+ import { fileURLToPath } from "url";
1861
+ async function startMCPServer() {
1862
+ const server = new Server(
1863
+ { name: "axint", version: pkg.version },
1864
+ { capabilities: { tools: {} } }
1865
+ );
1866
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1867
+ tools: [
1868
+ {
1869
+ name: "axint_scaffold",
1870
+ description: "Generate a starter TypeScript intent file using the axint SDK. Pass a PascalCase name, a description, and optionally a domain (messaging, productivity, health, finance, commerce, media, navigation, smart-home) and a list of parameters. Returns ready-to-save source code that compiles with `axint compile`.",
1871
+ inputSchema: {
1872
+ type: "object",
1873
+ properties: {
1874
+ name: {
1875
+ type: "string",
1876
+ description: "PascalCase name for the intent, e.g., 'CreateEvent'"
1877
+ },
1878
+ description: {
1879
+ type: "string",
1880
+ description: "Human-readable description of what the intent does"
1881
+ },
1882
+ domain: {
1883
+ type: "string",
1884
+ description: "Optional Apple App Intent domain (messaging, productivity, health, finance, commerce, media, navigation, smart-home)"
1885
+ },
1886
+ params: {
1887
+ type: "array",
1888
+ description: "Optional initial parameters. Each item: { name, type, description }. Supported types: string, int, double, float, boolean, date, duration, url.",
1889
+ items: {
1890
+ type: "object",
1891
+ properties: {
1892
+ name: { type: "string" },
1893
+ type: { type: "string" },
1894
+ description: { type: "string" }
1895
+ },
1896
+ required: ["name", "type", "description"]
1897
+ }
1898
+ }
1899
+ },
1900
+ required: ["name", "description"]
1901
+ }
1902
+ },
1903
+ {
1904
+ name: "axint_compile",
1905
+ description: "Compile a TypeScript intent definition into a native Swift App Intent. Optionally emits Info.plist and entitlements fragments alongside the Swift file. Pass the full TypeScript source code using the defineIntent() API.",
1906
+ inputSchema: {
1907
+ type: "object",
1908
+ properties: {
1909
+ source: {
1910
+ type: "string",
1911
+ description: "TypeScript source code containing a defineIntent() call"
1912
+ },
1913
+ fileName: {
1914
+ type: "string",
1915
+ description: "Optional file name for error messages"
1916
+ },
1917
+ emitInfoPlist: {
1918
+ type: "boolean",
1919
+ description: "When true, also returns an Info.plist XML fragment for the intent's declared infoPlistKeys"
1920
+ },
1921
+ emitEntitlements: {
1922
+ type: "boolean",
1923
+ description: "When true, also returns an .entitlements XML fragment for the intent's declared entitlements"
1924
+ }
1925
+ },
1926
+ required: ["source"]
1927
+ }
1928
+ },
1929
+ {
1930
+ name: "axint_validate",
1931
+ description: "Validate a TypeScript intent definition without generating Swift output. Returns diagnostics with error codes and fix suggestions.",
1932
+ inputSchema: {
1933
+ type: "object",
1934
+ properties: {
1935
+ source: {
1936
+ type: "string",
1937
+ description: "TypeScript source code containing a defineIntent() call"
1938
+ }
1939
+ },
1940
+ required: ["source"]
1941
+ }
1942
+ },
1943
+ {
1944
+ name: "axint_list_templates",
1945
+ description: "List the bundled reference templates. Use `axint_template` to fetch the full source of a specific template by id.",
1946
+ inputSchema: {
1947
+ type: "object",
1948
+ properties: {}
1949
+ }
1950
+ },
1951
+ {
1952
+ name: "axint_template",
1953
+ description: "Return the full TypeScript source code of a bundled reference template by id. Use `axint_list_templates` to discover valid ids.",
1954
+ inputSchema: {
1955
+ type: "object",
1956
+ properties: {
1957
+ id: {
1958
+ type: "string",
1959
+ description: "Template id (e.g., 'send-message', 'create-event')"
1960
+ }
1961
+ },
1962
+ required: ["id"]
1963
+ }
1964
+ }
1965
+ ]
1966
+ }));
1967
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1968
+ const { name, arguments: args } = request.params;
1969
+ try {
1970
+ if (name === "axint_scaffold") {
1971
+ const a = args;
1972
+ const source = scaffoldIntent({
1973
+ name: a.name,
1974
+ description: a.description,
1975
+ domain: a.domain,
1976
+ params: a.params
1977
+ });
1978
+ return { content: [{ type: "text", text: source }] };
1979
+ }
1980
+ if (name === "axint_compile") {
1981
+ const a = args;
1982
+ const result = compileSource(a.source, a.fileName || "<mcp>", {
1983
+ emitInfoPlist: a.emitInfoPlist,
1984
+ emitEntitlements: a.emitEntitlements
1985
+ });
1986
+ if (result.success && result.output) {
1987
+ const parts = [
1988
+ "// \u2500\u2500\u2500 Swift \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1989
+ result.output.swiftCode
1990
+ ];
1991
+ if (result.output.infoPlistFragment) {
1992
+ parts.push("// \u2500\u2500\u2500 Info.plist fragment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1993
+ parts.push(result.output.infoPlistFragment);
1994
+ }
1995
+ if (result.output.entitlementsFragment) {
1996
+ parts.push("// \u2500\u2500\u2500 .entitlements fragment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1997
+ parts.push(result.output.entitlementsFragment);
1998
+ }
1999
+ return {
2000
+ content: [{ type: "text", text: parts.join("\n") }]
2001
+ };
2002
+ }
2003
+ const errorText = result.diagnostics.map((d) => `[${d.code}] ${d.severity}: ${d.message}`).join("\n");
2004
+ return {
2005
+ content: [{ type: "text", text: errorText }],
2006
+ isError: true
2007
+ };
2008
+ }
2009
+ if (name === "axint_validate") {
2010
+ const a = args;
2011
+ const result = compileSource(a.source, "<validate>");
2012
+ const text = result.diagnostics.length > 0 ? result.diagnostics.map((d) => `[${d.code}] ${d.severity}: ${d.message}`).join("\n") : "Valid intent definition. No issues found.";
2013
+ return { content: [{ type: "text", text }] };
2014
+ }
2015
+ if (name === "axint_list_templates") {
2016
+ const list = TEMPLATES.map(
2017
+ (t) => `${t.id} \u2014 ${t.title}${t.domain ? ` [${t.domain}]` : ""}`
2018
+ ).join("\n");
2019
+ return {
2020
+ content: [
2021
+ {
2022
+ type: "text",
2023
+ text: list || "No templates registered."
2024
+ }
2025
+ ]
2026
+ };
2027
+ }
2028
+ if (name === "axint_template") {
2029
+ const a = args;
2030
+ const tpl = getTemplate(a.id);
2031
+ if (!tpl) {
2032
+ return {
2033
+ content: [
2034
+ {
2035
+ type: "text",
2036
+ text: `Unknown template id: ${a.id}. Use axint_list_templates to see available ids.`
2037
+ }
2038
+ ],
2039
+ isError: true
2040
+ };
2041
+ }
2042
+ return { content: [{ type: "text", text: tpl.source }] };
2043
+ }
2044
+ return {
2045
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
2046
+ isError: true
2047
+ };
2048
+ } catch (err) {
2049
+ return {
2050
+ content: [
2051
+ {
2052
+ type: "text",
2053
+ text: `Tool error: ${err instanceof Error ? err.message : String(err)}`
2054
+ }
2055
+ ],
2056
+ isError: true
2057
+ };
2058
+ }
2059
+ });
2060
+ const transport = new StdioServerTransport();
2061
+ await server.connect(transport);
2062
+ }
2063
+ var __dirname, pkg;
2064
+ var init_server = __esm({
2065
+ "src/mcp/server.ts"() {
2066
+ "use strict";
2067
+ init_compiler();
2068
+ init_scaffold();
2069
+ init_templates();
2070
+ __dirname = dirname(fileURLToPath(import.meta.url));
2071
+ pkg = JSON.parse(
2072
+ readFileSync2(resolve(__dirname, "../../package.json"), "utf-8")
2073
+ );
2074
+ }
2075
+ });
2076
+
2077
+ // src/cli/index.ts
2078
+ init_compiler();
2079
+ import { Command } from "commander";
2080
+ import {
2081
+ readFileSync as readFileSync3,
2082
+ writeFileSync,
2083
+ mkdirSync,
2084
+ existsSync as existsSync3,
2085
+ watch as fsWatch,
2086
+ statSync
2087
+ } from "fs";
2088
+ import { resolve as resolve2, dirname as dirname2, basename } from "path";
2089
+ import { spawn as spawn4 } from "child_process";
2090
+ import { fileURLToPath as fileURLToPath2 } from "url";
2091
+
2092
+ // src/core/eject.ts
2093
+ init_compiler();
2094
+ function ejectIntent(source, fileName, options = {}) {
2095
+ const compileResult = compileSource(source, fileName, {
2096
+ validate: true,
2097
+ emitInfoPlist: true,
2098
+ emitEntitlements: true
2099
+ });
2100
+ if (!compileResult.success || !compileResult.output) {
2101
+ throw new Error(
2102
+ `Compilation failed: ${compileResult.diagnostics.filter((d) => d.severity === "error").map((d) => d.message).join("; ")}`
2103
+ );
2104
+ }
2105
+ const { ir, swiftCode, infoPlistFragment, entitlementsFragment } = compileResult.output;
2106
+ const outDir = options.outDir ?? ".";
2107
+ const ejectedSwift = transformSwiftForEject(swiftCode, ir);
2108
+ const intentFileName = `${ir.name}Intent.swift`;
2109
+ const swiftPath = `${outDir}/${intentFileName}`;
2110
+ const plistPath = infoPlistFragment ? `${outDir}/${ir.name}Intent.plist.fragment.xml` : null;
2111
+ const entitlementsPath = entitlementsFragment ? `${outDir}/${ir.name}Intent.entitlements.fragment.xml` : null;
2112
+ const testPath = options.includeTests ? `${outDir}/${ir.name}IntentTests.swift` : null;
2113
+ const result = {
2114
+ swiftFile: {
2115
+ path: swiftPath,
2116
+ content: ejectedSwift
2117
+ }
2118
+ };
2119
+ if (infoPlistFragment && plistPath) {
2120
+ result.infoPlist = {
2121
+ path: plistPath,
2122
+ content: infoPlistFragment
2123
+ };
2124
+ }
2125
+ if (entitlementsFragment && entitlementsPath) {
2126
+ result.entitlements = {
2127
+ path: entitlementsPath,
2128
+ content: entitlementsFragment
2129
+ };
2130
+ }
2131
+ if (options.includeTests && testPath) {
2132
+ result.testFile = {
2133
+ path: testPath,
2134
+ content: generateTestFile(ir)
2135
+ };
2136
+ }
2137
+ return result;
2138
+ }
2139
+ function transformSwiftForEject(swiftCode, ir) {
2140
+ const lines = swiftCode.split("\n");
2141
+ const result = [];
2142
+ let inHeader = true;
2143
+ for (let i = 0; i < lines.length; i++) {
2144
+ const line = lines[i];
2145
+ if (inHeader && line.startsWith("// ")) {
2146
+ if (i === 0) {
2147
+ result.push(`// ${ir.name}Intent.swift`);
2148
+ } else if (line.includes("Generated by Axint")) {
2149
+ result.push(
2150
+ `// Originally generated by Axint (https://github.com/agenticempire/axint)`
2151
+ );
2152
+ } else if (line.includes("Do not edit manually")) {
2153
+ } else {
2154
+ result.push(line);
2155
+ }
2156
+ } else if (line === "") {
2157
+ inHeader = false;
2158
+ result.push(line);
2159
+ } else {
2160
+ inHeader = false;
2161
+ if (line.includes("// TODO: Implement your intent logic here.")) {
2162
+ result.push(` // TODO: Implement your intent logic here.`);
2163
+ result.push(` //`);
2164
+ result.push(
2165
+ ` // For more information about App Intents, see:`
2166
+ );
2167
+ result.push(
2168
+ ` // https://developer.apple.com/documentation/appintents`
2169
+ );
2170
+ } else {
2171
+ result.push(line);
2172
+ }
2173
+ }
2174
+ }
2175
+ return result.join("\n");
2176
+ }
2177
+ function generateTestFile(ir) {
2178
+ const lines = [];
2179
+ lines.push(`// ${ir.name}IntentTests.swift`);
2180
+ lines.push(`// Ejected from Axint`);
2181
+ lines.push(``);
2182
+ lines.push(`import XCTest`);
2183
+ lines.push(`import AppIntents`);
2184
+ lines.push(``);
2185
+ lines.push(`final class ${ir.name}IntentTests: XCTestCase {`);
2186
+ lines.push(``);
2187
+ lines.push(` func testIntentInitialization() throws {`);
2188
+ lines.push(` let intent = ${ir.name}Intent()`);
2189
+ lines.push(` XCTAssertEqual(${ir.name}Intent.title.stringValue, "${ir.title}")`);
2190
+ lines.push(` }`);
2191
+ lines.push(``);
2192
+ lines.push(` func testIntentPerform() async throws {`);
2193
+ lines.push(` let intent = ${ir.name}Intent()`);
2194
+ lines.push(` // TODO: Set up intent parameters and test perform()`);
2195
+ lines.push(` // let result = try await intent.perform()`);
2196
+ lines.push(` // XCTAssertNotNil(result)`);
2197
+ lines.push(` }`);
2198
+ lines.push(``);
2199
+ lines.push(`}`);
2200
+ lines.push(``);
2201
+ return lines.join("\n");
2202
+ }
2203
+
2204
+ // src/cli/scaffold.ts
2205
+ init_templates();
2206
+ import { mkdir, writeFile, readdir } from "fs/promises";
2207
+ import { existsSync } from "fs";
2208
+ import { join, relative } from "path";
2209
+ import { spawn } from "child_process";
2210
+ async function scaffoldProject(opts) {
2211
+ const { targetDir, projectName, template, version, install } = opts;
2212
+ const tpl = getTemplate(template);
2213
+ if (!tpl) {
2214
+ throw new Error(
2215
+ `Unknown template "${template}". Run \`axint templates\` to see available templates.`
2216
+ );
2217
+ }
2218
+ if (existsSync(targetDir)) {
2219
+ const entries = await readdir(targetDir).catch(() => []);
2220
+ const populated = entries.filter((e) => !e.startsWith(".git") && e !== ".DS_Store");
2221
+ if (populated.length > 0) {
2222
+ throw new Error(
2223
+ `Directory "${targetDir}" is not empty. Pick an empty folder or use \`axint init my-new-app\`.`
2224
+ );
2225
+ }
2226
+ } else {
2227
+ await mkdir(targetDir, { recursive: true });
2228
+ }
2229
+ const files = [];
2230
+ const write = async (rel, content) => {
2231
+ const abs = join(targetDir, rel);
2232
+ await mkdir(join(abs, "..").replace(/[/\\][^/\\]+$/, ""), { recursive: true }).catch(
2233
+ () => void 0
2234
+ );
2235
+ const parent = abs.substring(
2236
+ 0,
2237
+ abs.lastIndexOf("/") === -1 ? abs.lastIndexOf("\\") : abs.lastIndexOf("/")
2238
+ );
2239
+ if (parent && parent !== abs) {
2240
+ await mkdir(parent, { recursive: true }).catch(() => void 0);
2241
+ }
2242
+ await writeFile(abs, content, "utf-8");
2243
+ files.push(relative(targetDir, abs));
2244
+ };
2245
+ await write(
2246
+ "package.json",
2247
+ JSON.stringify(
2248
+ {
2249
+ name: projectName,
2250
+ version: "0.0.1",
2251
+ private: true,
2252
+ type: "module",
2253
+ scripts: {
2254
+ compile: `axint compile intents/${template}.ts --out ios/Intents/`,
2255
+ "compile:plist": `axint compile intents/${template}.ts --out ios/Intents/ --emit-info-plist --emit-entitlements`,
2256
+ validate: `axint validate intents/${template}.ts`,
2257
+ sandbox: `axint validate intents/${template}.ts --sandbox`
2258
+ },
2259
+ dependencies: {
2260
+ "@axintai/compiler": `^${version}`
2261
+ }
2262
+ },
2263
+ null,
2264
+ 2
2265
+ ) + "\n"
2266
+ );
2267
+ await write(
2268
+ "tsconfig.json",
2269
+ JSON.stringify(
2270
+ {
2271
+ compilerOptions: {
2272
+ target: "ES2022",
2273
+ module: "NodeNext",
2274
+ moduleResolution: "NodeNext",
2275
+ strict: true,
2276
+ esModuleInterop: true,
2277
+ skipLibCheck: true,
2278
+ noEmit: true,
2279
+ isolatedModules: true,
2280
+ verbatimModuleSyntax: false,
2281
+ resolveJsonModule: true
2282
+ },
2283
+ include: ["intents/**/*.ts"]
2284
+ },
2285
+ null,
2286
+ 2
2287
+ ) + "\n"
2288
+ );
2289
+ await write(
2290
+ ".gitignore",
2291
+ ["node_modules", "dist", ".DS_Store", ".axint-sandbox", "*.log", ""].join("\n")
2292
+ );
2293
+ await write(`intents/${template}.ts`, tpl.source);
2294
+ await write(
2295
+ ".vscode/mcp.json",
2296
+ JSON.stringify(
2297
+ {
2298
+ mcpServers: {
2299
+ axint: {
2300
+ command: "npx",
2301
+ args: ["-y", "@axintai/compiler", "axint-mcp"]
2302
+ }
2303
+ }
2304
+ },
2305
+ null,
2306
+ 2
2307
+ ) + "\n"
2308
+ );
2309
+ await write("README.md", scaffoldReadme(projectName, template, tpl.title, version));
2310
+ await mkdir(join(targetDir, "ios", "Intents"), { recursive: true });
2311
+ await write("ios/Intents/.gitkeep", "");
2312
+ if (install) {
2313
+ await runNpmInstall(targetDir);
2314
+ }
2315
+ return {
2316
+ files,
2317
+ entryFile: `${template}.ts`
2318
+ };
2319
+ }
2320
+ function scaffoldReadme(name, template, title, version) {
2321
+ return `# ${name}
2322
+
2323
+ An [Axint](https://axint.ai) project \u2014 write App Intents in TypeScript, ship them to Siri.
2324
+
2325
+ Generated from the **${title}** template, pinned to \`@axintai/compiler@^${version}\`.
2326
+
2327
+ ## Compile it
2328
+
2329
+ \`\`\`bash
2330
+ npm install
2331
+ npm run compile
2332
+ \`\`\`
2333
+
2334
+ Output lands in \`ios/Intents/\`. Drag that folder into your Xcode target and you're done.
2335
+
2336
+ ## Validate it
2337
+
2338
+ \`\`\`bash
2339
+ npm run validate # fast IR + Swift lint
2340
+ npm run sandbox # stage 4: swift build in an SPM sandbox (macOS only)
2341
+ \`\`\`
2342
+
2343
+ ## Use with AI coding tools
2344
+
2345
+ The \`.vscode/mcp.json\` file is pre-wired for Cursor, Claude Code, and Windsurf.
2346
+ Any agent that supports MCP can now call \`axint_compile\`, \`axint_validate\`,
2347
+ \`axint_scaffold\`, and \`axint_template\` against this project.
2348
+
2349
+ ## Next
2350
+
2351
+ - Edit \`intents/${template}.ts\` \u2014 this is your App Intent source of truth.
2352
+ - Add more intents in the \`intents/\` folder.
2353
+ - Run \`axint templates\` to see every bundled starter.
2354
+ - Read the docs at https://github.com/agenticempire/axint#readme
2355
+
2356
+ ---
2357
+
2358
+ _Generated by \`axint init\`_
2359
+ `;
2360
+ }
2361
+ function runNpmInstall(cwd) {
2362
+ return new Promise((resolve3, reject) => {
2363
+ const child = spawn("npm", ["install"], { cwd, stdio: "inherit" });
2364
+ child.on("exit", (code) => {
2365
+ if (code === 0) resolve3();
2366
+ else reject(new Error(`npm install exited with code ${code}`));
2367
+ });
2368
+ child.on("error", reject);
2369
+ });
2370
+ }
2371
+
2372
+ // src/cli/index.ts
2373
+ init_templates();
2374
+ var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
2375
+ var pkg2 = JSON.parse(readFileSync3(resolve2(__dirname2, "../../package.json"), "utf-8"));
2376
+ var VERSION = pkg2.version;
2377
+ var program = new Command();
2378
+ program.name("axint").description(
2379
+ "The open-source compiler that transforms AI agent definitions into native Apple App Intents."
2380
+ ).version(VERSION);
2381
+ program.command("init").description("Scaffold a new Axint project (zero-config, ready to compile)").argument("[dir]", "Project directory (defaults to current dir)", ".").option(
2382
+ "-t, --template <name>",
2383
+ "Starter template (send-message, create-event, book-ride, ...)",
2384
+ "create-event"
2385
+ ).option("--no-install", "Skip running `npm install`").option("--name <name>", "Project name (defaults to the directory name)").action(
2386
+ async (dir, options) => {
2387
+ const targetDir = resolve2(dir);
2388
+ const projectName = options.name ?? basename(targetDir);
2389
+ try {
2390
+ const result = await scaffoldProject({
2391
+ targetDir,
2392
+ projectName,
2393
+ template: options.template,
2394
+ version: VERSION,
2395
+ install: options.install
2396
+ });
2397
+ console.log();
2398
+ console.log(` \x1B[38;5;208m\u25C6\x1B[0m \x1B[1mAxint\x1B[0m \xB7 project ready`);
2399
+ console.log();
2400
+ console.log(
2401
+ ` \x1B[2m${result.files.length} files written to ${targetDir}\x1B[0m`
2402
+ );
2403
+ console.log();
2404
+ console.log(` \x1B[1mNext:\x1B[0m`);
2405
+ if (dir !== ".") console.log(` cd ${dir}`);
2406
+ if (options.install) {
2407
+ console.log(
2408
+ ` npx axint compile intents/${result.entryFile} --out ios/Intents/`
2409
+ );
2410
+ } else {
2411
+ console.log(` npm install`);
2412
+ console.log(
2413
+ ` npx axint compile intents/${result.entryFile} --out ios/Intents/`
2414
+ );
2415
+ }
2416
+ console.log();
2417
+ console.log(
2418
+ ` \x1B[2mDocs: https://github.com/agenticempire/axint#readme\x1B[0m`
2419
+ );
2420
+ console.log(
2421
+ ` \x1B[2mMCP: npx axint-mcp (add to Claude Code, Cursor, Windsurf)\x1B[0m`
2422
+ );
2423
+ console.log();
2424
+ } catch (err) {
2425
+ console.error(`\x1B[31merror:\x1B[0m ${err.message ?? err}`);
2426
+ process.exit(1);
2427
+ }
2428
+ }
2429
+ );
2430
+ program.command("compile").description("Compile a TypeScript intent definition into Swift").argument("<file>", "Path to the TypeScript intent definition").option("-o, --out <dir>", "Output directory for generated Swift", ".").option("--no-validate", "Skip validation of generated Swift").option("--stdout", "Print generated Swift to stdout instead of writing a file").option("--json", "Output result as JSON (machine-readable)").option(
2431
+ "--emit-info-plist",
2432
+ "Emit a <Name>.plist.fragment.xml with NSAppIntentsDomains next to the Swift file"
2433
+ ).option(
2434
+ "--emit-entitlements",
2435
+ "Emit a <Name>.entitlements.fragment.xml next to the Swift file"
2436
+ ).option(
2437
+ "--sandbox",
2438
+ "Run stage 4 validation: swift build in an SPM sandbox (macOS only)"
2439
+ ).option(
2440
+ "--format",
2441
+ "Pipe generated Swift through swift-format with the Axint house style (macOS/Linux if swift-format is on $PATH)"
2442
+ ).option(
2443
+ "--strict-format",
2444
+ "Fail the build if swift-format is missing or errors (implies --format)"
2445
+ ).option(
2446
+ "--from-ir",
2447
+ "Treat <file> as IR JSON (from Python SDK or any language) instead of TypeScript. Use - to read from stdin."
2448
+ ).action(
2449
+ async (file, options) => {
2450
+ const filePath = resolve2(file);
2451
+ try {
2452
+ let result;
2453
+ if (options.fromIr) {
2454
+ let irRaw;
2455
+ if (file === "-") {
2456
+ const chunks = [];
2457
+ for await (const chunk of process.stdin) {
2458
+ chunks.push(chunk);
2459
+ }
2460
+ irRaw = Buffer.concat(chunks).toString("utf-8");
2461
+ } else {
2462
+ irRaw = readFileSync3(filePath, "utf-8");
2463
+ }
2464
+ let parsed;
2465
+ try {
2466
+ parsed = JSON.parse(irRaw);
2467
+ } catch {
2468
+ console.error(`\x1B[31merror:\x1B[0m Invalid JSON in ${file}`);
2469
+ process.exit(1);
2470
+ }
2471
+ const irData = Array.isArray(parsed) ? parsed[0] : parsed;
2472
+ if (!irData || typeof irData !== "object") {
2473
+ console.error(
2474
+ `\x1B[31merror:\x1B[0m Expected an IR object or array in ${file}`
2475
+ );
2476
+ process.exit(1);
2477
+ }
2478
+ const ir = irFromJSON(irData);
2479
+ result = compileFromIR(ir, {
2480
+ outDir: options.out,
2481
+ validate: options.validate,
2482
+ emitInfoPlist: options.emitInfoPlist,
2483
+ emitEntitlements: options.emitEntitlements
2484
+ });
2485
+ } else {
2486
+ result = compileFile(filePath, {
2487
+ outDir: options.out,
2488
+ validate: options.validate,
2489
+ emitInfoPlist: options.emitInfoPlist,
2490
+ emitEntitlements: options.emitEntitlements
2491
+ });
2492
+ }
2493
+ if (options.json) {
2494
+ console.log(
2495
+ JSON.stringify(
2496
+ {
2497
+ success: result.success,
2498
+ swift: result.output?.swiftCode ?? null,
2499
+ outputPath: result.output?.outputPath ?? null,
2500
+ infoPlistFragment: result.output?.infoPlistFragment ?? null,
2501
+ entitlementsFragment: result.output?.entitlementsFragment ?? null,
2502
+ diagnostics: result.diagnostics.map((d) => ({
2503
+ code: d.code,
2504
+ severity: d.severity,
2505
+ message: d.message,
2506
+ file: d.file,
2507
+ line: d.line,
2508
+ suggestion: d.suggestion
2509
+ }))
2510
+ },
2511
+ null,
2512
+ 2
2513
+ )
2514
+ );
2515
+ if (!result.success) process.exit(1);
2516
+ return;
2517
+ }
2518
+ for (const d of result.diagnostics) {
2519
+ const prefix = d.severity === "error" ? "\x1B[31merror\x1B[0m" : d.severity === "warning" ? "\x1B[33mwarning\x1B[0m" : "\x1B[36minfo\x1B[0m";
2520
+ console.error(` ${prefix}[${d.code}]: ${d.message}`);
2521
+ if (d.file) console.error(` --> ${d.file}${d.line ? `:${d.line}` : ""}`);
2522
+ if (d.suggestion) console.error(` = help: ${d.suggestion}`);
2523
+ console.error();
2524
+ }
2525
+ if (!result.success || !result.output) {
2526
+ console.error(
2527
+ `\x1B[31mCompilation failed with ${result.diagnostics.filter((d) => d.severity === "error").length} error(s)\x1B[0m`
2528
+ );
2529
+ process.exit(1);
2530
+ }
2531
+ if (options.format || options.strictFormat) {
2532
+ try {
2533
+ const { formatSwift: formatSwift2 } = await Promise.resolve().then(() => (init_format(), format_exports));
2534
+ const fmt = await formatSwift2(result.output.swiftCode, {
2535
+ strict: options.strictFormat
2536
+ });
2537
+ if (fmt.ran) {
2538
+ result.output.swiftCode = fmt.formatted;
2539
+ } else if (!options.json) {
2540
+ console.error(
2541
+ `\x1B[33mwarning:\x1B[0m swift-format skipped \u2014 ${fmt.reason}`
2542
+ );
2543
+ }
2544
+ } catch (fmtErr) {
2545
+ if (options.strictFormat) {
2546
+ console.error(`\x1B[31merror:\x1B[0m ${fmtErr.message}`);
2547
+ process.exit(1);
2548
+ }
2549
+ console.error(
2550
+ `\x1B[33mwarning:\x1B[0m swift-format skipped \u2014 ${fmtErr.message}`
2551
+ );
2552
+ }
2553
+ }
2554
+ if (options.stdout) {
2555
+ console.log(result.output.swiftCode);
2556
+ } else {
2557
+ const outPath = resolve2(result.output.outputPath);
2558
+ mkdirSync(dirname2(outPath), { recursive: true });
2559
+ writeFileSync(outPath, result.output.swiftCode, "utf-8");
2560
+ console.log(`\x1B[32m\u2713\x1B[0m Compiled ${result.output.ir.name} \u2192 ${outPath}`);
2561
+ if (options.emitInfoPlist && result.output.infoPlistFragment) {
2562
+ const plistPath = outPath.replace(/\.swift$/, ".plist.fragment.xml");
2563
+ writeFileSync(plistPath, result.output.infoPlistFragment, "utf-8");
2564
+ console.log(`\x1B[32m\u2713\x1B[0m Info.plist fragment \u2192 ${plistPath}`);
2565
+ }
2566
+ if (options.emitEntitlements && result.output.entitlementsFragment) {
2567
+ const entPath = outPath.replace(/\.swift$/, ".entitlements.fragment.xml");
2568
+ writeFileSync(entPath, result.output.entitlementsFragment, "utf-8");
2569
+ console.log(`\x1B[32m\u2713\x1B[0m Entitlements fragment \u2192 ${entPath}`);
2570
+ }
2571
+ }
2572
+ if (options.sandbox && !options.stdout) {
2573
+ try {
2574
+ const { sandboxCompile: sandboxCompile2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
2575
+ console.log();
2576
+ console.log(`\x1B[36m\u2192\x1B[0m Stage 4: SPM sandbox compile...`);
2577
+ const sandboxResult = await sandboxCompile2(result.output.swiftCode, {
2578
+ intentName: result.output.ir.name
2579
+ });
2580
+ if (sandboxResult.ok) {
2581
+ console.log(
2582
+ `\x1B[32m\u2713\x1B[0m Swift builds cleanly (${sandboxResult.durationMs}ms in ${sandboxResult.sandboxPath})`
2583
+ );
2584
+ } else {
2585
+ console.error(
2586
+ `\x1B[31m\u2717\x1B[0m Sandbox compile failed:
2587
+ ${sandboxResult.stderr}`
2588
+ );
2589
+ process.exit(1);
2590
+ }
2591
+ } catch (sbErr) {
2592
+ console.error(
2593
+ `\x1B[33mwarning:\x1B[0m sandbox compile skipped \u2014 ${sbErr.message}`
2594
+ );
2595
+ }
2596
+ }
2597
+ const warnings = result.diagnostics.filter(
2598
+ (d) => d.severity === "warning"
2599
+ ).length;
2600
+ if (warnings > 0) {
2601
+ console.log(` ${warnings} warning(s)`);
2602
+ }
2603
+ } catch (err) {
2604
+ if (err && typeof err === "object" && "format" in err && typeof err.format === "function") {
2605
+ console.error(err.format());
2606
+ } else {
2607
+ console.error(`\x1B[31merror:\x1B[0m ${err}`);
2608
+ }
2609
+ process.exit(1);
2610
+ }
2611
+ }
2612
+ );
2613
+ program.command("validate").description("Validate a TypeScript intent definition without generating output").argument("<file>", "Path to the TypeScript intent definition").option(
2614
+ "--sandbox",
2615
+ "Run stage 4 validation: swift build in an SPM sandbox (macOS only)"
2616
+ ).action(async (file, options) => {
2617
+ const filePath = resolve2(file);
2618
+ try {
2619
+ const result = compileFile(filePath, { validate: true });
2620
+ for (const d of result.diagnostics) {
2621
+ const prefix = d.severity === "error" ? "\x1B[31merror\x1B[0m" : d.severity === "warning" ? "\x1B[33mwarning\x1B[0m" : "\x1B[36minfo\x1B[0m";
2622
+ console.error(` ${prefix}[${d.code}]: ${d.message}`);
2623
+ if (d.suggestion) console.error(` = help: ${d.suggestion}`);
2624
+ }
2625
+ if (!result.success) {
2626
+ process.exit(1);
2627
+ }
2628
+ if (options.sandbox && result.output) {
2629
+ const { sandboxCompile: sandboxCompile2 } = await Promise.resolve().then(() => (init_sandbox(), sandbox_exports));
2630
+ console.log(`\x1B[36m\u2192\x1B[0m Stage 4: SPM sandbox compile...`);
2631
+ const sandboxResult = await sandboxCompile2(result.output.swiftCode, {
2632
+ intentName: result.output.ir.name
2633
+ });
2634
+ if (!sandboxResult.ok) {
2635
+ console.error(`\x1B[31m\u2717\x1B[0m ${sandboxResult.stderr}`);
2636
+ process.exit(1);
2637
+ }
2638
+ console.log(
2639
+ `\x1B[32m\u2713\x1B[0m Valid intent definition (sandbox-verified, ${sandboxResult.durationMs}ms)`
2640
+ );
2641
+ } else {
2642
+ console.log(`\x1B[32m\u2713\x1B[0m Valid intent definition`);
2643
+ }
2644
+ } catch (err) {
2645
+ if (err && typeof err === "object" && "format" in err && typeof err.format === "function") {
2646
+ console.error(err.format());
2647
+ } else {
2648
+ console.error(`\x1B[31merror:\x1B[0m ${err}`);
2649
+ }
2650
+ process.exit(1);
2651
+ }
2652
+ });
2653
+ program.command("eject").description("Eject an intent to standalone Swift with no Axint dependency").argument("<file>", "Path to the TypeScript intent definition").option("-o, --out <dir>", "Output directory for ejected files", ".").option("--include-tests", "Generate a basic XCTest file alongside the Swift").option(
2654
+ "--format",
2655
+ "Pipe generated Swift through swift-format with the Axint house style (macOS/Linux if swift-format is on $PATH)"
2656
+ ).action(
2657
+ async (file, options) => {
2658
+ const filePath = resolve2(file);
2659
+ try {
2660
+ let source;
2661
+ try {
2662
+ source = readFileSync3(filePath, "utf-8");
2663
+ } catch (_err) {
2664
+ console.error(`\x1B[31merror:\x1B[0m Cannot read file: ${filePath}`);
2665
+ process.exit(1);
2666
+ }
2667
+ const result = ejectIntent(source, basename(filePath), {
2668
+ outDir: options.out,
2669
+ includeTests: options.includeTests,
2670
+ format: options.format
2671
+ });
2672
+ const filesWritten = [];
2673
+ mkdirSync(dirname2(result.swiftFile.path), { recursive: true });
2674
+ writeFileSync(result.swiftFile.path, result.swiftFile.content, "utf-8");
2675
+ filesWritten.push("Swift");
2676
+ if (result.infoPlist) {
2677
+ writeFileSync(result.infoPlist.path, result.infoPlist.content, "utf-8");
2678
+ filesWritten.push("Info.plist fragment");
2679
+ }
2680
+ if (result.entitlements) {
2681
+ writeFileSync(result.entitlements.path, result.entitlements.content, "utf-8");
2682
+ filesWritten.push("entitlements fragment");
2683
+ }
2684
+ if (result.testFile) {
2685
+ writeFileSync(result.testFile.path, result.testFile.content, "utf-8");
2686
+ filesWritten.push("XCTest file");
2687
+ }
2688
+ console.log();
2689
+ console.log(
2690
+ `\x1B[32m\u2713\x1B[0m Ejected \u2192 ${filesWritten.length} file(s) (${filesWritten.join(", ")})`
2691
+ );
2692
+ console.log();
2693
+ console.log(` \x1B[1mOutput directory:\x1B[0m ${resolve2(options.out)}`);
2694
+ console.log();
2695
+ console.log(` These files are now standalone and have no Axint dependency.`);
2696
+ console.log(
2697
+ ` You can commit them to version control and use them in your project.`
2698
+ );
2699
+ console.log();
2700
+ } catch (err) {
2701
+ if (err && typeof err === "object" && "format" in err && typeof err.format === "function") {
2702
+ console.error(err.format());
2703
+ } else {
2704
+ console.error(`\x1B[31merror:\x1B[0m ${err.message ?? err}`);
2705
+ }
2706
+ process.exit(1);
2707
+ }
2708
+ }
2709
+ );
2710
+ program.command("templates").description("List bundled intent templates").argument("[name]", "Template name to print (omit to list all)").option("--json", "Output as JSON").action((name, options) => {
2711
+ if (!name) {
2712
+ const list = listTemplates();
2713
+ if (options.json) {
2714
+ console.log(JSON.stringify(list, null, 2));
2715
+ return;
2716
+ }
2717
+ console.log(` \x1B[1mBundled templates\x1B[0m (${list.length})`);
2718
+ console.log();
2719
+ for (const t of list) {
2720
+ console.log(
2721
+ ` \x1B[38;5;208m\u25C6\x1B[0m ${t.name} \x1B[2m\u2014 ${t.description}\x1B[0m`
2722
+ );
2723
+ }
2724
+ console.log();
2725
+ console.log(
2726
+ ` \x1B[2mUse: axint templates <name> or axint init -t <name>\x1B[0m`
2727
+ );
2728
+ return;
2729
+ }
2730
+ const tpl = getTemplate(name);
2731
+ if (!tpl) {
2732
+ console.error(`\x1B[31merror:\x1B[0m template "${name}" not found`);
2733
+ process.exit(1);
2734
+ }
2735
+ if (options.json) {
2736
+ console.log(JSON.stringify(tpl, null, 2));
2737
+ return;
2738
+ }
2739
+ console.log(tpl.source);
2740
+ });
2741
+ program.command("login").description("Authenticate with the Axint Registry via GitHub").action(async () => {
2742
+ const { homedir } = await import("os");
2743
+ const { join: join4 } = await import("path");
2744
+ const { spawn: spawn5 } = await import("child_process");
2745
+ const configDir = join4(homedir(), ".axint");
2746
+ const credPath = join4(configDir, "credentials.json");
2747
+ const registryUrl = process.env.AXINT_REGISTRY_URL ?? "https://registry.axint.ai";
2748
+ console.log();
2749
+ console.log(` \x1B[38;5;208m\u25C6\x1B[0m \x1B[1mAxint\x1B[0m \xB7 login`);
2750
+ console.log();
2751
+ try {
2752
+ const res = await fetch(`${registryUrl}/api/v1/auth/device-code`, {
2753
+ method: "POST",
2754
+ headers: { "Content-Type": "application/json" },
2755
+ body: JSON.stringify({ client_id: "axint-cli" })
2756
+ });
2757
+ if (!res.ok) {
2758
+ console.error(
2759
+ `\x1B[31merror:\x1B[0m Failed to start login flow (HTTP ${res.status})`
2760
+ );
2761
+ process.exit(1);
2762
+ }
2763
+ const { device_code, user_code, verification_uri, interval } = await res.json();
2764
+ console.log(` Open this URL in your browser:`);
2765
+ console.log();
2766
+ console.log(` \x1B[1;4m${verification_uri}\x1B[0m`);
2767
+ console.log();
2768
+ console.log(` And enter this code: \x1B[1;38;5;208m${user_code}\x1B[0m`);
2769
+ console.log();
2770
+ console.log(` \x1B[2mWaiting for authorization\u2026\x1B[0m`);
2771
+ try {
2772
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2773
+ spawn5(openCmd, [verification_uri], { stdio: "ignore", detached: true }).unref();
2774
+ } catch {
2775
+ }
2776
+ const pollInterval = (interval ?? 5) * 1e3;
2777
+ let token = null;
2778
+ for (let i = 0; i < 60; i++) {
2779
+ await new Promise((r) => setTimeout(r, pollInterval));
2780
+ const pollRes = await fetch(`${registryUrl}/api/v1/auth/token`, {
2781
+ method: "POST",
2782
+ headers: { "Content-Type": "application/json" },
2783
+ body: JSON.stringify({ device_code, grant_type: "device_code" })
2784
+ });
2785
+ if (pollRes.ok) {
2786
+ const data = await pollRes.json();
2787
+ token = data.access_token;
2788
+ break;
2789
+ }
2790
+ const err = await pollRes.json();
2791
+ if (err.error === "authorization_pending") continue;
2792
+ if (err.error === "slow_down") {
2793
+ await new Promise((r) => setTimeout(r, 5e3));
2794
+ continue;
2795
+ }
2796
+ if (err.error === "expired_token") {
2797
+ console.error(
2798
+ `\x1B[31merror:\x1B[0m Login timed out. Run \`axint login\` again.`
2799
+ );
2800
+ process.exit(1);
2801
+ }
2802
+ console.error(`\x1B[31merror:\x1B[0m ${err.error ?? "Unknown error"}`);
2803
+ process.exit(1);
2804
+ }
2805
+ if (!token) {
2806
+ console.error(`\x1B[31merror:\x1B[0m Login timed out after 5 minutes.`);
2807
+ process.exit(1);
2808
+ }
2809
+ mkdirSync(configDir, { recursive: true });
2810
+ writeFileSync(
2811
+ credPath,
2812
+ JSON.stringify({ access_token: token, registry: registryUrl }, null, 2),
2813
+ "utf-8"
2814
+ );
2815
+ console.log(
2816
+ ` \x1B[32m\u2713\x1B[0m Logged in! Credentials saved to \x1B[2m${credPath}\x1B[0m`
2817
+ );
2818
+ console.log();
2819
+ } catch (err) {
2820
+ console.error(`\x1B[31merror:\x1B[0m ${err.message ?? err}`);
2821
+ process.exit(1);
2822
+ }
2823
+ });
2824
+ program.command("publish").description("Publish an intent to the Axint Registry").option("--dry-run", "Validate and show what would be published without uploading").option("--tag <tags...>", "Override tags").action(async (options) => {
2825
+ const cwd = process.cwd();
2826
+ const configPath = resolve2(cwd, "axint.json");
2827
+ console.log();
2828
+ console.log(` \x1B[38;5;208m\u25C6\x1B[0m \x1B[1mAxint\x1B[0m \xB7 publish`);
2829
+ console.log();
2830
+ if (!existsSync3(configPath)) {
2831
+ console.error(` \x1B[31merror:\x1B[0m No axint.json found in ${cwd}`);
2832
+ console.error(` \x1B[2mRun \`axint init\` to create one.\x1B[0m`);
2833
+ process.exit(1);
2834
+ }
2835
+ let config;
2836
+ try {
2837
+ config = JSON.parse(readFileSync3(configPath, "utf-8"));
2838
+ } catch {
2839
+ console.error(` \x1B[31merror:\x1B[0m Failed to parse axint.json`);
2840
+ process.exit(1);
2841
+ }
2842
+ const entryFile = config.entry ?? "intent.ts";
2843
+ const entryPath = resolve2(cwd, entryFile);
2844
+ if (!existsSync3(entryPath)) {
2845
+ console.error(` \x1B[31merror:\x1B[0m Entry file not found: ${entryFile}`);
2846
+ process.exit(1);
2847
+ }
2848
+ console.log(` \x1B[2m\u23FA\x1B[0m Compiling ${entryFile}\u2026`);
2849
+ let result;
2850
+ try {
2851
+ result = await compileFile(entryPath, {});
2852
+ } catch (err) {
2853
+ console.error(` \x1B[31m\u2717\x1B[0m Compilation failed: ${err.message}`);
2854
+ process.exit(1);
2855
+ }
2856
+ if (!result.success || !result.output) {
2857
+ console.error(` \x1B[31m\u2717\x1B[0m Compilation failed`);
2858
+ for (const d of result.diagnostics) {
2859
+ console.error(` [${d.code}] ${d.message}`);
2860
+ }
2861
+ process.exit(1);
2862
+ }
2863
+ console.log(
2864
+ ` \x1B[32m\u2713\x1B[0m Compiled \u2192 ${result.output.swiftCode.split("\n").length} lines of Swift`
2865
+ );
2866
+ let readme;
2867
+ const readmePath = resolve2(cwd, config.readme ?? "README.md");
2868
+ if (existsSync3(readmePath)) {
2869
+ readme = readFileSync3(readmePath, "utf-8");
2870
+ }
2871
+ let pySource;
2872
+ const pyPath = resolve2(cwd, entryFile.replace(/\.ts$/, ".py"));
2873
+ if (existsSync3(pyPath)) {
2874
+ pySource = readFileSync3(pyPath, "utf-8");
2875
+ }
2876
+ const tags = options.tag ?? config.tags ?? [];
2877
+ const namespace = config.namespace.startsWith("@") ? config.namespace : `@${config.namespace}`;
2878
+ const payload = {
2879
+ namespace,
2880
+ slug: config.slug,
2881
+ name: config.name,
2882
+ version: config.version,
2883
+ description: config.description,
2884
+ readme,
2885
+ primary_language: config.primary_language ?? (pySource ? "both" : "typescript"),
2886
+ surface_areas: config.surface_areas ?? [],
2887
+ tags,
2888
+ license: config.license ?? "Apache-2.0",
2889
+ homepage: config.homepage,
2890
+ repository: config.repository,
2891
+ ts_source: readFileSync3(entryPath, "utf-8"),
2892
+ py_source: pySource,
2893
+ swift_output: result.output.swiftCode,
2894
+ plist_fragment: result.output.infoPlistFragment ?? null,
2895
+ ir: result.output.ir ?? {},
2896
+ compiler_version: VERSION
2897
+ };
2898
+ if (options.dryRun) {
2899
+ console.log(` \x1B[32m\u2713\x1B[0m Validation passed`);
2900
+ console.log();
2901
+ console.log(
2902
+ ` Would publish: \x1B[1m${namespace}/${config.slug}@${config.version}\x1B[0m`
2903
+ );
2904
+ console.log(
2905
+ ` Bundle size: ${Buffer.from(JSON.stringify(payload)).byteLength} bytes`
2906
+ );
2907
+ console.log(` Tags: ${tags.join(", ") || "(none)"}`);
2908
+ console.log();
2909
+ return;
2910
+ }
2911
+ const { homedir } = await import("os");
2912
+ const { join: join4 } = await import("path");
2913
+ const credPath = join4(homedir(), ".axint", "credentials.json");
2914
+ if (!existsSync3(credPath)) {
2915
+ console.error(` \x1B[31merror:\x1B[0m Not logged in. Run \`axint login\` first.`);
2916
+ process.exit(1);
2917
+ }
2918
+ let creds;
2919
+ try {
2920
+ creds = JSON.parse(readFileSync3(credPath, "utf-8"));
2921
+ } catch {
2922
+ console.error(
2923
+ ` \x1B[31merror:\x1B[0m Corrupt credentials file. Run \`axint login\` again.`
2924
+ );
2925
+ process.exit(1);
2926
+ }
2927
+ const registryUrl = creds.registry ?? "https://registry.axint.ai";
2928
+ console.log(` \x1B[2m\u23FA\x1B[0m Publishing to ${registryUrl}\u2026`);
2929
+ try {
2930
+ const res = await fetch(`${registryUrl}/api/v1/publish`, {
2931
+ method: "POST",
2932
+ headers: {
2933
+ "Content-Type": "application/json",
2934
+ Authorization: `Bearer ${creds.access_token}`,
2935
+ "X-Axint-Version": VERSION
2936
+ },
2937
+ body: JSON.stringify(payload)
2938
+ });
2939
+ if (!res.ok) {
2940
+ const err = await res.json().catch(() => ({ detail: res.statusText }));
2941
+ console.error(
2942
+ ` \x1B[31m\u2717\x1B[0m ${err.title ?? "Publish failed"}: ${err.detail ?? res.statusText}`
2943
+ );
2944
+ process.exit(1);
2945
+ }
2946
+ const data = await res.json();
2947
+ console.log(` \x1B[32m\u2713\x1B[0m Published!`);
2948
+ console.log();
2949
+ console.log(` ${data.url}`);
2950
+ console.log();
2951
+ console.log(` \x1B[2mInstall: axint add ${namespace}/${config.slug}\x1B[0m`);
2952
+ console.log();
2953
+ } catch (err) {
2954
+ console.error(` \x1B[31merror:\x1B[0m ${err.message ?? err}`);
2955
+ process.exit(1);
2956
+ }
2957
+ });
2958
+ program.command("add").description("Install a template from the Axint Registry").argument(
2959
+ "<package>",
2960
+ "Template to install (e.g., @axintai/create-event or @axintai/create-event@1.0.0)"
2961
+ ).option("--to <dir>", "Target directory", "intents").action(async (pkg3, options) => {
2962
+ console.log();
2963
+ console.log(` \x1B[38;5;208m\u25C6\x1B[0m \x1B[1mAxint\x1B[0m \xB7 add`);
2964
+ console.log();
2965
+ const match = pkg3.match(/^(@[a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)(?:@(.+))?$/);
2966
+ if (!match) {
2967
+ console.error(
2968
+ ` \x1B[31merror:\x1B[0m Invalid package format. Expected: @namespace/slug or @namespace/slug@version`
2969
+ );
2970
+ process.exit(1);
2971
+ }
2972
+ const [, namespace, slug, version] = match;
2973
+ const registryUrl = process.env.AXINT_REGISTRY_URL ?? "https://registry.axint.ai";
2974
+ console.log(
2975
+ ` \x1B[2m\u23FA\x1B[0m Fetching ${namespace}/${slug}${version ? `@${version}` : ""}\u2026`
2976
+ );
2977
+ try {
2978
+ const params = new URLSearchParams({ namespace, slug });
2979
+ if (version) params.set("version", version);
2980
+ const res = await fetch(`${registryUrl}/api/v1/install?${params}`, {
2981
+ headers: { "X-Axint-Version": VERSION }
2982
+ });
2983
+ if (!res.ok) {
2984
+ const err = await res.json().catch(() => ({ detail: res.statusText }));
2985
+ console.error(
2986
+ ` \x1B[31m\u2717\x1B[0m ${err.detail ?? `Template not found (HTTP ${res.status})`}`
2987
+ );
2988
+ process.exit(1);
2989
+ }
2990
+ const data = await res.json();
2991
+ const targetDir = resolve2(options.to, slug);
2992
+ mkdirSync(targetDir, { recursive: true });
2993
+ const ver = data.version;
2994
+ const filesWritten = [];
2995
+ if (ver.ts_source) {
2996
+ writeFileSync(resolve2(targetDir, "intent.ts"), ver.ts_source, "utf-8");
2997
+ filesWritten.push("intent.ts");
2998
+ }
2999
+ if (ver.py_source) {
3000
+ writeFileSync(resolve2(targetDir, "intent.py"), ver.py_source, "utf-8");
3001
+ filesWritten.push("intent.py");
3002
+ }
3003
+ writeFileSync(resolve2(targetDir, "intent.swift"), ver.swift_output, "utf-8");
3004
+ filesWritten.push("intent.swift");
3005
+ console.log(
3006
+ ` \x1B[32m\u2713\x1B[0m Installed ${data.template.full_name}@${ver.version}`
3007
+ );
3008
+ console.log(` \u2192 ${targetDir}/`);
3009
+ filesWritten.forEach((f) => console.log(` ${f}`));
3010
+ console.log();
3011
+ console.log(` \x1B[1mNext:\x1B[0m`);
3012
+ console.log(` axint compile ${options.to}/${slug}/intent.ts --out ios/Intents/`);
3013
+ console.log();
3014
+ } catch (err) {
3015
+ console.error(` \x1B[31merror:\x1B[0m ${err.message ?? err}`);
3016
+ process.exit(1);
3017
+ }
3018
+ });
3019
+ program.command("search").description("Search the Axint Registry for intent templates").argument("[query]", "Search term (lists popular packages if omitted)").option("--limit <n>", "Max results", "20").option("--json", "Output as JSON").action(
3020
+ async (query, options) => {
3021
+ const registryUrl = process.env.AXINT_REGISTRY_URL ?? "https://registry.axint.ai";
3022
+ const limit = Math.max(1, Math.min(100, parseInt(options.limit, 10) || 20));
3023
+ console.log();
3024
+ if (!options.json) {
3025
+ console.log(
3026
+ ` \x1B[38;5;208m\u25C6\x1B[0m \x1B[1mAxint\x1B[0m \xB7 search ${query ? `"${query}"` : ""}`
3027
+ );
3028
+ console.log();
3029
+ }
3030
+ try {
3031
+ const params = new URLSearchParams({ limit: String(limit) });
3032
+ if (query) params.set("q", query);
3033
+ const res = await fetch(`${registryUrl}/api/v1/search?${params}`, {
3034
+ headers: { "X-Axint-Version": VERSION }
3035
+ });
3036
+ if (!res.ok) {
3037
+ console.error(`\x1B[31merror:\x1B[0m Search failed (HTTP ${res.status})`);
3038
+ process.exit(1);
3039
+ }
3040
+ const data = await res.json();
3041
+ if (options.json) {
3042
+ console.log(JSON.stringify(data, null, 2));
3043
+ return;
3044
+ }
3045
+ if (data.results.length === 0) {
3046
+ console.log(` No packages found`);
3047
+ console.log();
3048
+ return;
3049
+ }
3050
+ for (const pkg3 of data.results) {
3051
+ const downloads = pkg3.downloads > 0 ? `\u25BC ${pkg3.downloads}` : "";
3052
+ const dl = downloads ? ` \x1B[2m${downloads}\x1B[0m` : "";
3053
+ console.log(
3054
+ ` \x1B[38;5;208m\u25C6\x1B[0m ${pkg3.package_name.padEnd(30)} ${pkg3.description.substring(0, 35).padEnd(35)}${dl}`
3055
+ );
3056
+ }
3057
+ console.log();
3058
+ console.log(
3059
+ ` ${data.results.length} package${data.results.length === 1 ? "" : "s"} found`
3060
+ );
3061
+ console.log();
3062
+ console.log(
3063
+ ` \x1B[2mInstall:\x1B[0m axint add ${data.results[0]?.package_name ?? "@namespace/slug"}`
3064
+ );
3065
+ console.log();
3066
+ } catch (err) {
3067
+ console.error(`\x1B[31merror:\x1B[0m ${err.message ?? err}`);
3068
+ process.exit(1);
3069
+ }
3070
+ }
3071
+ );
3072
+ async function runSwiftBuild2(projectPath) {
3073
+ return new Promise((resolve3) => {
3074
+ const t0 = performance.now();
3075
+ const proc = spawn4("swift", ["build"], {
3076
+ cwd: projectPath,
3077
+ stdio: ["ignore", "inherit", "inherit"]
3078
+ });
3079
+ proc.on("close", (code) => {
3080
+ if (code === 0) {
3081
+ const dt = (performance.now() - t0).toFixed(0);
3082
+ console.log();
3083
+ console.log(`\x1B[38;5;208m\u2500 swift build\x1B[0m`);
3084
+ console.log(`\x1B[32m\u2713\x1B[0m Build succeeded \x1B[90m(${dt}ms)\x1B[0m`);
3085
+ console.log();
3086
+ resolve3(true);
3087
+ } else {
3088
+ console.log();
3089
+ console.log(`\x1B[38;5;208m\u2500 swift build\x1B[0m`);
3090
+ console.log(`\x1B[31m\u2717\x1B[0m Build failed (exit code: ${code})`);
3091
+ console.log("\x1B[90mContinuing to watch for changes\u2026\x1B[0m");
3092
+ console.log();
3093
+ resolve3(false);
3094
+ }
3095
+ });
3096
+ proc.on("error", (err) => {
3097
+ console.log();
3098
+ console.log(`\x1B[38;5;208m\u2500 swift build\x1B[0m`);
3099
+ console.error(`\x1B[31m\u2717\x1B[0m Error: ${err.message}`);
3100
+ console.log("\x1B[90mContinuing to watch for changes\u2026\x1B[0m");
3101
+ console.log();
3102
+ resolve3(false);
3103
+ });
3104
+ });
3105
+ }
3106
+ program.command("watch").description("Watch intent files and recompile on change").argument("<file>", "Path to a TypeScript intent file or directory of intents").option("-o, --out <dir>", "Output directory for generated Swift", ".").option("--no-validate", "Skip validation of generated Swift").option("--emit-info-plist", "Emit Info.plist fragments alongside Swift files").option("--emit-entitlements", "Emit entitlements fragments alongside Swift files").option("--format", "Pipe generated Swift through swift-format").option(
3107
+ "--strict-format",
3108
+ "Fail if swift-format is missing or errors (implies --format)"
3109
+ ).option(
3110
+ "--swift-build",
3111
+ "Run `swift build` in the project after successful compilation"
3112
+ ).option(
3113
+ "--swift-project <path>",
3114
+ "Path to the Swift project root (defaults to --out parent directory)"
3115
+ ).action(
3116
+ async (file, options) => {
3117
+ const target = resolve2(file);
3118
+ const isDir = existsSync3(target) && statSync(target).isDirectory();
3119
+ const filesToWatch = [];
3120
+ if (isDir) {
3121
+ const { readdirSync } = await import("fs");
3122
+ for (const entry of readdirSync(target)) {
3123
+ if (entry.endsWith(".ts") && !entry.endsWith(".d.ts")) {
3124
+ filesToWatch.push(resolve2(target, entry));
3125
+ }
3126
+ }
3127
+ if (filesToWatch.length === 0) {
3128
+ console.error(`\x1B[31merror:\x1B[0m No .ts files found in ${target}`);
3129
+ process.exit(1);
3130
+ }
3131
+ } else {
3132
+ if (!existsSync3(target)) {
3133
+ console.error(`\x1B[31merror:\x1B[0m File not found: ${target}`);
3134
+ process.exit(1);
3135
+ }
3136
+ filesToWatch.push(target);
3137
+ }
3138
+ const swiftProjectPath = options.swiftProject ?? dirname2(resolve2(options.out));
3139
+ function compileOne(filePath) {
3140
+ const t0 = performance.now();
3141
+ const result = compileFile(filePath, {
3142
+ outDir: options.out,
3143
+ validate: options.validate,
3144
+ emitInfoPlist: options.emitInfoPlist,
3145
+ emitEntitlements: options.emitEntitlements
3146
+ });
3147
+ for (const d of result.diagnostics) {
3148
+ const prefix = d.severity === "error" ? "\x1B[31merror\x1B[0m" : d.severity === "warning" ? "\x1B[33mwarning\x1B[0m" : "\x1B[36minfo\x1B[0m";
3149
+ console.error(` ${prefix}[${d.code}]: ${d.message}`);
3150
+ if (d.file) console.error(` --> ${d.file}${d.line ? `:${d.line}` : ""}`);
3151
+ if (d.suggestion) console.error(` = help: ${d.suggestion}`);
3152
+ }
3153
+ if (!result.success || !result.output) {
3154
+ const errors = result.diagnostics.filter((d) => d.severity === "error").length;
3155
+ console.error(`\x1B[31m\u2717\x1B[0m ${basename(filePath)} \u2014 ${errors} error(s)`);
3156
+ return false;
3157
+ }
3158
+ const swiftCode = result.output.swiftCode;
3159
+ if (options.format || options.strictFormat) {
3160
+ }
3161
+ const outPath = resolve2(result.output.outputPath);
3162
+ mkdirSync(dirname2(outPath), { recursive: true });
3163
+ writeFileSync(outPath, swiftCode, "utf-8");
3164
+ if (options.emitInfoPlist && result.output.infoPlistFragment) {
3165
+ const plistPath = outPath.replace(/\.swift$/, ".plist.fragment.xml");
3166
+ writeFileSync(plistPath, result.output.infoPlistFragment, "utf-8");
3167
+ }
3168
+ if (options.emitEntitlements && result.output.entitlementsFragment) {
3169
+ const entPath = outPath.replace(/\.swift$/, ".entitlements.fragment.xml");
3170
+ writeFileSync(entPath, result.output.entitlementsFragment, "utf-8");
3171
+ }
3172
+ const dt = (performance.now() - t0).toFixed(1);
3173
+ console.log(
3174
+ `\x1B[32m\u2713\x1B[0m ${result.output.ir.name} \u2192 ${outPath} \x1B[90m(${dt}ms)\x1B[0m`
3175
+ );
3176
+ return true;
3177
+ }
3178
+ async function compileWithFormat(filePath) {
3179
+ if (!options.format && !options.strictFormat) {
3180
+ return compileOne(filePath);
3181
+ }
3182
+ const t0 = performance.now();
3183
+ const result = compileFile(filePath, {
3184
+ outDir: options.out,
3185
+ validate: options.validate,
3186
+ emitInfoPlist: options.emitInfoPlist,
3187
+ emitEntitlements: options.emitEntitlements
3188
+ });
3189
+ for (const d of result.diagnostics) {
3190
+ const prefix = d.severity === "error" ? "\x1B[31merror\x1B[0m" : d.severity === "warning" ? "\x1B[33mwarning\x1B[0m" : "\x1B[36minfo\x1B[0m";
3191
+ console.error(` ${prefix}[${d.code}]: ${d.message}`);
3192
+ if (d.file) console.error(` --> ${d.file}${d.line ? `:${d.line}` : ""}`);
3193
+ if (d.suggestion) console.error(` = help: ${d.suggestion}`);
3194
+ }
3195
+ if (!result.success || !result.output) {
3196
+ const errors = result.diagnostics.filter((d) => d.severity === "error").length;
3197
+ console.error(`\x1B[31m\u2717\x1B[0m ${basename(filePath)} \u2014 ${errors} error(s)`);
3198
+ return false;
3199
+ }
3200
+ let swiftCode = result.output.swiftCode;
3201
+ try {
3202
+ const { formatSwift: formatSwift2 } = await Promise.resolve().then(() => (init_format(), format_exports));
3203
+ const fmt = await formatSwift2(swiftCode, { strict: options.strictFormat });
3204
+ if (fmt.ran) swiftCode = fmt.formatted;
3205
+ } catch (fmtErr) {
3206
+ if (options.strictFormat) {
3207
+ console.error(`\x1B[31merror:\x1B[0m ${fmtErr.message}`);
3208
+ return false;
3209
+ }
3210
+ }
3211
+ const outPath = resolve2(result.output.outputPath);
3212
+ mkdirSync(dirname2(outPath), { recursive: true });
3213
+ writeFileSync(outPath, swiftCode, "utf-8");
3214
+ if (options.emitInfoPlist && result.output.infoPlistFragment) {
3215
+ writeFileSync(
3216
+ outPath.replace(/\.swift$/, ".plist.fragment.xml"),
3217
+ result.output.infoPlistFragment,
3218
+ "utf-8"
3219
+ );
3220
+ }
3221
+ if (options.emitEntitlements && result.output.entitlementsFragment) {
3222
+ writeFileSync(
3223
+ outPath.replace(/\.swift$/, ".entitlements.fragment.xml"),
3224
+ result.output.entitlementsFragment,
3225
+ "utf-8"
3226
+ );
3227
+ }
3228
+ const dt = (performance.now() - t0).toFixed(1);
3229
+ console.log(
3230
+ `\x1B[32m\u2713\x1B[0m ${result.output.ir.name} \u2192 ${outPath} \x1B[90m(${dt}ms)\x1B[0m`
3231
+ );
3232
+ return true;
3233
+ }
3234
+ console.log(`\x1B[1maxint watch\x1B[0m \u2014 ${filesToWatch.length} file(s)
3235
+ `);
3236
+ let ok = 0;
3237
+ let fail = 0;
3238
+ for (const f of filesToWatch) {
3239
+ if (await compileWithFormat(f)) {
3240
+ ok++;
3241
+ } else {
3242
+ fail++;
3243
+ }
3244
+ }
3245
+ console.log();
3246
+ if (fail > 0) {
3247
+ console.log(
3248
+ `\x1B[33m\u26A0\x1B[0m ${ok} compiled, ${fail} failed \u2014 watching for changes\u2026
3249
+ `
3250
+ );
3251
+ } else {
3252
+ console.log(`\x1B[32m\u2713\x1B[0m ${ok} compiled \u2014 watching for changes\u2026
3253
+ `);
3254
+ if (options.swiftBuild) {
3255
+ await runSwiftBuild2(swiftProjectPath);
3256
+ }
3257
+ }
3258
+ const pending = /* @__PURE__ */ new Map();
3259
+ const DEBOUNCE_MS = 150;
3260
+ let batchInProgress = false;
3261
+ function onFileChange(filePath) {
3262
+ const existing = pending.get(filePath);
3263
+ if (existing) clearTimeout(existing);
3264
+ pending.set(
3265
+ filePath,
3266
+ setTimeout(async () => {
3267
+ pending.delete(filePath);
3268
+ if (batchInProgress) return;
3269
+ batchInProgress = true;
3270
+ try {
3271
+ const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
3272
+ console.log(`\x1B[90m[${now}]\x1B[0m ${basename(filePath)} changed`);
3273
+ const compiled = await compileWithFormat(filePath);
3274
+ console.log();
3275
+ if (compiled && options.swiftBuild) {
3276
+ await runSwiftBuild2(swiftProjectPath);
3277
+ }
3278
+ } finally {
3279
+ batchInProgress = false;
3280
+ }
3281
+ }, DEBOUNCE_MS)
3282
+ );
3283
+ }
3284
+ if (isDir) {
3285
+ const dirWatcher = fsWatch(target, { persistent: true }, (_event, filename) => {
3286
+ if (filename && typeof filename === "string" && filename.endsWith(".ts") && !filename.endsWith(".d.ts")) {
3287
+ onFileChange(resolve2(target, filename));
3288
+ }
3289
+ });
3290
+ dirWatcher.on("error", (err) => {
3291
+ console.error(`\x1B[31m\u2717\x1B[0m watcher error: ${err.message}`);
3292
+ });
3293
+ } else {
3294
+ const parentDir = dirname2(target);
3295
+ const targetBase = basename(target);
3296
+ const fileWatcher = fsWatch(
3297
+ parentDir,
3298
+ { persistent: true },
3299
+ (_event, filename) => {
3300
+ if (filename === targetBase) {
3301
+ onFileChange(target);
3302
+ }
3303
+ }
3304
+ );
3305
+ fileWatcher.on("error", (err) => {
3306
+ console.error(`\x1B[31m\u2717\x1B[0m watcher error: ${err.message}`);
3307
+ });
3308
+ }
3309
+ process.on("SIGINT", () => {
3310
+ console.log("\n\x1B[90mStopped watching.\x1B[0m");
3311
+ process.exit(0);
3312
+ });
3313
+ }
3314
+ );
3315
+ program.command("mcp").description(
3316
+ "Start the Axint MCP server (stdio) for Claude Code, Cursor, Windsurf, Zed, or any MCP client"
3317
+ ).action(async () => {
3318
+ const { startMCPServer: startMCPServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
3319
+ await startMCPServer2();
3320
+ });
3321
+ function __axintExistsSync(p) {
3322
+ return existsSync3(p);
3323
+ }
3324
+ program.parse();
3325
+ export {
3326
+ __axintExistsSync
3327
+ };
868
3328
  //# sourceMappingURL=index.js.map