@appilots/cli 0.1.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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/cli/index.js +4178 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.mts +891 -0
- package/dist/index.d.ts +891 -0
- package/dist/index.js +3109 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3070 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the MCP Generator.
|
|
3
|
+
*/
|
|
4
|
+
interface AnalyzerConfig {
|
|
5
|
+
/** Root directory of the React Native project */
|
|
6
|
+
rootDir: string;
|
|
7
|
+
/** Glob patterns for source files */
|
|
8
|
+
include?: string[];
|
|
9
|
+
/** Glob patterns to exclude */
|
|
10
|
+
exclude?: string[];
|
|
11
|
+
/** TypeScript/JavaScript parser options */
|
|
12
|
+
parserPlugins?: string[];
|
|
13
|
+
}
|
|
14
|
+
interface ScreenDescriptor {
|
|
15
|
+
name: string;
|
|
16
|
+
filePath: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
components: ComponentDescriptor[];
|
|
20
|
+
forms: FormDescriptor[];
|
|
21
|
+
actions: ActionDescriptor[];
|
|
22
|
+
navigationTargets: string[];
|
|
23
|
+
/**
|
|
24
|
+
* Compact operational hints derived from the structured map. These are
|
|
25
|
+
* designed for the agent prompt, not as the canonical source of truth.
|
|
26
|
+
*/
|
|
27
|
+
agentHints?: ScreenAgentHints;
|
|
28
|
+
/** Stable, addressable UI targets the agent can interact with. */
|
|
29
|
+
targets?: TargetDescriptor[];
|
|
30
|
+
/** Common multi-step tasks inferred from forms, lists, actions and navigation. */
|
|
31
|
+
flows?: FlowDescriptor[];
|
|
32
|
+
collections?: CollectionDescriptor[];
|
|
33
|
+
/**
|
|
34
|
+
* Dev-defined prompts to surface as chips in the chat when the user lands
|
|
35
|
+
* on this screen. Extracted from `registerScreen({ suggestedPrompts: [...] })`.
|
|
36
|
+
* Persists into the MCP doc so the dashboard can render them and the SDK
|
|
37
|
+
* can fall back to them on first run before any popular history exists.
|
|
38
|
+
*/
|
|
39
|
+
suggestedPrompts?: string[];
|
|
40
|
+
/**
|
|
41
|
+
* Permission/safety metadata extracted from a `@appilots-permissions`
|
|
42
|
+
* JSDoc tag on the screen component. Layered under the dashboard
|
|
43
|
+
* configuration at runtime — see `apps/api/src/common/services/permissions.ts`.
|
|
44
|
+
*/
|
|
45
|
+
permissions?: ScreenPermissionDescriptor;
|
|
46
|
+
/**
|
|
47
|
+
* Convenience flag for screens marked PII via JSDoc (e.g.
|
|
48
|
+
* `@appilots-pii` or `permissions.isPii: true`). Lifted up so the
|
|
49
|
+
* runtime can apply project-wide PII guards without re-parsing
|
|
50
|
+
* the permissions object.
|
|
51
|
+
*/
|
|
52
|
+
isPii?: boolean;
|
|
53
|
+
}
|
|
54
|
+
interface ScreenPermissionDescriptor {
|
|
55
|
+
agentAccess?: 'read' | 'write' | 'none';
|
|
56
|
+
requiresConfirmation?: boolean;
|
|
57
|
+
blockedActions?: string[];
|
|
58
|
+
isPii?: boolean;
|
|
59
|
+
}
|
|
60
|
+
interface NavigationGraph {
|
|
61
|
+
screens: Record<string, NavigationNode>;
|
|
62
|
+
initialScreen: string;
|
|
63
|
+
navigators: NavigatorDescriptor[];
|
|
64
|
+
}
|
|
65
|
+
interface NavigationNode {
|
|
66
|
+
screenName: string;
|
|
67
|
+
navigatorType: 'stack' | 'tab' | 'drawer';
|
|
68
|
+
parentNavigator?: string;
|
|
69
|
+
reachableFrom: string[];
|
|
70
|
+
reachableTo: string[];
|
|
71
|
+
params?: ParamDescriptor[];
|
|
72
|
+
}
|
|
73
|
+
interface NavigatorDescriptor {
|
|
74
|
+
name: string;
|
|
75
|
+
type: 'stack' | 'tab' | 'drawer';
|
|
76
|
+
screens: string[];
|
|
77
|
+
parentNavigator?: string;
|
|
78
|
+
}
|
|
79
|
+
interface ParamDescriptor {
|
|
80
|
+
name: string;
|
|
81
|
+
type: string;
|
|
82
|
+
required: boolean;
|
|
83
|
+
defaultValue?: unknown;
|
|
84
|
+
}
|
|
85
|
+
interface ComponentDescriptor {
|
|
86
|
+
name: string;
|
|
87
|
+
type: 'view' | 'input' | 'button' | 'list' | 'modal' | 'custom';
|
|
88
|
+
props?: Record<string, string>;
|
|
89
|
+
children?: ComponentDescriptor[];
|
|
90
|
+
testID?: string;
|
|
91
|
+
accessibilityLabel?: string;
|
|
92
|
+
}
|
|
93
|
+
interface FormDescriptor {
|
|
94
|
+
id: string;
|
|
95
|
+
fields: FormFieldDescriptor[];
|
|
96
|
+
submitAction?: string;
|
|
97
|
+
validationRules?: Record<string, string>;
|
|
98
|
+
}
|
|
99
|
+
interface FormFieldDescriptor {
|
|
100
|
+
name: string;
|
|
101
|
+
label?: string;
|
|
102
|
+
type: 'text' | 'number' | 'email' | 'phone' | 'select' | 'toggle' | 'date' | 'custom';
|
|
103
|
+
required: boolean;
|
|
104
|
+
placeholder?: string;
|
|
105
|
+
options?: {
|
|
106
|
+
label: string;
|
|
107
|
+
value: string;
|
|
108
|
+
}[];
|
|
109
|
+
defaultValue?: unknown;
|
|
110
|
+
locator?: LocatorDescriptor;
|
|
111
|
+
sourceComponent?: string;
|
|
112
|
+
valueBinding?: string;
|
|
113
|
+
errorBinding?: string;
|
|
114
|
+
}
|
|
115
|
+
interface CollectionDescriptor {
|
|
116
|
+
id: string;
|
|
117
|
+
component: 'FlatList' | 'SectionList' | 'VirtualizedList' | 'FlashList' | 'ScrollView' | string;
|
|
118
|
+
itemType?: string;
|
|
119
|
+
dataSource?: string;
|
|
120
|
+
keyField?: string;
|
|
121
|
+
renderItem?: string;
|
|
122
|
+
displayFields?: string[];
|
|
123
|
+
rowAction?: RowActionDescriptor;
|
|
124
|
+
rowActions?: RowActionDescriptor[];
|
|
125
|
+
identityFields?: string[];
|
|
126
|
+
searchField?: string;
|
|
127
|
+
}
|
|
128
|
+
interface RowActionDescriptor {
|
|
129
|
+
type: 'navigation' | 'custom';
|
|
130
|
+
targetScreen?: string;
|
|
131
|
+
params?: Record<string, string>;
|
|
132
|
+
handler?: string;
|
|
133
|
+
description?: string;
|
|
134
|
+
}
|
|
135
|
+
interface ActionDescriptor {
|
|
136
|
+
id: string;
|
|
137
|
+
type: 'navigation' | 'submit' | 'api_call' | 'state_change' | 'custom';
|
|
138
|
+
label?: string;
|
|
139
|
+
description?: string;
|
|
140
|
+
targetScreen?: string;
|
|
141
|
+
handler?: string;
|
|
142
|
+
locator?: LocatorDescriptor;
|
|
143
|
+
sourceComponent?: string;
|
|
144
|
+
opensModal?: string;
|
|
145
|
+
opensBottomSheet?: string;
|
|
146
|
+
successSignal?: SignalDescriptor;
|
|
147
|
+
failureSignal?: SignalDescriptor;
|
|
148
|
+
nativeConfirmationExpected?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* BACKLOG 2.2 — Destructive flag.
|
|
151
|
+
* Set to `true` when:
|
|
152
|
+
* - the dev annotated the call site with `@appilots-destructive`,
|
|
153
|
+
* - the handler name matches a destructive verb (delete/destroy/
|
|
154
|
+
* remove/discard/wipe/revoke/etc.),
|
|
155
|
+
* - or a sibling JSX `destructive` prop / `aria-destructive`
|
|
156
|
+
* attribute is true.
|
|
157
|
+
* The runtime auto-injects a `confirm` action before executing
|
|
158
|
+
* any action carrying `destructive: true` (unless the project
|
|
159
|
+
* has whitelisted the action id in `neverConfirmActions`).
|
|
160
|
+
*/
|
|
161
|
+
destructive?: boolean;
|
|
162
|
+
/**
|
|
163
|
+
* Structured action semantics for runtimes that need to enforce
|
|
164
|
+
* policy in multilingual apps without parsing labels. Prefer these
|
|
165
|
+
* fields when the app/developer can declare them explicitly.
|
|
166
|
+
*/
|
|
167
|
+
effect?: 'read' | 'write' | 'destructive' | string;
|
|
168
|
+
riskLevel?: 'low' | 'medium' | 'high' | string;
|
|
169
|
+
requiresConfirmation?: boolean;
|
|
170
|
+
/**
|
|
171
|
+
* Static-analysis hints derived by the MCP generator from the
|
|
172
|
+
* action's handler body. Used by the SDK to refine waiting and
|
|
173
|
+
* loading heuristics. None of these fields are guessed by the LLM
|
|
174
|
+
* — the generator either has enough evidence to populate the
|
|
175
|
+
* field or leaves it undefined. When all three are undefined, the
|
|
176
|
+
* SDK falls back to the agnostic runtime signals (ActivityIndicator,
|
|
177
|
+
* pressed-button-still-disabled, fingerprint quiescence).
|
|
178
|
+
*
|
|
179
|
+
* See packages/mcp-generator/docs/INFERRED_ACTION_FIELDS.md for the
|
|
180
|
+
* detection rules.
|
|
181
|
+
*/
|
|
182
|
+
appilotsInferred?: AppilotsInferredAction;
|
|
183
|
+
}
|
|
184
|
+
interface LocatorDescriptor {
|
|
185
|
+
id?: string;
|
|
186
|
+
appilotsId?: string;
|
|
187
|
+
testID?: string;
|
|
188
|
+
accessibilityLabel?: string;
|
|
189
|
+
label?: string;
|
|
190
|
+
source?: 'appilotsId' | 'testID' | 'accessibilityLabel' | 'label' | 'inferred' | string;
|
|
191
|
+
}
|
|
192
|
+
interface SignalDescriptor {
|
|
193
|
+
type: 'navigation' | 'goBack' | 'toast' | 'modal' | 'inline-error' | 'data-arrival' | 'loading' | 'none' | string;
|
|
194
|
+
target?: string;
|
|
195
|
+
description?: string;
|
|
196
|
+
}
|
|
197
|
+
interface TargetDescriptor {
|
|
198
|
+
id: string;
|
|
199
|
+
role: 'button' | 'submit' | 'input' | 'toggle' | 'select' | 'date' | 'list' | 'row' | 'menuItem' | 'modal' | 'custom' | string;
|
|
200
|
+
label?: string;
|
|
201
|
+
locator?: LocatorDescriptor;
|
|
202
|
+
handler?: string;
|
|
203
|
+
actionId?: string;
|
|
204
|
+
fieldName?: string;
|
|
205
|
+
targetScreen?: string;
|
|
206
|
+
destructive?: boolean;
|
|
207
|
+
requiresConfirmation?: boolean;
|
|
208
|
+
opensModal?: string;
|
|
209
|
+
opensBottomSheet?: string;
|
|
210
|
+
sourceComponent?: string;
|
|
211
|
+
}
|
|
212
|
+
interface FlowDescriptor {
|
|
213
|
+
id: string;
|
|
214
|
+
title: string;
|
|
215
|
+
intent: 'navigate' | 'form_submit' | 'list_action' | 'destructive_action' | 'inline_action' | string;
|
|
216
|
+
steps: FlowStepDescriptor[];
|
|
217
|
+
waitPolicy?: WaitPolicyDescriptor;
|
|
218
|
+
safetyNotes?: string[];
|
|
219
|
+
}
|
|
220
|
+
interface FlowStepDescriptor {
|
|
221
|
+
type: 'navigate' | 'fill' | 'press' | 'select' | 'toggle' | 'wait' | 'confirm' | 'choose-list-item' | string;
|
|
222
|
+
target?: string;
|
|
223
|
+
label?: string;
|
|
224
|
+
description?: string;
|
|
225
|
+
required?: boolean;
|
|
226
|
+
}
|
|
227
|
+
interface WaitPolicyDescriptor {
|
|
228
|
+
expectedOutcome?: AppilotsInferredAction['expectedOutcome'] | 'goBack' | 'toast' | 'modal' | string;
|
|
229
|
+
signals?: SignalDescriptor[];
|
|
230
|
+
maxMs?: number;
|
|
231
|
+
}
|
|
232
|
+
interface ScreenAgentHints {
|
|
233
|
+
primaryGoal?: string;
|
|
234
|
+
commonTasks?: string[];
|
|
235
|
+
preferredTargets?: string[];
|
|
236
|
+
waitPolicy?: WaitPolicyDescriptor;
|
|
237
|
+
safetyNotes?: string[];
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Inferred metadata about an action's expected async behavior.
|
|
241
|
+
*
|
|
242
|
+
* Emitted by the MCP generator when it can statically prove (from
|
|
243
|
+
* the handler source) one of the following — never from heuristic
|
|
244
|
+
* guessing on labels or props alone.
|
|
245
|
+
*/
|
|
246
|
+
interface AppilotsInferredAction {
|
|
247
|
+
/**
|
|
248
|
+
* True when the handler awaits a Promise (await fetch, axios,
|
|
249
|
+
* useMutation.mutateAsync, etc.) or contains `.then(`/`.catch(`
|
|
250
|
+
* on a returned Promise. Tells the SDK that pressing this button
|
|
251
|
+
* is expected to trigger async work, and the runtime wait window
|
|
252
|
+
* should be more generous (10s instead of 6s).
|
|
253
|
+
*/
|
|
254
|
+
isAsyncTrigger?: boolean;
|
|
255
|
+
/**
|
|
256
|
+
* The most-likely observable outcome of the action, when the
|
|
257
|
+
* generator can detect one:
|
|
258
|
+
* - 'navigation' : handler calls navigation.navigate /
|
|
259
|
+
* push / replace AFTER the await. SDK
|
|
260
|
+
* expects a route change.
|
|
261
|
+
* - 'inline-feedback' : handler updates local state (toast,
|
|
262
|
+
* validation, list re-render) without
|
|
263
|
+
* navigating. SDK waits for fingerprint
|
|
264
|
+
* quiescence only.
|
|
265
|
+
* - 'data-arrival' : handler triggers a data load whose
|
|
266
|
+
* result is rendered into the current
|
|
267
|
+
* screen (e.g. fetching list items into
|
|
268
|
+
* a FlatList). SDK waits for the
|
|
269
|
+
* fingerprint to change AND then settle.
|
|
270
|
+
* - 'mixed' : generator detected more than one of
|
|
271
|
+
* the above. SDK uses the most generous
|
|
272
|
+
* (data-arrival) waiting policy.
|
|
273
|
+
* - 'none' : handler is synchronous local state
|
|
274
|
+
* update only. SDK waits minimally.
|
|
275
|
+
*/
|
|
276
|
+
expectedOutcome?: 'navigation' | 'inline-feedback' | 'data-arrival' | 'mixed' | 'none';
|
|
277
|
+
/**
|
|
278
|
+
* Names of boolean state variables the generator found being set
|
|
279
|
+
* to `true` immediately before the await and `false` immediately
|
|
280
|
+
* after. Names only — not runtime values; the SDK can't observe
|
|
281
|
+
* React state directly, so this field is currently informational
|
|
282
|
+
* (surfaced in dashboards and used by future tooling). Common
|
|
283
|
+
* shapes: `['isSubmitting']`, `['isLoading', 'isFetching']`.
|
|
284
|
+
*/
|
|
285
|
+
loadingStateBindings?: string[];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Analyzes React Native source files to extract screen definitions,
|
|
290
|
+
* their components, forms, and available actions.
|
|
291
|
+
*
|
|
292
|
+
* The analyzer uses Babel AST to:
|
|
293
|
+
* 1. Extract optional registerScreen() calls (developer-provided metadata)
|
|
294
|
+
* 2. Detect exported component names
|
|
295
|
+
* 3. Find navigation patterns (navigation.navigate calls)
|
|
296
|
+
* 4. Identify form inputs (TextInput, Input components)
|
|
297
|
+
* 5. Extract button/action handlers
|
|
298
|
+
*
|
|
299
|
+
* §1 Strict mode: when `strictScreens` is enabled in config, only files
|
|
300
|
+
* containing a `registerScreen()` call or matching `screenPatterns` globs
|
|
301
|
+
* are included in the output. This avoids components/hooks/utils being
|
|
302
|
+
* listed as screens.
|
|
303
|
+
*/
|
|
304
|
+
declare class ScreenAnalyzer {
|
|
305
|
+
private config;
|
|
306
|
+
private verbose;
|
|
307
|
+
/** When true, only files with registerScreen or matching screen patterns are included */
|
|
308
|
+
private strictScreens;
|
|
309
|
+
/** Glob patterns that identify screen files in strict mode */
|
|
310
|
+
private screenPatterns;
|
|
311
|
+
/** §D: Count of screens filtered out in strict mode (available after analyze()) */
|
|
312
|
+
screensFilteredOut: number;
|
|
313
|
+
constructor(config: AnalyzerConfig, options?: {
|
|
314
|
+
strictScreens?: boolean;
|
|
315
|
+
screenPatterns?: string[];
|
|
316
|
+
});
|
|
317
|
+
/** Analyze all screens in the project */
|
|
318
|
+
analyze(): Promise<ScreenDescriptor[]>;
|
|
319
|
+
/** Analyze a single file. Returns descriptor + whether registerScreen was found. */
|
|
320
|
+
analyzeFile(filePath: string): Promise<ScreenDescriptor | null>;
|
|
321
|
+
/**
|
|
322
|
+
* §B: Detect actual presence of registerScreen() call in the AST.
|
|
323
|
+
* This is more reliable than checking for title/description which are optional.
|
|
324
|
+
*/
|
|
325
|
+
private detectRegisterScreenCall;
|
|
326
|
+
/**
|
|
327
|
+
* Extract metadata from registerScreen() call
|
|
328
|
+
*/
|
|
329
|
+
private extractRegisterScreenMetadata;
|
|
330
|
+
/**
|
|
331
|
+
* Parse the object passed to registerScreen()
|
|
332
|
+
*/
|
|
333
|
+
private parseRegisterScreenObject;
|
|
334
|
+
/**
|
|
335
|
+
* Parse actions array from registerScreen
|
|
336
|
+
*/
|
|
337
|
+
private parseActionsArray;
|
|
338
|
+
/**
|
|
339
|
+
* Parse a single action object
|
|
340
|
+
*/
|
|
341
|
+
private parseActionObject;
|
|
342
|
+
private parseAppilotsInferredObject;
|
|
343
|
+
/**
|
|
344
|
+
* Parse fields array from registerScreen
|
|
345
|
+
*/
|
|
346
|
+
private parseFieldsArray;
|
|
347
|
+
/**
|
|
348
|
+
* Parse a single field object
|
|
349
|
+
*/
|
|
350
|
+
private parseFieldObject;
|
|
351
|
+
/**
|
|
352
|
+
* Parse options array for select fields
|
|
353
|
+
*/
|
|
354
|
+
private parseOptionsArray;
|
|
355
|
+
private mergeForms;
|
|
356
|
+
private findEquivalentField;
|
|
357
|
+
private namedSubmitAction;
|
|
358
|
+
private mergeFieldMetadata;
|
|
359
|
+
private findFormWithSharedFields;
|
|
360
|
+
/**
|
|
361
|
+
* Extract the name of the default exported component
|
|
362
|
+
*/
|
|
363
|
+
private extractDefaultComponentName;
|
|
364
|
+
/**
|
|
365
|
+
* Extract navigation targets from navigation.navigate() calls
|
|
366
|
+
*/
|
|
367
|
+
private extractNavigationTargets;
|
|
368
|
+
/**
|
|
369
|
+
* Extract form information from JSX
|
|
370
|
+
*/
|
|
371
|
+
private extractForms;
|
|
372
|
+
/**
|
|
373
|
+
* Extract field metadata from a TextInput/Input element
|
|
374
|
+
*/
|
|
375
|
+
private extractFieldFromInputElement;
|
|
376
|
+
private isWeakInferredFieldName;
|
|
377
|
+
/**
|
|
378
|
+
* Extract component structure from JSX
|
|
379
|
+
*/
|
|
380
|
+
private extractComponents;
|
|
381
|
+
/**
|
|
382
|
+
* Infer component type from component name
|
|
383
|
+
*/
|
|
384
|
+
private inferComponentType;
|
|
385
|
+
/**
|
|
386
|
+
* Extract actions from button/touchable elements
|
|
387
|
+
*/
|
|
388
|
+
private extractActions;
|
|
389
|
+
private normalizeLabel;
|
|
390
|
+
private mergeActionMetadata;
|
|
391
|
+
private enrichActionsFromHandlers;
|
|
392
|
+
private collectButtonHandlersByLabel;
|
|
393
|
+
private collectHandlerInfo;
|
|
394
|
+
private analyzeHandlerFunction;
|
|
395
|
+
/**
|
|
396
|
+
* BACKLOG 2.2 — heuristic destructive detection.
|
|
397
|
+
*
|
|
398
|
+
* Combines several signals:
|
|
399
|
+
* - JSX `destructive` boolean prop (`<Pressable destructive />`)
|
|
400
|
+
* - JSX `aria-destructive` attribute
|
|
401
|
+
* - Handler name contains a generic destructive verb
|
|
402
|
+
* (delete/destroy/remove/discard/wipe/revoke/etc.). `cancel` is intentionally excluded —
|
|
403
|
+
* too many "Cancelar" buttons that just dismiss modals.
|
|
404
|
+
* - testID / id contains a destructive verb
|
|
405
|
+
*
|
|
406
|
+
* Default-allow with explicit opt-out via `@appilots-non-destructive`
|
|
407
|
+
* is more dangerous than the inverse, so we default-deny: the
|
|
408
|
+
* heuristic must hit OR the JSDoc tag must be present.
|
|
409
|
+
*/
|
|
410
|
+
private isElementDestructive;
|
|
411
|
+
/**
|
|
412
|
+
* Extract action metadata from a button element
|
|
413
|
+
*/
|
|
414
|
+
private extractActionFromButton;
|
|
415
|
+
private extractInlineOnPressMetadata;
|
|
416
|
+
private extractFirstHandlerName;
|
|
417
|
+
private mergeLocator;
|
|
418
|
+
private expressionToBinding;
|
|
419
|
+
private slugifyActionId;
|
|
420
|
+
private extractCollections;
|
|
421
|
+
private collectRenderItemFunctions;
|
|
422
|
+
private jsxExpressionIdentifier;
|
|
423
|
+
private extractKeyField;
|
|
424
|
+
private extractRowAction;
|
|
425
|
+
private extractNavigationParams;
|
|
426
|
+
private extractItemFields;
|
|
427
|
+
private inferItemType;
|
|
428
|
+
private inferIdentityFields;
|
|
429
|
+
private inferSearchField;
|
|
430
|
+
/**
|
|
431
|
+
* Extract literal values from AST nodes
|
|
432
|
+
*/
|
|
433
|
+
private extractLiteralValue;
|
|
434
|
+
/**
|
|
435
|
+
* Extract screen name from file path
|
|
436
|
+
* E.g., /src/screens/ItemListScreen.tsx -> ItemListScreen
|
|
437
|
+
*/
|
|
438
|
+
private extractScreenName;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Analyzes React Navigation configuration to build a navigation graph.
|
|
443
|
+
*/
|
|
444
|
+
declare class NavigationAnalyzer {
|
|
445
|
+
private config;
|
|
446
|
+
/** Extra glob patterns for navigation file discovery (added to defaults) */
|
|
447
|
+
private navigationInclude;
|
|
448
|
+
/** Extra glob patterns to exclude from navigation analysis */
|
|
449
|
+
private navigationExclude;
|
|
450
|
+
constructor(config: AnalyzerConfig, options?: {
|
|
451
|
+
navigationInclude?: string[];
|
|
452
|
+
navigationExclude?: string[];
|
|
453
|
+
});
|
|
454
|
+
/** Build the full navigation graph */
|
|
455
|
+
analyze(): Promise<NavigationGraph>;
|
|
456
|
+
/** Find all navigation-related files */
|
|
457
|
+
private findNavigationFiles;
|
|
458
|
+
/** Parse navigator definitions from a file */
|
|
459
|
+
private parseNavigators;
|
|
460
|
+
/** Extract screens from a navigator JSX element */
|
|
461
|
+
private extractScreensFromNavigator;
|
|
462
|
+
/** Extract string attribute value from JSX attributes */
|
|
463
|
+
private extractAttributeValue;
|
|
464
|
+
/** Parse TypeScript type exports (ParamList types) */
|
|
465
|
+
private parseParamTypes;
|
|
466
|
+
/** Extract param entries from a TypeScript type literal */
|
|
467
|
+
private extractParamListEntries;
|
|
468
|
+
/** Extract param descriptors from a TypeScript type */
|
|
469
|
+
private extractParamsFromType;
|
|
470
|
+
/** Convert TypeScript type annotation to string */
|
|
471
|
+
private typeToString;
|
|
472
|
+
/** Infer navigator type from type name */
|
|
473
|
+
private inferTypeFromName;
|
|
474
|
+
/** Attach parsed type params to navigator screens */
|
|
475
|
+
private attachParamsToNavigators;
|
|
476
|
+
/** Build the complete navigation graph */
|
|
477
|
+
private buildNavigationGraph;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Analyzes React Native components to extract their semantic meaning,
|
|
482
|
+
* props, and interaction points.
|
|
483
|
+
*/
|
|
484
|
+
declare class ComponentAnalyzer {
|
|
485
|
+
private config;
|
|
486
|
+
constructor(config: AnalyzerConfig);
|
|
487
|
+
/** Analyze components in a file */
|
|
488
|
+
analyzeFile(filePath: string): Promise<ComponentDescriptor[]>;
|
|
489
|
+
private extractComponentFromJSXElement;
|
|
490
|
+
private getElementName;
|
|
491
|
+
private determineComponentType;
|
|
492
|
+
private extractProps;
|
|
493
|
+
private extractAccessibilityProps;
|
|
494
|
+
private extractChildren;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Analyzes form structures in React Native screens, extracting
|
|
499
|
+
* field definitions, validation rules, and submit actions.
|
|
500
|
+
*/
|
|
501
|
+
declare class FormAnalyzer {
|
|
502
|
+
private config;
|
|
503
|
+
private stateVariables;
|
|
504
|
+
private inputElements;
|
|
505
|
+
private submitButtons;
|
|
506
|
+
constructor(config: AnalyzerConfig);
|
|
507
|
+
/** Analyze forms in a file */
|
|
508
|
+
analyzeFile(filePath: string): Promise<FormDescriptor[]>;
|
|
509
|
+
private extractStateVariables;
|
|
510
|
+
private extractFormElements;
|
|
511
|
+
private extractInputInfo;
|
|
512
|
+
private extractButtonInfo;
|
|
513
|
+
private extractAttributeValue;
|
|
514
|
+
private getElementName;
|
|
515
|
+
private extractValidationRules;
|
|
516
|
+
private extractRuleFromCondition;
|
|
517
|
+
private buildForms;
|
|
518
|
+
private inferFieldType;
|
|
519
|
+
private isFieldRequired;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
interface MCPGeneratorOptions {
|
|
523
|
+
/** Output format (only 'json' supported currently) */
|
|
524
|
+
format: 'json';
|
|
525
|
+
/** Output directory */
|
|
526
|
+
outputDir: string;
|
|
527
|
+
/** Whether to include source file paths */
|
|
528
|
+
includeSourcePaths?: boolean;
|
|
529
|
+
/** Whether to generate a single file or per-screen files */
|
|
530
|
+
splitOutput?: boolean;
|
|
531
|
+
/** MCP document version */
|
|
532
|
+
version?: string;
|
|
533
|
+
}
|
|
534
|
+
interface MCPDocument {
|
|
535
|
+
version: string;
|
|
536
|
+
generatedAt: string;
|
|
537
|
+
projectName: string;
|
|
538
|
+
screens: ScreenDescriptor[];
|
|
539
|
+
navigation: NavigationGraph;
|
|
540
|
+
metadata: MCPMetadata;
|
|
541
|
+
}
|
|
542
|
+
interface MCPMetadata {
|
|
543
|
+
generatorVersion: string;
|
|
544
|
+
/**
|
|
545
|
+
* Host app version read from the project's package.json. Lets the
|
|
546
|
+
* backend match the MCP document to the app build that generated it
|
|
547
|
+
* (B.4 — OTA fleets run several app versions at once).
|
|
548
|
+
*/
|
|
549
|
+
appVersion?: string;
|
|
550
|
+
totalScreens: number;
|
|
551
|
+
totalForms: number;
|
|
552
|
+
totalActions: number;
|
|
553
|
+
/** Number of source files matched by glob (before screen filtering) */
|
|
554
|
+
analyzedFiles: number;
|
|
555
|
+
/** Number of files excluded by strict screen filtering (only set when strictScreens is on) */
|
|
556
|
+
screensFilteredOut?: number;
|
|
557
|
+
}
|
|
558
|
+
interface MCPOutput {
|
|
559
|
+
document: MCPDocument;
|
|
560
|
+
filePath: string;
|
|
561
|
+
format: 'json';
|
|
562
|
+
checksum: string;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Main generator that orchestrates analyzers and produces MCP documents.
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```ts
|
|
570
|
+
* const generator = new MCPGenerator({
|
|
571
|
+
* rootDir: './my-rn-app',
|
|
572
|
+
* outputDir: '.appilots',
|
|
573
|
+
* format: 'json',
|
|
574
|
+
* });
|
|
575
|
+
*
|
|
576
|
+
* const output = await generator.generate();
|
|
577
|
+
* console.log(`Generated MCP at: ${output.filePath}`);
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
interface MCPGeneratorConfig {
|
|
581
|
+
rootDir: string;
|
|
582
|
+
outputDir?: string;
|
|
583
|
+
format?: 'json';
|
|
584
|
+
version?: string;
|
|
585
|
+
includeSourcePaths?: boolean;
|
|
586
|
+
splitOutput?: boolean;
|
|
587
|
+
/** §1: Only include files with registerScreen() or matching screen patterns */
|
|
588
|
+
strictScreens?: boolean;
|
|
589
|
+
/** §1: Custom glob patterns for screen file detection */
|
|
590
|
+
screenPatterns?: string[];
|
|
591
|
+
/** §5: Additional navigation file patterns (added to defaults) */
|
|
592
|
+
navigationInclude?: string[];
|
|
593
|
+
/** §5: Exclude patterns for navigation analysis */
|
|
594
|
+
navigationExclude?: string[];
|
|
595
|
+
/** Source include patterns */
|
|
596
|
+
include?: string[];
|
|
597
|
+
/** Source exclude patterns */
|
|
598
|
+
exclude?: string[];
|
|
599
|
+
}
|
|
600
|
+
declare class MCPGenerator {
|
|
601
|
+
private analyzerConfig;
|
|
602
|
+
private options;
|
|
603
|
+
private generatorConfig;
|
|
604
|
+
constructor(config: MCPGeneratorConfig);
|
|
605
|
+
/** Generate MCP documents from the project */
|
|
606
|
+
generate(): Promise<MCPOutput>;
|
|
607
|
+
/**
|
|
608
|
+
* Read the previously stored checksum from disk.
|
|
609
|
+
* Returns null if no checksum file exists (first run).
|
|
610
|
+
*
|
|
611
|
+
* §4: This replaces the incomplete `hasChanged` static method.
|
|
612
|
+
* To detect actual changes, compare this value with `output.checksum`
|
|
613
|
+
* after calling `generate()`.
|
|
614
|
+
*/
|
|
615
|
+
static readPreviousChecksum(outputDir: string): Promise<string | null>;
|
|
616
|
+
/**
|
|
617
|
+
* Calculate SHA-256 checksum of content
|
|
618
|
+
*/
|
|
619
|
+
private calculateChecksum;
|
|
620
|
+
private mergeForm;
|
|
621
|
+
private findEquivalentField;
|
|
622
|
+
private mergeField;
|
|
623
|
+
private namedSubmitAction;
|
|
624
|
+
private isWeakInferredFieldName;
|
|
625
|
+
private findFormWithSharedFields;
|
|
626
|
+
/**
|
|
627
|
+
* Get project name and version from package.json in rootDir
|
|
628
|
+
*/
|
|
629
|
+
private getProjectInfo;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Appilots configuration interface
|
|
634
|
+
*/
|
|
635
|
+
interface AppilotsConfig {
|
|
636
|
+
/** API key for authenticating with Appilots backend */
|
|
637
|
+
apiKey: string;
|
|
638
|
+
/**
|
|
639
|
+
* Project ID on Appilots dashboard. Optional at load time — the API key
|
|
640
|
+
* already identifies the project server-side (env-only CI workflows run
|
|
641
|
+
* without a .appilotsrc), so commands must not assume it is present.
|
|
642
|
+
*/
|
|
643
|
+
projectId?: string;
|
|
644
|
+
/** Server URL for Appilots API */
|
|
645
|
+
serverUrl: string;
|
|
646
|
+
/** Output directory for generated files */
|
|
647
|
+
outputDir?: string;
|
|
648
|
+
/** Glob patterns to include in analysis */
|
|
649
|
+
include?: string[];
|
|
650
|
+
/** Glob patterns to exclude from analysis */
|
|
651
|
+
exclude?: string[];
|
|
652
|
+
/** Automatically activate generated MCP on sync */
|
|
653
|
+
autoActivate?: boolean;
|
|
654
|
+
/**
|
|
655
|
+
* Strict screen filtering (§1).
|
|
656
|
+
* When true, only files with registerScreen() calls or matching
|
|
657
|
+
* screenPatterns are included as screens in the MCP document.
|
|
658
|
+
* Default: true (exclude components/hooks/config files from the MCP screen map)
|
|
659
|
+
*/
|
|
660
|
+
strictScreens?: boolean;
|
|
661
|
+
/**
|
|
662
|
+
* Glob patterns that identify screen files (used with strictScreens).
|
|
663
|
+
* Default: ['**\/*Screen.{ts,tsx}', '**\/screens/**\/*.{ts,tsx}']
|
|
664
|
+
*/
|
|
665
|
+
screenPatterns?: string[];
|
|
666
|
+
/**
|
|
667
|
+
* Additional glob patterns for navigation file discovery (§5).
|
|
668
|
+
* These are ADDED to the default patterns, not replacing them.
|
|
669
|
+
*/
|
|
670
|
+
navigationInclude?: string[];
|
|
671
|
+
/**
|
|
672
|
+
* Glob patterns to exclude from navigation analysis (§5).
|
|
673
|
+
*/
|
|
674
|
+
navigationExclude?: string[];
|
|
675
|
+
/** `appilots eval` settings — see docs: Testing your agent between releases. */
|
|
676
|
+
eval?: EvalConfig;
|
|
677
|
+
}
|
|
678
|
+
interface EvalConfig {
|
|
679
|
+
/** Directory of `*.json` scenario files. Default: `<outputDir>/scenarios`. */
|
|
680
|
+
scenariosDir?: string;
|
|
681
|
+
/** Committed baseline file. Default: `<outputDir>/eval-baseline.json`. */
|
|
682
|
+
baselinePath?: string;
|
|
683
|
+
/** Minimum fraction of scenarios that must pass (0-1). Default: 0.95. */
|
|
684
|
+
minPassRate?: number;
|
|
685
|
+
/** Max fractional token increase vs baseline before it's flagged as a
|
|
686
|
+
* regression on an otherwise-passing scenario. Default: 0.25 (+25%). */
|
|
687
|
+
maxTokenRegression?: number;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Validation result type
|
|
691
|
+
*/
|
|
692
|
+
interface ValidationResult {
|
|
693
|
+
valid: boolean;
|
|
694
|
+
errors: string[];
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Environment overrides recognized by the CLI. Precedence when loading:
|
|
698
|
+
* command-line flags (applied by each command) > env vars > .appilotsrc.
|
|
699
|
+
*/
|
|
700
|
+
interface EnvOverrides {
|
|
701
|
+
apiKey?: string;
|
|
702
|
+
projectId?: string;
|
|
703
|
+
serverUrl?: string;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Reads APPILOTS_* environment variables, treating blank values as unset.
|
|
707
|
+
*/
|
|
708
|
+
declare function getEnvOverrides(env?: NodeJS.ProcessEnv): EnvOverrides;
|
|
709
|
+
/**
|
|
710
|
+
* Loads configuration from the nearest .appilotsrc merged with APPILOTS_*
|
|
711
|
+
* environment variables (env wins). Works without a .appilotsrc when
|
|
712
|
+
* APPILOTS_API_KEY is set, so CI can run `appilots sync` with env vars only.
|
|
713
|
+
*
|
|
714
|
+
* @returns AppilotsConfig if a file or APPILOTS_API_KEY exists, null otherwise
|
|
715
|
+
* @throws when the file is unparseable or the merged config fails validation
|
|
716
|
+
*/
|
|
717
|
+
declare function loadConfig(): AppilotsConfig | null;
|
|
718
|
+
/**
|
|
719
|
+
* Saves configuration to .appilotsrc in the given directory
|
|
720
|
+
*
|
|
721
|
+
* @param dir Directory to write .appilotsrc to
|
|
722
|
+
* @param config Partial config to merge with defaults
|
|
723
|
+
*/
|
|
724
|
+
declare function saveConfig(dir: string, config: Partial<AppilotsConfig>): void;
|
|
725
|
+
/**
|
|
726
|
+
* Finds and returns the path to the nearest .appilotsrc file by walking up
|
|
727
|
+
* the directory tree from cwd. Follows the same logic as .npmrc lookup.
|
|
728
|
+
*
|
|
729
|
+
* @returns Path to .appilotsrc if found, null otherwise
|
|
730
|
+
*/
|
|
731
|
+
declare function getConfigPath(): string | null;
|
|
732
|
+
/**
|
|
733
|
+
* Validates an Appilots configuration object for required fields and types
|
|
734
|
+
*
|
|
735
|
+
* @param config Configuration object to validate
|
|
736
|
+
* @returns ValidationResult with valid flag and array of error messages
|
|
737
|
+
*/
|
|
738
|
+
declare function validateConfig(config: any): ValidationResult;
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Metadata lint (OKR-005 KR4) — every mutating action registered in the
|
|
742
|
+
* MCP document should declare `effect` and `riskLevel` so the runtime can
|
|
743
|
+
* enforce safety policy without parsing labels. The generator extracts
|
|
744
|
+
* these fields when declared in registerScreen(); this lint surfaces the
|
|
745
|
+
* ones that were not, instead of letting them pass silently.
|
|
746
|
+
*/
|
|
747
|
+
interface MetadataLintWarning {
|
|
748
|
+
screen: string;
|
|
749
|
+
actionId: string;
|
|
750
|
+
actionType: ActionDescriptor['type'];
|
|
751
|
+
missing: ('effect' | 'riskLevel')[];
|
|
752
|
+
reason: string;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Scans every screen action and returns one warning per mutating action
|
|
756
|
+
* that is missing `effect` and/or `riskLevel`.
|
|
757
|
+
*/
|
|
758
|
+
declare function lintActionMetadata(document: MCPDocument): MetadataLintWarning[];
|
|
759
|
+
/**
|
|
760
|
+
* Formats lint warnings for terminal output: one line per action plus an
|
|
761
|
+
* aggregate line, e.g.
|
|
762
|
+
* VehicleDetails.deleteVehicle (submit): missing effect, riskLevel
|
|
763
|
+
*/
|
|
764
|
+
declare function formatMetadataWarnings(warnings: MetadataLintWarning[]): string[];
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* HTTP client for communicating with the Appilots API backend
|
|
768
|
+
*/
|
|
769
|
+
/**
|
|
770
|
+
* Result from a sync operation
|
|
771
|
+
*/
|
|
772
|
+
interface SyncResult {
|
|
773
|
+
success: boolean;
|
|
774
|
+
unchanged: boolean;
|
|
775
|
+
id?: string;
|
|
776
|
+
checksum?: string;
|
|
777
|
+
screensCount?: number;
|
|
778
|
+
formsCount?: number;
|
|
779
|
+
actionsCount?: number;
|
|
780
|
+
error?: string;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* One eval scenario as sent to `POST /cli/eval/run`. Shape mirrors the
|
|
784
|
+
* server's `evalScenarioRequestSchema` — kept as a plain interface here
|
|
785
|
+
* (no @appilots/shared import) since this package publishes standalone.
|
|
786
|
+
*/
|
|
787
|
+
interface EvalScenarioRequest {
|
|
788
|
+
id: string;
|
|
789
|
+
screen?: string;
|
|
790
|
+
observation?: unknown;
|
|
791
|
+
prompt: string;
|
|
792
|
+
modelOverride?: string;
|
|
793
|
+
}
|
|
794
|
+
interface EvalScenarioResult {
|
|
795
|
+
id: string;
|
|
796
|
+
content: string;
|
|
797
|
+
actions: Array<{
|
|
798
|
+
id: string;
|
|
799
|
+
type: string;
|
|
800
|
+
payload: Record<string, unknown>;
|
|
801
|
+
}>;
|
|
802
|
+
totalTokens: number;
|
|
803
|
+
totalCostUsd: number;
|
|
804
|
+
totalLatencyMs: number;
|
|
805
|
+
modelUsed: string;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Result from an eval run
|
|
809
|
+
*/
|
|
810
|
+
interface EvalRunResult {
|
|
811
|
+
results?: EvalScenarioResult[];
|
|
812
|
+
error?: string;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Result from a status query
|
|
816
|
+
*/
|
|
817
|
+
interface StatusResult {
|
|
818
|
+
project?: {
|
|
819
|
+
id: string;
|
|
820
|
+
name: string;
|
|
821
|
+
};
|
|
822
|
+
activeMcp?: {
|
|
823
|
+
id: string;
|
|
824
|
+
version: string;
|
|
825
|
+
screensCount: number;
|
|
826
|
+
formsCount: number;
|
|
827
|
+
actionsCount: number;
|
|
828
|
+
};
|
|
829
|
+
error?: string;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Appilots API client configuration
|
|
833
|
+
*/
|
|
834
|
+
interface APIClientConfig {
|
|
835
|
+
serverUrl: string;
|
|
836
|
+
apiKey: string;
|
|
837
|
+
/** Per-request timeout in ms (default 30s) */
|
|
838
|
+
timeoutMs?: number;
|
|
839
|
+
/** Retries on network errors / 5xx (default 2) */
|
|
840
|
+
maxRetries?: number;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* HTTP client for communicating with Appilots API
|
|
844
|
+
*/
|
|
845
|
+
declare class AppilotsAPIClient {
|
|
846
|
+
private serverUrl;
|
|
847
|
+
private apiKey;
|
|
848
|
+
private timeoutMs;
|
|
849
|
+
private maxRetries;
|
|
850
|
+
constructor(config: APIClientConfig);
|
|
851
|
+
/**
|
|
852
|
+
* fetch with a hard timeout and exponential-backoff retries. Retries
|
|
853
|
+
* only on network failures and 5xx responses — 4xx are the caller's
|
|
854
|
+
* problem and retrying them would just spam the server.
|
|
855
|
+
*/
|
|
856
|
+
private request;
|
|
857
|
+
/**
|
|
858
|
+
* Syncs content with the Appilots backend
|
|
859
|
+
*
|
|
860
|
+
* @param content The MCP content object to sync
|
|
861
|
+
* @param version Version string for the content
|
|
862
|
+
* @param appVersion Host app version (package.json) — sent as
|
|
863
|
+
* X-App-Version so the backend can associate the MCP
|
|
864
|
+
* document with the app build that produced it
|
|
865
|
+
* @returns SyncResult with success status and metadata
|
|
866
|
+
*/
|
|
867
|
+
sync(content: object, version: string, appVersion?: string): Promise<SyncResult>;
|
|
868
|
+
/**
|
|
869
|
+
* Gets the status of the Appilots project and active MCP
|
|
870
|
+
*
|
|
871
|
+
* @returns StatusResult with project and active MCP information
|
|
872
|
+
*/
|
|
873
|
+
status(): Promise<StatusResult>;
|
|
874
|
+
/**
|
|
875
|
+
* Runs a batch of eval scenarios against the project's MCP document in
|
|
876
|
+
* decision/dry-run mode — no side effects, same relay path as the
|
|
877
|
+
* dashboard's sandbox, authenticated with this client's API key.
|
|
878
|
+
*
|
|
879
|
+
* @param scenarios Scenarios to run, capped server-side at
|
|
880
|
+
* EVAL_MAX_SCENARIOS_PER_RUN (currently 20) as a cost guard.
|
|
881
|
+
*/
|
|
882
|
+
evalRun(scenarios: EvalScenarioRequest[]): Promise<EvalRunResult>;
|
|
883
|
+
/**
|
|
884
|
+
* Checks if the Appilots API server is healthy
|
|
885
|
+
*
|
|
886
|
+
* @returns true if server is healthy, false otherwise
|
|
887
|
+
*/
|
|
888
|
+
health(): Promise<boolean>;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export { type ActionDescriptor, type AnalyzerConfig, AppilotsAPIClient, type AppilotsConfig, ComponentAnalyzer, type ComponentDescriptor, type EnvOverrides, type FlowDescriptor, type FlowStepDescriptor, FormAnalyzer, type FormDescriptor, type FormFieldDescriptor, type LocatorDescriptor, type MCPDocument, MCPGenerator, type MCPGeneratorConfig, type MCPGeneratorOptions, type MCPOutput, type MetadataLintWarning, NavigationAnalyzer, type NavigationGraph, type NavigationNode, type NavigatorDescriptor, type ParamDescriptor, type ScreenAgentHints, ScreenAnalyzer, type ScreenDescriptor, type SignalDescriptor, type StatusResult, type SyncResult, type TargetDescriptor, type WaitPolicyDescriptor, formatMetadataWarnings, getConfigPath, getEnvOverrides, lintActionMetadata, loadConfig, saveConfig, validateConfig };
|