@farmslot/adapter-rn 0.15.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/bin/farmslot-adapter-rn.mjs +10 -0
  5. package/dist/agent-device-ui-transport.d.ts +88 -0
  6. package/dist/agent-device-ui-transport.d.ts.map +1 -0
  7. package/dist/agent-device-ui-transport.js +1148 -0
  8. package/dist/agent-device-ui-transport.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +169 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/constants.d.ts +6 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/constants.js +6 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/doctor.d.ts +17 -0
  18. package/dist/doctor.d.ts.map +1 -0
  19. package/dist/doctor.js +125 -0
  20. package/dist/doctor.js.map +1 -0
  21. package/dist/index.d.ts +8 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +8 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/json.d.ts +2 -0
  26. package/dist/json.d.ts.map +1 -0
  27. package/dist/json.js +5 -0
  28. package/dist/json.js.map +1 -0
  29. package/dist/metro-recipe-bridge-transport.d.ts +12 -0
  30. package/dist/metro-recipe-bridge-transport.d.ts.map +1 -0
  31. package/dist/metro-recipe-bridge-transport.js +63 -0
  32. package/dist/metro-recipe-bridge-transport.js.map +1 -0
  33. package/dist/redaction.d.ts +3 -0
  34. package/dist/redaction.d.ts.map +1 -0
  35. package/dist/redaction.js +57 -0
  36. package/dist/redaction.js.map +1 -0
  37. package/dist/runner.d.ts +20 -0
  38. package/dist/runner.d.ts.map +1 -0
  39. package/dist/runner.js +270 -0
  40. package/dist/runner.js.map +1 -0
  41. package/dist/scaffold.d.ts +14 -0
  42. package/dist/scaffold.d.ts.map +1 -0
  43. package/dist/scaffold.js +82 -0
  44. package/dist/scaffold.js.map +1 -0
  45. package/dist/simctl-video-recorder.d.ts +6 -0
  46. package/dist/simctl-video-recorder.d.ts.map +1 -0
  47. package/dist/simctl-video-recorder.js +160 -0
  48. package/dist/simctl-video-recorder.js.map +1 -0
  49. package/dist/version.d.ts +3 -0
  50. package/dist/version.d.ts.map +1 -0
  51. package/dist/version.js +11 -0
  52. package/dist/version.js.map +1 -0
  53. package/package.json +81 -0
  54. package/templates/scripts/agentic/recipe/README.md +15 -0
  55. package/templates/scripts/agentic/recipe/action-manifest.json +547 -0
  56. package/templates/scripts/agentic/recipe/action-manifest.with-bridge.json +599 -0
  57. package/templates/scripts/agentic/recipe/recipes/expo.config.recipe.json +37 -0
  58. package/templates/scripts/agentic/validate-recipe.sh +64 -0
  59. package/templates/src/farmslot/RecipeBridgeProvider.tsx +127 -0
  60. package/templates/src/farmslot/RecipeHud.tsx +138 -0
  61. package/templates/src/farmslot/index.ts +11 -0
@@ -0,0 +1,127 @@
1
+ import React, { createContext, useEffect, useMemo, useState } from 'react';
2
+ import { Platform, View } from 'react-native';
3
+
4
+ import { type FarmslotRecipeHudState, RecipeHud, type RecipeHudOptions } from './RecipeHud';
5
+
6
+ export interface FarmslotRecipeBridgeCommand {
7
+ command: string;
8
+ nodeId: string;
9
+ payload?: Record<string, unknown>;
10
+ }
11
+
12
+ export interface FarmslotRecipeBridgeApi {
13
+ handleCommand(command: FarmslotRecipeBridgeCommand): Promise<unknown>;
14
+ }
15
+
16
+ export interface RecipeBridgeProviderOptions {
17
+ bridgeName?: string;
18
+ hud?: RecipeHudOptions;
19
+ isEnabled?: () => boolean;
20
+ renderHud?: (state: FarmslotRecipeHudState | null) => React.ReactNode;
21
+ }
22
+
23
+ declare global {
24
+ var __FARMSLOT_RECIPE_BRIDGE__: FarmslotRecipeBridgeApi | undefined;
25
+ }
26
+
27
+ export const FarmslotRecipeBridgeContext = createContext<FarmslotRecipeBridgeApi | null>(null);
28
+
29
+ interface RecipeBridgeProviderProps extends RecipeBridgeProviderOptions {
30
+ children: React.ReactNode;
31
+ }
32
+
33
+ export function RecipeBridgeProvider({
34
+ bridgeName = '@farmslot/adapter-rn',
35
+ children,
36
+ hud: hudOptions,
37
+ isEnabled = isRecipeBridgeEnabled,
38
+ renderHud,
39
+ }: Readonly<RecipeBridgeProviderProps>): React.ReactElement {
40
+ const enabled = isEnabled();
41
+ const [hud, setHud] = useState<FarmslotRecipeHudState | null>(null);
42
+
43
+ const bridge = useMemo<FarmslotRecipeBridgeApi>(
44
+ () => ({
45
+ async handleCommand(command) {
46
+ if (!enabled) {
47
+ return { ok: false, error: 'Farmslot recipe bridge is disabled.' };
48
+ }
49
+ if (command.command === 'status') {
50
+ return { ok: true, bridge: bridgeName, platform: Platform.OS, hud: true };
51
+ }
52
+ if (command.command === 'hud') {
53
+ setHud(normalizeHudState(command.payload));
54
+ return { ok: true };
55
+ }
56
+ if (command.command === 'trace') {
57
+ return { ok: true, trace: command.payload ?? {} };
58
+ }
59
+ return { ok: false, error: `Unsupported Farmslot bridge command: ${command.command}` };
60
+ },
61
+ }),
62
+ [bridgeName, enabled],
63
+ );
64
+
65
+ useEffect(() => {
66
+ if (!enabled) return;
67
+ globalThis.__FARMSLOT_RECIPE_BRIDGE__ = bridge;
68
+ return () => {
69
+ if (globalThis.__FARMSLOT_RECIPE_BRIDGE__ === bridge) {
70
+ globalThis.__FARMSLOT_RECIPE_BRIDGE__ = undefined;
71
+ }
72
+ };
73
+ }, [bridge, enabled]);
74
+
75
+ if (!enabled) return <>{children}</>;
76
+
77
+ return (
78
+ <FarmslotRecipeBridgeContext.Provider value={bridge}>
79
+ <View style={{ flex: 1 }}>
80
+ {children}
81
+ {renderHud ? renderHud(hud) : <RecipeHud state={hud} {...hudOptions} />}
82
+ </View>
83
+ </FarmslotRecipeBridgeContext.Provider>
84
+ );
85
+ }
86
+
87
+ function isRecipeBridgeEnabled(): boolean {
88
+ return Boolean(__DEV__ && process.env.EXPO_PUBLIC_FARMSLOT_RECIPE_BRIDGE === '1');
89
+ }
90
+
91
+ function normalizeHudState(payload: Record<string, unknown> | undefined): FarmslotRecipeHudState {
92
+ const status = parseStatus(payload?.status);
93
+ const progress = isRecord(payload?.progress) ? payload.progress : undefined;
94
+ return {
95
+ status,
96
+ intent: asString(
97
+ payload?.intent ?? payload?.text,
98
+ status === 'fail' ? 'Recipe failed' : 'Recipe HUD missing intent',
99
+ ),
100
+ currentStep: asOptionalNumber(
101
+ payload?.currentStep ?? payload?.current_step ?? progress?.current,
102
+ ),
103
+ totalSteps: asOptionalNumber(payload?.totalSteps ?? payload?.total_steps ?? progress?.total),
104
+ error: asOptionalString(payload?.error),
105
+ };
106
+ }
107
+
108
+ function parseStatus(value: unknown): FarmslotRecipeHudState['status'] {
109
+ if (value === 'idle' || value === 'running' || value === 'pass' || value === 'fail') return value;
110
+ return 'running';
111
+ }
112
+
113
+ function isRecord(value: unknown): value is Record<string, unknown> {
114
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
115
+ }
116
+
117
+ function asString(value: unknown, fallback: string): string {
118
+ return typeof value === 'string' && value.trim() ? value : fallback;
119
+ }
120
+
121
+ function asOptionalString(value: unknown): string | undefined {
122
+ return typeof value === 'string' && value.trim() ? value : undefined;
123
+ }
124
+
125
+ function asOptionalNumber(value: unknown): number | undefined {
126
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
127
+ }
@@ -0,0 +1,138 @@
1
+ import React from 'react';
2
+ import {
3
+ type StyleProp,
4
+ StyleSheet,
5
+ Text,
6
+ type TextStyle,
7
+ View,
8
+ type ViewStyle,
9
+ } from 'react-native';
10
+
11
+ export type FarmslotRecipeHudStatus = 'idle' | 'running' | 'pass' | 'fail';
12
+
13
+ export interface FarmslotRecipeHudState {
14
+ status: FarmslotRecipeHudStatus;
15
+ intent: string;
16
+ currentStep?: number;
17
+ totalSteps?: number;
18
+ error?: string;
19
+ }
20
+
21
+ export interface FarmslotRecipeHudText {
22
+ badge?: (state: FarmslotRecipeHudState) => string;
23
+ intent?: (state: FarmslotRecipeHudState) => string;
24
+ error?: (state: FarmslotRecipeHudState) => string | undefined;
25
+ }
26
+
27
+ export interface FarmslotRecipeHudStyles {
28
+ container?: StyleProp<ViewStyle>;
29
+ line?: StyleProp<TextStyle>;
30
+ badgeText?: StyleProp<TextStyle>;
31
+ badgeTextRunning?: StyleProp<TextStyle>;
32
+ badgeTextPass?: StyleProp<TextStyle>;
33
+ badgeTextFail?: StyleProp<TextStyle>;
34
+ intent?: StyleProp<TextStyle>;
35
+ error?: StyleProp<TextStyle>;
36
+ }
37
+
38
+ export interface RecipeHudOptions {
39
+ text?: FarmslotRecipeHudText;
40
+ styles?: FarmslotRecipeHudStyles;
41
+ }
42
+
43
+ export interface RecipeHudProps extends RecipeHudOptions {
44
+ state: FarmslotRecipeHudState | null;
45
+ }
46
+
47
+ export function RecipeHud({
48
+ state,
49
+ styles: customStyles,
50
+ text,
51
+ }: Readonly<RecipeHudProps>): React.ReactElement | null {
52
+ if (!state || state.status === 'idle') return null;
53
+ const badge = text?.badge?.(state) ?? formatBadge(state);
54
+ const intent = text?.intent?.(state) ?? state.intent;
55
+ const error = text?.error?.(state) ?? state.error;
56
+ const badgeTone = badgeToneForStatus(state.status);
57
+ return (
58
+ <View pointerEvents="none" style={[styles.container, customStyles?.container]}>
59
+ <Text style={[styles.line, customStyles?.line]}>
60
+ <Text
61
+ style={[
62
+ styles.badgeText,
63
+ badgeTone === 'fail'
64
+ ? styles.badgeTextFail
65
+ : badgeTone === 'pass'
66
+ ? styles.badgeTextPass
67
+ : styles.badgeTextRunning,
68
+ customStyles?.badgeText,
69
+ badgeTone === 'fail'
70
+ ? customStyles?.badgeTextFail
71
+ : badgeTone === 'pass'
72
+ ? customStyles?.badgeTextPass
73
+ : customStyles?.badgeTextRunning,
74
+ ]}
75
+ >
76
+ {badge}
77
+ </Text>
78
+ {intent ? <Text style={customStyles?.intent}>{` ${intent}`}</Text> : null}
79
+ </Text>
80
+ {error ? <Text style={[styles.error, customStyles?.error]}>{error}</Text> : null}
81
+ </View>
82
+ );
83
+ }
84
+
85
+ function formatBadge(state: FarmslotRecipeHudState): string {
86
+ const status = state.status === 'running' ? 'run' : state.status;
87
+ const progress = formatProgress(state.currentStep, state.totalSteps);
88
+ return [status, progress].filter(Boolean).join(' ').toUpperCase();
89
+ }
90
+
91
+ function formatProgress(currentStep?: number, totalSteps?: number): string {
92
+ if (!currentStep || !totalSteps) return '';
93
+ return `${currentStep}/${totalSteps}`;
94
+ }
95
+
96
+ function badgeToneForStatus(status: FarmslotRecipeHudStatus): 'running' | 'pass' | 'fail' {
97
+ if (status === 'fail') return 'fail';
98
+ if (status === 'pass') return 'pass';
99
+ return 'running';
100
+ }
101
+
102
+ const styles = StyleSheet.create({
103
+ container: {
104
+ position: 'absolute',
105
+ left: 0,
106
+ right: 0,
107
+ bottom: 0,
108
+ zIndex: 9999,
109
+ backgroundColor: 'rgba(0, 0, 0, 0.58)',
110
+ paddingHorizontal: 10,
111
+ paddingVertical: 3,
112
+ },
113
+ line: {
114
+ color: '#ffffff',
115
+ fontSize: 11,
116
+ fontWeight: '700',
117
+ lineHeight: 14,
118
+ },
119
+ badgeText: {
120
+ fontSize: 9,
121
+ fontWeight: '800',
122
+ },
123
+ badgeTextRunning: {
124
+ color: '#00ff88',
125
+ },
126
+ badgeTextPass: {
127
+ color: '#00ff88',
128
+ },
129
+ badgeTextFail: {
130
+ color: '#ff4d4f',
131
+ },
132
+ error: {
133
+ color: '#e6e6e6',
134
+ fontSize: 10,
135
+ fontWeight: '400',
136
+ lineHeight: 12,
137
+ },
138
+ });
@@ -0,0 +1,11 @@
1
+ export type { FarmslotRecipeBridgeApi, FarmslotRecipeBridgeCommand } from './RecipeBridgeProvider';
2
+ export { FarmslotRecipeBridgeContext, RecipeBridgeProvider } from './RecipeBridgeProvider';
3
+ export type {
4
+ FarmslotRecipeHudState,
5
+ FarmslotRecipeHudStatus,
6
+ FarmslotRecipeHudStyles,
7
+ FarmslotRecipeHudText,
8
+ RecipeHudOptions,
9
+ RecipeHudProps,
10
+ } from './RecipeHud';
11
+ export { RecipeHud } from './RecipeHud';