@axintai/compiler 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -9
- package/dist/cli/index.js +2332 -178
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.d.ts +125 -3
- package/dist/core/index.js +592 -42
- package/dist/core/index.js.map +1 -1
- package/dist/mcp/index.js +410 -33
- package/dist/mcp/index.js.map +1 -1
- package/dist/sdk/index.d.ts +55 -2
- package/dist/sdk/index.js +30 -1
- package/dist/sdk/index.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -51,6 +51,10 @@ function irTypeToSwift(type) {
|
|
|
51
51
|
return `${irTypeToSwift(type.innerType)}?`;
|
|
52
52
|
case "entity":
|
|
53
53
|
return type.entityName;
|
|
54
|
+
case "entityQuery":
|
|
55
|
+
return `${type.entityName}Query`;
|
|
56
|
+
case "dynamicOptions":
|
|
57
|
+
return `[DynamicOptionsResult<${irTypeToSwift(type.valueType)}>]`;
|
|
54
58
|
case "enum":
|
|
55
59
|
return type.name;
|
|
56
60
|
}
|
|
@@ -66,6 +70,9 @@ function parseIntentSource(source, filePath = "<stdin>") {
|
|
|
66
70
|
// setParentNodes
|
|
67
71
|
ts.ScriptKind.TS
|
|
68
72
|
);
|
|
73
|
+
const entities = findDefineEntityCalls(sourceFile).map(
|
|
74
|
+
(call) => parseEntityDefinition(call, filePath, sourceFile)
|
|
75
|
+
);
|
|
69
76
|
const defineIntentCall = findDefineIntentCall(sourceFile);
|
|
70
77
|
if (!defineIntentCall) {
|
|
71
78
|
throw new ParserError(
|
|
@@ -128,6 +135,10 @@ function parseIntentSource(source, filePath = "<stdin>") {
|
|
|
128
135
|
const entitlements = readStringArray(entitlementsNode);
|
|
129
136
|
const infoPlistNode = props.get("infoPlistKeys");
|
|
130
137
|
const infoPlistKeys = readStringRecord(infoPlistNode);
|
|
138
|
+
const donateOnPerformNode = props.get("donateOnPerform");
|
|
139
|
+
const donateOnPerform = readBooleanLiteral(donateOnPerformNode);
|
|
140
|
+
const customResultTypeNode = props.get("customResultType");
|
|
141
|
+
const customResultType = readStringLiteral(customResultTypeNode);
|
|
131
142
|
return {
|
|
132
143
|
name,
|
|
133
144
|
title,
|
|
@@ -139,7 +150,10 @@ function parseIntentSource(source, filePath = "<stdin>") {
|
|
|
139
150
|
sourceFile: filePath,
|
|
140
151
|
entitlements: entitlements.length > 0 ? entitlements : void 0,
|
|
141
152
|
infoPlistKeys: Object.keys(infoPlistKeys).length > 0 ? infoPlistKeys : void 0,
|
|
142
|
-
isDiscoverable: isDiscoverable ?? void 0
|
|
153
|
+
isDiscoverable: isDiscoverable ?? void 0,
|
|
154
|
+
entities: entities.length > 0 ? entities : void 0,
|
|
155
|
+
donateOnPerform: donateOnPerform ?? void 0,
|
|
156
|
+
customResultType: customResultType ?? void 0
|
|
143
157
|
};
|
|
144
158
|
}
|
|
145
159
|
function findDefineIntentCall(node) {
|
|
@@ -155,6 +169,18 @@ function findDefineIntentCall(node) {
|
|
|
155
169
|
visit(node);
|
|
156
170
|
return found;
|
|
157
171
|
}
|
|
172
|
+
function findDefineEntityCalls(node) {
|
|
173
|
+
const found = [];
|
|
174
|
+
const visit = (n) => {
|
|
175
|
+
if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === "defineEntity") {
|
|
176
|
+
found.push(n);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
ts.forEachChild(n, visit);
|
|
180
|
+
};
|
|
181
|
+
visit(node);
|
|
182
|
+
return found;
|
|
183
|
+
}
|
|
158
184
|
function propertyMap(obj) {
|
|
159
185
|
const map = /* @__PURE__ */ new Map();
|
|
160
186
|
for (const prop of obj.properties) {
|
|
@@ -208,6 +234,77 @@ function readStringRecord(node) {
|
|
|
208
234
|
}
|
|
209
235
|
return rec;
|
|
210
236
|
}
|
|
237
|
+
function parseEntityDefinition(call, filePath, sourceFile) {
|
|
238
|
+
const arg = call.arguments[0];
|
|
239
|
+
if (!arg || !ts.isObjectLiteralExpression(arg)) {
|
|
240
|
+
throw new ParserError(
|
|
241
|
+
"AX015",
|
|
242
|
+
"defineEntity() must be called with an object literal",
|
|
243
|
+
filePath,
|
|
244
|
+
posOf(sourceFile, call),
|
|
245
|
+
"Pass an object: defineEntity({ name, display, properties, query })"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const props = propertyMap(arg);
|
|
249
|
+
const name = readStringLiteral(props.get("name"));
|
|
250
|
+
if (!name) {
|
|
251
|
+
throw new ParserError(
|
|
252
|
+
"AX016",
|
|
253
|
+
"Entity definition missing required field: name",
|
|
254
|
+
filePath,
|
|
255
|
+
posOf(sourceFile, arg),
|
|
256
|
+
'Add a name field: name: "Task"'
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const displayNode = props.get("display");
|
|
260
|
+
if (!displayNode || !ts.isObjectLiteralExpression(displayNode)) {
|
|
261
|
+
throw new ParserError(
|
|
262
|
+
"AX017",
|
|
263
|
+
"Entity definition missing required field: display",
|
|
264
|
+
filePath,
|
|
265
|
+
posOf(sourceFile, arg),
|
|
266
|
+
'Add display field: display: { title: "name", subtitle: "status" }'
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
const displayProps = propertyMap(displayNode);
|
|
270
|
+
const displayRepresentation = {
|
|
271
|
+
title: readStringLiteral(displayProps.get("title")) || "name",
|
|
272
|
+
subtitle: readStringLiteral(displayProps.get("subtitle")) || void 0,
|
|
273
|
+
image: readStringLiteral(displayProps.get("image")) || void 0
|
|
274
|
+
};
|
|
275
|
+
const propertiesNode = props.get("properties");
|
|
276
|
+
const properties = propertiesNode ? extractParameters(propertiesNode, filePath, sourceFile) : [];
|
|
277
|
+
const queryTypeNode = props.get("query");
|
|
278
|
+
const queryTypeStr = readStringLiteral(queryTypeNode);
|
|
279
|
+
const queryType = validateQueryType(queryTypeStr, filePath, sourceFile, queryTypeNode);
|
|
280
|
+
return {
|
|
281
|
+
name,
|
|
282
|
+
displayRepresentation,
|
|
283
|
+
properties,
|
|
284
|
+
queryType
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function validateQueryType(value, filePath, sourceFile, node) {
|
|
288
|
+
if (!value) {
|
|
289
|
+
throw new ParserError(
|
|
290
|
+
"AX018",
|
|
291
|
+
"Entity definition missing required field: query",
|
|
292
|
+
filePath,
|
|
293
|
+
node ? posOf(sourceFile, node) : void 0,
|
|
294
|
+
'Add query field: query: "string" (or "all", "id", "property")'
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const valid = ["all", "id", "string", "property"];
|
|
298
|
+
if (!valid.includes(value)) {
|
|
299
|
+
throw new ParserError(
|
|
300
|
+
"AX019",
|
|
301
|
+
`Invalid query type: "${value}". Must be one of: all, id, string, property`,
|
|
302
|
+
filePath,
|
|
303
|
+
node ? posOf(sourceFile, node) : void 0
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return value;
|
|
307
|
+
}
|
|
211
308
|
function extractParameters(node, filePath, sourceFile) {
|
|
212
309
|
if (!ts.isObjectLiteralExpression(node)) {
|
|
213
310
|
throw new ParserError(
|
|
@@ -223,20 +320,20 @@ function extractParameters(node, filePath, sourceFile) {
|
|
|
223
320
|
if (!ts.isPropertyAssignment(prop)) continue;
|
|
224
321
|
const paramName = propertyKeyName(prop.name);
|
|
225
322
|
if (!paramName) continue;
|
|
226
|
-
const { typeName, description, configObject } = extractParamCall(
|
|
323
|
+
const { typeName, description, configObject, callExpr } = extractParamCall(
|
|
227
324
|
prop.initializer,
|
|
228
325
|
filePath,
|
|
229
326
|
sourceFile
|
|
230
327
|
);
|
|
231
|
-
const resolvedType = resolveParamType(typeName, filePath, sourceFile, prop);
|
|
328
|
+
const resolvedType = resolveParamType(typeName, filePath, sourceFile, prop, callExpr);
|
|
232
329
|
const isOptional = configObject ? readBooleanLiteral(configObject.get("required")) === false : false;
|
|
233
330
|
const defaultExpr = configObject?.get("default");
|
|
234
331
|
const defaultValue = defaultExpr ? evaluateLiteral(defaultExpr) : void 0;
|
|
235
332
|
const titleFromConfig = configObject ? readStringLiteral(configObject.get("title")) : null;
|
|
236
333
|
const irType = isOptional ? {
|
|
237
334
|
kind: "optional",
|
|
238
|
-
innerType:
|
|
239
|
-
} :
|
|
335
|
+
innerType: resolvedType
|
|
336
|
+
} : resolvedType;
|
|
240
337
|
params.push({
|
|
241
338
|
name: paramName,
|
|
242
339
|
type: irType,
|
|
@@ -268,34 +365,98 @@ function extractParamCall(expr, filePath, sourceFile) {
|
|
|
268
365
|
);
|
|
269
366
|
}
|
|
270
367
|
const typeName = expr.expression.name.text;
|
|
271
|
-
|
|
272
|
-
|
|
368
|
+
let descriptionArg;
|
|
369
|
+
let configArg;
|
|
370
|
+
if (typeName === "entity" && expr.arguments.length >= 2) {
|
|
371
|
+
descriptionArg = expr.arguments[1];
|
|
372
|
+
configArg = expr.arguments[2];
|
|
373
|
+
} else if (typeName === "dynamicOptions" && expr.arguments.length >= 3) {
|
|
374
|
+
descriptionArg = expr.arguments[2];
|
|
375
|
+
configArg = expr.arguments[3];
|
|
376
|
+
} else {
|
|
377
|
+
descriptionArg = expr.arguments[0];
|
|
378
|
+
configArg = expr.arguments[1];
|
|
379
|
+
}
|
|
273
380
|
const description = descriptionArg ? readStringLiteral(descriptionArg) : null;
|
|
274
381
|
if (description === null) {
|
|
275
382
|
throw new ParserError(
|
|
276
383
|
"AX008",
|
|
277
|
-
`param.${typeName}() requires a string description
|
|
384
|
+
`param.${typeName}() requires a string description`,
|
|
278
385
|
filePath,
|
|
279
386
|
posOf(sourceFile, expr),
|
|
280
387
|
`Example: param.${typeName}("Human-readable description")`
|
|
281
388
|
);
|
|
282
389
|
}
|
|
283
390
|
const configObject = configArg && ts.isObjectLiteralExpression(configArg) ? propertyMap(configArg) : null;
|
|
284
|
-
return { typeName, description, configObject };
|
|
391
|
+
return { typeName, description, configObject, callExpr: expr };
|
|
285
392
|
}
|
|
286
|
-
function resolveParamType(typeName, filePath, sourceFile, node) {
|
|
393
|
+
function resolveParamType(typeName, filePath, sourceFile, node, callExpr) {
|
|
287
394
|
if (PARAM_TYPES.has(typeName)) {
|
|
288
|
-
return typeName;
|
|
395
|
+
return { kind: "primitive", value: typeName };
|
|
289
396
|
}
|
|
290
397
|
if (typeName in LEGACY_PARAM_ALIASES) {
|
|
291
|
-
return
|
|
398
|
+
return {
|
|
399
|
+
kind: "primitive",
|
|
400
|
+
value: LEGACY_PARAM_ALIASES[typeName]
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (typeName === "entity") {
|
|
404
|
+
if (!callExpr || callExpr.arguments.length === 0) {
|
|
405
|
+
throw new ParserError(
|
|
406
|
+
"AX020",
|
|
407
|
+
"param.entity() requires the entity name as the first argument",
|
|
408
|
+
filePath,
|
|
409
|
+
posOf(sourceFile, node),
|
|
410
|
+
'Example: param.entity("Task", "Reference an entity")'
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
const entityName = readStringLiteral(callExpr.arguments[0]);
|
|
414
|
+
if (!entityName) {
|
|
415
|
+
throw new ParserError(
|
|
416
|
+
"AX021",
|
|
417
|
+
"param.entity() requires a string entity name",
|
|
418
|
+
filePath,
|
|
419
|
+
posOf(sourceFile, node)
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
kind: "entity",
|
|
424
|
+
entityName,
|
|
425
|
+
properties: []
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (typeName === "dynamicOptions") {
|
|
429
|
+
if (!callExpr || callExpr.arguments.length < 2) {
|
|
430
|
+
throw new ParserError(
|
|
431
|
+
"AX022",
|
|
432
|
+
"param.dynamicOptions() requires (providerName, paramType)",
|
|
433
|
+
filePath,
|
|
434
|
+
posOf(sourceFile, node),
|
|
435
|
+
'Example: param.dynamicOptions("PlaylistProvider", param.string(...))'
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
const providerName = readStringLiteral(callExpr.arguments[0]);
|
|
439
|
+
if (!providerName) {
|
|
440
|
+
throw new ParserError(
|
|
441
|
+
"AX023",
|
|
442
|
+
"param.dynamicOptions() provider name must be a string",
|
|
443
|
+
filePath,
|
|
444
|
+
posOf(sourceFile, node)
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
const valueType = { kind: "primitive", value: "string" };
|
|
448
|
+
return {
|
|
449
|
+
kind: "dynamicOptions",
|
|
450
|
+
valueType,
|
|
451
|
+
providerName
|
|
452
|
+
};
|
|
292
453
|
}
|
|
293
454
|
throw new ParserError(
|
|
294
455
|
"AX005",
|
|
295
456
|
`Unknown param type: param.${typeName}`,
|
|
296
457
|
filePath,
|
|
297
458
|
posOf(sourceFile, node),
|
|
298
|
-
`Supported types: ${[...PARAM_TYPES].join(", ")}`
|
|
459
|
+
`Supported types: ${[...PARAM_TYPES].join(", ")}, entity, dynamicOptions`
|
|
299
460
|
);
|
|
300
461
|
}
|
|
301
462
|
function evaluateLiteral(node) {
|
|
@@ -413,6 +574,14 @@ function generateSwift(intent) {
|
|
|
413
574
|
lines.push(`import AppIntents`);
|
|
414
575
|
lines.push(`import Foundation`);
|
|
415
576
|
lines.push(``);
|
|
577
|
+
if (intent.entities && intent.entities.length > 0) {
|
|
578
|
+
for (const entity of intent.entities) {
|
|
579
|
+
lines.push(generateEntity(entity));
|
|
580
|
+
lines.push(``);
|
|
581
|
+
lines.push(generateEntityQuery(entity));
|
|
582
|
+
lines.push(``);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
416
585
|
lines.push(`struct ${intent.name}Intent: AppIntent {`);
|
|
417
586
|
lines.push(
|
|
418
587
|
` static let title: LocalizedStringResource = "${escapeSwiftString(intent.title)}"`
|
|
@@ -430,27 +599,109 @@ function generateSwift(intent) {
|
|
|
430
599
|
if (intent.parameters.length > 0) {
|
|
431
600
|
lines.push(``);
|
|
432
601
|
}
|
|
433
|
-
const returnTypeSignature = generateReturnSignature(
|
|
602
|
+
const returnTypeSignature = generateReturnSignature(
|
|
603
|
+
intent.returnType,
|
|
604
|
+
intent.customResultType
|
|
605
|
+
);
|
|
434
606
|
lines.push(` func perform() async throws -> ${returnTypeSignature} {`);
|
|
435
607
|
lines.push(` // TODO: Implement your intent logic here.`);
|
|
436
608
|
if (intent.parameters.length > 0) {
|
|
437
609
|
const paramList = intent.parameters.map((p) => `\\(${p.name})`).join(", ");
|
|
438
610
|
lines.push(` // Parameters available: ${paramList}`);
|
|
439
611
|
}
|
|
440
|
-
|
|
612
|
+
if (intent.donateOnPerform) {
|
|
613
|
+
lines.push(` `);
|
|
614
|
+
lines.push(` // Donate this intent to Siri and Spotlight`);
|
|
615
|
+
lines.push(` try? await IntentDonationManager.shared.donate(intent: self)`);
|
|
616
|
+
}
|
|
617
|
+
lines.push(generatePerformReturn(intent.returnType, intent.customResultType));
|
|
441
618
|
lines.push(` }`);
|
|
442
619
|
lines.push(`}`);
|
|
443
620
|
lines.push(``);
|
|
444
621
|
return lines.join("\n");
|
|
445
622
|
}
|
|
623
|
+
function generateEntity(entity) {
|
|
624
|
+
const lines = [];
|
|
625
|
+
const propertyNames = new Set(entity.properties.map((p) => p.name));
|
|
626
|
+
lines.push(`struct ${entity.name}: AppEntity {`);
|
|
627
|
+
lines.push(` static var defaultQuery = ${entity.name}Query()`);
|
|
628
|
+
lines.push(``);
|
|
629
|
+
const hasId = propertyNames.has("id");
|
|
630
|
+
if (!hasId) {
|
|
631
|
+
lines.push(` var id: String`);
|
|
632
|
+
}
|
|
633
|
+
for (const prop of entity.properties) {
|
|
634
|
+
const swiftType = irTypeToSwift(prop.type);
|
|
635
|
+
lines.push(` var ${prop.name}: ${swiftType}`);
|
|
636
|
+
}
|
|
637
|
+
lines.push(``);
|
|
638
|
+
lines.push(
|
|
639
|
+
` static let typeDisplayRepresentation: TypeDisplayRepresentation = TypeDisplayRepresentation(`
|
|
640
|
+
);
|
|
641
|
+
lines.push(
|
|
642
|
+
` name: LocalizedStringResource("${escapeSwiftString(entity.name)}")`
|
|
643
|
+
);
|
|
644
|
+
lines.push(` )`);
|
|
645
|
+
lines.push(``);
|
|
646
|
+
lines.push(` var displayRepresentation: DisplayRepresentation {`);
|
|
647
|
+
lines.push(` DisplayRepresentation(`);
|
|
648
|
+
lines.push(
|
|
649
|
+
` title: "\\(${entity.displayRepresentation.title})"${entity.displayRepresentation.subtitle || entity.displayRepresentation.image ? "," : ""}`
|
|
650
|
+
);
|
|
651
|
+
if (entity.displayRepresentation.subtitle) {
|
|
652
|
+
const hasImage = !!entity.displayRepresentation.image;
|
|
653
|
+
lines.push(
|
|
654
|
+
` subtitle: "\\(${entity.displayRepresentation.subtitle})"${hasImage ? "," : ""}`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
if (entity.displayRepresentation.image) {
|
|
658
|
+
lines.push(
|
|
659
|
+
` image: .init(systemName: "${escapeSwiftString(entity.displayRepresentation.image)}")`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
lines.push(` )`);
|
|
663
|
+
lines.push(` }`);
|
|
664
|
+
lines.push(`}`);
|
|
665
|
+
return lines.join("\n");
|
|
666
|
+
}
|
|
667
|
+
function generateEntityQuery(entity) {
|
|
668
|
+
const lines = [];
|
|
669
|
+
const queryType = entity.queryType;
|
|
670
|
+
lines.push(`struct ${entity.name}Query: EntityQuery {`);
|
|
671
|
+
lines.push(
|
|
672
|
+
` func entities(for identifiers: [${entity.name}.ID]) async throws -> [${entity.name}] {`
|
|
673
|
+
);
|
|
674
|
+
lines.push(` // TODO: Fetch entities by IDs`);
|
|
675
|
+
lines.push(` return []`);
|
|
676
|
+
lines.push(` }`);
|
|
677
|
+
lines.push(``);
|
|
678
|
+
if (queryType === "all") {
|
|
679
|
+
lines.push(` func allEntities() async throws -> [${entity.name}] {`);
|
|
680
|
+
lines.push(` // TODO: Return all entities`);
|
|
681
|
+
lines.push(` return []`);
|
|
682
|
+
lines.push(` }`);
|
|
683
|
+
} else if (queryType === "id") {
|
|
684
|
+
lines.push(` // ID-based query is provided by the entities(for:) method above`);
|
|
685
|
+
} else if (queryType === "string") {
|
|
686
|
+
lines.push(
|
|
687
|
+
` func entities(matching string: String) async throws -> [${entity.name}] {`
|
|
688
|
+
);
|
|
689
|
+
lines.push(` // TODO: Search entities by string`);
|
|
690
|
+
lines.push(` return []`);
|
|
691
|
+
lines.push(` }`);
|
|
692
|
+
} else if (queryType === "property") {
|
|
693
|
+
lines.push(` // Property-based query: implement using EntityPropertyQuery`);
|
|
694
|
+
lines.push(` // Example: property.where { \\$0.status == "active" }`);
|
|
695
|
+
}
|
|
696
|
+
lines.push(`}`);
|
|
697
|
+
return lines.join("\n");
|
|
698
|
+
}
|
|
446
699
|
function generateInfoPlistFragment(intent) {
|
|
447
700
|
const keys = intent.infoPlistKeys;
|
|
448
701
|
if (!keys || Object.keys(keys).length === 0) return void 0;
|
|
449
702
|
const lines = [];
|
|
450
703
|
lines.push(`<?xml version="1.0" encoding="UTF-8"?>`);
|
|
451
|
-
lines.push(
|
|
452
|
-
`<!-- Info.plist fragment generated by Axint for ${intent.name}Intent -->`
|
|
453
|
-
);
|
|
704
|
+
lines.push(`<!-- Info.plist fragment generated by Axint for ${intent.name}Intent -->`);
|
|
454
705
|
lines.push(`<!-- Merge these keys into your app's Info.plist. -->`);
|
|
455
706
|
lines.push(`<plist version="1.0">`);
|
|
456
707
|
lines.push(`<dict>`);
|
|
@@ -507,7 +758,10 @@ function generateParameter(param) {
|
|
|
507
758
|
lines.push(``);
|
|
508
759
|
return lines.join("\n");
|
|
509
760
|
}
|
|
510
|
-
function generateReturnSignature(type) {
|
|
761
|
+
function generateReturnSignature(type, customResultType) {
|
|
762
|
+
if (customResultType) {
|
|
763
|
+
return customResultType;
|
|
764
|
+
}
|
|
511
765
|
if (type.kind === "primitive") {
|
|
512
766
|
const swift = irTypeToSwift(type);
|
|
513
767
|
return `some IntentResult & ReturnsValue<${swift}>`;
|
|
@@ -518,8 +772,11 @@ function generateReturnSignature(type) {
|
|
|
518
772
|
}
|
|
519
773
|
return `some IntentResult`;
|
|
520
774
|
}
|
|
521
|
-
function generatePerformReturn(type) {
|
|
775
|
+
function generatePerformReturn(type, customResultType) {
|
|
522
776
|
const indent = " ";
|
|
777
|
+
if (customResultType) {
|
|
778
|
+
return `${indent}// TODO: Return a ${customResultType} instance`;
|
|
779
|
+
}
|
|
523
780
|
if (type.kind === "primitive") {
|
|
524
781
|
return `${indent}return .result(value: ${defaultLiteralFor(type.value)})`;
|
|
525
782
|
}
|
|
@@ -664,6 +921,54 @@ function validateIntent(intent) {
|
|
|
664
921
|
});
|
|
665
922
|
}
|
|
666
923
|
}
|
|
924
|
+
if (intent.entities) {
|
|
925
|
+
for (const entity of intent.entities) {
|
|
926
|
+
diagnostics.push(...validateEntity(entity, intent.sourceFile));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return diagnostics;
|
|
930
|
+
}
|
|
931
|
+
function validateEntity(entity, sourceFile) {
|
|
932
|
+
const diagnostics = [];
|
|
933
|
+
if (!entity.name || !/^[A-Z][a-zA-Z0-9]*$/.test(entity.name)) {
|
|
934
|
+
diagnostics.push({
|
|
935
|
+
code: "AX110",
|
|
936
|
+
severity: "error",
|
|
937
|
+
message: `Entity name "${entity.name}" must be PascalCase (e.g., "Task", "Playlist")`,
|
|
938
|
+
file: sourceFile,
|
|
939
|
+
suggestion: `Rename to "${toPascalCase(entity.name)}"`
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
if (entity.properties.length === 0) {
|
|
943
|
+
diagnostics.push({
|
|
944
|
+
code: "AX111",
|
|
945
|
+
severity: "error",
|
|
946
|
+
message: `Entity "${entity.name}" must have at least one property`,
|
|
947
|
+
file: sourceFile,
|
|
948
|
+
suggestion: "Add properties to define the entity's structure"
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
const titleProp = entity.displayRepresentation.title;
|
|
952
|
+
const propertyNames = new Set(entity.properties.map((p) => p.name));
|
|
953
|
+
if (titleProp && !propertyNames.has(titleProp)) {
|
|
954
|
+
diagnostics.push({
|
|
955
|
+
code: "AX112",
|
|
956
|
+
severity: "warning",
|
|
957
|
+
message: `Display title "${titleProp}" does not reference an existing property`,
|
|
958
|
+
file: sourceFile,
|
|
959
|
+
suggestion: `Available properties: ${[...propertyNames].join(", ")}`
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
const validQueryTypes = ["all", "id", "string", "property"];
|
|
963
|
+
if (!validQueryTypes.includes(entity.queryType)) {
|
|
964
|
+
diagnostics.push({
|
|
965
|
+
code: "AX113",
|
|
966
|
+
severity: "error",
|
|
967
|
+
message: `Entity query type "${entity.queryType}" is not valid`,
|
|
968
|
+
file: sourceFile,
|
|
969
|
+
suggestion: `Use one of: ${validQueryTypes.join(", ")}`
|
|
970
|
+
});
|
|
971
|
+
}
|
|
667
972
|
return diagnostics;
|
|
668
973
|
}
|
|
669
974
|
function validateSwiftSource(swift) {
|
|
@@ -698,7 +1003,6 @@ function toPascalCase(s) {
|
|
|
698
1003
|
|
|
699
1004
|
// src/core/compiler.ts
|
|
700
1005
|
function compileSource(source, fileName = "<stdin>", options = {}) {
|
|
701
|
-
const diagnostics = [];
|
|
702
1006
|
let ir;
|
|
703
1007
|
try {
|
|
704
1008
|
ir = parseIntentSource(source, fileName);
|
|
@@ -720,6 +1024,10 @@ function compileSource(source, fileName = "<stdin>", options = {}) {
|
|
|
720
1024
|
}
|
|
721
1025
|
throw err;
|
|
722
1026
|
}
|
|
1027
|
+
return compileFromIR(ir, options);
|
|
1028
|
+
}
|
|
1029
|
+
function compileFromIR(ir, options = {}) {
|
|
1030
|
+
const diagnostics = [];
|
|
723
1031
|
const irDiagnostics = validateIntent(ir);
|
|
724
1032
|
diagnostics.push(...irDiagnostics);
|
|
725
1033
|
if (irDiagnostics.some((d) => d.severity === "error")) {
|
|
@@ -780,7 +1088,7 @@ ${paramLines.join("\n")}
|
|
|
780
1088
|
lines.push(` * Generated by axint_scaffold. Edit freely, then run:`);
|
|
781
1089
|
lines.push(` * npx axint compile src/intents/${kebab(name)}.ts`);
|
|
782
1090
|
lines.push(` */`);
|
|
783
|
-
lines.push(`import { defineIntent, param } from "
|
|
1091
|
+
lines.push(`import { defineIntent, param } from "@axintai/compiler";`);
|
|
784
1092
|
lines.push(``);
|
|
785
1093
|
lines.push(`export default defineIntent({`);
|
|
786
1094
|
lines.push(` name: ${JSON.stringify(name)},`);
|
|
@@ -828,7 +1136,7 @@ var sendMessage = {
|
|
|
828
1136
|
domain: "messaging",
|
|
829
1137
|
category: "messaging",
|
|
830
1138
|
description: "Send a text message to a contact.",
|
|
831
|
-
source: `import { defineIntent, param } from "
|
|
1139
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
832
1140
|
|
|
833
1141
|
export default defineIntent({
|
|
834
1142
|
name: "SendMessage",
|
|
@@ -853,7 +1161,7 @@ var createEvent = {
|
|
|
853
1161
|
domain: "productivity",
|
|
854
1162
|
category: "productivity",
|
|
855
1163
|
description: "Create a calendar event with a title, date, and duration.",
|
|
856
|
-
source: `import { defineIntent, param } from "
|
|
1164
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
857
1165
|
|
|
858
1166
|
export default defineIntent({
|
|
859
1167
|
name: "CreateEvent",
|
|
@@ -883,7 +1191,7 @@ var bookRide = {
|
|
|
883
1191
|
domain: "navigation",
|
|
884
1192
|
category: "navigation",
|
|
885
1193
|
description: "Request a ride from a pickup location to a destination.",
|
|
886
|
-
source: `import { defineIntent, param } from "
|
|
1194
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
887
1195
|
|
|
888
1196
|
export default defineIntent({
|
|
889
1197
|
name: "BookRide",
|
|
@@ -908,7 +1216,7 @@ var getDirections = {
|
|
|
908
1216
|
domain: "navigation",
|
|
909
1217
|
category: "navigation",
|
|
910
1218
|
description: "Get turn-by-turn directions to a destination.",
|
|
911
|
-
source: `import { defineIntent, param } from "
|
|
1219
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
912
1220
|
|
|
913
1221
|
export default defineIntent({
|
|
914
1222
|
name: "GetDirections",
|
|
@@ -934,7 +1242,7 @@ var playTrack = {
|
|
|
934
1242
|
domain: "media",
|
|
935
1243
|
category: "media",
|
|
936
1244
|
description: "Play a specific track or song.",
|
|
937
|
-
source: `import { defineIntent, param } from "
|
|
1245
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
938
1246
|
|
|
939
1247
|
export default defineIntent({
|
|
940
1248
|
name: "PlayTrack",
|
|
@@ -959,7 +1267,7 @@ var createNote = {
|
|
|
959
1267
|
domain: "productivity",
|
|
960
1268
|
category: "productivity",
|
|
961
1269
|
description: "Create a new note with a title and body.",
|
|
962
|
-
source: `import { defineIntent, param } from "
|
|
1270
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
963
1271
|
|
|
964
1272
|
export default defineIntent({
|
|
965
1273
|
name: "CreateNote",
|
|
@@ -984,7 +1292,7 @@ var logExpense = {
|
|
|
984
1292
|
domain: "finance",
|
|
985
1293
|
category: "finance",
|
|
986
1294
|
description: "Log a financial expense with amount, category, and note.",
|
|
987
|
-
source: `import { defineIntent, param } from "
|
|
1295
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
988
1296
|
|
|
989
1297
|
export default defineIntent({
|
|
990
1298
|
name: "LogExpense",
|
|
@@ -1012,7 +1320,7 @@ var logWorkout = {
|
|
|
1012
1320
|
domain: "health",
|
|
1013
1321
|
category: "health",
|
|
1014
1322
|
description: "Log a workout with duration, type, and calories burned.",
|
|
1015
|
-
source: `import { defineIntent, param } from "
|
|
1323
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
1016
1324
|
|
|
1017
1325
|
export default defineIntent({
|
|
1018
1326
|
name: "LogWorkout",
|
|
@@ -1042,7 +1350,7 @@ var setThermostat = {
|
|
|
1042
1350
|
domain: "smart-home",
|
|
1043
1351
|
category: "smart-home",
|
|
1044
1352
|
description: "Set a smart-home thermostat to a target temperature.",
|
|
1045
|
-
source: `import { defineIntent, param } from "
|
|
1353
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
1046
1354
|
|
|
1047
1355
|
export default defineIntent({
|
|
1048
1356
|
name: "SetThermostat",
|
|
@@ -1067,7 +1375,7 @@ var placeOrder = {
|
|
|
1067
1375
|
domain: "commerce",
|
|
1068
1376
|
category: "commerce",
|
|
1069
1377
|
description: "Place a commerce order for a product.",
|
|
1070
|
-
source: `import { defineIntent, param } from "
|
|
1378
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
1071
1379
|
|
|
1072
1380
|
export default defineIntent({
|
|
1073
1381
|
name: "PlaceOrder",
|
|
@@ -1085,6 +1393,73 @@ export default defineIntent({
|
|
|
1085
1393
|
});
|
|
1086
1394
|
`
|
|
1087
1395
|
};
|
|
1396
|
+
var searchTasks = {
|
|
1397
|
+
id: "search-tasks",
|
|
1398
|
+
name: "search-tasks",
|
|
1399
|
+
title: "Search Tasks",
|
|
1400
|
+
domain: "productivity",
|
|
1401
|
+
category: "productivity",
|
|
1402
|
+
description: "Search for tasks using EntityQuery with string-based search.",
|
|
1403
|
+
source: `import { defineIntent, defineEntity, param } from "@axintai/compiler";
|
|
1404
|
+
|
|
1405
|
+
defineEntity({
|
|
1406
|
+
name: "Task",
|
|
1407
|
+
display: {
|
|
1408
|
+
title: "name",
|
|
1409
|
+
subtitle: "status",
|
|
1410
|
+
},
|
|
1411
|
+
properties: {
|
|
1412
|
+
id: param.string("Unique task identifier"),
|
|
1413
|
+
name: param.string("Task name"),
|
|
1414
|
+
status: param.string("Task status (todo, in-progress, done)"),
|
|
1415
|
+
dueDate: param.date("Due date"),
|
|
1416
|
+
},
|
|
1417
|
+
query: "string",
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
export default defineIntent({
|
|
1421
|
+
name: "SearchTasks",
|
|
1422
|
+
title: "Search Tasks",
|
|
1423
|
+
description: "Search for tasks by name or status.",
|
|
1424
|
+
domain: "productivity",
|
|
1425
|
+
params: {
|
|
1426
|
+
query: param.string("Search query"),
|
|
1427
|
+
status: param.string("Filter by status (optional)", { required: false }),
|
|
1428
|
+
},
|
|
1429
|
+
donateOnPerform: true,
|
|
1430
|
+
perform: async ({ query, status }) => {
|
|
1431
|
+
// TODO: Search your task database with the query
|
|
1432
|
+
// Use status filter if provided
|
|
1433
|
+
return { found: true, results: 0 };
|
|
1434
|
+
},
|
|
1435
|
+
});
|
|
1436
|
+
`
|
|
1437
|
+
};
|
|
1438
|
+
var dynamicPlaylist = {
|
|
1439
|
+
id: "dynamic-playlist",
|
|
1440
|
+
name: "dynamic-playlist",
|
|
1441
|
+
title: "Dynamic Playlist",
|
|
1442
|
+
domain: "media",
|
|
1443
|
+
category: "media",
|
|
1444
|
+
description: "Create a playlist by name, mood, and track count.",
|
|
1445
|
+
source: `import { defineIntent, param } from "@axintai/compiler";
|
|
1446
|
+
|
|
1447
|
+
export default defineIntent({
|
|
1448
|
+
name: "DynamicPlaylist",
|
|
1449
|
+
title: "Create Dynamic Playlist",
|
|
1450
|
+
description: "Create a playlist with a given mood or genre.",
|
|
1451
|
+
domain: "media",
|
|
1452
|
+
params: {
|
|
1453
|
+
name: param.string("Playlist name"),
|
|
1454
|
+
mood: param.string("Mood or genre (e.g., chill, workout, focus)"),
|
|
1455
|
+
trackCount: param.int("Number of tracks", { default: 20 }),
|
|
1456
|
+
},
|
|
1457
|
+
perform: async ({ name, mood }) => {
|
|
1458
|
+
return { playlistId: "playlist_placeholder" };
|
|
1459
|
+
},
|
|
1460
|
+
});
|
|
1461
|
+
`
|
|
1462
|
+
};
|
|
1088
1463
|
var TEMPLATES = [
|
|
1089
1464
|
sendMessage,
|
|
1090
1465
|
createEvent,
|
|
@@ -1095,7 +1470,9 @@ var TEMPLATES = [
|
|
|
1095
1470
|
logExpense,
|
|
1096
1471
|
logWorkout,
|
|
1097
1472
|
setThermostat,
|
|
1098
|
-
placeOrder
|
|
1473
|
+
placeOrder,
|
|
1474
|
+
searchTasks,
|
|
1475
|
+
dynamicPlaylist
|
|
1099
1476
|
];
|
|
1100
1477
|
function getTemplate(id) {
|
|
1101
1478
|
return TEMPLATES.find((t) => t.id === id);
|