@appilots/cli 0.4.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1901 -300
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +77 -9
- package/dist/index.d.ts +77 -9
- package/dist/index.js +794 -149
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +794 -150
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/index.mjs
CHANGED
|
@@ -56,6 +56,45 @@ function hasJsxAttribute(element, name) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// src/ast/jsx/classify.ts
|
|
59
|
+
var NON_INTERACTION_HANDLERS = /* @__PURE__ */ new Set([
|
|
60
|
+
"onLayout",
|
|
61
|
+
"onScroll",
|
|
62
|
+
"onScrollBeginDrag",
|
|
63
|
+
"onScrollEndDrag",
|
|
64
|
+
"onMomentumScrollBegin",
|
|
65
|
+
"onMomentumScrollEnd",
|
|
66
|
+
"onContentSizeChange",
|
|
67
|
+
"onEndReached",
|
|
68
|
+
"onViewableItemsChanged",
|
|
69
|
+
"onLoad",
|
|
70
|
+
"onLoadStart",
|
|
71
|
+
"onLoadEnd",
|
|
72
|
+
"onError",
|
|
73
|
+
"onProgress",
|
|
74
|
+
"onFocus",
|
|
75
|
+
"onBlur"
|
|
76
|
+
]);
|
|
77
|
+
var VALUE_HANDLERS = /* @__PURE__ */ new Set([
|
|
78
|
+
"onChange",
|
|
79
|
+
"onChangeText",
|
|
80
|
+
"onValueChange",
|
|
81
|
+
"onSelect",
|
|
82
|
+
"onSelectionChange",
|
|
83
|
+
"onSubmitEditing"
|
|
84
|
+
]);
|
|
85
|
+
function pressHandlerProp(element) {
|
|
86
|
+
let fallback;
|
|
87
|
+
for (const attr of element.attributes) {
|
|
88
|
+
if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") continue;
|
|
89
|
+
const name = attr.name.name;
|
|
90
|
+
if (!/^on[A-Z]/.test(name)) continue;
|
|
91
|
+
if (attr.value?.type !== "JSXExpressionContainer") continue;
|
|
92
|
+
if (name === "onPress") return name;
|
|
93
|
+
if (NON_INTERACTION_HANDLERS.has(name) || VALUE_HANDLERS.has(name)) continue;
|
|
94
|
+
fallback ??= name;
|
|
95
|
+
}
|
|
96
|
+
return fallback;
|
|
97
|
+
}
|
|
59
98
|
var VIEW_COMPONENTS = /* @__PURE__ */ new Set(["View", "ScrollView", "SafeAreaView", "KeyboardAvoidingView"]);
|
|
60
99
|
var INPUT_COMPONENTS = /* @__PURE__ */ new Set(["TextInput", "Input", "HocInput"]);
|
|
61
100
|
var BUTTON_COMPONENTS = /* @__PURE__ */ new Set(["TouchableOpacity", "Pressable", "Button"]);
|
|
@@ -70,17 +109,16 @@ function classifyJsxComponent(name, element) {
|
|
|
70
109
|
if (INPUT_COMPONENTS.has(name)) return "input";
|
|
71
110
|
if (BUTTON_COMPONENTS.has(name)) return "button";
|
|
72
111
|
if (element) {
|
|
73
|
-
const hasOptions = hasJsxAttribute(element, "options");
|
|
112
|
+
const hasOptions = hasJsxAttribute(element, "options") || hasJsxAttribute(element, "segments") || hasJsxAttribute(element, "choices") || hasJsxAttribute(element, "tabs");
|
|
74
113
|
const hasValue = hasJsxAttribute(element, "value");
|
|
75
114
|
const hasChecked = hasJsxAttribute(element, "checked") || hasJsxAttribute(element, "selected");
|
|
76
115
|
const hasOnChange = hasJsxAttribute(element, "onChange") || hasJsxAttribute(element, "onValueChange") || hasJsxAttribute(element, "onChangeText");
|
|
77
|
-
const hasOnPress = hasJsxAttribute(element, "onPress");
|
|
78
116
|
if (hasOptions && (hasValue || hasOnChange)) return "select";
|
|
79
117
|
if (hasChecked && hasOnChange) return "toggle";
|
|
80
118
|
if (hasOnChange && (hasJsxAttribute(element, "label") || hasJsxAttribute(element, "placeholder"))) {
|
|
81
119
|
return "input";
|
|
82
120
|
}
|
|
83
|
-
if (
|
|
121
|
+
if (pressHandlerProp(element)) return "button";
|
|
84
122
|
if (getStringAttr(element, "visible") || getExpressionIdentifierAttr(element, "visible")) {
|
|
85
123
|
return "modal";
|
|
86
124
|
}
|
|
@@ -418,12 +456,17 @@ var ScreenAnalyzer = class {
|
|
|
418
456
|
}
|
|
419
457
|
} catch (error) {
|
|
420
458
|
if (this.verbose) {
|
|
421
|
-
console.warn(
|
|
459
|
+
console.warn(
|
|
460
|
+
`[ScreenAnalyzer] Failed to parse ${file}:`,
|
|
461
|
+
error instanceof Error ? error.message : error
|
|
462
|
+
);
|
|
422
463
|
}
|
|
423
464
|
}
|
|
424
465
|
}
|
|
425
466
|
if (this.verbose && this.strictScreens) {
|
|
426
|
-
console.log(
|
|
467
|
+
console.log(
|
|
468
|
+
`[ScreenAnalyzer] Strict mode: ${screens.length} included, ${this.screensFilteredOut} filtered out`
|
|
469
|
+
);
|
|
427
470
|
}
|
|
428
471
|
return screens;
|
|
429
472
|
}
|
|
@@ -711,7 +754,11 @@ var ScreenAnalyzer = class {
|
|
|
711
754
|
for (const form of secondary) {
|
|
712
755
|
const existing = out.find((candidate) => candidate.id === form.id) ?? this.findFormWithSharedFields(out, form);
|
|
713
756
|
if (!existing) {
|
|
714
|
-
out.push({
|
|
757
|
+
out.push({
|
|
758
|
+
...form,
|
|
759
|
+
fields: [...form.fields],
|
|
760
|
+
submitAction: this.namedSubmitAction(form.submitAction)
|
|
761
|
+
});
|
|
715
762
|
continue;
|
|
716
763
|
}
|
|
717
764
|
for (const field of form.fields) {
|
|
@@ -730,9 +777,12 @@ var ScreenAnalyzer = class {
|
|
|
730
777
|
findEquivalentField(fields, incoming) {
|
|
731
778
|
return fields.find((field) => {
|
|
732
779
|
if (field.name && incoming.name && field.name === incoming.name) return true;
|
|
733
|
-
if (field.valueBinding && incoming.valueBinding && field.valueBinding === incoming.valueBinding)
|
|
734
|
-
|
|
735
|
-
if (field.
|
|
780
|
+
if (field.valueBinding && incoming.valueBinding && field.valueBinding === incoming.valueBinding)
|
|
781
|
+
return true;
|
|
782
|
+
if (field.locator?.id && incoming.locator?.id && field.locator.id === incoming.locator.id)
|
|
783
|
+
return true;
|
|
784
|
+
if (field.placeholder && incoming.placeholder && field.placeholder === incoming.placeholder)
|
|
785
|
+
return true;
|
|
736
786
|
if (field.locator?.accessibilityLabel && incoming.locator?.accessibilityLabel && field.locator.accessibilityLabel === incoming.locator.accessibilityLabel) {
|
|
737
787
|
return true;
|
|
738
788
|
}
|
|
@@ -892,9 +942,11 @@ var ScreenAnalyzer = class {
|
|
|
892
942
|
source: field.locator?.source ?? "accessibilityLabel"
|
|
893
943
|
});
|
|
894
944
|
} else if (attrName === "keyboardType") {
|
|
895
|
-
if (attr.value.value === "email-address" || attr.value.value === "email")
|
|
945
|
+
if (attr.value.value === "email-address" || attr.value.value === "email")
|
|
946
|
+
field.type = "email";
|
|
896
947
|
if (attr.value.value === "phone-pad") field.type = "phone";
|
|
897
|
-
if (attr.value.value === "numeric" || attr.value.value === "number-pad")
|
|
948
|
+
if (attr.value.value === "numeric" || attr.value.value === "number-pad")
|
|
949
|
+
field.type = "number";
|
|
898
950
|
}
|
|
899
951
|
} else if (BabelTypes.isJSXExpressionContainer(attr.value)) {
|
|
900
952
|
if (attrName === "value" && BabelTypes.isIdentifier(attr.value.expression)) {
|
|
@@ -1060,8 +1112,10 @@ var ScreenAnalyzer = class {
|
|
|
1060
1112
|
};
|
|
1061
1113
|
if (handler.nativeConfirmationExpected) action.nativeConfirmationExpected = true;
|
|
1062
1114
|
if (handler.targetScreen && !action.targetScreen) action.targetScreen = handler.targetScreen;
|
|
1063
|
-
if (handler.successSignal && !action.successSignal)
|
|
1064
|
-
|
|
1115
|
+
if (handler.successSignal && !action.successSignal)
|
|
1116
|
+
action.successSignal = handler.successSignal;
|
|
1117
|
+
if (handler.failureSignal && !action.failureSignal)
|
|
1118
|
+
action.failureSignal = handler.failureSignal;
|
|
1065
1119
|
if (handler.opensModal && !action.opensModal) action.opensModal = handler.opensModal;
|
|
1066
1120
|
if (handler.destructive || action.destructive === true || action.requiresConfirmation === true || action.effect === "destructive" || action.riskLevel === "high") {
|
|
1067
1121
|
action.destructive = true;
|
|
@@ -1079,6 +1133,7 @@ var ScreenAnalyzer = class {
|
|
|
1079
1133
|
if (!BabelTypes.isJSXIdentifier(element.name)) return;
|
|
1080
1134
|
const componentName = element.name.name;
|
|
1081
1135
|
if (classifyJsxComponent(componentName, element) !== "button") return;
|
|
1136
|
+
const pressProp = pressHandlerProp(element);
|
|
1082
1137
|
let label;
|
|
1083
1138
|
let handler;
|
|
1084
1139
|
for (const attr of element.attributes) {
|
|
@@ -1088,7 +1143,7 @@ var ScreenAnalyzer = class {
|
|
|
1088
1143
|
if ((attrName === "title" || attrName === "accessibilityLabel" || attrName === "label") && BabelTypes.isStringLiteral(attr.value)) {
|
|
1089
1144
|
label = attr.value.value;
|
|
1090
1145
|
}
|
|
1091
|
-
if (attrName ===
|
|
1146
|
+
if (attrName === pressProp && BabelTypes.isJSXExpressionContainer(attr.value) && BabelTypes.isIdentifier(attr.value.expression)) {
|
|
1092
1147
|
handler = attr.value.expression.name;
|
|
1093
1148
|
}
|
|
1094
1149
|
}
|
|
@@ -1154,10 +1209,13 @@ var ScreenAnalyzer = class {
|
|
|
1154
1209
|
}
|
|
1155
1210
|
};
|
|
1156
1211
|
if (fn.body) {
|
|
1157
|
-
traverse4(
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1212
|
+
traverse4(
|
|
1213
|
+
fn.body,
|
|
1214
|
+
{
|
|
1215
|
+
noScope: true,
|
|
1216
|
+
enter: (nodePath) => inspectNode(nodePath.node)
|
|
1217
|
+
}
|
|
1218
|
+
);
|
|
1161
1219
|
}
|
|
1162
1220
|
const expectedOutcome = targetScreen ? "navigation" : hasStateSetter ? "inline-feedback" : hasAwait || hasThen ? "mixed" : "none";
|
|
1163
1221
|
return {
|
|
@@ -1215,6 +1273,7 @@ var ScreenAnalyzer = class {
|
|
|
1215
1273
|
};
|
|
1216
1274
|
const componentName = BabelTypes.isJSXIdentifier(element.name) ? element.name.name : void 0;
|
|
1217
1275
|
action.sourceComponent = componentName;
|
|
1276
|
+
const pressProp = pressHandlerProp(element) ?? "onPress";
|
|
1218
1277
|
let handlerName;
|
|
1219
1278
|
for (const attr of element.attributes) {
|
|
1220
1279
|
if (!BabelTypes.isJSXAttribute(attr)) continue;
|
|
@@ -1249,7 +1308,7 @@ var ScreenAnalyzer = class {
|
|
|
1249
1308
|
}
|
|
1250
1309
|
}
|
|
1251
1310
|
} else if (BabelTypes.isJSXExpressionContainer(attr.value)) {
|
|
1252
|
-
if (attrName ===
|
|
1311
|
+
if (attrName === pressProp && BabelTypes.isIdentifier(attr.value.expression)) {
|
|
1253
1312
|
handlerName = attr.value.expression.name.toLowerCase();
|
|
1254
1313
|
action.handler = attr.value.expression.name;
|
|
1255
1314
|
if (handlerName.includes("submit")) {
|
|
@@ -1257,7 +1316,7 @@ var ScreenAnalyzer = class {
|
|
|
1257
1316
|
} else if (handlerName.includes("navigate")) {
|
|
1258
1317
|
action.type = "navigation";
|
|
1259
1318
|
}
|
|
1260
|
-
} else if (attrName ===
|
|
1319
|
+
} else if (attrName === pressProp) {
|
|
1261
1320
|
const inline = this.extractInlineOnPressMetadata(attr.value.expression);
|
|
1262
1321
|
if (inline.handler) {
|
|
1263
1322
|
handlerName = inline.handler.toLowerCase();
|
|
@@ -1281,7 +1340,11 @@ var ScreenAnalyzer = class {
|
|
|
1281
1340
|
}
|
|
1282
1341
|
}
|
|
1283
1342
|
if (!action.locator && action.id) {
|
|
1284
|
-
action.locator = {
|
|
1343
|
+
action.locator = {
|
|
1344
|
+
id: action.id,
|
|
1345
|
+
label: action.label,
|
|
1346
|
+
source: action.label ? "label" : "inferred"
|
|
1347
|
+
};
|
|
1285
1348
|
}
|
|
1286
1349
|
if (this.isElementDestructive(element, handlerName, action.id)) {
|
|
1287
1350
|
action.destructive = true;
|
|
@@ -1423,24 +1486,27 @@ var ScreenAnalyzer = class {
|
|
|
1423
1486
|
}
|
|
1424
1487
|
extractRowAction(fn) {
|
|
1425
1488
|
let action;
|
|
1426
|
-
traverse4(
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1489
|
+
traverse4(
|
|
1490
|
+
fn.body,
|
|
1491
|
+
{
|
|
1492
|
+
noScope: true,
|
|
1493
|
+
CallExpression: (nodePath) => {
|
|
1494
|
+
const node = nodePath.node;
|
|
1495
|
+
if (!BabelTypes.isMemberExpression(node.callee) || !BabelTypes.isIdentifier(node.callee.object) || node.callee.object.name !== "navigation" || !BabelTypes.isIdentifier(node.callee.property) || !["navigate", "push", "replace"].includes(node.callee.property.name)) {
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
const firstArg = node.arguments[0];
|
|
1499
|
+
if (!BabelTypes.isStringLiteral(firstArg)) return;
|
|
1500
|
+
const params = this.extractNavigationParams(node.arguments[1]);
|
|
1501
|
+
action = {
|
|
1502
|
+
type: "navigation",
|
|
1503
|
+
targetScreen: firstArg.value,
|
|
1504
|
+
...Object.keys(params).length > 0 ? { params } : {},
|
|
1505
|
+
description: `Pressing a row opens ${firstArg.value}`
|
|
1506
|
+
};
|
|
1432
1507
|
}
|
|
1433
|
-
const firstArg = node.arguments[0];
|
|
1434
|
-
if (!BabelTypes.isStringLiteral(firstArg)) return;
|
|
1435
|
-
const params = this.extractNavigationParams(node.arguments[1]);
|
|
1436
|
-
action = {
|
|
1437
|
-
type: "navigation",
|
|
1438
|
-
targetScreen: firstArg.value,
|
|
1439
|
-
...Object.keys(params).length > 0 ? { params } : {},
|
|
1440
|
-
description: `Pressing a row opens ${firstArg.value}`
|
|
1441
|
-
};
|
|
1442
1508
|
}
|
|
1443
|
-
|
|
1509
|
+
);
|
|
1444
1510
|
return action;
|
|
1445
1511
|
}
|
|
1446
1512
|
extractNavigationParams(arg) {
|
|
@@ -1473,15 +1539,18 @@ var ScreenAnalyzer = class {
|
|
|
1473
1539
|
} else if (BabelTypes.isIdentifier(firstParam)) {
|
|
1474
1540
|
itemNames.add(firstParam.name);
|
|
1475
1541
|
}
|
|
1476
|
-
traverse4(
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1542
|
+
traverse4(
|
|
1543
|
+
fn.body,
|
|
1544
|
+
{
|
|
1545
|
+
noScope: true,
|
|
1546
|
+
MemberExpression: (nodePath) => {
|
|
1547
|
+
const node = nodePath.node;
|
|
1548
|
+
if (BabelTypes.isIdentifier(node.object) && itemNames.has(node.object.name) && BabelTypes.isIdentifier(node.property)) {
|
|
1549
|
+
fields.add(node.property.name);
|
|
1550
|
+
}
|
|
1482
1551
|
}
|
|
1483
1552
|
}
|
|
1484
|
-
|
|
1553
|
+
);
|
|
1485
1554
|
return Array.from(fields).sort();
|
|
1486
1555
|
}
|
|
1487
1556
|
inferItemType(dataSource, renderItem, displayFields) {
|
|
@@ -1493,11 +1562,19 @@ var ScreenAnalyzer = class {
|
|
|
1493
1562
|
if (displayFields.length > 0) return "Item";
|
|
1494
1563
|
return void 0;
|
|
1495
1564
|
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Identity comes from how a field is NAMED, not from what the app
|
|
1567
|
+
* sells: `id`/`uuid`/`key`/`slug` are conventions any codebase uses,
|
|
1568
|
+
* `name`/`title`/`email` are how any row introduces itself. `plate`
|
|
1569
|
+
* used to sit in this list and read like one of them — but it is the
|
|
1570
|
+
* example app's schema, and no other tenant ever got its equivalent
|
|
1571
|
+
* (`mrn`, `trackingNumber`, `sku`) added here.
|
|
1572
|
+
*/
|
|
1496
1573
|
inferIdentityFields(keyField, displayFields) {
|
|
1497
1574
|
const out = /* @__PURE__ */ new Set();
|
|
1498
1575
|
if (keyField) out.add(keyField);
|
|
1499
1576
|
for (const field of displayFields) {
|
|
1500
|
-
if (/^(id|uuid|key|name|title|
|
|
1577
|
+
if (/^(id|uuid|key|name|title|email|slug)$/i.test(field)) out.add(field);
|
|
1501
1578
|
}
|
|
1502
1579
|
return Array.from(out);
|
|
1503
1580
|
}
|
|
@@ -1508,17 +1585,23 @@ var ScreenAnalyzer = class {
|
|
|
1508
1585
|
CallExpression: (nodePath) => {
|
|
1509
1586
|
const node = nodePath.node;
|
|
1510
1587
|
if (!BabelTypes.isMemberExpression(node.callee)) return;
|
|
1511
|
-
if (!BabelTypes.isIdentifier(node.callee.property) || node.callee.property.name !== "filter")
|
|
1512
|
-
|
|
1588
|
+
if (!BabelTypes.isIdentifier(node.callee.property) || node.callee.property.name !== "filter")
|
|
1589
|
+
return;
|
|
1590
|
+
if (!BabelTypes.isIdentifier(node.callee.object) || node.callee.object.name !== dataSource)
|
|
1591
|
+
return;
|
|
1513
1592
|
const fn = node.arguments[0];
|
|
1514
|
-
if (!BabelTypes.isArrowFunctionExpression(fn) && !BabelTypes.isFunctionExpression(fn))
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1593
|
+
if (!BabelTypes.isArrowFunctionExpression(fn) && !BabelTypes.isFunctionExpression(fn))
|
|
1594
|
+
return;
|
|
1595
|
+
traverse4(
|
|
1596
|
+
fn.body,
|
|
1597
|
+
{
|
|
1598
|
+
noScope: true,
|
|
1599
|
+
Identifier: (innerPath) => {
|
|
1600
|
+
const name = innerPath.node.name;
|
|
1601
|
+
if (/query|search|filter/i.test(name)) queryBinding = queryBinding ?? name;
|
|
1602
|
+
}
|
|
1520
1603
|
}
|
|
1521
|
-
|
|
1604
|
+
);
|
|
1522
1605
|
}
|
|
1523
1606
|
});
|
|
1524
1607
|
return queryBinding;
|
|
@@ -1662,11 +1745,7 @@ var NavigationAnalyzer = class {
|
|
|
1662
1745
|
if (BabelTypes.isJSXAttribute(initialRouteAttr) && BabelTypes.isStringLiteral(initialRouteAttr.value)) {
|
|
1663
1746
|
navigator.initialRouteName = initialRouteAttr.value.value;
|
|
1664
1747
|
}
|
|
1665
|
-
const screens = this.extractScreensFromNavigator(
|
|
1666
|
-
node,
|
|
1667
|
-
objectName,
|
|
1668
|
-
navigator.type
|
|
1669
|
-
);
|
|
1748
|
+
const screens = this.extractScreensFromNavigator(node, objectName, navigator.type);
|
|
1670
1749
|
screensByNavigator.set(objectName, screens);
|
|
1671
1750
|
}
|
|
1672
1751
|
}
|
|
@@ -1683,27 +1762,112 @@ var NavigationAnalyzer = class {
|
|
|
1683
1762
|
}
|
|
1684
1763
|
return navigators;
|
|
1685
1764
|
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Is this JSX element `<navigatorVarName.MEMBER …>`?
|
|
1767
|
+
*/
|
|
1768
|
+
// Deliberately NOT a type predicate (`node is t.JSXElement`): a false
|
|
1769
|
+
// result would then narrow `node` to "not a JSXElement", and the very
|
|
1770
|
+
// next check — the same node against a different member — would see
|
|
1771
|
+
// `never`. It is a question about the member name, not about the node
|
|
1772
|
+
// kind.
|
|
1773
|
+
isNavigatorMember(node, navigatorVarName, member) {
|
|
1774
|
+
if (!BabelTypes.isJSXElement(node)) return false;
|
|
1775
|
+
const name = node.openingElement.name;
|
|
1776
|
+
return BabelTypes.isJSXMemberExpression(name) && BabelTypes.isJSXIdentifier(name.object) && name.object.name === navigatorVarName && BabelTypes.isJSXIdentifier(name.property) && name.property.name === member;
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Flatten a navigator's JSX children into the `<X.Screen>` elements they
|
|
1780
|
+
* contain, unwrapping every container a real app puts in between.
|
|
1781
|
+
*
|
|
1782
|
+
* The old version compared `t.isJSXElement(child)` against the direct
|
|
1783
|
+
* children only. A ternary is a `JSXExpressionContainer`, so an
|
|
1784
|
+
* auth-gated root — the modal shape of a commercial app, and the shape
|
|
1785
|
+
* of this repo's own `apps/example-app` — contributed ZERO screens to
|
|
1786
|
+
* the graph while `appilots sync` reported success (#396).
|
|
1787
|
+
*
|
|
1788
|
+
* Both branches of a conditional are collected on purpose. The graph is
|
|
1789
|
+
* a design-time map of what routes EXIST, not a prediction of which one
|
|
1790
|
+
* a given session will render; the agent needs the destination name to
|
|
1791
|
+
* be there whether or not the user happens to be logged in right now.
|
|
1792
|
+
*/
|
|
1793
|
+
collectScreenElements(children, navigatorVarName, depth = 0) {
|
|
1794
|
+
if (depth > 12) return [];
|
|
1795
|
+
const found = [];
|
|
1796
|
+
const visit = (node) => {
|
|
1797
|
+
if (!node) return;
|
|
1798
|
+
if (this.isNavigatorMember(node, navigatorVarName, "Screen")) {
|
|
1799
|
+
found.push(node);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
if (this.isNavigatorMember(node, navigatorVarName, "Group")) {
|
|
1803
|
+
const group = node;
|
|
1804
|
+
found.push(...this.collectScreenElements(group.children, navigatorVarName, depth + 1));
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
if (BabelTypes.isJSXElement(node)) {
|
|
1808
|
+
const name = node.openingElement.name;
|
|
1809
|
+
const isForeignNavigator = BabelTypes.isJSXMemberExpression(name) && BabelTypes.isJSXIdentifier(name.property) && name.property.name === "Navigator";
|
|
1810
|
+
if (isForeignNavigator) return;
|
|
1811
|
+
found.push(...this.collectScreenElements(node.children, navigatorVarName, depth + 1));
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (BabelTypes.isJSXFragment(node)) {
|
|
1815
|
+
found.push(...this.collectScreenElements(node.children, navigatorVarName, depth + 1));
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
if (BabelTypes.isJSXExpressionContainer(node)) {
|
|
1819
|
+
visit(node.expression);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
if (BabelTypes.isConditionalExpression(node)) {
|
|
1823
|
+
visit(node.consequent);
|
|
1824
|
+
visit(node.alternate);
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (BabelTypes.isLogicalExpression(node)) {
|
|
1828
|
+
visit(node.left);
|
|
1829
|
+
visit(node.right);
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (BabelTypes.isCallExpression(node)) {
|
|
1833
|
+
for (const arg of node.arguments) {
|
|
1834
|
+
if (BabelTypes.isArrowFunctionExpression(arg) || BabelTypes.isFunctionExpression(arg)) {
|
|
1835
|
+
if (BabelTypes.isBlockStatement(arg.body)) {
|
|
1836
|
+
for (const stmt of arg.body.body) {
|
|
1837
|
+
if (BabelTypes.isReturnStatement(stmt)) visit(stmt.argument);
|
|
1838
|
+
}
|
|
1839
|
+
} else {
|
|
1840
|
+
visit(arg.body);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
if (BabelTypes.isArrayExpression(node)) {
|
|
1847
|
+
for (const el of node.elements) visit(el);
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
if (BabelTypes.isTSAsExpression(node) || BabelTypes.isTSNonNullExpression(node)) {
|
|
1851
|
+
visit(node.expression);
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
for (const child of children) visit(child);
|
|
1855
|
+
return found;
|
|
1856
|
+
}
|
|
1686
1857
|
/** Extract screens from a navigator JSX element */
|
|
1687
1858
|
extractScreensFromNavigator(navigatorElement, navigatorVarName, navigatorType) {
|
|
1688
1859
|
const screens = [];
|
|
1689
1860
|
if (!navigatorElement.children) return screens;
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
name: screenName,
|
|
1701
|
-
navigatorName: navigatorVarName,
|
|
1702
|
-
navigatorType
|
|
1703
|
-
});
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1861
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1862
|
+
for (const element of this.collectScreenElements(navigatorElement.children, navigatorVarName)) {
|
|
1863
|
+
const screenName = this.extractAttributeValue(element.openingElement.attributes, "name");
|
|
1864
|
+
if (!screenName || seen.has(screenName)) continue;
|
|
1865
|
+
seen.add(screenName);
|
|
1866
|
+
screens.push({
|
|
1867
|
+
name: screenName,
|
|
1868
|
+
navigatorName: navigatorVarName,
|
|
1869
|
+
navigatorType
|
|
1870
|
+
});
|
|
1707
1871
|
}
|
|
1708
1872
|
return screens;
|
|
1709
1873
|
}
|
|
@@ -1762,9 +1926,7 @@ var NavigationAnalyzer = class {
|
|
|
1762
1926
|
if (member.type === "TSPropertySignature" && member.key) {
|
|
1763
1927
|
const keyName = member.key.type === "Identifier" ? member.key.name : null;
|
|
1764
1928
|
if (keyName && member.typeAnnotation) {
|
|
1765
|
-
const params = this.extractParamsFromType(
|
|
1766
|
-
member.typeAnnotation.typeAnnotation
|
|
1767
|
-
);
|
|
1929
|
+
const params = this.extractParamsFromType(member.typeAnnotation.typeAnnotation);
|
|
1768
1930
|
entries.set(keyName, params);
|
|
1769
1931
|
}
|
|
1770
1932
|
}
|
|
@@ -1843,6 +2005,11 @@ var NavigationAnalyzer = class {
|
|
|
1843
2005
|
let initialScreen = "";
|
|
1844
2006
|
for (const navigator of parsedNavigators) {
|
|
1845
2007
|
const screenNames = navigator.screens.map((s) => s.name);
|
|
2008
|
+
if (screenNames.length === 0) {
|
|
2009
|
+
console.warn(
|
|
2010
|
+
`[MCPGenerator] navigator "${navigator.name}" (${navigator.type}) declared no reachable screens. Its routes will be missing from the app map, so the agent will have no destination name for them. If the screens are there in the source, this is a parser gap \u2014 please report it with the file.`
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
1846
2013
|
navigators.push({
|
|
1847
2014
|
name: navigator.name,
|
|
1848
2015
|
type: navigator.type,
|
|
@@ -2630,7 +2797,9 @@ var WebScreenAnalyzer = class {
|
|
|
2630
2797
|
include = ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"],
|
|
2631
2798
|
exclude = ["**/node_modules/**", "**/dist/**", "**/build/**"]
|
|
2632
2799
|
} = this.config;
|
|
2633
|
-
const files = await glob(include, { cwd: this.config.rootDir, ignore: exclude })
|
|
2800
|
+
const files = (await glob(include, { cwd: this.config.rootDir, ignore: exclude })).filter(
|
|
2801
|
+
(file) => !file.endsWith(".d.ts")
|
|
2802
|
+
);
|
|
2634
2803
|
const patternMatches = await glob(this.screenPatterns, {
|
|
2635
2804
|
cwd: this.config.rootDir,
|
|
2636
2805
|
ignore: exclude
|
|
@@ -3001,7 +3170,8 @@ var WebScreenAnalyzer = class {
|
|
|
3001
3170
|
if (type === "email") return "email";
|
|
3002
3171
|
if (type === "tel") return "phone";
|
|
3003
3172
|
if (type === "number") return "number";
|
|
3004
|
-
if (type === "date" || type === "datetime-local" || type === "month" || type === "week")
|
|
3173
|
+
if (type === "date" || type === "datetime-local" || type === "month" || type === "week")
|
|
3174
|
+
return "date";
|
|
3005
3175
|
const inputMode = getStringAttr(opening, "inputMode") ?? getStringAttr(opening, "inputmode");
|
|
3006
3176
|
if (inputMode === "email") return "email";
|
|
3007
3177
|
if (inputMode === "tel") return "phone";
|
|
@@ -3315,7 +3485,9 @@ var WebScreenAnalyzer = class {
|
|
|
3315
3485
|
}
|
|
3316
3486
|
const callback = expr.arguments[0];
|
|
3317
3487
|
if (!BabelTypes.isArrowFunctionExpression(callback) && !BabelTypes.isFunctionExpression(callback)) return;
|
|
3318
|
-
const enclosing = nodePath.findParent(
|
|
3488
|
+
const enclosing = nodePath.findParent(
|
|
3489
|
+
(p) => p.isJSXElement()
|
|
3490
|
+
);
|
|
3319
3491
|
const enclosingName = enclosing ? getJsxElementName(enclosing.node.openingElement) : null;
|
|
3320
3492
|
const enclosingRole = enclosing && enclosingName ? classifyWebJsxComponent(enclosingName, enclosing.node.openingElement) : null;
|
|
3321
3493
|
const returnsListItem = callbackReturnsTag(callback, /* @__PURE__ */ new Set(["li", "tr"]));
|
|
@@ -3417,16 +3589,19 @@ function firstCalledFunctionName(node) {
|
|
|
3417
3589
|
}
|
|
3418
3590
|
function containsWindowConfirm(body) {
|
|
3419
3591
|
let found = false;
|
|
3420
|
-
traverse4(
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
found = true;
|
|
3592
|
+
traverse4(
|
|
3593
|
+
body,
|
|
3594
|
+
{
|
|
3595
|
+
noScope: true,
|
|
3596
|
+
CallExpression: (nodePath) => {
|
|
3597
|
+
const callee = nodePath.node.callee;
|
|
3598
|
+
if (BabelTypes.isIdentifier(callee) && callee.name === "confirm") found = true;
|
|
3599
|
+
if (BabelTypes.isMemberExpression(callee) && BabelTypes.isIdentifier(callee.object) && callee.object.name === "window" && BabelTypes.isIdentifier(callee.property) && callee.property.name === "confirm") {
|
|
3600
|
+
found = true;
|
|
3601
|
+
}
|
|
3427
3602
|
}
|
|
3428
3603
|
}
|
|
3429
|
-
|
|
3604
|
+
);
|
|
3430
3605
|
return found;
|
|
3431
3606
|
}
|
|
3432
3607
|
function mergeLocator(current, next) {
|
|
@@ -3466,32 +3641,38 @@ function collectionItemNames(callback) {
|
|
|
3466
3641
|
function collectionDisplayFields(callback, itemNames) {
|
|
3467
3642
|
const fields = /* @__PURE__ */ new Set();
|
|
3468
3643
|
if (!callback.body) return [];
|
|
3469
|
-
traverse4(
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3644
|
+
traverse4(
|
|
3645
|
+
callback.body,
|
|
3646
|
+
{
|
|
3647
|
+
noScope: true,
|
|
3648
|
+
MemberExpression: (nodePath) => {
|
|
3649
|
+
const node = nodePath.node;
|
|
3650
|
+
if (BabelTypes.isIdentifier(node.object) && itemNames.has(node.object.name) && BabelTypes.isIdentifier(node.property)) {
|
|
3651
|
+
fields.add(node.property.name);
|
|
3652
|
+
}
|
|
3475
3653
|
}
|
|
3476
3654
|
}
|
|
3477
|
-
|
|
3655
|
+
);
|
|
3478
3656
|
return Array.from(fields).sort();
|
|
3479
3657
|
}
|
|
3480
3658
|
function collectionKeyField(callback, itemNames) {
|
|
3481
3659
|
let keyField;
|
|
3482
3660
|
if (!callback.body) return void 0;
|
|
3483
|
-
traverse4(
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3661
|
+
traverse4(
|
|
3662
|
+
callback.body,
|
|
3663
|
+
{
|
|
3664
|
+
noScope: true,
|
|
3665
|
+
JSXAttribute: (nodePath) => {
|
|
3666
|
+
const attr = nodePath.node;
|
|
3667
|
+
if (!BabelTypes.isJSXIdentifier(attr.name) || attr.name.name !== "key") return;
|
|
3668
|
+
if (!attr.value || !BabelTypes.isJSXExpressionContainer(attr.value)) return;
|
|
3669
|
+
const expr = attr.value.expression;
|
|
3670
|
+
if (BabelTypes.isMemberExpression(expr) && BabelTypes.isIdentifier(expr.object) && itemNames.has(expr.object.name) && BabelTypes.isIdentifier(expr.property)) {
|
|
3671
|
+
keyField = keyField ?? expr.property.name;
|
|
3672
|
+
}
|
|
3492
3673
|
}
|
|
3493
3674
|
}
|
|
3494
|
-
|
|
3675
|
+
);
|
|
3495
3676
|
return keyField;
|
|
3496
3677
|
}
|
|
3497
3678
|
function collectionRowAction(callback) {
|
|
@@ -3539,7 +3720,7 @@ function inferIdentityFields(keyField, displayFields) {
|
|
|
3539
3720
|
const out = /* @__PURE__ */ new Set();
|
|
3540
3721
|
if (keyField) out.add(keyField);
|
|
3541
3722
|
for (const field of displayFields) {
|
|
3542
|
-
if (/^(id|uuid|key|name|title|
|
|
3723
|
+
if (/^(id|uuid|key|name|title|email|slug)$/i.test(field)) out.add(field);
|
|
3543
3724
|
}
|
|
3544
3725
|
return Array.from(out);
|
|
3545
3726
|
}
|
|
@@ -7923,7 +8104,7 @@ var coerce = {
|
|
|
7923
8104
|
};
|
|
7924
8105
|
var NEVER = INVALID;
|
|
7925
8106
|
|
|
7926
|
-
// ../shared/dist/chunk-
|
|
8107
|
+
// ../shared/dist/chunk-PKS3VDNB.mjs
|
|
7927
8108
|
var locatorSourceSchema = external_exports.enum([
|
|
7928
8109
|
"appilotsId",
|
|
7929
8110
|
"testID",
|
|
@@ -7949,7 +8130,16 @@ var locatorDescriptorSchema = external_exports.object({
|
|
|
7949
8130
|
source: locatorSourceSchema.optional()
|
|
7950
8131
|
}).passthrough();
|
|
7951
8132
|
var signalDescriptorSchema = external_exports.object({
|
|
7952
|
-
type: external_exports.enum([
|
|
8133
|
+
type: external_exports.enum([
|
|
8134
|
+
"navigation",
|
|
8135
|
+
"goBack",
|
|
8136
|
+
"toast",
|
|
8137
|
+
"modal",
|
|
8138
|
+
"inline-error",
|
|
8139
|
+
"data-arrival",
|
|
8140
|
+
"loading",
|
|
8141
|
+
"none"
|
|
8142
|
+
]).or(external_exports.string().min(1).max(60)),
|
|
7953
8143
|
target: external_exports.string().optional(),
|
|
7954
8144
|
description: external_exports.string().optional()
|
|
7955
8145
|
}).passthrough();
|
|
@@ -7963,7 +8153,20 @@ var waitPolicyDescriptorSchema = external_exports.object({
|
|
|
7963
8153
|
signals: external_exports.array(signalDescriptorSchema).optional(),
|
|
7964
8154
|
maxMs: external_exports.number().finite().optional()
|
|
7965
8155
|
}).passthrough();
|
|
7966
|
-
var targetDescriptorRoleSchema = external_exports.enum([
|
|
8156
|
+
var targetDescriptorRoleSchema = external_exports.enum([
|
|
8157
|
+
"button",
|
|
8158
|
+
"submit",
|
|
8159
|
+
"input",
|
|
8160
|
+
"toggle",
|
|
8161
|
+
"select",
|
|
8162
|
+
"date",
|
|
8163
|
+
"list",
|
|
8164
|
+
"row",
|
|
8165
|
+
"menuItem",
|
|
8166
|
+
"modal",
|
|
8167
|
+
"custom"
|
|
8168
|
+
]).or(external_exports.string().min(1).max(60));
|
|
8169
|
+
var targetOriginSchema = external_exports.enum(["analyzer", "manifest"]).or(external_exports.string().min(1).max(40));
|
|
7967
8170
|
var targetDescriptorSchema = external_exports.object({
|
|
7968
8171
|
id: external_exports.string(),
|
|
7969
8172
|
role: targetDescriptorRoleSchema,
|
|
@@ -7977,10 +8180,20 @@ var targetDescriptorSchema = external_exports.object({
|
|
|
7977
8180
|
requiresConfirmation: external_exports.boolean().optional(),
|
|
7978
8181
|
opensModal: external_exports.string().optional(),
|
|
7979
8182
|
opensBottomSheet: external_exports.string().optional(),
|
|
7980
|
-
sourceComponent: external_exports.string().optional()
|
|
8183
|
+
sourceComponent: external_exports.string().optional(),
|
|
8184
|
+
origin: targetOriginSchema.optional()
|
|
7981
8185
|
}).passthrough();
|
|
7982
8186
|
var flowStepDescriptorSchema = external_exports.object({
|
|
7983
|
-
type: external_exports.enum([
|
|
8187
|
+
type: external_exports.enum([
|
|
8188
|
+
"navigate",
|
|
8189
|
+
"fill",
|
|
8190
|
+
"press",
|
|
8191
|
+
"select",
|
|
8192
|
+
"toggle",
|
|
8193
|
+
"wait",
|
|
8194
|
+
"confirm",
|
|
8195
|
+
"choose-list-item"
|
|
8196
|
+
]).or(external_exports.string().min(1).max(60)),
|
|
7984
8197
|
target: external_exports.string().optional(),
|
|
7985
8198
|
label: external_exports.string().optional(),
|
|
7986
8199
|
description: external_exports.string().optional(),
|
|
@@ -8202,6 +8415,15 @@ external_exports.object({
|
|
|
8202
8415
|
password: external_exports.string().min(1, "Password is required"),
|
|
8203
8416
|
code: totpCodeSchema
|
|
8204
8417
|
});
|
|
8418
|
+
external_exports.object({
|
|
8419
|
+
password: external_exports.string().min(1, "Password is required"),
|
|
8420
|
+
code: totpCodeSchema.optional(),
|
|
8421
|
+
recoveryCode: recoveryCodeSchema.optional(),
|
|
8422
|
+
confirm: external_exports.literal("DELETE MY ACCOUNT")
|
|
8423
|
+
}).refine((v) => !(v.code && v.recoveryCode), {
|
|
8424
|
+
message: "Provide either a TOTP code or a recovery code, not both",
|
|
8425
|
+
path: ["code"]
|
|
8426
|
+
});
|
|
8205
8427
|
var createProjectSchema = external_exports.object({
|
|
8206
8428
|
name: external_exports.string().min(1).max(100),
|
|
8207
8429
|
description: external_exports.string().max(500).optional(),
|
|
@@ -8237,6 +8459,7 @@ external_exports.object({
|
|
|
8237
8459
|
content: external_exports.record(external_exports.unknown())
|
|
8238
8460
|
});
|
|
8239
8461
|
var apiKeyScopeSchema = external_exports.enum(["sdk", "operator"]);
|
|
8462
|
+
var apiKeyEnvironmentSchema = external_exports.enum(["test", "live"]);
|
|
8240
8463
|
external_exports.object({
|
|
8241
8464
|
name: external_exports.string().min(1).max(100),
|
|
8242
8465
|
// Required for `sdk` (default), forbidden/ignored for `operator` —
|
|
@@ -8244,6 +8467,7 @@ external_exports.object({
|
|
|
8244
8467
|
// missing scope still validates as the pre-existing SDK shape.
|
|
8245
8468
|
projectId: external_exports.string().min(1).optional(),
|
|
8246
8469
|
scope: apiKeyScopeSchema.default("sdk"),
|
|
8470
|
+
environment: apiKeyEnvironmentSchema.optional(),
|
|
8247
8471
|
expiresAt: external_exports.string().datetime().optional()
|
|
8248
8472
|
}).refine((v) => v.scope !== "sdk" || !!v.projectId, {
|
|
8249
8473
|
message: 'projectId is required for scope "sdk"',
|
|
@@ -8251,13 +8475,23 @@ external_exports.object({
|
|
|
8251
8475
|
});
|
|
8252
8476
|
var boundedString = (max) => external_exports.string().max(max);
|
|
8253
8477
|
var clientPlatformSchema = external_exports.enum(["react-native", "web", "android", "ios"]).or(external_exports.string().min(1).max(40));
|
|
8478
|
+
var identityProvenanceSchema = external_exports.enum(["declared", "derived", "positional"]).or(external_exports.string().min(1).max(40));
|
|
8254
8479
|
var snapshotInputSchema = external_exports.object({
|
|
8255
8480
|
id: boundedString(160).optional(),
|
|
8481
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8482
|
+
/**
|
|
8483
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8484
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8485
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8486
|
+
* before the field existed), which must never be read as `false`.
|
|
8487
|
+
*/
|
|
8488
|
+
onScreen: external_exports.boolean().optional(),
|
|
8256
8489
|
label: boundedString(300).optional(),
|
|
8257
8490
|
value: boundedString(4096).optional(),
|
|
8258
8491
|
placeholder: boundedString(300).optional(),
|
|
8259
8492
|
editable: external_exports.boolean().optional(),
|
|
8260
8493
|
secure: external_exports.boolean().optional(),
|
|
8494
|
+
submitsOnReturn: external_exports.boolean().optional(),
|
|
8261
8495
|
type: boundedString(40).optional(),
|
|
8262
8496
|
required: external_exports.boolean().optional(),
|
|
8263
8497
|
invalid: external_exports.boolean().optional(),
|
|
@@ -8265,6 +8499,14 @@ var snapshotInputSchema = external_exports.object({
|
|
|
8265
8499
|
}).passthrough();
|
|
8266
8500
|
var snapshotButtonSchema = external_exports.object({
|
|
8267
8501
|
id: boundedString(160).optional(),
|
|
8502
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8503
|
+
/**
|
|
8504
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8505
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8506
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8507
|
+
* before the field existed), which must never be read as `false`.
|
|
8508
|
+
*/
|
|
8509
|
+
onScreen: external_exports.boolean().optional(),
|
|
8268
8510
|
label: boundedString(300).optional(),
|
|
8269
8511
|
disabled: external_exports.boolean().optional(),
|
|
8270
8512
|
inModal: external_exports.boolean().optional(),
|
|
@@ -8274,12 +8516,28 @@ var snapshotButtonSchema = external_exports.object({
|
|
|
8274
8516
|
}).passthrough();
|
|
8275
8517
|
var snapshotToggleSchema = external_exports.object({
|
|
8276
8518
|
id: boundedString(160).optional(),
|
|
8519
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8520
|
+
/**
|
|
8521
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8522
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8523
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8524
|
+
* before the field existed), which must never be read as `false`.
|
|
8525
|
+
*/
|
|
8526
|
+
onScreen: external_exports.boolean().optional(),
|
|
8277
8527
|
label: boundedString(300).optional(),
|
|
8278
8528
|
value: external_exports.boolean().optional(),
|
|
8279
8529
|
inModal: external_exports.boolean().optional()
|
|
8280
8530
|
}).passthrough();
|
|
8281
8531
|
var snapshotSliderSchema = external_exports.object({
|
|
8282
8532
|
id: boundedString(160).optional(),
|
|
8533
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8534
|
+
/**
|
|
8535
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8536
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8537
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8538
|
+
* before the field existed), which must never be read as `false`.
|
|
8539
|
+
*/
|
|
8540
|
+
onScreen: external_exports.boolean().optional(),
|
|
8283
8541
|
label: boundedString(300).optional(),
|
|
8284
8542
|
value: external_exports.number().finite().optional(),
|
|
8285
8543
|
min: external_exports.number().finite().optional(),
|
|
@@ -8320,6 +8578,26 @@ var snapshotListSchema = external_exports.object({
|
|
|
8320
8578
|
// should chase, and the relay only reads a known subset.
|
|
8321
8579
|
items: external_exports.array(external_exports.record(external_exports.unknown())).max(500).optional()
|
|
8322
8580
|
}).passthrough();
|
|
8581
|
+
var snapshotNativeDialogSchema = external_exports.object({
|
|
8582
|
+
id: boundedString(160),
|
|
8583
|
+
title: boundedString(300).optional(),
|
|
8584
|
+
message: boundedString(2e3).optional(),
|
|
8585
|
+
buttons: external_exports.array(
|
|
8586
|
+
external_exports.object({
|
|
8587
|
+
label: boundedString(160),
|
|
8588
|
+
style: external_exports.enum(["default", "cancel", "destructive"]).optional()
|
|
8589
|
+
}).passthrough()
|
|
8590
|
+
).max(10)
|
|
8591
|
+
}).passthrough();
|
|
8592
|
+
var snapshotScrollableSchema = external_exports.object({
|
|
8593
|
+
id: boundedString(160),
|
|
8594
|
+
containerType: boundedString(60).optional(),
|
|
8595
|
+
label: boundedString(300).optional(),
|
|
8596
|
+
scrollOffsetY: external_exports.number().finite().optional(),
|
|
8597
|
+
canScrollUp: external_exports.boolean().optional(),
|
|
8598
|
+
canScrollDown: external_exports.boolean().optional(),
|
|
8599
|
+
horizontal: external_exports.boolean().optional()
|
|
8600
|
+
}).passthrough();
|
|
8323
8601
|
var snapshotChoiceGroupSchema = external_exports.object({
|
|
8324
8602
|
index: external_exports.number().int().optional(),
|
|
8325
8603
|
id: boundedString(160).optional(),
|
|
@@ -8336,6 +8614,14 @@ var snapshotElementSchema = external_exports.object({
|
|
|
8336
8614
|
disabled: external_exports.boolean().optional(),
|
|
8337
8615
|
selected: external_exports.boolean().optional(),
|
|
8338
8616
|
source: boundedString(40).optional(),
|
|
8617
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8618
|
+
/**
|
|
8619
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8620
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8621
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8622
|
+
* before the field existed), which must never be read as `false`.
|
|
8623
|
+
*/
|
|
8624
|
+
onScreen: external_exports.boolean().optional(),
|
|
8339
8625
|
targetId: boundedString(160).optional(),
|
|
8340
8626
|
listContext: external_exports.record(external_exports.unknown()).optional(),
|
|
8341
8627
|
inModal: external_exports.boolean().optional()
|
|
@@ -8352,7 +8638,34 @@ var agentSnapshotSchema = external_exports.object({
|
|
|
8352
8638
|
modalOpen: external_exports.boolean().optional(),
|
|
8353
8639
|
lists: external_exports.array(snapshotListSchema).max(100).optional(),
|
|
8354
8640
|
choiceGroups: external_exports.array(snapshotChoiceGroupSchema).max(100).optional(),
|
|
8355
|
-
|
|
8641
|
+
scrollables: external_exports.array(snapshotScrollableSchema).max(20).optional(),
|
|
8642
|
+
nativeDialog: snapshotNativeDialogSchema.optional(),
|
|
8643
|
+
elements: external_exports.array(snapshotElementSchema).max(1e3).optional(),
|
|
8644
|
+
/**
|
|
8645
|
+
* The client clamped this observation to the caps above and lost
|
|
8646
|
+
* data doing it (`clampSnapshotToWireLimits` in
|
|
8647
|
+
* `@appilots/client-core`). Read by the prompt serializer so the
|
|
8648
|
+
* model is told the view is partial — a screen truncated at 500
|
|
8649
|
+
* texts must not be read as a screen that only has 500 things on
|
|
8650
|
+
* it. Optional: older SDKs never clamp and never send it.
|
|
8651
|
+
*/
|
|
8652
|
+
truncated: external_exports.boolean().optional(),
|
|
8653
|
+
/**
|
|
8654
|
+
* How many of this screen's controls the client could name, split by
|
|
8655
|
+
* `identityProvenance`. Diagnostic only — the relay never grounds an
|
|
8656
|
+
* action on it — but it is the number that says whether an app needs
|
|
8657
|
+
* annotating, and comparing it between a debug and a release build is
|
|
8658
|
+
* the first direct measurement of what minification costs the agent.
|
|
8659
|
+
*/
|
|
8660
|
+
stats: external_exports.object({
|
|
8661
|
+
visitedFibers: external_exports.number().int().nonnegative().optional(),
|
|
8662
|
+
skippedHidden: external_exports.number().int().nonnegative().optional(),
|
|
8663
|
+
identity: external_exports.object({
|
|
8664
|
+
declared: external_exports.number().int().nonnegative().optional(),
|
|
8665
|
+
derived: external_exports.number().int().nonnegative().optional(),
|
|
8666
|
+
positional: external_exports.number().int().nonnegative().optional()
|
|
8667
|
+
}).passthrough().optional()
|
|
8668
|
+
}).passthrough().optional()
|
|
8356
8669
|
}).passthrough();
|
|
8357
8670
|
var agentContextSchema = external_exports.object({
|
|
8358
8671
|
/**
|
|
@@ -8402,6 +8715,19 @@ var agentContextSchema = external_exports.object({
|
|
|
8402
8715
|
).max(200).optional(),
|
|
8403
8716
|
loadingPending: external_exports.boolean().optional()
|
|
8404
8717
|
}).passthrough();
|
|
8718
|
+
var introspectionFailureReasonSchema = external_exports.enum([
|
|
8719
|
+
"sentinel-missing-internals",
|
|
8720
|
+
"never-mounted",
|
|
8721
|
+
"render-crashed",
|
|
8722
|
+
"names-mangled",
|
|
8723
|
+
"walk-recognized-nothing"
|
|
8724
|
+
]);
|
|
8725
|
+
var introspectionDiagnosticsSchema = external_exports.object({
|
|
8726
|
+
captured: external_exports.boolean(),
|
|
8727
|
+
failureReason: introspectionFailureReasonSchema.nullish(),
|
|
8728
|
+
reactVersion: boundedString(32).nullish(),
|
|
8729
|
+
failureCount: external_exports.number().int().min(0).max(1e6).optional()
|
|
8730
|
+
});
|
|
8405
8731
|
external_exports.object({
|
|
8406
8732
|
content: external_exports.string().min(1).max(4096),
|
|
8407
8733
|
sessionId: external_exports.string().optional(),
|
|
@@ -8411,7 +8737,20 @@ external_exports.object({
|
|
|
8411
8737
|
* the active MCP doc and emits `mcp_version_mismatch` telemetry when
|
|
8412
8738
|
* they differ. Optional — older SDKs simply skip the check.
|
|
8413
8739
|
*/
|
|
8414
|
-
mcpVersion: external_exports.string().max(64).optional()
|
|
8740
|
+
mcpVersion: external_exports.string().max(64).optional(),
|
|
8741
|
+
/**
|
|
8742
|
+
* SDK-side introspection health. Only sent when it FAILED — a client
|
|
8743
|
+
* that reached the React tree omits the field entirely, so the happy
|
|
8744
|
+
* path costs nothing on the wire.
|
|
8745
|
+
*
|
|
8746
|
+
* Without this, an app whose introspection is dead sends a snapshot
|
|
8747
|
+
* that is empty but perfectly well-formed, indistinguishable from a
|
|
8748
|
+
* genuinely empty screen. The server emits
|
|
8749
|
+
* `introspection_unavailable` so the failure is visible to us instead
|
|
8750
|
+
* of waiting for a bug report. Optional — older SDKs simply never
|
|
8751
|
+
* send it.
|
|
8752
|
+
*/
|
|
8753
|
+
introspection: introspectionDiagnosticsSchema.optional()
|
|
8415
8754
|
});
|
|
8416
8755
|
var agentActionTypeSchema = external_exports.enum([
|
|
8417
8756
|
"navigate",
|
|
@@ -8422,11 +8761,22 @@ var agentActionTypeSchema = external_exports.enum([
|
|
|
8422
8761
|
"custom"
|
|
8423
8762
|
]);
|
|
8424
8763
|
var navigationPayloadSchema = external_exports.object({
|
|
8425
|
-
|
|
8764
|
+
// Optional ONLY so `goBack` can exist (#398): it returns to whatever
|
|
8765
|
+
// is beneath on the stack and has no destination to name. Every other
|
|
8766
|
+
// action is still required to carry one — enforced by the refine
|
|
8767
|
+
// below rather than by the field, so the error names the real problem
|
|
8768
|
+
// ("navigate needs a screenName") instead of a missing key.
|
|
8769
|
+
screenName: external_exports.string().min(1).optional(),
|
|
8426
8770
|
params: external_exports.record(external_exports.unknown()).optional(),
|
|
8427
8771
|
navigationAction: external_exports.enum(["push", "navigate", "replace", "goBack", "reset"]).default("navigate").optional(),
|
|
8428
8772
|
path: external_exports.array(external_exports.string().min(1)).optional()
|
|
8429
|
-
}).passthrough()
|
|
8773
|
+
}).passthrough().refine(
|
|
8774
|
+
(payload) => payload.navigationAction === "goBack" || typeof payload.screenName === "string" && payload.screenName.length > 0,
|
|
8775
|
+
{
|
|
8776
|
+
message: 'screenName is required for every navigationAction except "goBack"',
|
|
8777
|
+
path: ["screenName"]
|
|
8778
|
+
}
|
|
8779
|
+
);
|
|
8430
8780
|
var formFillFieldSchema = external_exports.object({
|
|
8431
8781
|
fieldId: external_exports.string().min(1),
|
|
8432
8782
|
fieldType: external_exports.enum(["text", "select", "toggle", "date", "number", "custom"]).default("text").optional(),
|
|
@@ -8760,6 +9110,8 @@ var sandboxObservationSchema = external_exports.object({
|
|
|
8760
9110
|
inputs: external_exports.array(
|
|
8761
9111
|
external_exports.object({
|
|
8762
9112
|
id: external_exports.string().max(120),
|
|
9113
|
+
provenance: identityProvenanceSchema.optional(),
|
|
9114
|
+
onScreen: external_exports.boolean().optional(),
|
|
8763
9115
|
label: external_exports.string().max(200).optional(),
|
|
8764
9116
|
value: external_exports.string().max(2e3).optional(),
|
|
8765
9117
|
disabled: external_exports.boolean().optional(),
|
|
@@ -8773,6 +9125,8 @@ var sandboxObservationSchema = external_exports.object({
|
|
|
8773
9125
|
buttons: external_exports.array(
|
|
8774
9126
|
external_exports.object({
|
|
8775
9127
|
id: external_exports.string().max(120),
|
|
9128
|
+
provenance: identityProvenanceSchema.optional(),
|
|
9129
|
+
onScreen: external_exports.boolean().optional(),
|
|
8776
9130
|
label: external_exports.string().max(200).optional(),
|
|
8777
9131
|
disabled: external_exports.boolean().optional(),
|
|
8778
9132
|
/** Risk metadata (e.g. "high") the SDK forwards for danger UI. */
|
|
@@ -8794,6 +9148,8 @@ var sandboxObservationSchema = external_exports.object({
|
|
|
8794
9148
|
sliders: external_exports.array(
|
|
8795
9149
|
external_exports.object({
|
|
8796
9150
|
id: external_exports.string().max(120),
|
|
9151
|
+
provenance: identityProvenanceSchema.optional(),
|
|
9152
|
+
onScreen: external_exports.boolean().optional(),
|
|
8797
9153
|
label: external_exports.string().max(200).optional(),
|
|
8798
9154
|
value: external_exports.number().finite().optional(),
|
|
8799
9155
|
min: external_exports.number().finite().optional(),
|
|
@@ -8823,6 +9179,44 @@ var sandboxObservationSchema = external_exports.object({
|
|
|
8823
9179
|
).max(200).optional()
|
|
8824
9180
|
}).passthrough()
|
|
8825
9181
|
).max(20).optional(),
|
|
9182
|
+
/**
|
|
9183
|
+
* Toggles exactly as the SDK snapshot reports them (#390).
|
|
9184
|
+
*
|
|
9185
|
+
* Their absence is why `generality-component-control-smarthome`
|
|
9186
|
+
* came back `errored` with a 422 instead of measuring anything:
|
|
9187
|
+
* this schema is `.strict()`, so a scenario that declares a switch
|
|
9188
|
+
* is rejected before the agent is ever asked. Which means the
|
|
9189
|
+
* corpus could not express the single control behind failure 02 of
|
|
9190
|
+
* the functional audit — "disable an agent; the switch never
|
|
9191
|
+
* moves". The one defect the eval most needed to catch was the one
|
|
9192
|
+
* shape it could not describe.
|
|
9193
|
+
*/
|
|
9194
|
+
toggles: external_exports.array(
|
|
9195
|
+
external_exports.object({
|
|
9196
|
+
id: external_exports.string().max(120),
|
|
9197
|
+
provenance: identityProvenanceSchema.optional(),
|
|
9198
|
+
onScreen: external_exports.boolean().optional(),
|
|
9199
|
+
label: external_exports.string().max(200).optional(),
|
|
9200
|
+
value: external_exports.boolean().optional(),
|
|
9201
|
+
disabled: external_exports.boolean().optional()
|
|
9202
|
+
})
|
|
9203
|
+
).max(100).optional(),
|
|
9204
|
+
/**
|
|
9205
|
+
* The interaction graph — the `el:*` ids the prompt tells the model
|
|
9206
|
+
* to prefer over ordinal handles. Passthrough per entry for the
|
|
9207
|
+
* same reason `lists` is: the SDK adds fields faster than this
|
|
9208
|
+
* schema should chase them.
|
|
9209
|
+
*/
|
|
9210
|
+
elements: external_exports.array(
|
|
9211
|
+
external_exports.object({
|
|
9212
|
+
id: external_exports.string().max(160),
|
|
9213
|
+
role: external_exports.string().max(40).optional(),
|
|
9214
|
+
label: external_exports.string().max(300).optional()
|
|
9215
|
+
}).passthrough()
|
|
9216
|
+
).max(500).optional(),
|
|
9217
|
+
/** Screen-level state the relay renders and branches on. */
|
|
9218
|
+
loading: external_exports.boolean().optional(),
|
|
9219
|
+
modalOpen: external_exports.boolean().optional(),
|
|
8826
9220
|
extra: external_exports.record(external_exports.unknown()).optional()
|
|
8827
9221
|
}).strict();
|
|
8828
9222
|
external_exports.object({
|
|
@@ -8863,13 +9257,29 @@ external_exports.object({
|
|
|
8863
9257
|
* heuristics, forces tool choice when the last user message carries a
|
|
8864
9258
|
* failure marker). Only meaningful alongside `history`.
|
|
8865
9259
|
*/
|
|
8866
|
-
isRecoveryHop: external_exports.boolean().optional()
|
|
9260
|
+
isRecoveryHop: external_exports.boolean().optional(),
|
|
9261
|
+
/**
|
|
9262
|
+
* Run this call against the application structure supplied here
|
|
9263
|
+
* instead of the project's active MCP document.
|
|
9264
|
+
*
|
|
9265
|
+
* Sandbox-only, and the relay honours it only on a `dryRun` call —
|
|
9266
|
+
* see `resolveMcpOverride` in apps/api. It exists because the eval
|
|
9267
|
+
* corpus could otherwise only ever describe ONE app: every scenario
|
|
9268
|
+
* resolves to the same seeded project, so every navigate destination,
|
|
9269
|
+
* every screen title and every bit of domain priming in the prompt
|
|
9270
|
+
* came from the same vehicle-management demo. A suite that can only
|
|
9271
|
+
* pose questions about one app cannot measure whether the agent works
|
|
9272
|
+
* on another one.
|
|
9273
|
+
*
|
|
9274
|
+
* Deliberately loose (`z.record`): the MCP document's shape is owned
|
|
9275
|
+
* by the generator, and pinning it here would turn every additive
|
|
9276
|
+
* generator change into a 422 in the sandbox.
|
|
9277
|
+
*/
|
|
9278
|
+
applicationStructure: external_exports.record(external_exports.unknown()).optional()
|
|
8867
9279
|
}).strict();
|
|
8868
9280
|
var relayDiagnosticsSchema = external_exports.object({
|
|
8869
9281
|
unverifiedTargets: external_exports.array(external_exports.string()),
|
|
8870
|
-
guardsFired: external_exports.array(
|
|
8871
|
-
external_exports.object({ guard: external_exports.string(), detail: external_exports.string().optional() })
|
|
8872
|
-
),
|
|
9282
|
+
guardsFired: external_exports.array(external_exports.object({ guard: external_exports.string(), detail: external_exports.string().optional() })),
|
|
8873
9283
|
repairMode: external_exports.enum(["repair", "strict", "disabled"]),
|
|
8874
9284
|
promptVersion: external_exports.string(),
|
|
8875
9285
|
// Defaulted for producers that predate the field: before it existed
|
|
@@ -9128,6 +9538,30 @@ external_exports.object({
|
|
|
9128
9538
|
name: external_exports.string().min(1).max(200).optional(),
|
|
9129
9539
|
identifiers: endUserIdentifiersSchema.optional()
|
|
9130
9540
|
});
|
|
9541
|
+
var MAX_EXTERNAL_USER_ID_LENGTH = 128;
|
|
9542
|
+
function normalizeExternalUserId(value) {
|
|
9543
|
+
let raw;
|
|
9544
|
+
if (typeof value === "string") raw = value;
|
|
9545
|
+
else if (typeof value === "number" && Number.isFinite(value)) raw = String(value);
|
|
9546
|
+
else return void 0;
|
|
9547
|
+
const trimmed = raw.trim();
|
|
9548
|
+
if (!trimmed) return void 0;
|
|
9549
|
+
if (trimmed.length > MAX_EXTERNAL_USER_ID_LENGTH) return void 0;
|
|
9550
|
+
return trimmed;
|
|
9551
|
+
}
|
|
9552
|
+
var MAX_DEVICE_INFO_BYTES = 4096;
|
|
9553
|
+
var sessionDeviceInfoSchema = external_exports.object({
|
|
9554
|
+
platform: external_exports.string().max(64).optional(),
|
|
9555
|
+
osVersion: external_exports.string().max(64).optional(),
|
|
9556
|
+
appVersion: external_exports.string().max(64).optional(),
|
|
9557
|
+
sdkVersion: external_exports.string().max(64).optional()
|
|
9558
|
+
}).passthrough().refine((info) => JSON.stringify(info).length <= MAX_DEVICE_INFO_BYTES, {
|
|
9559
|
+
message: `device_info exceeds ${MAX_DEVICE_INFO_BYTES} bytes`
|
|
9560
|
+
});
|
|
9561
|
+
external_exports.object({
|
|
9562
|
+
userId: external_exports.preprocess(normalizeExternalUserId, external_exports.string().optional()),
|
|
9563
|
+
deviceInfo: sessionDeviceInfoSchema.optional().catch(void 0)
|
|
9564
|
+
});
|
|
9131
9565
|
external_exports.object({
|
|
9132
9566
|
projectId: external_exports.string().min(1).max(64),
|
|
9133
9567
|
externalId: external_exports.string().min(1).max(128),
|
|
@@ -9135,6 +9569,27 @@ external_exports.object({
|
|
|
9135
9569
|
identifiers: endUserIdentifiersSchema.optional()
|
|
9136
9570
|
});
|
|
9137
9571
|
|
|
9572
|
+
// src/utils/stringify.ts
|
|
9573
|
+
function stringifyUnknown(value) {
|
|
9574
|
+
if (value instanceof Error) {
|
|
9575
|
+
const message = value.message || value.name;
|
|
9576
|
+
const cause = value.cause;
|
|
9577
|
+
if (cause instanceof Error && cause.message && cause.message !== message) {
|
|
9578
|
+
return `${message} (${cause.message})`;
|
|
9579
|
+
}
|
|
9580
|
+
return message;
|
|
9581
|
+
}
|
|
9582
|
+
if (typeof value === "string") return value;
|
|
9583
|
+
if (value === null || value === void 0) return String(value);
|
|
9584
|
+
if (typeof value !== "object") return String(value);
|
|
9585
|
+
try {
|
|
9586
|
+
const json = JSON.stringify(value);
|
|
9587
|
+
if (json && json !== "{}") return json;
|
|
9588
|
+
} catch {
|
|
9589
|
+
}
|
|
9590
|
+
return String(value);
|
|
9591
|
+
}
|
|
9592
|
+
|
|
9138
9593
|
// src/manifest/loadManifest.ts
|
|
9139
9594
|
var DEFAULT_MANIFEST_FILENAME = "appilots.manifest.json";
|
|
9140
9595
|
var manifestSchema = external_exports.object({
|
|
@@ -9151,14 +9606,16 @@ async function loadManifest(rootDir, manifestPath) {
|
|
|
9151
9606
|
if (error?.code === "ENOENT") {
|
|
9152
9607
|
return { manifest: null, resolvedPath };
|
|
9153
9608
|
}
|
|
9154
|
-
throw new Error(
|
|
9609
|
+
throw new Error(
|
|
9610
|
+
`Failed to read Appilots manifest at ${resolvedPath}: ${stringifyUnknown(error)}`
|
|
9611
|
+
);
|
|
9155
9612
|
}
|
|
9156
9613
|
let parsedJson;
|
|
9157
9614
|
try {
|
|
9158
9615
|
parsedJson = JSON.parse(raw);
|
|
9159
9616
|
} catch (error) {
|
|
9160
9617
|
throw new Error(
|
|
9161
|
-
`Appilots manifest at ${resolvedPath} is not valid JSON: ${
|
|
9618
|
+
`Appilots manifest at ${resolvedPath} is not valid JSON: ${stringifyUnknown(error)}`
|
|
9162
9619
|
);
|
|
9163
9620
|
}
|
|
9164
9621
|
const result = manifestSchema.safeParse(parsedJson);
|
|
@@ -9172,11 +9629,47 @@ Each entry in "screens" must match the ScreenDescriptor shape (see docs/platform
|
|
|
9172
9629
|
}
|
|
9173
9630
|
return { manifest: result.data, resolvedPath };
|
|
9174
9631
|
}
|
|
9632
|
+
function mergeScreen(base, declared) {
|
|
9633
|
+
const fields = { ...base };
|
|
9634
|
+
for (const [key, value] of Object.entries(declared)) {
|
|
9635
|
+
if (value === void 0) continue;
|
|
9636
|
+
if (Array.isArray(value) && value.length === 0) continue;
|
|
9637
|
+
fields[key] = value;
|
|
9638
|
+
}
|
|
9639
|
+
const merged = fields;
|
|
9640
|
+
merged.targets = mergeById(base.targets, markManifestOrigin(declared.targets));
|
|
9641
|
+
merged.actions = mergeById(base.actions, declared.actions) ?? [];
|
|
9642
|
+
if (base.agentHints || declared.agentHints) {
|
|
9643
|
+
merged.agentHints = { ...base.agentHints, ...declared.agentHints };
|
|
9644
|
+
}
|
|
9645
|
+
return merged;
|
|
9646
|
+
}
|
|
9647
|
+
function mergeById(base, overlay) {
|
|
9648
|
+
if (!overlay || overlay.length === 0) return base;
|
|
9649
|
+
if (!base || base.length === 0) return overlay;
|
|
9650
|
+
const byId = /* @__PURE__ */ new Map();
|
|
9651
|
+
for (const entry of base) byId.set(entry.id, entry);
|
|
9652
|
+
for (const entry of overlay) {
|
|
9653
|
+
const existing = byId.get(entry.id);
|
|
9654
|
+
byId.set(entry.id, existing ? { ...existing, ...entry } : entry);
|
|
9655
|
+
}
|
|
9656
|
+
return Array.from(byId.values());
|
|
9657
|
+
}
|
|
9658
|
+
function markManifestOrigin(targets) {
|
|
9659
|
+
if (!targets || targets.length === 0) return targets;
|
|
9660
|
+
return targets.map((target) => ({ origin: "manifest", ...target }));
|
|
9661
|
+
}
|
|
9175
9662
|
function mergeManifestScreens(analyzerScreens, manifestScreens) {
|
|
9176
9663
|
if (!manifestScreens || manifestScreens.length === 0) return analyzerScreens;
|
|
9177
9664
|
const byName = /* @__PURE__ */ new Map();
|
|
9178
9665
|
for (const screen of analyzerScreens) byName.set(screen.name, screen);
|
|
9179
|
-
for (const
|
|
9666
|
+
for (const declared of manifestScreens) {
|
|
9667
|
+
const base = byName.get(declared.name);
|
|
9668
|
+
byName.set(
|
|
9669
|
+
declared.name,
|
|
9670
|
+
base ? mergeScreen(base, declared) : { ...declared, targets: markManifestOrigin(declared.targets) }
|
|
9671
|
+
);
|
|
9672
|
+
}
|
|
9180
9673
|
return Array.from(byName.values());
|
|
9181
9674
|
}
|
|
9182
9675
|
function mergeManifestNavigation(analyzerNavigation, manifestNavigation) {
|
|
@@ -9466,7 +9959,12 @@ var MCPGenerator = class _MCPGenerator {
|
|
|
9466
9959
|
}
|
|
9467
9960
|
const mergedScreens = mergeManifestScreens(analyzed.screens, manifest?.screens);
|
|
9468
9961
|
const navigation = mergeManifestNavigation(analyzed.navigation, manifest?.navigation);
|
|
9469
|
-
const agentReadyScreens = mergedScreens.map(
|
|
9962
|
+
const agentReadyScreens = mergedScreens.map(
|
|
9963
|
+
(screen) => enrichScreenForAgent({
|
|
9964
|
+
...screen,
|
|
9965
|
+
filePath: screen.filePath ? path8__default.relative(this.generatorConfig.rootDir, screen.filePath) : screen.filePath
|
|
9966
|
+
})
|
|
9967
|
+
);
|
|
9470
9968
|
const projectInfo = await this.getProjectInfo();
|
|
9471
9969
|
const projectName = projectInfo.name;
|
|
9472
9970
|
console.log(`[MCPGenerator] Project name: ${projectName}`);
|
|
@@ -9489,10 +9987,7 @@ var MCPGenerator = class _MCPGenerator {
|
|
|
9489
9987
|
};
|
|
9490
9988
|
const serialized = JSON.stringify(document, null, 2);
|
|
9491
9989
|
const checksum = this.calculateChecksum(serialized);
|
|
9492
|
-
const filePath = path8__default.resolve(
|
|
9493
|
-
outputDir,
|
|
9494
|
-
`mcp-document.${this.options.format}`
|
|
9495
|
-
);
|
|
9990
|
+
const filePath = path8__default.resolve(outputDir, `mcp-document.${this.options.format}`);
|
|
9496
9991
|
await writeFile(filePath, serialized, "utf-8");
|
|
9497
9992
|
console.log(`[MCPGenerator] Document written to: ${filePath}`);
|
|
9498
9993
|
const checksumFilePath = path8__default.resolve(outputDir, ".appilots-checksum");
|
|
@@ -9547,6 +10042,70 @@ var MCPGenerator = class _MCPGenerator {
|
|
|
9547
10042
|
}
|
|
9548
10043
|
}
|
|
9549
10044
|
};
|
|
10045
|
+
var DEFAULT_SERVER_URL = "http://localhost:4000";
|
|
10046
|
+
var KNOWN_CONFIG_KEYS = [
|
|
10047
|
+
"apiKey",
|
|
10048
|
+
"projectId",
|
|
10049
|
+
"serverUrl",
|
|
10050
|
+
"outputDir",
|
|
10051
|
+
"include",
|
|
10052
|
+
"exclude",
|
|
10053
|
+
"autoActivate",
|
|
10054
|
+
"strictScreens",
|
|
10055
|
+
"screenPatterns",
|
|
10056
|
+
"navigationInclude",
|
|
10057
|
+
"navigationExclude",
|
|
10058
|
+
"platform",
|
|
10059
|
+
"manifestPath",
|
|
10060
|
+
"eval"
|
|
10061
|
+
];
|
|
10062
|
+
var KEY_ALIASES = {
|
|
10063
|
+
apiUrl: "serverUrl",
|
|
10064
|
+
// `@appilots/sdk`'s own name for this. One `.appilotsrc` feeds both
|
|
10065
|
+
// packages, so an integrator who wrote the SDK's key should not be told
|
|
10066
|
+
// it is unrecognized.
|
|
10067
|
+
apiBaseUrl: "serverUrl",
|
|
10068
|
+
apiURL: "serverUrl",
|
|
10069
|
+
baseUrl: "serverUrl",
|
|
10070
|
+
url: "serverUrl",
|
|
10071
|
+
host: "serverUrl",
|
|
10072
|
+
endpoint: "serverUrl",
|
|
10073
|
+
server: "serverUrl",
|
|
10074
|
+
key: "apiKey",
|
|
10075
|
+
token: "apiKey",
|
|
10076
|
+
project: "projectId",
|
|
10077
|
+
sourceDir: "include",
|
|
10078
|
+
srcDir: "include",
|
|
10079
|
+
source: "include",
|
|
10080
|
+
outDir: "outputDir"
|
|
10081
|
+
};
|
|
10082
|
+
function editDistance(a, b) {
|
|
10083
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
10084
|
+
for (let i = 1; i <= a.length; i++) {
|
|
10085
|
+
const current = [i];
|
|
10086
|
+
for (let j = 1; j <= b.length; j++) {
|
|
10087
|
+
const deletion = (previous[j] ?? 0) + 1;
|
|
10088
|
+
const insertion = (current[j - 1] ?? 0) + 1;
|
|
10089
|
+
const substitution = (previous[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
10090
|
+
current.push(Math.min(deletion, insertion, substitution));
|
|
10091
|
+
}
|
|
10092
|
+
previous = current;
|
|
10093
|
+
}
|
|
10094
|
+
return previous[b.length] ?? 0;
|
|
10095
|
+
}
|
|
10096
|
+
function suggestConfigKey(key) {
|
|
10097
|
+
const alias = KEY_ALIASES[key];
|
|
10098
|
+
if (alias) return alias;
|
|
10099
|
+
const lower = key.toLowerCase();
|
|
10100
|
+
let best;
|
|
10101
|
+
for (const known of KNOWN_CONFIG_KEYS) {
|
|
10102
|
+
const distance = editDistance(lower, known.toLowerCase());
|
|
10103
|
+
if (distance <= 2 && (!best || distance < best.distance)) {
|
|
10104
|
+
best = { key: known, distance };
|
|
10105
|
+
}
|
|
10106
|
+
}
|
|
10107
|
+
return best?.key;
|
|
10108
|
+
}
|
|
9550
10109
|
function getEnvOverrides(env = process.env) {
|
|
9551
10110
|
const clean = (value) => {
|
|
9552
10111
|
const trimmed = value?.trim();
|
|
@@ -9558,7 +10117,7 @@ function getEnvOverrides(env = process.env) {
|
|
|
9558
10117
|
serverUrl: clean(env.APPILOTS_SERVER_URL)
|
|
9559
10118
|
};
|
|
9560
10119
|
}
|
|
9561
|
-
function loadConfig() {
|
|
10120
|
+
function loadConfig(onWarn) {
|
|
9562
10121
|
const configPath = getConfigPath();
|
|
9563
10122
|
const env = getEnvOverrides();
|
|
9564
10123
|
let fileConfig = {};
|
|
@@ -9566,13 +10125,15 @@ function loadConfig() {
|
|
|
9566
10125
|
try {
|
|
9567
10126
|
fileConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
9568
10127
|
} catch (error) {
|
|
9569
|
-
throw new Error(
|
|
10128
|
+
throw new Error(
|
|
10129
|
+
`Failed to parse .appilotsrc at ${configPath}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
10130
|
+
);
|
|
9570
10131
|
}
|
|
9571
10132
|
} else if (!env.apiKey) {
|
|
9572
10133
|
return null;
|
|
9573
10134
|
}
|
|
9574
10135
|
const merged = {
|
|
9575
|
-
serverUrl:
|
|
10136
|
+
serverUrl: DEFAULT_SERVER_URL,
|
|
9576
10137
|
outputDir: ".appilots",
|
|
9577
10138
|
autoActivate: true,
|
|
9578
10139
|
strictScreens: true,
|
|
@@ -9582,6 +10143,12 @@ function loadConfig() {
|
|
|
9582
10143
|
...env.serverUrl ? { serverUrl: env.serverUrl } : {}
|
|
9583
10144
|
};
|
|
9584
10145
|
const validation = validateConfig(merged);
|
|
10146
|
+
if (onWarn) {
|
|
10147
|
+
const source = configPath ?? "environment variables";
|
|
10148
|
+
for (const warning of validation.warnings) {
|
|
10149
|
+
onWarn(`${source}: ${warning}`);
|
|
10150
|
+
}
|
|
10151
|
+
}
|
|
9585
10152
|
if (!validation.valid) {
|
|
9586
10153
|
const source = configPath ?? "environment variables";
|
|
9587
10154
|
throw new Error(
|
|
@@ -9597,7 +10164,7 @@ function saveConfig(dir, config) {
|
|
|
9597
10164
|
const mergedConfig = {
|
|
9598
10165
|
apiKey: config.apiKey || existingConfig?.apiKey || "",
|
|
9599
10166
|
projectId: config.projectId || existingConfig?.projectId || "",
|
|
9600
|
-
serverUrl: config.serverUrl || existingConfig?.serverUrl ||
|
|
10167
|
+
serverUrl: config.serverUrl || existingConfig?.serverUrl || DEFAULT_SERVER_URL,
|
|
9601
10168
|
outputDir: config.outputDir || existingConfig?.outputDir || ".appilots",
|
|
9602
10169
|
autoActivate: config.autoActivate !== void 0 ? config.autoActivate : existingConfig?.autoActivate ?? true,
|
|
9603
10170
|
include: config.include || existingConfig?.include,
|
|
@@ -9613,7 +10180,9 @@ function saveConfig(dir, config) {
|
|
|
9613
10180
|
try {
|
|
9614
10181
|
writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
|
|
9615
10182
|
} catch (error) {
|
|
9616
|
-
throw new Error(
|
|
10183
|
+
throw new Error(
|
|
10184
|
+
`Failed to write .appilotsrc to ${configPath}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
10185
|
+
);
|
|
9617
10186
|
}
|
|
9618
10187
|
}
|
|
9619
10188
|
function getConfigPath() {
|
|
@@ -9640,14 +10209,25 @@ function getConfigPath() {
|
|
|
9640
10209
|
}
|
|
9641
10210
|
function validateConfig(config) {
|
|
9642
10211
|
const errors = [];
|
|
10212
|
+
const warnings = [];
|
|
9643
10213
|
if (!config || typeof config !== "object") {
|
|
9644
10214
|
return {
|
|
9645
10215
|
valid: false,
|
|
9646
|
-
errors: ["Configuration must be an object"]
|
|
10216
|
+
errors: ["Configuration must be an object"],
|
|
10217
|
+
warnings
|
|
9647
10218
|
};
|
|
9648
10219
|
}
|
|
10220
|
+
for (const key of Object.keys(config)) {
|
|
10221
|
+
if (KNOWN_CONFIG_KEYS.includes(key)) continue;
|
|
10222
|
+
const suggestion = suggestConfigKey(key);
|
|
10223
|
+
warnings.push(
|
|
10224
|
+
`unknown key "${key}" \u2014 ignored` + (suggestion ? ` (did you mean "${suggestion}"?)` : "")
|
|
10225
|
+
);
|
|
10226
|
+
}
|
|
9649
10227
|
if (!config.apiKey || typeof config.apiKey !== "string") {
|
|
9650
|
-
errors.push(
|
|
10228
|
+
errors.push(
|
|
10229
|
+
"apiKey is required and must be a string (set it in .appilotsrc or via APPILOTS_API_KEY)"
|
|
10230
|
+
);
|
|
9651
10231
|
} else if (!config.apiKey.startsWith("ak_")) {
|
|
9652
10232
|
errors.push('apiKey must start with "ak_"');
|
|
9653
10233
|
}
|
|
@@ -9709,7 +10289,8 @@ function validateConfig(config) {
|
|
|
9709
10289
|
}
|
|
9710
10290
|
return {
|
|
9711
10291
|
valid: errors.length === 0,
|
|
9712
|
-
errors
|
|
10292
|
+
errors,
|
|
10293
|
+
warnings
|
|
9713
10294
|
};
|
|
9714
10295
|
}
|
|
9715
10296
|
|
|
@@ -9755,19 +10336,76 @@ function formatMetadataWarnings(warnings) {
|
|
|
9755
10336
|
);
|
|
9756
10337
|
}
|
|
9757
10338
|
|
|
10339
|
+
// ../shared/dist/chunk-UEMUBXFH.mjs
|
|
10340
|
+
var DEFAULT_API_BASE_URL = "https://api.appilots.com";
|
|
10341
|
+
var APPILOTS_API_PATH_PREFIX = "/api/v1";
|
|
10342
|
+
function normalizeApiBaseUrl(configured) {
|
|
10343
|
+
const raw = (configured ?? DEFAULT_API_BASE_URL).trim().replace(/\/+$/, "");
|
|
10344
|
+
if (!raw) return `${DEFAULT_API_BASE_URL}${APPILOTS_API_PATH_PREFIX}`;
|
|
10345
|
+
if (new RegExp(`${APPILOTS_API_PATH_PREFIX}(/|$)`).test(raw)) return raw;
|
|
10346
|
+
return `${raw}${APPILOTS_API_PATH_PREFIX}`;
|
|
10347
|
+
}
|
|
10348
|
+
|
|
9758
10349
|
// src/services/api-client.ts
|
|
9759
|
-
var
|
|
10350
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
9760
10351
|
var DEFAULT_MAX_RETRIES = 2;
|
|
9761
10352
|
var RETRY_BASE_DELAY_MS = 500;
|
|
10353
|
+
function describeNetworkError(url, err) {
|
|
10354
|
+
let origin = url;
|
|
10355
|
+
try {
|
|
10356
|
+
origin = new URL(url).origin;
|
|
10357
|
+
} catch {
|
|
10358
|
+
}
|
|
10359
|
+
const cause = err?.cause;
|
|
10360
|
+
const code = typeof cause?.code === "string" ? cause.code : void 0;
|
|
10361
|
+
const detail = code ?? stringifyUnknown(err);
|
|
10362
|
+
return new Error(`Cannot reach Appilots server at ${origin} (${detail})`);
|
|
10363
|
+
}
|
|
10364
|
+
function errorCodeOf(payload) {
|
|
10365
|
+
const error = payload?.error;
|
|
10366
|
+
if (error && typeof error === "object") {
|
|
10367
|
+
const { code } = error;
|
|
10368
|
+
if (typeof code === "string" && code.trim()) return code;
|
|
10369
|
+
}
|
|
10370
|
+
return void 0;
|
|
10371
|
+
}
|
|
10372
|
+
function describeApiError(payload, fallback) {
|
|
10373
|
+
const error = payload?.error;
|
|
10374
|
+
if (typeof error === "string" && error.trim()) return error;
|
|
10375
|
+
if (error && typeof error === "object") {
|
|
10376
|
+
const { code, message: message2 } = error;
|
|
10377
|
+
const text = typeof message2 === "string" && message2.trim() ? message2 : void 0;
|
|
10378
|
+
const tag = typeof code === "string" && code.trim() ? code : void 0;
|
|
10379
|
+
if (text && tag) return `${text} (${tag})`;
|
|
10380
|
+
if (text) return text;
|
|
10381
|
+
if (tag) return tag;
|
|
10382
|
+
}
|
|
10383
|
+
const message = payload?.message;
|
|
10384
|
+
if (typeof message === "string" && message.trim()) return message;
|
|
10385
|
+
return fallback;
|
|
10386
|
+
}
|
|
9762
10387
|
var AppilotsAPIClient = class {
|
|
9763
|
-
|
|
10388
|
+
/**
|
|
10389
|
+
* `serverUrl` with the API's mount prefix resolved — every path below
|
|
10390
|
+
* is relative to THIS, not to the configured origin.
|
|
10391
|
+
*
|
|
10392
|
+
* It used to be the raw `serverUrl` with `/api/v1` hardcoded into each
|
|
10393
|
+
* path, which made the field mean something different here than in the
|
|
10394
|
+
* SDK. Both read the same `.appilotsrc`: the SDK completes the prefix
|
|
10395
|
+
* when it is missing, so `https://api.appilots.com/api/v1` is correct
|
|
10396
|
+
* there — and here that same value produced
|
|
10397
|
+
* `/api/v1/api/v1/cli/sync`, a 404 whose body reads `Route not found`.
|
|
10398
|
+
* Sharing `normalizeApiBaseUrl` is what makes one file mean one thing:
|
|
10399
|
+
* with or without the prefix now works in both.
|
|
10400
|
+
*/
|
|
10401
|
+
baseUrl;
|
|
9764
10402
|
apiKey;
|
|
9765
10403
|
timeoutMs;
|
|
9766
10404
|
maxRetries;
|
|
9767
10405
|
constructor(config) {
|
|
9768
|
-
this.
|
|
10406
|
+
this.baseUrl = normalizeApiBaseUrl(config.serverUrl);
|
|
9769
10407
|
this.apiKey = config.apiKey;
|
|
9770
|
-
this.timeoutMs = config.timeoutMs ??
|
|
10408
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
9771
10409
|
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
9772
10410
|
}
|
|
9773
10411
|
/**
|
|
@@ -9791,7 +10429,7 @@ var AppilotsAPIClient = class {
|
|
|
9791
10429
|
}
|
|
9792
10430
|
return response;
|
|
9793
10431
|
} catch (err) {
|
|
9794
|
-
lastError = controller.signal.aborted ? new Error(`Request timed out after ${this.timeoutMs}ms: ${url}`) : err;
|
|
10432
|
+
lastError = controller.signal.aborted ? new Error(`Request timed out after ${this.timeoutMs}ms: ${url}`) : describeNetworkError(url, err);
|
|
9795
10433
|
} finally {
|
|
9796
10434
|
clearTimeout(timer);
|
|
9797
10435
|
}
|
|
@@ -9810,7 +10448,7 @@ var AppilotsAPIClient = class {
|
|
|
9810
10448
|
*/
|
|
9811
10449
|
async sync(content, version, appVersion) {
|
|
9812
10450
|
try {
|
|
9813
|
-
const response = await this.request(`${this.
|
|
10451
|
+
const response = await this.request(`${this.baseUrl}/cli/sync`, {
|
|
9814
10452
|
method: "POST",
|
|
9815
10453
|
headers: {
|
|
9816
10454
|
"Content-Type": "application/json",
|
|
@@ -9824,7 +10462,9 @@ var AppilotsAPIClient = class {
|
|
|
9824
10462
|
return {
|
|
9825
10463
|
success: false,
|
|
9826
10464
|
unchanged: false,
|
|
9827
|
-
error: errorData
|
|
10465
|
+
error: describeApiError(errorData, `HTTP ${response.status}: ${response.statusText}`),
|
|
10466
|
+
errorCode: errorCodeOf(errorData),
|
|
10467
|
+
errorStatus: response.status
|
|
9828
10468
|
};
|
|
9829
10469
|
}
|
|
9830
10470
|
const json = await response.json();
|
|
@@ -9837,7 +10477,8 @@ var AppilotsAPIClient = class {
|
|
|
9837
10477
|
return {
|
|
9838
10478
|
success: false,
|
|
9839
10479
|
unchanged: false,
|
|
9840
|
-
error: error instanceof Error ? error.message : "Failed to sync with Appilots API"
|
|
10480
|
+
error: error instanceof Error ? error.message : "Failed to sync with Appilots API",
|
|
10481
|
+
errorStatus: 0
|
|
9841
10482
|
};
|
|
9842
10483
|
}
|
|
9843
10484
|
}
|
|
@@ -9848,7 +10489,7 @@ var AppilotsAPIClient = class {
|
|
|
9848
10489
|
*/
|
|
9849
10490
|
async status() {
|
|
9850
10491
|
try {
|
|
9851
|
-
const response = await this.request(`${this.
|
|
10492
|
+
const response = await this.request(`${this.baseUrl}/cli/status`, {
|
|
9852
10493
|
method: "GET",
|
|
9853
10494
|
headers: {
|
|
9854
10495
|
Authorization: `Bearer ${this.apiKey}`
|
|
@@ -9857,7 +10498,7 @@ var AppilotsAPIClient = class {
|
|
|
9857
10498
|
if (!response.ok) {
|
|
9858
10499
|
const errorData = await response.json().catch(() => ({}));
|
|
9859
10500
|
return {
|
|
9860
|
-
error: errorData
|
|
10501
|
+
error: describeApiError(errorData, `HTTP ${response.status}: ${response.statusText}`)
|
|
9861
10502
|
};
|
|
9862
10503
|
}
|
|
9863
10504
|
const json = await response.json();
|
|
@@ -9879,7 +10520,7 @@ var AppilotsAPIClient = class {
|
|
|
9879
10520
|
*/
|
|
9880
10521
|
async evalRun(scenarios) {
|
|
9881
10522
|
try {
|
|
9882
|
-
const response = await this.request(`${this.
|
|
10523
|
+
const response = await this.request(`${this.baseUrl}/cli/eval/run`, {
|
|
9883
10524
|
method: "POST",
|
|
9884
10525
|
headers: {
|
|
9885
10526
|
"Content-Type": "application/json",
|
|
@@ -9910,7 +10551,7 @@ var AppilotsAPIClient = class {
|
|
|
9910
10551
|
*/
|
|
9911
10552
|
async health() {
|
|
9912
10553
|
try {
|
|
9913
|
-
const response = await this.request(`${this.
|
|
10554
|
+
const response = await this.request(`${this.baseUrl}/health`, {
|
|
9914
10555
|
method: "GET"
|
|
9915
10556
|
});
|
|
9916
10557
|
return response.ok;
|
|
@@ -9920,6 +10561,9 @@ var AppilotsAPIClient = class {
|
|
|
9920
10561
|
}
|
|
9921
10562
|
};
|
|
9922
10563
|
|
|
9923
|
-
|
|
10564
|
+
// src/version.ts
|
|
10565
|
+
var CLI_VERSION = "0.10.0";
|
|
10566
|
+
|
|
10567
|
+
export { AppilotsAPIClient, CLI_VERSION, ComponentAnalyzer, DEFAULT_MANIFEST_FILENAME, DEFAULT_WEB_SCREEN_PATTERNS, FormAnalyzer, GenericPlatformAnalyzer, MCPGenerator, NavigationAnalyzer, ReactNativePlatformAnalyzer, ReactWebPlatformAnalyzer, ScreenAnalyzer, WebNavigationAnalyzer, WebScreenAnalyzer, formatMetadataWarnings, getConfigPath, getEnvOverrides, lintActionMetadata, loadConfig, loadManifest, mergeManifestNavigation, mergeManifestScreens, resolvePathToScreen, saveConfig, screenNameFromPath, validateConfig };
|
|
9924
10568
|
//# sourceMappingURL=index.mjs.map
|
|
9925
10569
|
//# sourceMappingURL=index.mjs.map
|