@appilots/cli 0.3.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -425,6 +425,14 @@ declare class ScreenAnalyzer {
425
425
  private extractNavigationParams;
426
426
  private extractItemFields;
427
427
  private inferItemType;
428
+ /**
429
+ * Identity comes from how a field is NAMED, not from what the app
430
+ * sells: `id`/`uuid`/`key`/`slug` are conventions any codebase uses,
431
+ * `name`/`title`/`email` are how any row introduces itself. `plate`
432
+ * used to sit in this list and read like one of them — but it is the
433
+ * example app's schema, and no other tenant ever got its equivalent
434
+ * (`mrn`, `trackingNumber`, `sku`) added here.
435
+ */
428
436
  private inferIdentityFields;
429
437
  private inferSearchField;
430
438
  /**
@@ -457,6 +465,26 @@ declare class NavigationAnalyzer {
457
465
  private findNavigationFiles;
458
466
  /** Parse navigator definitions from a file */
459
467
  private parseNavigators;
468
+ /**
469
+ * Is this JSX element `<navigatorVarName.MEMBER …>`?
470
+ */
471
+ private isNavigatorMember;
472
+ /**
473
+ * Flatten a navigator's JSX children into the `<X.Screen>` elements they
474
+ * contain, unwrapping every container a real app puts in between.
475
+ *
476
+ * The old version compared `t.isJSXElement(child)` against the direct
477
+ * children only. A ternary is a `JSXExpressionContainer`, so an
478
+ * auth-gated root — the modal shape of a commercial app, and the shape
479
+ * of this repo's own `apps/example-app` — contributed ZERO screens to
480
+ * the graph while `appilots sync` reported success (#396).
481
+ *
482
+ * Both branches of a conditional are collected on purpose. The graph is
483
+ * a design-time map of what routes EXIST, not a prediction of which one
484
+ * a given session will render; the agent needs the destination name to
485
+ * be there whether or not the user happens to be logged in right now.
486
+ */
487
+ private collectScreenElements;
460
488
  /** Extract screens from a navigator JSX element */
461
489
  private extractScreensFromNavigator;
462
490
  /** Extract string attribute value from JSX attributes */
@@ -604,6 +632,189 @@ declare class GenericPlatformAnalyzer implements PlatformAnalyzer {
604
632
  analyze(_config: AnalyzerConfig, _options: PlatformAnalyzerOptions): Promise<PlatformAnalyzerResult>;
605
633
  }
606
634
 
635
+ /**
636
+ * `PlatformAnalyzer` for React web apps (`.appilotsrc` `platform:
637
+ * "web"`) — source-level inference instead of the manifest-only path
638
+ * `GenericPlatformAnalyzer` provides. Orchestrates:
639
+ *
640
+ * - `WebScreenAnalyzer` — screens, forms, actions, collections from
641
+ * DOM-vocabulary JSX (`onClick`, `data-testid`, `type=`, ...)
642
+ * - `WebNavigationAnalyzer` — a navigation graph from React Router
643
+ * configuration (`createBrowserRouter`, `<Route path element>`)
644
+ *
645
+ * The screen analyzer records navigation targets as ROUTE PATHS
646
+ * (`/vehicles/new`); this orchestrator owns the route table and
647
+ * resolves them to screen names, leaving unresolved paths as-is (still
648
+ * useful to the agent, and valid against the wire schema).
649
+ *
650
+ * Strict-screen filtering (on by default, like RN): a file counts as a
651
+ * screen when it calls `registerScreen()`, matches a screen glob
652
+ * (web defaults: `pages/`, `routes/`, `views/`, `app/`, `*Page`/
653
+ * `*Screen`/`*View`), or its component is rendered by a detected route
654
+ * — that last rule is what "the detected route patterns" adds over
655
+ * static globs.
656
+ *
657
+ * A declared `appilots.manifest.json` still merges ON TOP of this
658
+ * analyzer's output in `MCPGenerator` (manifest wins on a screen-name
659
+ * conflict) — same precedence as every other platform.
660
+ */
661
+ declare class ReactWebPlatformAnalyzer implements PlatformAnalyzer {
662
+ readonly platform = "web";
663
+ analyze(config: AnalyzerConfig, options: PlatformAnalyzerOptions): Promise<PlatformAnalyzerResult>;
664
+ private isScreen;
665
+ /** Replace route-path references with screen names where the route table resolves them. */
666
+ private resolveRoutePaths;
667
+ }
668
+
669
+ /**
670
+ * One analyzed source file, before strict-screen filtering. The
671
+ * orchestrator (`ReactWebPlatformAnalyzer`) decides inclusion — it also
672
+ * knows the route table, so a file can qualify as a screen by being a
673
+ * route component even when it matches no glob pattern.
674
+ */
675
+ interface WebScreenCandidate {
676
+ descriptor: ScreenDescriptor;
677
+ hasRegisterScreen: boolean;
678
+ matchesScreenPattern: boolean;
679
+ }
680
+ interface WebScreenAnalysis {
681
+ candidates: WebScreenCandidate[];
682
+ /** Files matched by the include globs (before screen filtering). */
683
+ analyzedFiles: number;
684
+ }
685
+ /**
686
+ * Default glob patterns that identify screen files in a web project —
687
+ * the RN defaults (`*Screen.tsx`, `screens/**`) don't match how web
688
+ * apps are laid out, so the web dialect looks at the conventional
689
+ * page/route/view directories instead.
690
+ */
691
+ declare const DEFAULT_WEB_SCREEN_PATTERNS: string[];
692
+ /**
693
+ * Web (DOM/JSX) sibling of `ScreenAnalyzer`. Same output contract
694
+ * (`ScreenDescriptor`), web-native vocabulary:
695
+ *
696
+ * - actions come from `onClick`/`onSubmit` (not `onPress`)
697
+ * - locators come from `data-testid`, `id`, `name`, `aria-label`, and
698
+ * `<label htmlFor>` association (plus `appilotsId`, shared with RN)
699
+ * - field types come from `type=` / `inputMode=` (not `keyboardType`)
700
+ * - navigation targets come from React Router (`useNavigate`'s
701
+ * `navigate('/x')`, `<Link to>`, `<Navigate to>`, internal `href`s);
702
+ * they are recorded as ROUTE PATHS here and resolved to screen names
703
+ * by the orchestrator, which owns the route table
704
+ */
705
+ declare class WebScreenAnalyzer {
706
+ private config;
707
+ private verbose;
708
+ private screenPatterns;
709
+ constructor(config: AnalyzerConfig, options?: {
710
+ screenPatterns?: string[];
711
+ });
712
+ analyze(): Promise<WebScreenAnalysis>;
713
+ analyzeFile(filePath: string): Promise<WebScreenCandidate | null>;
714
+ private detectRegisterScreenCall;
715
+ private extractRegisterScreenMetadata;
716
+ /** Default-exported component name, else the first exported capitalized function. */
717
+ private extractComponentName;
718
+ private extractComponents;
719
+ private collectHtmlForLabels;
720
+ private extractForms;
721
+ private isSubmitElement;
722
+ private extractField;
723
+ private inferInputType;
724
+ private extractSelectOptions;
725
+ private mergeForms;
726
+ private extractActions;
727
+ private extractActionFromElement;
728
+ private mergeActionMetadata;
729
+ private enrichActionsFromHandlers;
730
+ /**
731
+ * Handler behavior via the shared, platform-neutral analyzer
732
+ * (async/await, `.then`, state setters, toasts, destructive verbs)
733
+ * plus the web-only signals: React Router navigation targets and
734
+ * `window.confirm(...)` as the native confirmation dialog.
735
+ */
736
+ private collectWebHandlerBehaviors;
737
+ private handlerNameFromAttr;
738
+ private extractNavigationTargets;
739
+ private extractCollections;
740
+ }
741
+
742
+ /**
743
+ * A single resolved React Router route: its full URL path and the screen
744
+ * (component) it renders. This is the web analog of a React Navigation
745
+ * `<Stack.Screen name=...>` entry.
746
+ */
747
+ interface WebRoute {
748
+ /** Full path with parent segments joined, e.g. `/vehicles/:id`. */
749
+ path: string;
750
+ /** Screen name — the route component's name, or derived from the path. */
751
+ screenName: string;
752
+ /** Path params extracted from `:segment` placeholders. */
753
+ params?: ParamDescriptor[];
754
+ /** True for `<Route index>` / `{ index: true }` routes. */
755
+ index?: boolean;
756
+ /**
757
+ * True when this route has child routes — it renders a layout wrapper
758
+ * around them, not a page of its own. A layout route loses to its own
759
+ * `index` child when both resolve to the same path, since the index
760
+ * child is what the user actually lands on.
761
+ */
762
+ layout?: boolean;
763
+ }
764
+ interface WebNavigationResult {
765
+ graph: NavigationGraph;
766
+ routes: WebRoute[];
767
+ }
768
+ /**
769
+ * Analyzes React Router route configuration to build a navigation graph
770
+ * — the sibling of `NavigationAnalyzer` (which is 100% react-navigation
771
+ * and never runs for `platform: 'web'`). Understands both declaration
772
+ * styles:
773
+ *
774
+ * - JSX: `<Routes><Route path="/x" element={<X/>}/></Routes>` (nested
775
+ * `<Route>` children join their parent's path)
776
+ * - Object config: `createBrowserRouter([{ path, element, children }])`
777
+ * (also `createHashRouter`, `createMemoryRouter`, `useRoutes`)
778
+ */
779
+ declare class WebNavigationAnalyzer {
780
+ private config;
781
+ private navigationInclude;
782
+ private navigationExclude;
783
+ constructor(config: AnalyzerConfig, options?: {
784
+ navigationInclude?: string[];
785
+ navigationExclude?: string[];
786
+ });
787
+ analyze(): Promise<WebNavigationResult>;
788
+ /** Files likely to contain route configuration. */
789
+ private findRouteFiles;
790
+ private extractJsxRoutes;
791
+ /** `element={<VehicleList/>}` or `Component={VehicleList}`. */
792
+ private componentNameFromElementAttr;
793
+ private extractObjectRoutes;
794
+ private visitRouteObjects;
795
+ private joinPaths;
796
+ private normalizePath;
797
+ private buildRoute;
798
+ private paramsFromPath;
799
+ /**
800
+ * One entry per path. When several declarations resolve to the same
801
+ * path, keep the one that best describes what the user lands on: a
802
+ * page beats a layout wrapper (an `index` child and its parent layout
803
+ * share a path), and a resolved component name beats a name derived
804
+ * from the path.
805
+ */
806
+ private dedupeRoutes;
807
+ private buildGraph;
808
+ }
809
+ /** `/vehicles/:id/edit` → `VehiclesIdEdit`; `/` → `Home`. */
810
+ declare function screenNameFromPath(routePath: string): string;
811
+ /**
812
+ * Match a navigated-to concrete path (e.g. `/vehicles/123`) against the
813
+ * route table, honoring `:param` segments. Returns the matched screen
814
+ * name, or undefined.
815
+ */
816
+ declare function resolvePathToScreen(routes: WebRoute[], target: string): string | undefined;
817
+
607
818
  /**
608
819
  * Default manifest filename, resolved relative to the project's
609
820
  * `rootDir` unless `.appilotsrc`'s `manifestPath` overrides it.
@@ -644,9 +855,8 @@ interface LoadManifestResult {
644
855
  declare function loadManifest(rootDir: string, manifestPath?: string): Promise<LoadManifestResult>;
645
856
  /**
646
857
  * Merge a declared manifest's screens on top of analyzer-derived screens.
647
- * The manifest wins on a screen-name conflict (it's the developer's
648
- * explicit, authoritative statement); screens only one side knows about
649
- * pass through unchanged.
858
+ * A screen only one side knows about passes through unchanged; a screen
859
+ * both sides know is merged key by key (see `mergeScreen`).
650
860
  */
651
861
  declare function mergeManifestScreens(analyzerScreens: ScreenDescriptor[], manifestScreens: ScreenDescriptor[] | undefined): ScreenDescriptor[];
652
862
  /**
@@ -735,10 +945,11 @@ interface MCPGeneratorConfig {
735
945
  exclude?: string[];
736
946
  /**
737
947
  * Which `PlatformAnalyzer` to run against the source tree. Defaults to
738
- * `'react-native'` (the existing Babel/JSX analyzer pipeline). Any
739
- * other value runs `GenericPlatformAnalyzer` — a safe no-op that does
740
- * no filesystem/AST work — so a platform with no source analyzer yet
741
- * still produces a valid document from a declared manifest alone.
948
+ * `'react-native'` (the existing Babel/JSX analyzer pipeline). `'web'`
949
+ * runs `ReactWebPlatformAnalyzer` (DOM-vocabulary JSX + React Router).
950
+ * Any other value runs `GenericPlatformAnalyzer` — a safe no-op that
951
+ * does no filesystem/AST work — so a platform with no source analyzer
952
+ * yet still produces a valid document from a declared manifest alone.
742
953
  */
743
954
  platform?: string;
744
955
  /**
@@ -764,8 +975,10 @@ declare class MCPGenerator {
764
975
  /**
765
976
  * Select the `PlatformAnalyzer` implementation for a `.appilotsrc`
766
977
  * `platform` value. `'react-native'` (or unset — the existing default)
767
- * gets the real Babel/JSX pipeline; anything else gets the no-op
768
- * generic analyzer, relying entirely on a declared manifest.
978
+ * gets the RN Babel/JSX pipeline; `'web'` gets the React web (DOM +
979
+ * React Router) pipeline; anything else gets the no-op generic
980
+ * analyzer, relying entirely on a declared manifest. The manifest
981
+ * still merges on top of every analyzer's output either way.
769
982
  */
770
983
  static createPlatformAnalyzer(platform?: string): PlatformAnalyzer;
771
984
  /** Generate MCP documents from the project */
@@ -836,11 +1049,13 @@ interface AppilotsConfig {
836
1049
  * Target platform for source analysis (multi-platform prep). Selects
837
1050
  * which `PlatformAnalyzer` the generator runs and its default globs.
838
1051
  * `'react-native'` (the default when unset) runs the existing
839
- * Babel/JSX analyzer pipeline. Any other value (`'web'`, `'android'`,
840
- * `'ios'`, a custom platform id, ...) runs a no-op analyzer that does
841
- * no source parsing — screens for that platform come entirely from a
842
- * declared manifest (see `manifestPath`). Forward-compatible: unknown
843
- * values degrade gracefully instead of erroring.
1052
+ * Babel/JSX analyzer pipeline. `'web'` runs the React web analyzer
1053
+ * (DOM-vocabulary JSX + React Router route discovery). Any other
1054
+ * value (`'android'`, `'ios'`, a custom platform id, ...) runs a
1055
+ * no-op analyzer that does no source parsing — screens for that
1056
+ * platform come entirely from a declared manifest (see
1057
+ * `manifestPath`). Forward-compatible: unknown values degrade
1058
+ * gracefully instead of erroring.
844
1059
  */
845
1060
  platform?: string;
846
1061
  /**
@@ -872,6 +1087,12 @@ interface EvalConfig {
872
1087
  interface ValidationResult {
873
1088
  valid: boolean;
874
1089
  errors: string[];
1090
+ /**
1091
+ * Non-fatal problems — today, keys the CLI does not understand. A typo
1092
+ * here is silently ignored at load time and only surfaces much later as
1093
+ * a confusing failure, so it is worth saying out loud.
1094
+ */
1095
+ warnings: string[];
875
1096
  }
876
1097
  /**
877
1098
  * Environment overrides recognized by the CLI. Precedence when loading:
@@ -891,10 +1112,12 @@ declare function getEnvOverrides(env?: NodeJS.ProcessEnv): EnvOverrides;
891
1112
  * environment variables (env wins). Works without a .appilotsrc when
892
1113
  * APPILOTS_API_KEY is set, so CI can run `appilots sync` with env vars only.
893
1114
  *
1115
+ * @param onWarn Called once per non-fatal problem (unrecognized keys).
1116
+ * Commands pass the logger's `warn`; omitting it keeps loading silent.
894
1117
  * @returns AppilotsConfig if a file or APPILOTS_API_KEY exists, null otherwise
895
1118
  * @throws when the file is unparseable or the merged config fails validation
896
1119
  */
897
- declare function loadConfig(): AppilotsConfig | null;
1120
+ declare function loadConfig(onWarn?: (message: string) => void): AppilotsConfig | null;
898
1121
  /**
899
1122
  * Saves configuration to .appilotsrc in the given directory
900
1123
  *
@@ -952,12 +1175,26 @@ declare function formatMetadataWarnings(warnings: MetadataLintWarning[]): string
952
1175
  interface SyncResult {
953
1176
  success: boolean;
954
1177
  unchanged: boolean;
1178
+ /**
1179
+ * Set by the server when an unchanged upload had to re-activate a
1180
+ * document that was stored but not active — i.e. something else (an
1181
+ * older version, a hand-activated document) was in front of the agent.
1182
+ */
1183
+ activated?: boolean;
955
1184
  id?: string;
956
1185
  checksum?: string;
957
1186
  screensCount?: number;
958
1187
  formsCount?: number;
959
1188
  actionsCount?: number;
960
1189
  error?: string;
1190
+ /**
1191
+ * The server's `error.code`, kept structured alongside the rendered
1192
+ * `error` text so callers can branch on the failure instead of
1193
+ * matching substrings. Absent when the failure produced no envelope.
1194
+ */
1195
+ errorCode?: string;
1196
+ /** HTTP status of the failed response; 0 for network/timeout failures. */
1197
+ errorStatus?: number;
961
1198
  }
962
1199
  /**
963
1200
  * One eval scenario as sent to `POST /cli/eval/run`. Shape mirrors the
@@ -1019,11 +1256,21 @@ interface APIClientConfig {
1019
1256
  /** Retries on network errors / 5xx (default 2) */
1020
1257
  maxRetries?: number;
1021
1258
  }
1022
- /**
1023
- * HTTP client for communicating with Appilots API
1024
- */
1025
1259
  declare class AppilotsAPIClient {
1026
- private serverUrl;
1260
+ /**
1261
+ * `serverUrl` with the API's mount prefix resolved — every path below
1262
+ * is relative to THIS, not to the configured origin.
1263
+ *
1264
+ * It used to be the raw `serverUrl` with `/api/v1` hardcoded into each
1265
+ * path, which made the field mean something different here than in the
1266
+ * SDK. Both read the same `.appilotsrc`: the SDK completes the prefix
1267
+ * when it is missing, so `https://api.appilots.com/api/v1` is correct
1268
+ * there — and here that same value produced
1269
+ * `/api/v1/api/v1/cli/sync`, a 404 whose body reads `Route not found`.
1270
+ * Sharing `normalizeApiBaseUrl` is what makes one file mean one thing:
1271
+ * with or without the prefix now works in both.
1272
+ */
1273
+ private baseUrl;
1027
1274
  private apiKey;
1028
1275
  private timeoutMs;
1029
1276
  private maxRetries;
@@ -1068,4 +1315,13 @@ declare class AppilotsAPIClient {
1068
1315
  health(): Promise<boolean>;
1069
1316
  }
1070
1317
 
1071
- export { type ActionDescriptor, type AnalyzerConfig, AppilotsAPIClient, type AppilotsConfig, type AppilotsManifest, ComponentAnalyzer, type ComponentDescriptor, DEFAULT_MANIFEST_FILENAME, type EnvOverrides, type FlowDescriptor, type FlowStepDescriptor, FormAnalyzer, type FormDescriptor, type FormFieldDescriptor, GenericPlatformAnalyzer, type LoadManifestResult, type LocatorDescriptor, type MCPDocument, MCPGenerator, type MCPGeneratorConfig, type MCPGeneratorOptions, type MCPOutput, type MetadataLintWarning, NavigationAnalyzer, type NavigationGraph, type NavigationNode, type NavigatorDescriptor, type ParamDescriptor, type PlatformAnalyzer, type PlatformAnalyzerOptions, type PlatformAnalyzerResult, ReactNativePlatformAnalyzer, type ScreenAgentHints, ScreenAnalyzer, type ScreenDescriptor, type SignalDescriptor, type StatusResult, type SyncResult, type TargetDescriptor, type WaitPolicyDescriptor, formatMetadataWarnings, getConfigPath, getEnvOverrides, lintActionMetadata, loadConfig, loadManifest, mergeManifestNavigation, mergeManifestScreens, saveConfig, validateConfig };
1318
+ /**
1319
+ * GENERATED by scripts/release/sync-sdk-versions.mjs — do not edit.
1320
+ *
1321
+ * The version `@appilots/cli` prints for `appilots --version` and in its
1322
+ * banner. Kept identical to package.json by the release flow;
1323
+ * `version.test.ts` fails if the two ever disagree.
1324
+ */
1325
+ declare const CLI_VERSION = "0.10.0";
1326
+
1327
+ export { type ActionDescriptor, type AnalyzerConfig, AppilotsAPIClient, type AppilotsConfig, type AppilotsManifest, CLI_VERSION, ComponentAnalyzer, type ComponentDescriptor, DEFAULT_MANIFEST_FILENAME, DEFAULT_WEB_SCREEN_PATTERNS, type EnvOverrides, type FlowDescriptor, type FlowStepDescriptor, FormAnalyzer, type FormDescriptor, type FormFieldDescriptor, GenericPlatformAnalyzer, type LoadManifestResult, type LocatorDescriptor, type MCPDocument, MCPGenerator, type MCPGeneratorConfig, type MCPGeneratorOptions, type MCPOutput, type MetadataLintWarning, NavigationAnalyzer, type NavigationGraph, type NavigationNode, type NavigatorDescriptor, type ParamDescriptor, type PlatformAnalyzer, type PlatformAnalyzerOptions, type PlatformAnalyzerResult, ReactNativePlatformAnalyzer, ReactWebPlatformAnalyzer, type ScreenAgentHints, ScreenAnalyzer, type ScreenDescriptor, type SignalDescriptor, type StatusResult, type SyncResult, type TargetDescriptor, type WaitPolicyDescriptor, WebNavigationAnalyzer, type WebNavigationResult, type WebRoute, WebScreenAnalyzer, formatMetadataWarnings, getConfigPath, getEnvOverrides, lintActionMetadata, loadConfig, loadManifest, mergeManifestNavigation, mergeManifestScreens, resolvePathToScreen, saveConfig, screenNameFromPath, validateConfig };