@appilots/cli 0.11.3 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,19 +1,356 @@
1
1
  import fs, { readFile, mkdir, writeFile } from 'fs/promises';
2
+ import { createHash } from 'crypto';
3
+ import traverse5 from '@babel/traverse';
4
+ import * as BabelTypes from '@babel/types';
2
5
  import * as path2 from 'path';
3
6
  import path2__default, { join, resolve } from 'path';
4
- import traverse4 from '@babel/traverse';
5
- import * as BabelTypes from '@babel/types';
6
7
  import fastGlob from 'fast-glob';
7
8
  import * as parser from '@babel/parser';
8
9
  import { parse } from '@babel/parser';
9
10
  import { promises, readFileSync, writeFileSync, existsSync, statSync, readdirSync } from 'fs';
10
- import { createHash } from 'crypto';
11
11
 
12
12
  var __defProp = Object.defineProperty;
13
13
  var __export = (target, all) => {
14
14
  for (var name in all)
15
15
  __defProp(target, name, { get: all[name], enumerable: true });
16
16
  };
17
+ var MAX_HOPS = 8;
18
+ function unwrap(path11) {
19
+ while (path11.isTSAsExpression() || path11.isTSTypeAssertion() || path11.isTSNonNullExpression() || path11.isTSSatisfiesExpression() || path11.isParenthesizedExpression())
20
+ path11 = path11.get("expression");
21
+ return path11;
22
+ }
23
+ function constantValue(path11, depth = 0) {
24
+ if (depth > MAX_HOPS) return void 0;
25
+ path11 = unwrap(path11);
26
+ if (!path11.isIdentifier()) return path11;
27
+ const binding = path11.scope.getBinding(path11.node.name);
28
+ if (!binding?.constant || !binding.path.isVariableDeclarator()) return path11;
29
+ const init = binding.path.get("init");
30
+ const resolved = init.node ? constantValue(init, depth + 1) : void 0;
31
+ if (resolved?.isObjectExpression() && binding.referencePaths.some(
32
+ (reference) => !reference.parentPath?.isJSXSpreadAttribute() && !reference.parentPath?.isSpreadElement()
33
+ ))
34
+ return void 0;
35
+ return resolved;
36
+ }
37
+ function propValue(path11, name) {
38
+ const readObject = (path12) => {
39
+ const resolved = constantValue(path12);
40
+ if (!resolved?.isObjectExpression()) return { blocked: true };
41
+ for (const property of [...resolved.get("properties")].reverse()) {
42
+ if (property.isSpreadElement()) {
43
+ const found = readObjectBounded(property.get("argument"));
44
+ if (found.value || found.blocked) return found;
45
+ } else if (property.isObjectProperty() || property.isObjectMethod()) {
46
+ if (property.node.computed) return { blocked: true };
47
+ const key = property.node.key;
48
+ if ((BabelTypes.isIdentifier(key) ? key.name : BabelTypes.isStringLiteral(key) ? key.value : "") === name)
49
+ return property.isObjectProperty() ? { value: property.get("value") } : { value: property };
50
+ }
51
+ }
52
+ return {};
53
+ };
54
+ let objectBudget = MAX_HOPS;
55
+ const readObjectBounded = (path12) => objectBudget-- > 0 ? readObject(path12) : { blocked: true };
56
+ for (const attr of [...path11.get("attributes")].reverse()) {
57
+ if (attr.isJSXAttribute() && BabelTypes.isJSXIdentifier(attr.node.name, { name })) {
58
+ const value = attr.get("value");
59
+ return value.isJSXExpressionContainer() ? unwrap(value.get("expression")) : value.node ? value : void 0;
60
+ }
61
+ if (attr.isJSXSpreadAttribute()) {
62
+ const found = readObjectBounded(attr.get("argument"));
63
+ if (found.value || found.blocked) return found.value;
64
+ }
65
+ }
66
+ return void 0;
67
+ }
68
+ function importIdentity(path11, depth = 0) {
69
+ if (depth > MAX_HOPS) return void 0;
70
+ path11 = unwrap(path11);
71
+ if (path11.isIdentifier() || path11.isJSXIdentifier()) {
72
+ const binding = path11.scope.getBinding(path11.node.name);
73
+ if (!binding?.constant) return void 0;
74
+ const declaration = binding.path;
75
+ if (declaration.parentPath?.isImportDeclaration()) {
76
+ const module = declaration.parentPath.node.source.value;
77
+ if (declaration.isImportSpecifier()) {
78
+ const imported = declaration.node.imported;
79
+ return { module, imported: BabelTypes.isIdentifier(imported) ? imported.name : imported.value };
80
+ }
81
+ if (declaration.isImportDefaultSpecifier()) return { module, imported: "default" };
82
+ if (declaration.isImportNamespaceSpecifier()) return { module, imported: "*" };
83
+ }
84
+ if (declaration.isVariableDeclarator() && declaration.get("init").node)
85
+ return importIdentity(declaration.get("init"), depth + 1);
86
+ }
87
+ if (path11.isJSXMemberExpression() || path11.isMemberExpression() && !path11.node.computed) {
88
+ const origin = importIdentity(path11.get("object"), depth + 1);
89
+ const key = path11.node.property;
90
+ if (origin && (BabelTypes.isIdentifier(key) || BabelTypes.isJSXIdentifier(key)) && (origin.imported === "*" || origin.module === "react" && origin.imported === "default"))
91
+ return { module: origin.module, imported: key.name };
92
+ }
93
+ return void 0;
94
+ }
95
+ function handlerFunction(path11, depth = 0) {
96
+ if (depth > MAX_HOPS) return void 0;
97
+ path11 = unwrap(path11);
98
+ if (path11.isFunction()) return path11;
99
+ if (path11.isIdentifier()) {
100
+ const binding = path11.scope.getBinding(path11.node.name);
101
+ if (!binding?.constant) return void 0;
102
+ if (binding.path.isFunctionDeclaration()) return binding.path;
103
+ if (binding.path.isVariableDeclarator() && binding.path.get("init").node)
104
+ return handlerFunction(binding.path.get("init"), depth + 1);
105
+ }
106
+ if (path11.isCallExpression()) {
107
+ const origin = importIdentity(path11.get("callee"));
108
+ if (origin?.module === "react" && origin.imported === "useCallback") {
109
+ const callback = path11.get("arguments")[0];
110
+ if (callback) return handlerFunction(callback, depth + 1);
111
+ }
112
+ }
113
+ if (path11.isMemberExpression() && !path11.node.computed && BabelTypes.isThisExpression(path11.node.object)) {
114
+ const key = path11.node.property;
115
+ if (!BabelTypes.isIdentifier(key)) return void 0;
116
+ const owner = path11.findParent((p) => p.isClassDeclaration() || p.isClassExpression());
117
+ if (!owner || !(owner.isClassDeclaration() || owner.isClassExpression())) return void 0;
118
+ for (const member of owner.get("body").get("body")) {
119
+ if (!(member.isClassMethod() || member.isClassProperty()) || member.node.computed || member.node.static)
120
+ continue;
121
+ if (!BabelTypes.isIdentifier(member.node.key, { name: key.name })) continue;
122
+ if (member.isClassMethod() && member.node.kind === "method") return member;
123
+ if (member.isClassProperty() && member.get("value").node)
124
+ return handlerFunction(member.get("value"), depth + 1);
125
+ }
126
+ }
127
+ return void 0;
128
+ }
129
+
130
+ // src/extractors/control-evidence.ts
131
+ function symbol(node) {
132
+ if (BabelTypes.isTSAsExpression(node) || BabelTypes.isTSTypeAssertion(node) || BabelTypes.isTSNonNullExpression(node) || BabelTypes.isTSSatisfiesExpression(node))
133
+ return symbol(node.expression);
134
+ if (BabelTypes.isIdentifier(node) || BabelTypes.isJSXIdentifier(node)) return node.name;
135
+ if (BabelTypes.isThisExpression(node)) return "this";
136
+ if (BabelTypes.isMemberExpression(node) && !node.computed || BabelTypes.isJSXMemberExpression(node)) {
137
+ const object = symbol(node.object), property = symbol(node.property);
138
+ return object && property ? `${object}.${property}` : void 0;
139
+ }
140
+ if (BabelTypes.isUnaryExpression(node) && node.operator === "!") {
141
+ const value = symbol(node.argument);
142
+ return value ? `!${value}` : void 0;
143
+ }
144
+ return void 0;
145
+ }
146
+ function attribute(node, name) {
147
+ return node.attributes.find(
148
+ (a) => BabelTypes.isJSXAttribute(a) && BabelTypes.isJSXIdentifier(a.name, { name })
149
+ );
150
+ }
151
+ var ICON_LIBRARIES = [
152
+ "lucide-react-native",
153
+ "lucide-react",
154
+ "@tamagui/lucide-icons",
155
+ "@expo/vector-icons",
156
+ "@react-native-vector-icons/",
157
+ "react-native-vector-icons/"
158
+ ];
159
+ function iconLibrary(module) {
160
+ return ICON_LIBRARIES.some(
161
+ (name) => name.endsWith("/") ? module.startsWith(name) : module === name || module.startsWith(name + "/")
162
+ );
163
+ }
164
+ function add(values, value, max = 12) {
165
+ if (value && value.length <= 240 && values.length < max && !values.includes(value))
166
+ values.push(value);
167
+ }
168
+ function iconName(path11, explicitIcon = false) {
169
+ const origin = importIdentity(path11);
170
+ if (origin && iconLibrary(origin.module) && origin.imported !== "*")
171
+ return origin.imported === "default" ? origin.module : `${origin.module}:${origin.imported}`;
172
+ const name = symbol(path11.node);
173
+ return name && (explicitIcon || /icon/i.test(name)) ? name : void 0;
174
+ }
175
+ function iconsInElement(path11, explicitIcon = false) {
176
+ const name = iconName(path11.get("name"), explicitIcon);
177
+ if (!name) return void 0;
178
+ const glyph = propValue(path11, "name");
179
+ const value = glyph && constantValue(glyph);
180
+ return value?.isStringLiteral() ? `${name}:${value.node.value}` : name;
181
+ }
182
+ function hasInteraction(path11) {
183
+ return ["onPress", "onLongPress", "onClick"].some(
184
+ (name) => attribute(path11.node, name) || propValue(path11, name)
185
+ );
186
+ }
187
+ function collectPresentationIcons(path11, icons) {
188
+ const namedIconProps = [
189
+ "icon",
190
+ "prefix",
191
+ "suffix",
192
+ "left",
193
+ "right",
194
+ "leadingIcon",
195
+ "trailingIcon",
196
+ "startIcon",
197
+ "endIcon",
198
+ "renderIcon"
199
+ ];
200
+ const props = new Set(namedIconProps);
201
+ for (const attr of path11.node.attributes)
202
+ if (BabelTypes.isJSXAttribute(attr) && BabelTypes.isJSXIdentifier(attr.name) && !/^on[A-Z]/.test(attr.name.name))
203
+ props.add(attr.name.name);
204
+ for (const prop of props) {
205
+ const icon = propValue(path11, prop);
206
+ if (icon) {
207
+ const value = constantValue(icon);
208
+ if (value?.isStringLiteral()) {
209
+ if (prop.toLowerCase().includes("icon")) add(icons, value.node.value, 8);
210
+ } else {
211
+ const explicitIcon = prop.toLowerCase() === "icon";
212
+ if (icon.isJSXElement()) {
213
+ if (hasInteraction(icon.get("openingElement"))) continue;
214
+ add(icons, iconsInElement(icon.get("openingElement"), explicitIcon), 8);
215
+ }
216
+ icon.traverse({
217
+ JSXAttribute(attr) {
218
+ if (BabelTypes.isJSXIdentifier(attr.node.name) && /^on[A-Z]/.test(attr.node.name.name))
219
+ attr.skip();
220
+ },
221
+ JSXElement(child) {
222
+ const opening = child.get("openingElement");
223
+ if (hasInteraction(opening)) {
224
+ child.skip();
225
+ return;
226
+ }
227
+ add(icons, iconsInElement(opening, explicitIcon), 8);
228
+ }
229
+ });
230
+ if (!icons.length && (namedIconProps.includes(prop) || explicitIcon))
231
+ add(icons, iconName(icon, explicitIcon), 8);
232
+ }
233
+ }
234
+ }
235
+ }
236
+ function collectIcons(path11) {
237
+ const icons = [];
238
+ collectPresentationIcons(path11, icons);
239
+ const jsx = path11.parentPath;
240
+ if (jsx.isJSXElement())
241
+ jsx.traverse({
242
+ // JSX mentioned inside an event callback is not a rendered child icon.
243
+ JSXAttribute(attr) {
244
+ attr.skip();
245
+ },
246
+ JSXElement(child) {
247
+ const opening = child.get("openingElement");
248
+ if (hasInteraction(opening)) {
249
+ child.skip();
250
+ return;
251
+ }
252
+ add(icons, iconsInElement(opening), 8);
253
+ collectPresentationIcons(opening, icons);
254
+ }
255
+ });
256
+ return icons;
257
+ }
258
+ function conditionsAt(path11) {
259
+ const conditions = [];
260
+ let child = path11;
261
+ for (let parent = child.parentPath; parent && !parent.isFunction(); child = parent, parent = parent.parentPath) {
262
+ let test;
263
+ let negated = false;
264
+ if (parent.isLogicalExpression() && parent.node.right === child.node) {
265
+ if (parent.node.operator === "&&") test = parent.node.left;
266
+ if (parent.node.operator === "||") {
267
+ test = parent.node.left;
268
+ negated = true;
269
+ }
270
+ } else if (parent.isConditionalExpression() && parent.node.test !== child.node) {
271
+ test = parent.node.test;
272
+ negated = parent.node.alternate === child.node;
273
+ } else if (parent.isIfStatement() && parent.node.test !== child.node) {
274
+ test = parent.node.test;
275
+ negated = parent.node.alternate === child.node;
276
+ }
277
+ const name = symbol(test);
278
+ if (name) add(conditions, negated ? name.startsWith("!") ? name.slice(1) : `!${name}` : name);
279
+ }
280
+ return conditions;
281
+ }
282
+ function collectHandlerEvidence(expression, evidence) {
283
+ const seen = /* @__PURE__ */ new Set();
284
+ let budget = 100;
285
+ const visit = (path11, depth) => {
286
+ if (depth > 4 || budget-- <= 0) return;
287
+ const fn = handlerFunction(path11);
288
+ if (!fn || seen.has(fn.node)) return;
289
+ seen.add(fn.node);
290
+ fn.traverse({
291
+ // Ignore uncalled helper definitions; inline callbacks remain source evidence.
292
+ Function(nested) {
293
+ if (!nested.parentPath.isCallExpression() && !nested.parentPath.isObjectProperty())
294
+ nested.skip();
295
+ },
296
+ CallExpression(call) {
297
+ if (budget-- <= 0) {
298
+ call.skip();
299
+ return;
300
+ }
301
+ add(evidence.calls, symbol(call.node.callee));
302
+ for (const arg of call.node.arguments) add(evidence.argumentBindings, symbol(arg));
303
+ visit(call.get("callee"), depth + 1);
304
+ if (!BabelTypes.isMemberExpression(call.node.callee) || call.node.callee.computed || !BabelTypes.isIdentifier(call.node.callee.property, { name: "alert" }))
305
+ return;
306
+ const callee = call.get("callee");
307
+ if (!callee.isMemberExpression()) return;
308
+ const origin = importIdentity(callee.get("object"));
309
+ if (origin?.module !== "react-native" || origin.imported !== "Alert") return;
310
+ const options = call.node.arguments[2];
311
+ const destructiveOption = BabelTypes.isArrayExpression(options) && options.elements.some(
312
+ (option) => BabelTypes.isObjectExpression(option) && option.properties.some(
313
+ (p) => BabelTypes.isObjectProperty(p) && !p.computed && symbol(p.key) === "style" && BabelTypes.isStringLiteral(p.value, { value: "destructive" })
314
+ )
315
+ );
316
+ if (destructiveOption)
317
+ evidence.nativeConfirmation = {
318
+ title: symbol(call.node.arguments[0]),
319
+ destructiveOption
320
+ };
321
+ }
322
+ });
323
+ };
324
+ visit(unwrap(expression), 0);
325
+ }
326
+ function extractControlEvidence(ast, source, file) {
327
+ const candidates = [];
328
+ const sourceHash = createHash("sha256").update(source).digest("hex");
329
+ traverse5(ast, {
330
+ JSXOpeningElement(path11) {
331
+ const node = path11.node;
332
+ if (attribute(node, "__appilotsControl")) return;
333
+ const value = propValue(path11, "onPress");
334
+ if (!value && !attribute(node, "onPress") || value?.isNullLiteral() || value?.isJSXEmptyExpression())
335
+ return;
336
+ const icons = collectIcons(path11);
337
+ if (!icons.length || node.end == null) return;
338
+ const evidence = {
339
+ version: 1,
340
+ siteId: createHash("sha256").update(`${file}:${sourceHash}:${node.start}`).digest("hex").slice(0, 20),
341
+ component: symbol(node.name) ?? "unknown",
342
+ icons,
343
+ ...value && symbol(value.node) ? { handler: symbol(value.node) } : {},
344
+ calls: [],
345
+ argumentBindings: [],
346
+ conditions: conditionsAt(path11)
347
+ };
348
+ if (value) collectHandlerEvidence(value, evidence);
349
+ candidates.push({ evidence, sourceHash, offset: node.end - (node.selfClosing ? 2 : 1) });
350
+ }
351
+ });
352
+ return candidates;
353
+ }
17
354
  function byCodeUnit(a, b) {
18
355
  return a < b ? -1 : a > b ? 1 : 0;
19
356
  }
@@ -135,7 +472,7 @@ function classifyJsxComponent(name, element) {
135
472
  }
136
473
  function collectFunctions(ast) {
137
474
  const handlers = /* @__PURE__ */ new Map();
138
- traverse4(ast, {
475
+ traverse5(ast, {
139
476
  FunctionDeclaration: (nodePath) => {
140
477
  if (nodePath.node.id?.name) handlers.set(nodePath.node.id.name, nodePath.node);
141
478
  },
@@ -238,7 +575,7 @@ function analyzeFunctionBehavior(name, fn, handlers, seen = /* @__PURE__ */ new
238
575
  }
239
576
  };
240
577
  if (fn.body) {
241
- traverse4(fn.body, {
578
+ traverse5(fn.body, {
242
579
  noScope: true,
243
580
  enter: (nodePath) => inspectNode(nodePath.node)
244
581
  });
@@ -266,7 +603,7 @@ function setterToStateName(setterName) {
266
603
  }
267
604
  function extractNavigationCalls(ast) {
268
605
  const calls = [];
269
- traverse4(ast, {
606
+ traverse5(ast, {
270
607
  noScope: !BabelTypes.isFile(ast),
271
608
  CallExpression: (nodePath) => {
272
609
  const node = nodePath.node;
@@ -390,6 +727,9 @@ function sanitisePermissionsObject(obj) {
390
727
  if (typeof obj.isPii === "boolean") {
391
728
  out.isPii = obj.isPii;
392
729
  }
730
+ if (typeof obj.assessment === "boolean") {
731
+ out.assessment = obj.assessment;
732
+ }
393
733
  if (Array.isArray(obj.blockedActions)) {
394
734
  const ids = obj.blockedActions.filter((x) => typeof x === "string" && !!x);
395
735
  if (ids.length > 0) out.blockedActions = ids;
@@ -417,6 +757,7 @@ var ScreenAnalyzer = class {
417
757
  routeTargetFiles;
418
758
  /** §D: Count of screens filtered out in strict mode (available after analyze()) */
419
759
  screensFilteredOut = 0;
760
+ controlEvidenceFiles = {};
420
761
  constructor(config, options) {
421
762
  this.config = config;
422
763
  this.routeTargetFiles = options?.routeTargetFiles ?? /* @__PURE__ */ new Set();
@@ -517,6 +858,11 @@ var ScreenAnalyzer = class {
517
858
  title: registerScreenMeta?.title,
518
859
  description: registerScreenMeta?.description,
519
860
  components,
861
+ controlCandidates: extractControlEvidence(
862
+ ast,
863
+ source,
864
+ path2__default.relative(this.config.rootDir, filePath)
865
+ ),
520
866
  forms,
521
867
  actions,
522
868
  navigationTargets,
@@ -527,6 +873,8 @@ var ScreenAnalyzer = class {
527
873
  ...permissionsFromJsDoc.isPii ? { isPii: true } : {}
528
874
  } : {}
529
875
  };
876
+ if (descriptor.controlCandidates?.length)
877
+ this.controlEvidenceFiles[path2__default.relative(this.config.rootDir, filePath).split(path2__default.sep).join("/")] = descriptor.controlCandidates;
530
878
  descriptor.__hasRegisterScreen = hasRegisterScreenCall;
531
879
  return descriptor;
532
880
  }
@@ -536,7 +884,7 @@ var ScreenAnalyzer = class {
536
884
  */
537
885
  detectRegisterScreenCall(ast) {
538
886
  let found = false;
539
- traverse4(ast, {
887
+ traverse5(ast, {
540
888
  CallExpression: (nodePath) => {
541
889
  if (found) return;
542
890
  const callee = nodePath.node.callee;
@@ -553,7 +901,7 @@ var ScreenAnalyzer = class {
553
901
  */
554
902
  extractRegisterScreenMetadata(ast) {
555
903
  let metadata = null;
556
- traverse4(ast, {
904
+ traverse5(ast, {
557
905
  CallExpression: (nodePath) => {
558
906
  const callee = nodePath.node.callee;
559
907
  if (BabelTypes.isIdentifier(callee) && callee.name === "registerScreen" || BabelTypes.isMemberExpression(callee) && BabelTypes.isIdentifier(callee.property) && callee.property.name === "registerScreen") {
@@ -850,7 +1198,7 @@ var ScreenAnalyzer = class {
850
1198
  */
851
1199
  extractDefaultComponentName(ast) {
852
1200
  let componentName = "";
853
- traverse4(ast, {
1201
+ traverse5(ast, {
854
1202
  ExportDefaultDeclaration: (nodePath) => {
855
1203
  const declaration = nodePath.node.declaration;
856
1204
  if (BabelTypes.isFunctionDeclaration(declaration) && declaration.id?.name) {
@@ -875,7 +1223,7 @@ var ScreenAnalyzer = class {
875
1223
  */
876
1224
  extractNavigationTargets(ast) {
877
1225
  const targets = /* @__PURE__ */ new Set();
878
- traverse4(ast, {
1226
+ traverse5(ast, {
879
1227
  CallExpression: (nodePath) => {
880
1228
  const callee = nodePath.node.callee;
881
1229
  if (BabelTypes.isMemberExpression(callee) && BabelTypes.isIdentifier(callee.object) && callee.object.name === "navigation" && BabelTypes.isIdentifier(callee.property) && callee.property.name === "navigate") {
@@ -894,7 +1242,7 @@ var ScreenAnalyzer = class {
894
1242
  extractForms(ast) {
895
1243
  const forms = [];
896
1244
  const fields = /* @__PURE__ */ new Map();
897
- traverse4(ast, {
1245
+ traverse5(ast, {
898
1246
  JSXOpeningElement: (nodePath) => {
899
1247
  const element = nodePath.node;
900
1248
  if (BabelTypes.isJSXIdentifier(element.name)) {
@@ -1015,7 +1363,7 @@ var ScreenAnalyzer = class {
1015
1363
  extractComponents(ast) {
1016
1364
  const components = [];
1017
1365
  const seen = /* @__PURE__ */ new Set();
1018
- traverse4(ast, {
1366
+ traverse5(ast, {
1019
1367
  JSXOpeningElement: (nodePath) => {
1020
1368
  const element = nodePath.node;
1021
1369
  if (BabelTypes.isJSXIdentifier(element.name)) {
@@ -1079,7 +1427,7 @@ var ScreenAnalyzer = class {
1079
1427
  const actionLabels = new Map(
1080
1428
  actions.filter((a) => a.label).map((a) => [this.normalizeLabel(a.label), a])
1081
1429
  );
1082
- traverse4(ast, {
1430
+ traverse5(ast, {
1083
1431
  JSXOpeningElement: (nodePath) => {
1084
1432
  const element = nodePath.node;
1085
1433
  if (BabelTypes.isJSXIdentifier(element.name)) {
@@ -1148,7 +1496,7 @@ var ScreenAnalyzer = class {
1148
1496
  }
1149
1497
  collectButtonHandlersByLabel(ast) {
1150
1498
  const out = /* @__PURE__ */ new Map();
1151
- traverse4(ast, {
1499
+ traverse5(ast, {
1152
1500
  JSXOpeningElement: (nodePath) => {
1153
1501
  const element = nodePath.node;
1154
1502
  if (!BabelTypes.isJSXIdentifier(element.name)) return;
@@ -1230,7 +1578,7 @@ var ScreenAnalyzer = class {
1230
1578
  }
1231
1579
  };
1232
1580
  if (fn.body) {
1233
- traverse4(
1581
+ traverse5(
1234
1582
  fn.body,
1235
1583
  {
1236
1584
  noScope: true,
@@ -1427,7 +1775,7 @@ var ScreenAnalyzer = class {
1427
1775
  extractCollections(ast) {
1428
1776
  const renderItemFns = this.collectRenderItemFunctions(ast);
1429
1777
  const collections = [];
1430
- traverse4(ast, {
1778
+ traverse5(ast, {
1431
1779
  JSXOpeningElement: (nodePath) => {
1432
1780
  const element = nodePath.node;
1433
1781
  if (!BabelTypes.isJSXIdentifier(element.name)) return;
@@ -1464,7 +1812,7 @@ var ScreenAnalyzer = class {
1464
1812
  }
1465
1813
  collectRenderItemFunctions(ast) {
1466
1814
  const out = /* @__PURE__ */ new Map();
1467
- traverse4(ast, {
1815
+ traverse5(ast, {
1468
1816
  VariableDeclarator: (nodePath) => {
1469
1817
  if (!BabelTypes.isIdentifier(nodePath.node.id)) return;
1470
1818
  const init = nodePath.node.init;
@@ -1507,7 +1855,7 @@ var ScreenAnalyzer = class {
1507
1855
  }
1508
1856
  extractRowAction(fn) {
1509
1857
  let action;
1510
- traverse4(
1858
+ traverse5(
1511
1859
  fn.body,
1512
1860
  {
1513
1861
  noScope: true,
@@ -1560,7 +1908,7 @@ var ScreenAnalyzer = class {
1560
1908
  } else if (BabelTypes.isIdentifier(firstParam)) {
1561
1909
  itemNames.add(firstParam.name);
1562
1910
  }
1563
- traverse4(
1911
+ traverse5(
1564
1912
  fn.body,
1565
1913
  {
1566
1914
  noScope: true,
@@ -1602,7 +1950,7 @@ var ScreenAnalyzer = class {
1602
1950
  inferSearchField(ast, dataSource) {
1603
1951
  if (!dataSource) return void 0;
1604
1952
  let queryBinding;
1605
- traverse4(ast, {
1953
+ traverse5(ast, {
1606
1954
  CallExpression: (nodePath) => {
1607
1955
  const node = nodePath.node;
1608
1956
  if (!BabelTypes.isMemberExpression(node.callee)) return;
@@ -1613,7 +1961,7 @@ var ScreenAnalyzer = class {
1613
1961
  const fn = node.arguments[0];
1614
1962
  if (!BabelTypes.isArrowFunctionExpression(fn) && !BabelTypes.isFunctionExpression(fn))
1615
1963
  return;
1616
- traverse4(
1964
+ traverse5(
1617
1965
  fn.body,
1618
1966
  {
1619
1967
  noScope: true,
@@ -1655,7 +2003,7 @@ var ScreenAnalyzer = class {
1655
2003
  }
1656
2004
  };
1657
2005
  var EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
1658
- var MAX_HOPS = 8;
2006
+ var MAX_HOPS2 = 8;
1659
2007
  var ModuleGraph = class {
1660
2008
  asts = /* @__PURE__ */ new Map();
1661
2009
  resolved = /* @__PURE__ */ new Map();
@@ -1774,7 +2122,7 @@ var ModuleGraph = class {
1774
2122
  * Devolve o arquivo e o nome sob o qual ele é definido lá.
1775
2123
  */
1776
2124
  resolveBinding(file, name, hops = 0) {
1777
- if (hops > MAX_HOPS) return null;
2125
+ if (hops > MAX_HOPS2) return null;
1778
2126
  if (this.topLevelInit(file, name) !== null) return { file, name };
1779
2127
  const binding = this.imports(file).get(name);
1780
2128
  if (binding) {
@@ -1828,7 +2176,7 @@ var ModuleGraph = class {
1828
2176
  * com interpolação nem valor calculado, de propósito.
1829
2177
  */
1830
2178
  stringConstant(file, node, hops = 0) {
1831
- if (!node || hops > MAX_HOPS) return null;
2179
+ if (!node || hops > MAX_HOPS2) return null;
1832
2180
  if (BabelTypes.isStringLiteral(node)) return node.value;
1833
2181
  if (BabelTypes.isTemplateLiteral(node)) {
1834
2182
  return node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? null : null;
@@ -1857,7 +2205,7 @@ var ModuleGraph = class {
1857
2205
  * que é como o `pocketpal` monta todas as telas do Drawer).
1858
2206
  */
1859
2207
  componentFile(file, node, hops = 0) {
1860
- if (!node || hops > MAX_HOPS) return null;
2208
+ if (!node || hops > MAX_HOPS2) return null;
1861
2209
  if (BabelTypes.isCallExpression(node)) {
1862
2210
  for (const arg of node.arguments) {
1863
2211
  if (BabelTypes.isIdentifier(arg) || BabelTypes.isMemberExpression(arg)) {
@@ -2053,7 +2401,7 @@ function workspaceGlobs(dir) {
2053
2401
  }
2054
2402
  function expandWorkspaceGlobs(root, globs) {
2055
2403
  const out = [];
2056
- const add = (dir) => {
2404
+ const add2 = (dir) => {
2057
2405
  const pkgPath = path2__default.join(dir, "package.json");
2058
2406
  if (!existsSync(pkgPath)) return;
2059
2407
  try {
@@ -2074,12 +2422,12 @@ function expandWorkspaceGlobs(root, globs) {
2074
2422
  for (const entry of entries) {
2075
2423
  const dir = path2__default.join(parent, entry);
2076
2424
  try {
2077
- if (statSync(dir).isDirectory()) add(dir);
2425
+ if (statSync(dir).isDirectory()) add2(dir);
2078
2426
  } catch {
2079
2427
  }
2080
2428
  }
2081
2429
  } else if (!glob.includes("*")) {
2082
- add(path2__default.join(root, glob));
2430
+ add2(path2__default.join(root, glob));
2083
2431
  }
2084
2432
  }
2085
2433
  return out.sort((a, b) => b.name.length - a.name.length);
@@ -2399,7 +2747,7 @@ var NavigationAnalyzer = class {
2399
2747
  const ast = this.graph.parse(filePath);
2400
2748
  if (!ast) return [];
2401
2749
  const out = /* @__PURE__ */ new Map();
2402
- traverse4(ast, {
2750
+ traverse5(ast, {
2403
2751
  CallExpression: (nodePath) => {
2404
2752
  const callee = nodePath.node.callee;
2405
2753
  if (!BabelTypes.isIdentifier(callee)) return;
@@ -2560,7 +2908,7 @@ var NavigationAnalyzer = class {
2560
2908
  const ast = this.graph.parse(filePath);
2561
2909
  if (!ast) return [];
2562
2910
  const found = /* @__PURE__ */ new Map();
2563
- traverse4(ast, {
2911
+ traverse5(ast, {
2564
2912
  JSXElement: (nodePath) => {
2565
2913
  const { node } = nodePath;
2566
2914
  const openingElement = node.openingElement;
@@ -2632,7 +2980,7 @@ var NavigationAnalyzer = class {
2632
2980
  if (!ast) return [];
2633
2981
  const byNavigator = /* @__PURE__ */ new Map();
2634
2982
  const resolved = /* @__PURE__ */ new Map();
2635
- traverse4(ast, {
2983
+ traverse5(ast, {
2636
2984
  JSXElement: (nodePath) => {
2637
2985
  const name = nodePath.node.openingElement.name;
2638
2986
  if (!BabelTypes.isJSXMemberExpression(name) || !BabelTypes.isJSXIdentifier(name.object)) return;
@@ -2865,7 +3213,7 @@ var NavigationAnalyzer = class {
2865
3213
  ...this.config.parserPlugins || []
2866
3214
  ]
2867
3215
  });
2868
- traverse4(ast, {
3216
+ traverse5(ast, {
2869
3217
  TSTypeAliasDeclaration: (nodePath) => {
2870
3218
  const { node } = nodePath;
2871
3219
  const typeName = node.id.name;
@@ -2938,7 +3286,7 @@ var NavigationAnalyzer = class {
2938
3286
  if (type.type === "TSUndefinedKeyword") return "undefined";
2939
3287
  if (type.type === "TSNullKeyword") return "null";
2940
3288
  if (type.type === "TSUnionType") {
2941
- return type.types.map((t13) => this.typeToString(t13)).join(" | ");
3289
+ return type.types.map((t15) => this.typeToString(t15)).join(" | ");
2942
3290
  }
2943
3291
  if (type.type === "TSTypeLiteral") {
2944
3292
  return "object";
@@ -2959,7 +3307,7 @@ var NavigationAnalyzer = class {
2959
3307
  /** Attach parsed type params to navigator screens */
2960
3308
  attachParamsToNavigators(navigators, types) {
2961
3309
  for (const navigator of navigators) {
2962
- const matchingType = types.find((t13) => t13.type === navigator.type);
3310
+ const matchingType = types.find((t15) => t15.type === navigator.type);
2963
3311
  if (matchingType) {
2964
3312
  for (const screen of navigator.screens) {
2965
3313
  const screenParams = matchingType.paramEntries.get(screen.name);
@@ -3048,7 +3396,7 @@ var ComponentAnalyzer = class {
3048
3396
  plugins: ["jsx", "typescript", ["decorators", { decoratorsBeforeExport: true }]]
3049
3397
  });
3050
3398
  const components = [];
3051
- traverse4(ast, {
3399
+ traverse5(ast, {
3052
3400
  JSXElement: (path11) => {
3053
3401
  const component = this.extractComponentFromJSXElement(path11.node);
3054
3402
  if (component) {
@@ -3176,12 +3524,12 @@ var FormAnalyzer = class {
3176
3524
  this.stateVariables.clear();
3177
3525
  this.inputElements = [];
3178
3526
  this.submitButtons = [];
3179
- traverse4(ast, {
3527
+ traverse5(ast, {
3180
3528
  CallExpression: (path11) => {
3181
3529
  this.extractStateVariables(path11.node);
3182
3530
  }
3183
3531
  });
3184
- traverse4(ast, {
3532
+ traverse5(ast, {
3185
3533
  JSXElement: (path11) => {
3186
3534
  this.extractFormElements(path11.node);
3187
3535
  }
@@ -3217,23 +3565,23 @@ var FormAnalyzer = class {
3217
3565
  for (const attr of openingElement.attributes) {
3218
3566
  if (BabelTypes.isJSXAttribute(attr) && BabelTypes.isJSXIdentifier(attr.name)) {
3219
3567
  const propName = attr.name.name;
3220
- const propValue = this.extractAttributeValue(attr.value);
3568
+ const propValue2 = this.extractAttributeValue(attr.value);
3221
3569
  switch (propName) {
3222
3570
  case "label":
3223
- info.label = propValue;
3571
+ info.label = propValue2;
3224
3572
  break;
3225
3573
  case "placeholder":
3226
- info.placeholder = propValue;
3574
+ info.placeholder = propValue2;
3227
3575
  break;
3228
3576
  case "keyboardType":
3229
- info.keyboardType = propValue;
3577
+ info.keyboardType = propValue2;
3230
3578
  break;
3231
3579
  case "testID":
3232
- info.testID = propValue;
3580
+ info.testID = propValue2;
3233
3581
  break;
3234
3582
  case "appilotsId":
3235
- info.appilotsId = propValue;
3236
- if (propValue) info.varName = propValue;
3583
+ info.appilotsId = propValue2;
3584
+ if (propValue2) info.varName = propValue2;
3237
3585
  break;
3238
3586
  case "value":
3239
3587
  if (attr.value && BabelTypes.isJSXExpressionContainer(attr.value) && BabelTypes.isIdentifier(attr.value.expression)) {
@@ -3282,7 +3630,7 @@ var FormAnalyzer = class {
3282
3630
  }
3283
3631
  extractValidationRules(ast) {
3284
3632
  const rules = {};
3285
- traverse4(ast, {
3633
+ traverse5(ast, {
3286
3634
  IfStatement: (path11) => {
3287
3635
  const test = path11.node.test;
3288
3636
  const rule = this.extractRuleFromCondition(test);
@@ -3436,7 +3784,7 @@ async function composeAffordances(options) {
3436
3784
  const exclusive = children.filter((c) => (fanIn.get(c) ?? 0) <= MAX_FAN_IN);
3437
3785
  if (exclusive.length === 0) continue;
3438
3786
  const actionIds = new Set(screen.actions.map((a) => a.id));
3439
- const targetIds = new Set((screen.targets ?? []).map((t13) => t13.id));
3787
+ const targetIds = new Set((screen.targets ?? []).map((t15) => t15.id));
3440
3788
  const formIds = new Set(screen.forms.map((f) => f.id));
3441
3789
  let gained = false;
3442
3790
  for (const file of exclusive) {
@@ -3578,6 +3926,7 @@ var ReactNativePlatformAnalyzer = class {
3578
3926
  });
3579
3927
  return {
3580
3928
  screens: enrichedScreens,
3929
+ controlEvidenceFiles: screenAnalyzer.controlEvidenceFiles,
3581
3930
  navigation,
3582
3931
  analyzedFiles: screenFiles.length,
3583
3932
  ...screenAnalyzer.screensFilteredOut > 0 ? { screensFilteredOut: screenAnalyzer.screensFilteredOut } : {},
@@ -3842,7 +4191,7 @@ function extractWebNavigationCalls(ast) {
3842
4191
  calls.push({ method: "navigate", targetPath: node.right.value });
3843
4192
  }
3844
4193
  };
3845
- traverse4(ast, {
4194
+ traverse5(ast, {
3846
4195
  noScope: !BabelTypes.isFile(ast),
3847
4196
  enter: (nodePath) => inspect(nodePath.node)
3848
4197
  });
@@ -3964,7 +4313,7 @@ var WebScreenAnalyzer = class {
3964
4313
  // ── registerScreen ────────────────────────────────────────────────
3965
4314
  detectRegisterScreenCall(ast) {
3966
4315
  let found = false;
3967
- traverse4(ast, {
4316
+ traverse5(ast, {
3968
4317
  CallExpression: (nodePath) => {
3969
4318
  if (found) return;
3970
4319
  if (isRegisterScreenCallee(nodePath.node.callee)) {
@@ -3977,7 +4326,7 @@ var WebScreenAnalyzer = class {
3977
4326
  }
3978
4327
  extractRegisterScreenMetadata(ast) {
3979
4328
  let plain = null;
3980
- traverse4(ast, {
4329
+ traverse5(ast, {
3981
4330
  CallExpression: (nodePath) => {
3982
4331
  if (!isRegisterScreenCallee(nodePath.node.callee)) return;
3983
4332
  const arg = nodePath.node.arguments[0];
@@ -4025,7 +4374,7 @@ var WebScreenAnalyzer = class {
4025
4374
  extractComponentName(ast) {
4026
4375
  let defaultName = "";
4027
4376
  let firstExported = "";
4028
- traverse4(ast, {
4377
+ traverse5(ast, {
4029
4378
  ExportDefaultDeclaration: (nodePath) => {
4030
4379
  const declaration = nodePath.node.declaration;
4031
4380
  if (BabelTypes.isFunctionDeclaration(declaration) && declaration.id?.name) {
@@ -4054,7 +4403,7 @@ var WebScreenAnalyzer = class {
4054
4403
  extractComponents(ast) {
4055
4404
  const components = [];
4056
4405
  const seen = /* @__PURE__ */ new Set();
4057
- traverse4(ast, {
4406
+ traverse5(ast, {
4058
4407
  JSXOpeningElement: (nodePath) => {
4059
4408
  const element = nodePath.node;
4060
4409
  const name = getJsxElementName(element);
@@ -4075,7 +4424,7 @@ var WebScreenAnalyzer = class {
4075
4424
  // ── <label htmlFor> association ───────────────────────────────────
4076
4425
  collectHtmlForLabels(ast) {
4077
4426
  const labels = /* @__PURE__ */ new Map();
4078
- traverse4(ast, {
4427
+ traverse5(ast, {
4079
4428
  JSXElement: (nodePath) => {
4080
4429
  const element = nodePath.node;
4081
4430
  if (getJsxElementName(element.openingElement) !== "label") return;
@@ -4118,7 +4467,7 @@ var WebScreenAnalyzer = class {
4118
4467
  formBuckets.set(formElement, bucket);
4119
4468
  return bucket;
4120
4469
  };
4121
- traverse4(ast, {
4470
+ traverse5(ast, {
4122
4471
  JSXElement: (nodePath) => {
4123
4472
  const element = nodePath.node;
4124
4473
  const name = getJsxElementName(element.openingElement);
@@ -4135,7 +4484,7 @@ var WebScreenAnalyzer = class {
4135
4484
  if (!bucket.fields.has(field.name)) bucket.fields.set(field.name, field);
4136
4485
  }
4137
4486
  });
4138
- traverse4(ast, {
4487
+ traverse5(ast, {
4139
4488
  JSXElement: (nodePath) => {
4140
4489
  const element = nodePath.node;
4141
4490
  const name = getJsxElementName(element.openingElement);
@@ -4313,7 +4662,7 @@ var WebScreenAnalyzer = class {
4313
4662
  const actionLabels = new Map(
4314
4663
  actions.filter((a) => a.label).map((a) => [normalizeLabel(a.label), a])
4315
4664
  );
4316
- traverse4(ast, {
4665
+ traverse5(ast, {
4317
4666
  JSXElement: (nodePath) => {
4318
4667
  const element = nodePath.node;
4319
4668
  const name = getJsxElementName(element.openingElement);
@@ -4544,7 +4893,7 @@ var WebScreenAnalyzer = class {
4544
4893
  for (const call of extractWebNavigationCalls(ast)) {
4545
4894
  if (call.targetPath && call.targetPath.startsWith("/")) targets.add(call.targetPath);
4546
4895
  }
4547
- traverse4(ast, {
4896
+ traverse5(ast, {
4548
4897
  JSXOpeningElement: (nodePath) => {
4549
4898
  const element = nodePath.node;
4550
4899
  const name = getJsxElementName(element);
@@ -4564,7 +4913,7 @@ var WebScreenAnalyzer = class {
4564
4913
  extractCollections(ast) {
4565
4914
  const collections = [];
4566
4915
  const seen = /* @__PURE__ */ new Set();
4567
- traverse4(ast, {
4916
+ traverse5(ast, {
4568
4917
  JSXExpressionContainer: (nodePath) => {
4569
4918
  const expr = nodePath.node.expression;
4570
4919
  if (!BabelTypes.isCallExpression(expr) || !BabelTypes.isMemberExpression(expr.callee) || !BabelTypes.isIdentifier(expr.callee.object) || !BabelTypes.isIdentifier(expr.callee.property) || expr.callee.property.name !== "map") {
@@ -4676,7 +5025,7 @@ function firstCalledFunctionName(node) {
4676
5025
  }
4677
5026
  function containsWindowConfirm(body) {
4678
5027
  let found = false;
4679
- traverse4(
5028
+ traverse5(
4680
5029
  body,
4681
5030
  {
4682
5031
  noScope: true,
@@ -4728,7 +5077,7 @@ function collectionItemNames(callback) {
4728
5077
  function collectionDisplayFields(callback, itemNames) {
4729
5078
  const fields = /* @__PURE__ */ new Set();
4730
5079
  if (!callback.body) return [];
4731
- traverse4(
5080
+ traverse5(
4732
5081
  callback.body,
4733
5082
  {
4734
5083
  noScope: true,
@@ -4745,7 +5094,7 @@ function collectionDisplayFields(callback, itemNames) {
4745
5094
  function collectionKeyField(callback, itemNames) {
4746
5095
  let keyField;
4747
5096
  if (!callback.body) return void 0;
4748
- traverse4(
5097
+ traverse5(
4749
5098
  callback.body,
4750
5099
  {
4751
5100
  noScope: true,
@@ -4888,7 +5237,7 @@ var WebNavigationAnalyzer = class {
4888
5237
  if (BabelTypes.isJSXElement(child)) visitRoute(child, fullPath);
4889
5238
  }
4890
5239
  };
4891
- traverse4(ast, {
5240
+ traverse5(ast, {
4892
5241
  JSXElement: (nodePath) => {
4893
5242
  const name = getJsxElementName(nodePath.node.openingElement);
4894
5243
  if (name !== "Routes" && name !== "Route") return;
@@ -4927,7 +5276,7 @@ var WebNavigationAnalyzer = class {
4927
5276
  "createMemoryRouter",
4928
5277
  "useRoutes"
4929
5278
  ]);
4930
- traverse4(ast, {
5279
+ traverse5(ast, {
4931
5280
  CallExpression: (nodePath) => {
4932
5281
  const callee = nodePath.node.callee;
4933
5282
  if (!BabelTypes.isIdentifier(callee) || !ROUTER_FACTORIES.has(callee.name)) return;
@@ -5360,8 +5709,8 @@ var ZodParsedType = util.arrayToEnum([
5360
5709
  "set"
5361
5710
  ]);
5362
5711
  var getParsedType = (data) => {
5363
- const t13 = typeof data;
5364
- switch (t13) {
5712
+ const t15 = typeof data;
5713
+ switch (t15) {
5365
5714
  case "undefined":
5366
5715
  return ZodParsedType.undefined;
5367
5716
  case "string":
@@ -9206,7 +9555,25 @@ function isSafeImageUrl(value) {
9206
9555
  return SAFE_IMAGE_URL_RE.test(trimmed);
9207
9556
  }
9208
9557
 
9209
- // ../shared/dist/chunk-NMQNNPSJ.mjs
9558
+ // ../shared/dist/chunk-KUMPS6RH.mjs
9559
+ var SUPPORTED_APPILOTS_LOCALES = ["pt-BR", "en", "es", "fr"];
9560
+
9561
+ // ../shared/dist/chunk-4RYPNBBY.mjs
9562
+ var symbols = external_exports.array(external_exports.string().max(240)).max(12);
9563
+ var controlEvidenceSchema = external_exports.object({
9564
+ version: external_exports.literal(1),
9565
+ siteId: external_exports.string().regex(/^[a-f0-9]{20}$/),
9566
+ component: external_exports.string().max(240),
9567
+ icons: symbols,
9568
+ handler: external_exports.string().max(240).optional(),
9569
+ calls: symbols,
9570
+ argumentBindings: symbols,
9571
+ conditions: symbols,
9572
+ nativeConfirmation: external_exports.object({
9573
+ title: external_exports.string().max(240).optional(),
9574
+ destructiveOption: external_exports.boolean()
9575
+ }).optional()
9576
+ });
9210
9577
  var locatorSourceSchema = external_exports.enum([
9211
9578
  "appilotsId",
9212
9579
  "testID",
@@ -9408,7 +9775,9 @@ var screenPermissionDescriptorSchema = external_exports.object({
9408
9775
  agentAccess: external_exports.enum(["read", "write", "none"]).optional(),
9409
9776
  requiresConfirmation: external_exports.boolean().optional(),
9410
9777
  blockedActions: external_exports.array(external_exports.string()).optional(),
9411
- isPii: external_exports.boolean().optional()
9778
+ isPii: external_exports.boolean().optional(),
9779
+ /** Graded assessment — see `screenPermissionSchema.assessment`. */
9780
+ assessment: external_exports.boolean().optional()
9412
9781
  }).passthrough();
9413
9782
  var screenDescriptorSchema = external_exports.object({
9414
9783
  name: external_exports.string(),
@@ -9633,7 +10002,9 @@ var snapshotInputSchema = external_exports.object({
9633
10002
  inModal: external_exports.boolean().optional()
9634
10003
  }).passthrough();
9635
10004
  var snapshotButtonSchema = external_exports.object({
10005
+ controlEvidence: controlEvidenceSchema.optional(),
9636
10006
  id: boundedString(160).optional(),
10007
+ dispatchable: external_exports.boolean().optional(),
9637
10008
  provenance: identityProvenanceSchema.optional(),
9638
10009
  /**
9639
10010
  * False when the control is mounted but currently OUTSIDE the window —
@@ -9694,7 +10065,36 @@ var snapshotListSchema = external_exports.object({
9694
10065
  */
9695
10066
  source: boundedString(40).optional(),
9696
10067
  itemCount: external_exports.number().int().optional(),
10068
+ /**
10069
+ * Size of the whole collection when the app declared it, for a list
10070
+ * that is a WINDOW onto more data than it holds.
10071
+ *
10072
+ * `itemCount` is how many rows the list is rendering from and
10073
+ * `visibleItemCount` how many of those are mounted; neither can
10074
+ * express "there are 36 and you are looking at the first 20",
10075
+ * because a paginated list's `data` is the page. Absent means
10076
+ * unknown — never "same as itemCount".
10077
+ */
10078
+ totalItemCount: external_exports.number().int().optional(),
9697
10079
  visibleItemCount: external_exports.number().int().optional(),
10080
+ viewportItemCount: external_exports.number().int().nonnegative().optional(),
10081
+ exploration: external_exports.object({
10082
+ revision: external_exports.number().int().nonnegative(),
10083
+ observedItemCount: external_exports.number().int().min(0).max(5e3),
10084
+ observedRanges: external_exports.array(
10085
+ external_exports.object({
10086
+ start: external_exports.number().int().nonnegative(),
10087
+ end: external_exports.number().int().nonnegative()
10088
+ })
10089
+ ).max(32),
10090
+ rangesTruncated: external_exports.boolean(),
10091
+ coverage: external_exports.enum(["partial", "all-loaded"]),
10092
+ pagination: external_exports.enum(["possible", "not-declared"]),
10093
+ scrollSteps: external_exports.number().int().nonnegative(),
10094
+ remainingScrollSteps: external_exports.number().int().nonnegative(),
10095
+ consecutiveNoProgress: external_exports.number().int().nonnegative(),
10096
+ lastScroll: external_exports.enum(["moved", "no-progress", "boundary", "unverified"]).optional()
10097
+ }).optional(),
9698
10098
  refreshing: external_exports.boolean().optional(),
9699
10099
  empty: external_exports.boolean().optional(),
9700
10100
  label: boundedString(300).optional(),
@@ -9742,6 +10142,7 @@ var snapshotChoiceGroupSchema = external_exports.object({
9742
10142
  }).passthrough();
9743
10143
  var snapshotElementSchema = external_exports.object({
9744
10144
  id: boundedString(160).optional(),
10145
+ dispatchable: external_exports.boolean().optional(),
9745
10146
  role: boundedString(40).optional(),
9746
10147
  label: boundedString(300).optional(),
9747
10148
  texts: external_exports.array(boundedString(500)).max(50).optional(),
@@ -9804,6 +10205,8 @@ var agentSnapshotSchema = external_exports.object({
9804
10205
  loadingFinished: external_exports.boolean().optional(),
9805
10206
  textsAdded: external_exports.number().int().nonnegative().optional(),
9806
10207
  textsRemoved: external_exports.number().int().nonnegative().optional(),
10208
+ buttonsAddedIndices: external_exports.array(external_exports.number().int().nonnegative()).max(12).optional(),
10209
+ buttonsRemoved: external_exports.number().int().nonnegative().optional(),
9807
10210
  visibleRowsDelta: external_exports.number().int().optional(),
9808
10211
  totalRowsDelta: external_exports.number().int().optional(),
9809
10212
  fieldsNewlyFilled: external_exports.array(boundedString(160)).max(12).optional(),
@@ -9829,6 +10232,16 @@ var agentSnapshotSchema = external_exports.object({
9829
10232
  }).passthrough().optional()
9830
10233
  }).passthrough();
9831
10234
  var agentContextSchema = external_exports.object({
10235
+ missionProtocol: external_exports.literal(1).optional(),
10236
+ missionId: boundedString(160).optional(),
10237
+ /** Preferred supported device language, reported by the SDK independently of map/UI labels. */
10238
+ deviceLocale: external_exports.enum(SUPPORTED_APPILOTS_LOCALES).optional(),
10239
+ /**
10240
+ * Per-session plain-language override (short sentences, no jargon,
10241
+ * numbered steps). Absent → the project's `plainLanguage` setting.
10242
+ * Changes how the reply is WORDED only — see `PLAIN_LANGUAGE_PROMPT`.
10243
+ */
10244
+ plainLanguage: external_exports.boolean().optional(),
9832
10245
  /**
9833
10246
  * Client platform this observation was captured on. Optional and
9834
10247
  * additive (see `clientPlatformSchema`) — absent means
@@ -10079,6 +10492,12 @@ var actionDiagnoseSchema = external_exports.object({
10079
10492
  requiresUserInput: external_exports.boolean().optional()
10080
10493
  });
10081
10494
  var actionResultSchema = external_exports.object({
10495
+ nativeConfirmation: external_exports.object({
10496
+ title: external_exports.string().max(300),
10497
+ message: external_exports.string().max(1e3).optional(),
10498
+ buttonLabel: external_exports.string().max(160).optional(),
10499
+ handlerCompleted: external_exports.boolean()
10500
+ }).optional(),
10082
10501
  actionId: external_exports.string(),
10083
10502
  type: external_exports.string(),
10084
10503
  success: external_exports.boolean(),
@@ -10145,7 +10564,14 @@ var screenPermissionSchema = external_exports.object({
10145
10564
  agentAccess: agentAccessLevelSchema.optional(),
10146
10565
  requiresConfirmation: external_exports.boolean().optional(),
10147
10566
  blockedActions: external_exports.array(external_exports.string().min(1)).optional(),
10148
- isPii: external_exports.boolean().optional()
10567
+ isPii: external_exports.boolean().optional(),
10568
+ /**
10569
+ * Graded assessment (exam, quiz, graded activity). The agent may
10570
+ * explain the screen and navigate away, never write: every action
10571
+ * except navigate/scroll is blocked server-side, and the model is told
10572
+ * to refuse up front instead of trying. Wins over `agentAccess: 'write'`.
10573
+ */
10574
+ assessment: external_exports.boolean().optional()
10149
10575
  }).strict();
10150
10576
  external_exports.object({
10151
10577
  agentCanWrite: external_exports.boolean(),
@@ -10153,6 +10579,10 @@ external_exports.object({
10153
10579
  confirmDestructive: external_exports.boolean(),
10154
10580
  /**
10155
10581
  * Map of screenName → override. Keys must be non-empty strings.
10582
+ * A key containing `*` or starting with `/` is a PATTERN, matched
10583
+ * case-insensitively against the screen name and the navigation path
10584
+ * (`*Quiz*`; a path key uses `*` per segment, see docs/permissions).
10585
+ * An exact key beats patterns.
10156
10586
  * Limited to 200 entries to keep the JSON column small and editable
10157
10587
  * in the dashboard table view.
10158
10588
  */
@@ -10206,7 +10636,7 @@ external_exports.object({
10206
10636
  /** null = remove webhook, undefined = leave unchanged. */
10207
10637
  budgetWebhookUrl: external_exports.string().url().nullable().optional()
10208
10638
  }).strict();
10209
- var localeSchema = external_exports.enum(["pt-BR", "en", "es"]);
10639
+ var localeSchema = external_exports.enum(SUPPORTED_APPILOTS_LOCALES);
10210
10640
  var hexColorSchema = external_exports.string().regex(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
10211
10641
  message: "Must be a hex color like #6366f1"
10212
10642
  });
@@ -10250,6 +10680,13 @@ var imageSourceField = external_exports.string().max(500).refine(isSafeImageUrl,
10250
10680
  external_exports.object({
10251
10681
  /** Tone / persona instructions appended to the system prompt. */
10252
10682
  personaPrompt: external_exports.string().max(2048).nullable(),
10683
+ /**
10684
+ * Accessibility: reply in plain language (short sentences, everyday
10685
+ * words, numbered steps, one question at a time). Server-side only,
10686
+ * like `personaPrompt`; a session can override it via
10687
+ * `context.plainLanguage`.
10688
+ */
10689
+ plainLanguage: external_exports.boolean(),
10253
10690
  /** Display name in the chat header (e.g. "Aria"). */
10254
10691
  assistantName: external_exports.string().max(60).nullable(),
10255
10692
  /** URL or remote asset id for the avatar shown next to assistant turns. */
@@ -10273,6 +10710,7 @@ external_exports.object({
10273
10710
  });
10274
10711
  external_exports.object({
10275
10712
  personaPrompt: external_exports.string().max(2048).nullable().optional(),
10713
+ plainLanguage: external_exports.boolean().optional(),
10276
10714
  assistantName: external_exports.string().max(60).nullable().optional(),
10277
10715
  assistantAvatar: imageSourceField.nullable().optional(),
10278
10716
  emptyStateIcon: imageSourceField.nullable().optional(),
@@ -10430,6 +10868,8 @@ external_exports.object({
10430
10868
  * the dev doesn't want their tokens metered.
10431
10869
  */
10432
10870
  silent: external_exports.boolean().optional(),
10871
+ /** Run with the plain-language reply style on/off, overriding the project. */
10872
+ plainLanguage: external_exports.boolean().optional(),
10433
10873
  /**
10434
10874
  * Prior turns of the conversation, in order, that precede `prompt`.
10435
10875
  * Lets a driver run the agent loop hop-by-hop headlessly (the multi-
@@ -11217,9 +11657,22 @@ var MCPGenerator = class _MCPGenerator {
11217
11657
  const filePath = path2__default.resolve(outputDir, `mcp-document.${this.options.format}`);
11218
11658
  await writeFile(filePath, serialized, "utf-8");
11219
11659
  console.log(`[MCPGenerator] Document written to: ${filePath}`);
11660
+ const controlFiles = analyzed.controlEvidenceFiles ?? {};
11661
+ await writeFile(
11662
+ path2__default.resolve(outputDir, "control-evidence.json"),
11663
+ JSON.stringify({ version: 1, files: controlFiles }, null, 2),
11664
+ "utf-8"
11665
+ );
11220
11666
  const checksumFilePath = path2__default.resolve(outputDir, ".appilots-checksum");
11221
11667
  await writeFile(checksumFilePath, checksum, "utf-8");
11222
11668
  console.log(`[MCPGenerator] Checksum written to: ${checksumFilePath}`);
11669
+ const evidenceCount = Object.values(controlFiles).reduce(
11670
+ (sum, entries) => sum + entries.length,
11671
+ 0
11672
+ );
11673
+ console.log(
11674
+ `[MCPGenerator] Source evidence: ${evidenceCount} icon controls across ${Object.keys(controlFiles).length} files (runtime binding required)`
11675
+ );
11223
11676
  console.log("[MCPGenerator] Generation complete!");
11224
11677
  return {
11225
11678
  document,
@@ -11285,7 +11738,8 @@ var KNOWN_CONFIG_KEYS = [
11285
11738
  "navigationExclude",
11286
11739
  "platform",
11287
11740
  "manifestPath",
11288
- "eval"
11741
+ "eval",
11742
+ "knowledge"
11289
11743
  ];
11290
11744
  var KEY_ALIASES = {
11291
11745
  apiUrl: "serverUrl",
@@ -11433,7 +11887,8 @@ function saveConfig(dir, config) {
11433
11887
  navigationExclude: config.navigationExclude || existingConfig?.navigationExclude,
11434
11888
  platform: config.platform || existingConfig?.platform,
11435
11889
  manifestPath: config.manifestPath || existingConfig?.manifestPath,
11436
- eval: config.eval || existingConfig?.eval
11890
+ eval: config.eval || existingConfig?.eval,
11891
+ knowledge: config.knowledge || existingConfig?.knowledge
11437
11892
  };
11438
11893
  try {
11439
11894
  writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
@@ -11526,6 +11981,19 @@ function validateConfig(config) {
11526
11981
  if (config.manifestPath !== void 0 && typeof config.manifestPath !== "string") {
11527
11982
  errors.push("manifestPath must be a string");
11528
11983
  }
11984
+ if (config.knowledge !== void 0) {
11985
+ if (typeof config.knowledge !== "object" || config.knowledge === null || Array.isArray(config.knowledge)) {
11986
+ errors.push("knowledge must be an object");
11987
+ } else {
11988
+ const kc = config.knowledge;
11989
+ for (const field of ["sources", "exclude"]) {
11990
+ const value = kc[field];
11991
+ if (value !== void 0 && (!Array.isArray(value) || !value.every((item) => typeof item === "string"))) {
11992
+ errors.push(`knowledge.${field} must be an array of strings`);
11993
+ }
11994
+ }
11995
+ }
11996
+ }
11529
11997
  if (config.eval !== void 0) {
11530
11998
  if (typeof config.eval !== "object" || config.eval === null || Array.isArray(config.eval)) {
11531
11999
  errors.push("eval must be an object");
@@ -11802,6 +12270,48 @@ var AppilotsAPIClient = class {
11802
12270
  };
11803
12271
  }
11804
12272
  }
12273
+ /**
12274
+ * Syncs knowledge documents with the Appilots backend.
12275
+ *
12276
+ * @param documents Array of documents to sync (filename, base64 content, mimeType, checksum)
12277
+ * @returns KnowledgeSyncResult with counts of uploaded, skipped, and errored docs
12278
+ */
12279
+ async knowledgeSync(documents) {
12280
+ try {
12281
+ const response = await this.request(`${this.baseUrl}/cli/knowledge/sync`, {
12282
+ method: "POST",
12283
+ headers: {
12284
+ "Content-Type": "application/json",
12285
+ Authorization: `Bearer ${this.apiKey}`
12286
+ },
12287
+ body: JSON.stringify({ documents })
12288
+ });
12289
+ if (!response.ok) {
12290
+ const errorData = await response.json().catch(() => ({}));
12291
+ return {
12292
+ success: false,
12293
+ uploaded: 0,
12294
+ skipped: 0,
12295
+ errors: 0,
12296
+ error: describeApiError(errorData, `HTTP ${response.status}: ${response.statusText}`)
12297
+ };
12298
+ }
12299
+ const json = await response.json();
12300
+ const inner = json.data ?? json;
12301
+ return {
12302
+ success: true,
12303
+ ...inner
12304
+ };
12305
+ } catch (error) {
12306
+ return {
12307
+ success: false,
12308
+ uploaded: 0,
12309
+ skipped: 0,
12310
+ errors: 0,
12311
+ error: error instanceof Error ? error.message : "Failed to sync knowledge with Appilots API"
12312
+ };
12313
+ }
12314
+ }
11805
12315
  /**
11806
12316
  * Checks if the Appilots API server is healthy
11807
12317
  *
@@ -11820,7 +12330,7 @@ var AppilotsAPIClient = class {
11820
12330
  };
11821
12331
 
11822
12332
  // src/version.ts
11823
- var CLI_VERSION = "0.11.3";
12333
+ var CLI_VERSION = "0.14.0";
11824
12334
 
11825
12335
  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 };
11826
12336
  //# sourceMappingURL=index.mjs.map