@epsilon-asi/actors 0.0.21 → 0.0.32

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 (198) hide show
  1. package/dist/browser/RuntimeConfig.d.ts +26 -0
  2. package/dist/browser/RuntimeConfig.d.ts.map +1 -1
  3. package/dist/browser/RuntimeConfig.js +29 -1
  4. package/dist/browser/RuntimeConfig.js.map +1 -1
  5. package/dist/core/ActorContext.d.ts +2 -0
  6. package/dist/core/ActorContext.d.ts.map +1 -1
  7. package/dist/core/ActorRunner.d.ts +3 -0
  8. package/dist/core/ActorRunner.d.ts.map +1 -1
  9. package/dist/core/ActorRunner.js +11 -1
  10. package/dist/core/ActorRunner.js.map +1 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/native/CompositeNativeWindowDriver.d.ts +11 -0
  16. package/dist/native/CompositeNativeWindowDriver.d.ts.map +1 -0
  17. package/dist/native/CompositeNativeWindowDriver.js +31 -0
  18. package/dist/native/CompositeNativeWindowDriver.js.map +1 -0
  19. package/dist/native/NativeActionRegistry.d.ts +14 -0
  20. package/dist/native/NativeActionRegistry.d.ts.map +1 -0
  21. package/dist/native/NativeActionRegistry.js +101 -0
  22. package/dist/native/NativeActionRegistry.js.map +1 -0
  23. package/dist/native/NativeAutomation.d.ts +3 -0
  24. package/dist/native/NativeAutomation.d.ts.map +1 -0
  25. package/dist/native/NativeAutomation.js +12 -0
  26. package/dist/native/NativeAutomation.js.map +1 -0
  27. package/dist/native/NativeCoordinateMapper.d.ts +23 -0
  28. package/dist/native/NativeCoordinateMapper.d.ts.map +1 -0
  29. package/dist/native/NativeCoordinateMapper.js +201 -0
  30. package/dist/native/NativeCoordinateMapper.js.map +1 -0
  31. package/dist/native/NativeFileDialogService.d.ts +26 -0
  32. package/dist/native/NativeFileDialogService.d.ts.map +1 -0
  33. package/dist/native/NativeFileDialogService.js +121 -0
  34. package/dist/native/NativeFileDialogService.js.map +1 -0
  35. package/dist/native/NativeImageFinder.d.ts +12 -0
  36. package/dist/native/NativeImageFinder.d.ts.map +1 -0
  37. package/dist/native/NativeImageFinder.js +29 -0
  38. package/dist/native/NativeImageFinder.js.map +1 -0
  39. package/dist/native/NativeKeyboard.d.ts +10 -0
  40. package/dist/native/NativeKeyboard.d.ts.map +1 -0
  41. package/dist/native/NativeKeyboard.js +16 -0
  42. package/dist/native/NativeKeyboard.js.map +1 -0
  43. package/dist/native/NativeMouse.d.ts +38 -0
  44. package/dist/native/NativeMouse.d.ts.map +1 -0
  45. package/dist/native/NativeMouse.js +82 -0
  46. package/dist/native/NativeMouse.js.map +1 -0
  47. package/dist/native/NativeWindowService.d.ts +31 -0
  48. package/dist/native/NativeWindowService.d.ts.map +1 -0
  49. package/dist/native/NativeWindowService.js +183 -0
  50. package/dist/native/NativeWindowService.js.map +1 -0
  51. package/dist/native/UnsupportedNativeAutomation.d.ts +4 -0
  52. package/dist/native/UnsupportedNativeAutomation.d.ts.map +1 -0
  53. package/dist/native/UnsupportedNativeAutomation.js +77 -0
  54. package/dist/native/UnsupportedNativeAutomation.js.map +1 -0
  55. package/dist/native/WindowMatcher.d.ts +4 -0
  56. package/dist/native/WindowMatcher.d.ts.map +1 -0
  57. package/dist/native/WindowMatcher.js +39 -0
  58. package/dist/native/WindowMatcher.js.map +1 -0
  59. package/dist/native/drivers.d.ts +37 -0
  60. package/dist/native/drivers.d.ts.map +1 -0
  61. package/dist/native/drivers.js +2 -0
  62. package/dist/native/drivers.js.map +1 -0
  63. package/dist/native/errors.d.ts +23 -0
  64. package/dist/native/errors.d.ts.map +1 -0
  65. package/dist/native/errors.js +45 -0
  66. package/dist/native/errors.js.map +1 -0
  67. package/dist/native/index.d.ts +13 -0
  68. package/dist/native/index.d.ts.map +1 -0
  69. package/dist/native/index.js +13 -0
  70. package/dist/native/index.js.map +1 -0
  71. package/dist/native/macos/MacOSAccessibilityWindowDriver.d.ts +11 -0
  72. package/dist/native/macos/MacOSAccessibilityWindowDriver.d.ts.map +1 -0
  73. package/dist/native/macos/MacOSAccessibilityWindowDriver.js +180 -0
  74. package/dist/native/macos/MacOSAccessibilityWindowDriver.js.map +1 -0
  75. package/dist/native/macos/MacOSAppleScriptClient.d.ts +24 -0
  76. package/dist/native/macos/MacOSAppleScriptClient.d.ts.map +1 -0
  77. package/dist/native/macos/MacOSAppleScriptClient.js +163 -0
  78. package/dist/native/macos/MacOSAppleScriptClient.js.map +1 -0
  79. package/dist/native/macos/MacOSFileDialogAccessibilityStrategy.d.ts +10 -0
  80. package/dist/native/macos/MacOSFileDialogAccessibilityStrategy.d.ts.map +1 -0
  81. package/dist/native/macos/MacOSFileDialogAccessibilityStrategy.js +12 -0
  82. package/dist/native/macos/MacOSFileDialogAccessibilityStrategy.js.map +1 -0
  83. package/dist/native/macos/MacOSNativeAutomation.d.ts +3 -0
  84. package/dist/native/macos/MacOSNativeAutomation.d.ts.map +1 -0
  85. package/dist/native/macos/MacOSNativeAutomation.js +88 -0
  86. package/dist/native/macos/MacOSNativeAutomation.js.map +1 -0
  87. package/dist/native/nut/NutNativeImageFinder.d.ts +17 -0
  88. package/dist/native/nut/NutNativeImageFinder.d.ts.map +1 -0
  89. package/dist/native/nut/NutNativeImageFinder.js +84 -0
  90. package/dist/native/nut/NutNativeImageFinder.js.map +1 -0
  91. package/dist/native/nut/NutNativeKeyboardDriver.d.ts +8 -0
  92. package/dist/native/nut/NutNativeKeyboardDriver.d.ts.map +1 -0
  93. package/dist/native/nut/NutNativeKeyboardDriver.js +39 -0
  94. package/dist/native/nut/NutNativeKeyboardDriver.js.map +1 -0
  95. package/dist/native/nut/NutNativeMouseDriver.d.ts +8 -0
  96. package/dist/native/nut/NutNativeMouseDriver.d.ts.map +1 -0
  97. package/dist/native/nut/NutNativeMouseDriver.js +24 -0
  98. package/dist/native/nut/NutNativeMouseDriver.js.map +1 -0
  99. package/dist/native/nut/NutNativeScreenDriver.d.ts +6 -0
  100. package/dist/native/nut/NutNativeScreenDriver.d.ts.map +1 -0
  101. package/dist/native/nut/NutNativeScreenDriver.js +12 -0
  102. package/dist/native/nut/NutNativeScreenDriver.js.map +1 -0
  103. package/dist/native/nut/NutNativeWindowDriver.d.ts +6 -0
  104. package/dist/native/nut/NutNativeWindowDriver.d.ts.map +1 -0
  105. package/dist/native/nut/NutNativeWindowDriver.js +53 -0
  106. package/dist/native/nut/NutNativeWindowDriver.js.map +1 -0
  107. package/dist/native/nut/loadNut.d.ts +58 -0
  108. package/dist/native/nut/loadNut.d.ts.map +1 -0
  109. package/dist/native/nut/loadNut.js +25 -0
  110. package/dist/native/nut/loadNut.js.map +1 -0
  111. package/dist/native/types.d.ts +194 -0
  112. package/dist/native/types.d.ts.map +1 -0
  113. package/dist/native/types.js +2 -0
  114. package/dist/native/types.js.map +1 -0
  115. package/dist/native/utils/appleScriptEscape.d.ts +7 -0
  116. package/dist/native/utils/appleScriptEscape.d.ts.map +1 -0
  117. package/dist/native/utils/appleScriptEscape.js +11 -0
  118. package/dist/native/utils/appleScriptEscape.js.map +1 -0
  119. package/dist/native/utils/geometry.d.ts +12 -0
  120. package/dist/native/utils/geometry.d.ts.map +1 -0
  121. package/dist/native/utils/geometry.js +77 -0
  122. package/dist/native/utils/geometry.js.map +1 -0
  123. package/dist/native/utils/redactNative.d.ts +2 -0
  124. package/dist/native/utils/redactNative.d.ts.map +1 -0
  125. package/dist/native/utils/redactNative.js +7 -0
  126. package/dist/native/utils/redactNative.js.map +1 -0
  127. package/dist/native/utils/waitFor.d.ts +7 -0
  128. package/dist/native/utils/waitFor.d.ts.map +1 -0
  129. package/dist/native/utils/waitFor.js +17 -0
  130. package/dist/native/utils/waitFor.js.map +1 -0
  131. package/dist/sites/upwork-com/upwork-com.actor.d.ts +4 -1
  132. package/dist/sites/upwork-com/upwork-com.actor.d.ts.map +1 -1
  133. package/dist/sites/upwork-com/upwork-com.actor.js +30 -12
  134. package/dist/sites/upwork-com/upwork-com.actor.js.map +1 -1
  135. package/dist/sites/upwork-com/upwork-com.types.d.ts +3 -1
  136. package/dist/sites/upwork-com/upwork-com.types.d.ts.map +1 -1
  137. package/dist/sites/upwork-com/upwork-com.types.js.map +1 -1
  138. package/dist/sites/upwork-com/util/parseJobApplicationDetails.d.ts +70 -0
  139. package/dist/sites/upwork-com/util/parseJobApplicationDetails.d.ts.map +1 -0
  140. package/dist/sites/upwork-com/util/parseJobApplicationDetails.js +334 -0
  141. package/dist/sites/upwork-com/util/parseJobApplicationDetails.js.map +1 -0
  142. package/dist/sites/upwork-com/util/scrapeJobListing.d.ts +1 -0
  143. package/dist/sites/upwork-com/util/scrapeJobListing.d.ts.map +1 -1
  144. package/dist/sites/upwork-com/util/scrapeJobListing.js +4 -0
  145. package/dist/sites/upwork-com/util/scrapeJobListing.js.map +1 -1
  146. package/package.json +5 -1
  147. package/src/browser/RuntimeConfig.ts +57 -1
  148. package/src/core/ActorContext.ts +2 -0
  149. package/src/core/ActorRunner.ts +13 -1
  150. package/src/index.ts +2 -0
  151. package/src/native/CompositeNativeWindowDriver.ts +30 -0
  152. package/src/native/NativeActionRegistry.ts +114 -0
  153. package/src/native/NativeAutomation.ts +15 -0
  154. package/src/native/NativeCoordinateMapper.ts +258 -0
  155. package/src/native/NativeFileDialogService.ts +138 -0
  156. package/src/native/NativeImageFinder.ts +33 -0
  157. package/src/native/NativeKeyboard.ts +18 -0
  158. package/src/native/NativeMouse.ts +116 -0
  159. package/src/native/NativeWindowService.ts +229 -0
  160. package/src/native/UnsupportedNativeAutomation.ts +92 -0
  161. package/src/native/WindowMatcher.ts +31 -0
  162. package/src/native/drivers.ts +38 -0
  163. package/src/native/errors.ts +51 -0
  164. package/src/native/index.ts +12 -0
  165. package/src/native/macos/MacOSAccessibilityWindowDriver.ts +183 -0
  166. package/src/native/macos/MacOSAppleScriptClient.ts +182 -0
  167. package/src/native/macos/MacOSFileDialogAccessibilityStrategy.ts +11 -0
  168. package/src/native/macos/MacOSNativeAutomation.ts +86 -0
  169. package/src/native/nut/NutNativeImageFinder.ts +98 -0
  170. package/src/native/nut/NutNativeKeyboardDriver.ts +38 -0
  171. package/src/native/nut/NutNativeMouseDriver.ts +27 -0
  172. package/src/native/nut/NutNativeScreenDriver.ts +14 -0
  173. package/src/native/nut/NutNativeWindowDriver.ts +61 -0
  174. package/src/native/nut/loadNut.ts +86 -0
  175. package/src/native/types.ts +224 -0
  176. package/src/native/utils/appleScriptEscape.ts +11 -0
  177. package/src/native/utils/geometry.ts +88 -0
  178. package/src/native/utils/redactNative.ts +6 -0
  179. package/src/native/utils/waitFor.ts +25 -0
  180. package/src/sites/upwork-com/upwork-com.actor.ts +46 -15
  181. package/src/sites/upwork-com/upwork-com.types.ts +4 -1
  182. package/src/sites/upwork-com/util/parseJobApplicationDetails.ts +622 -0
  183. package/src/sites/upwork-com/util/scrapeJobListing.ts +4 -3
  184. package/tests/fixtures/makeContext.ts +7 -2
  185. package/tests/fixtures/native/FakeNativeAutomation.ts +138 -0
  186. package/tests/unit/browser/RuntimeConfig.native.test.ts +63 -0
  187. package/tests/unit/core/ActorRunner.native.test.ts +69 -0
  188. package/tests/unit/native/MacOSAppleScriptClient.test.ts +35 -0
  189. package/tests/unit/native/NativeActionRegistry.test.ts +34 -0
  190. package/tests/unit/native/NativeCoordinateMapper.test.ts +92 -0
  191. package/tests/unit/native/NativeFileDialogService.test.ts +91 -0
  192. package/tests/unit/native/NativeMouse.test.ts +91 -0
  193. package/tests/unit/native/NativeWindowService.test.ts +87 -0
  194. package/tests/unit/native/WindowMatcher.test.ts +32 -0
  195. package/tests/unit/native/appleScriptEscape.test.ts +9 -0
  196. package/tests/unit/sites/myvistage-com.login.test.ts +1 -1
  197. package/tests/unit/sites/myvistage-com.postComment.test.ts +0 -1
  198. package/tests/unit/sites/upwork-com.login.test.ts +1 -1
@@ -0,0 +1,224 @@
1
+ import type { BrowserSession } from '../browser/BrowserSession.js';
2
+ import type { PageLike } from '../browser/PuppeteerLike.js';
3
+ import type { RuntimeConfig } from '../browser/RuntimeConfig.js';
4
+ import type { Logger } from '../logging/Logger.js';
5
+
6
+ export type NativePlatform = 'macos';
7
+ export type NativeMouseButton = 'left' | 'right' | 'middle';
8
+ export type NativeFileDialogStrategy = 'auto' | 'applescript' | 'visual' | 'keyboard';
9
+ export type NativeWindowControlStrategy = 'auto' | 'accessibility' | 'visual';
10
+
11
+ export interface NativePoint {
12
+ x: number;
13
+ y: number;
14
+ }
15
+
16
+ export interface ScreenPoint extends NativePoint {}
17
+ export interface BrowserPoint extends NativePoint {}
18
+
19
+ export interface NativeSize {
20
+ width: number;
21
+ height: number;
22
+ }
23
+
24
+ export interface NativeRegion extends NativePoint, NativeSize {}
25
+
26
+ export interface WaitOptions {
27
+ timeoutMs?: number;
28
+ intervalMs?: number;
29
+ }
30
+
31
+ export interface WindowMatcher {
32
+ appName?: string;
33
+ title?: string;
34
+ titleIncludes?: string;
35
+ titleRegex?: RegExp | string;
36
+ bundleId?: string;
37
+ role?: string;
38
+ }
39
+
40
+ export interface NativeWindowInfo {
41
+ id?: string | number;
42
+ title: string;
43
+ appName?: string;
44
+ bundleId?: string;
45
+ role?: string;
46
+ region: NativeRegion;
47
+ isFocused?: boolean;
48
+ isMinimized?: boolean;
49
+ isFullscreen?: boolean;
50
+ }
51
+
52
+ export interface EnsureWindowInViewOptions extends WaitOptions {
53
+ restore?: boolean;
54
+ focus?: boolean;
55
+ moveIntoPrimaryDisplay?: boolean;
56
+ minimumVisibleWidth?: number;
57
+ minimumVisibleHeight?: number;
58
+ }
59
+
60
+ export interface WindowButtonOptions extends WaitOptions {
61
+ strategy?: NativeWindowControlStrategy;
62
+ fullscreenButtonTemplate?: string;
63
+ searchRegionPaddingPx?: number;
64
+ }
65
+
66
+ export interface SelectFileOptions extends WaitOptions {
67
+ appName?: string;
68
+ dialogTitle?: string;
69
+ openButtonTemplate?: string;
70
+ inputFieldTemplate?: string;
71
+ strategy?: NativeFileDialogStrategy;
72
+ clickOpen?: boolean;
73
+ }
74
+
75
+ export interface NativeMouseMoveOptions {
76
+ precise?: boolean;
77
+ jitterPixels?: number;
78
+ minJitterPixels?: number;
79
+ moveSpeed?: number;
80
+ disableHumanPath?: boolean;
81
+ }
82
+
83
+ export interface NativeMouseClickOptions extends NativeMouseMoveOptions {
84
+ button?: NativeMouseButton;
85
+ delayBeforeClickMs?: number;
86
+ delayAfterMoveMs?: number;
87
+ regionPaddingPx?: number;
88
+ }
89
+
90
+ export interface NativeKeyboardTypeOptions {
91
+ delayMs?: number;
92
+ }
93
+
94
+ export interface NativeKeyboard {
95
+ type(text: string, options?: NativeKeyboardTypeOptions): Promise<void>;
96
+ pressKey(key: string): Promise<void>;
97
+ pressShortcut(keys: readonly string[]): Promise<void>;
98
+ }
99
+
100
+ export interface NativeMouse {
101
+ moveTo(point: ScreenPoint, options?: NativeMouseMoveOptions): Promise<void>;
102
+ click(point: ScreenPoint, options?: NativeMouseClickOptions): Promise<void>;
103
+ clickRegion(region: NativeRegion, options?: NativeMouseClickOptions): Promise<void>;
104
+ clickBrowserPoint(page: PageLike, point: BrowserPoint, options?: NativeMouseClickOptions): Promise<void>;
105
+ }
106
+
107
+ export interface NativeTemplateRef {
108
+ name: string;
109
+ buffer?: Uint8Array;
110
+ }
111
+
112
+ export interface NativeImageSearchOptions extends WaitOptions {
113
+ searchRegion?: NativeRegion;
114
+ confidence?: number;
115
+ }
116
+
117
+ export interface NativeImageMatch {
118
+ region: NativeRegion;
119
+ confidence?: number;
120
+ }
121
+
122
+ export interface NativeImageFinder {
123
+ findTemplate(template: string | NativeTemplateRef, options?: NativeImageSearchOptions): Promise<NativeImageMatch | null>;
124
+ waitForTemplate(template: string | NativeTemplateRef, options?: NativeImageSearchOptions): Promise<NativeImageMatch>;
125
+ }
126
+
127
+ export interface NativeWindowService {
128
+ list(): Promise<NativeWindowInfo[]>;
129
+ find(matcher: WindowMatcher, options?: WaitOptions): Promise<NativeWindowInfo | null>;
130
+ waitFor(matcher: WindowMatcher, options?: WaitOptions): Promise<NativeWindowInfo>;
131
+ isOpen(matcher: WindowMatcher): Promise<boolean>;
132
+ focus(matcher: WindowMatcher): Promise<NativeWindowInfo>;
133
+ ensureInView(matcher: WindowMatcher, options?: EnsureWindowInViewOptions): Promise<NativeWindowInfo>;
134
+ setFullscreen(matcher: WindowMatcher, fullscreen: boolean, options?: WindowButtonOptions): Promise<void>;
135
+ toggleFullscreen(matcher: WindowMatcher, options?: WindowButtonOptions): Promise<void>;
136
+ minimize(matcher: WindowMatcher): Promise<void>;
137
+ restore(matcher: WindowMatcher): Promise<void>;
138
+ }
139
+
140
+ export interface NativeFileDialogService {
141
+ selectFile(filePath: string, options?: SelectFileOptions): Promise<void>;
142
+ }
143
+
144
+ export interface BrowserViewportSnapshot {
145
+ screenX: number;
146
+ screenY: number;
147
+ outerWidth: number;
148
+ outerHeight: number;
149
+ innerWidth: number;
150
+ innerHeight: number;
151
+ devicePixelRatio: number;
152
+ }
153
+
154
+ export interface CoordinateTransform {
155
+ offsetX: number;
156
+ offsetY: number;
157
+ scaleX: number;
158
+ scaleY: number;
159
+ calibratedAt: number;
160
+ viewport: Pick<BrowserViewportSnapshot, 'innerWidth' | 'innerHeight' | 'devicePixelRatio'>;
161
+ window?: NativeWindowInfo;
162
+ }
163
+
164
+ export interface CalibrationOptions extends WaitOptions {
165
+ markerSizePx?: number;
166
+ markerA?: BrowserPoint;
167
+ markerB?: BrowserPoint;
168
+ cacheTtlMs?: number;
169
+ windowMatcher?: WindowMatcher;
170
+ }
171
+
172
+ export interface CoordinateMapOptions {
173
+ page?: PageLike;
174
+ useCache?: boolean;
175
+ }
176
+
177
+ export interface NativeCoordinateMapper {
178
+ calibrate(page: PageLike, options?: CalibrationOptions): Promise<CoordinateTransform>;
179
+ browserToScreen(point: BrowserPoint, options?: CoordinateMapOptions): Promise<ScreenPoint>;
180
+ screenToBrowser(point: ScreenPoint, options?: CoordinateMapOptions): Promise<BrowserPoint>;
181
+ invalidate(): void;
182
+ }
183
+
184
+ export interface NativeActionContext {
185
+ automation: NativeAutomation;
186
+ logger: Logger;
187
+ }
188
+
189
+ export interface NativeAction<TInput = unknown, TOutput = unknown> {
190
+ name: string;
191
+ description?: string;
192
+ run(context: NativeActionContext, input: TInput): Promise<TOutput>;
193
+ }
194
+
195
+ export interface NativeActionMetadata {
196
+ name: string;
197
+ description?: string;
198
+ }
199
+
200
+ export interface NativeActionRegistry {
201
+ register<TInput, TOutput>(action: NativeAction<TInput, TOutput>): this;
202
+ get<TInput = unknown, TOutput = unknown>(name: string): NativeAction<TInput, TOutput>;
203
+ run<TInput = unknown, TOutput = unknown>(name: string, input: TInput): Promise<TOutput>;
204
+ list(): NativeActionMetadata[];
205
+ }
206
+
207
+ export interface NativeAutomation {
208
+ windows: NativeWindowService;
209
+ fileDialogs: NativeFileDialogService;
210
+ mouse: NativeMouse;
211
+ keyboard: NativeKeyboard;
212
+ coordinates: NativeCoordinateMapper;
213
+ images: NativeImageFinder;
214
+ actions: NativeActionRegistry;
215
+ }
216
+
217
+ export interface NativeAutomationFactoryArgs {
218
+ session: BrowserSession;
219
+ page: PageLike;
220
+ config: RuntimeConfig;
221
+ logger: Logger;
222
+ }
223
+
224
+ export type NativeAutomationFactory = (args: NativeAutomationFactoryArgs) => NativeAutomation;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Escapes arbitrary text for use as an AppleScript string literal.
3
+ * AppleScript string literals are double-quoted and escape double quotes/backslashes.
4
+ */
5
+ export function appleScriptString(value: string): string {
6
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
7
+ }
8
+
9
+ export function appleScriptRegexLiteral(value: RegExp | string): string {
10
+ return value instanceof RegExp ? value.source : value;
11
+ }
@@ -0,0 +1,88 @@
1
+ import type { NativePoint, NativeRegion } from '../types.js';
2
+
3
+ export function centerOf(region: NativeRegion): NativePoint {
4
+ return {
5
+ x: region.x + region.width / 2,
6
+ y: region.y + region.height / 2
7
+ };
8
+ }
9
+
10
+ export function normalizeRegion(region: NativeRegion): NativeRegion {
11
+ return {
12
+ x: region.x,
13
+ y: region.y,
14
+ width: Math.max(0, region.width),
15
+ height: Math.max(0, region.height)
16
+ };
17
+ }
18
+
19
+ export function isPointInsideRegion(point: NativePoint, region: NativeRegion): boolean {
20
+ return point.x >= region.x
21
+ && point.x <= region.x + region.width
22
+ && point.y >= region.y
23
+ && point.y <= region.y + region.height;
24
+ }
25
+
26
+ export function clampPointToRegion(point: NativePoint, region: NativeRegion): NativePoint {
27
+ return {
28
+ x: Math.min(Math.max(point.x, region.x), region.x + region.width),
29
+ y: Math.min(Math.max(point.y, region.y), region.y + region.height)
30
+ };
31
+ }
32
+
33
+ export function paddedRegion(region: NativeRegion, paddingPx: number): NativeRegion {
34
+ const x = region.x + paddingPx;
35
+ const y = region.y + paddingPx;
36
+ return normalizeRegion({
37
+ x,
38
+ y,
39
+ width: region.width - paddingPx * 2,
40
+ height: region.height - paddingPx * 2
41
+ });
42
+ }
43
+
44
+ export function intersects(a: NativeRegion, b: NativeRegion): boolean {
45
+ return a.x < b.x + b.width
46
+ && a.x + a.width > b.x
47
+ && a.y < b.y + b.height
48
+ && a.y + a.height > b.y;
49
+ }
50
+
51
+ export function intersection(a: NativeRegion, b: NativeRegion): NativeRegion {
52
+ const x = Math.max(a.x, b.x);
53
+ const y = Math.max(a.y, b.y);
54
+ const right = Math.min(a.x + a.width, b.x + b.width);
55
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
56
+
57
+ return normalizeRegion({
58
+ x,
59
+ y,
60
+ width: right - x,
61
+ height: bottom - y
62
+ });
63
+ }
64
+
65
+ export function visibleEnough(region: NativeRegion, viewport: NativeRegion, minimumWidth: number, minimumHeight: number): boolean {
66
+ if (!intersects(region, viewport)) return false;
67
+ const visible = intersection(region, viewport);
68
+ return visible.width >= Math.min(region.width, minimumWidth) && visible.height >= Math.min(region.height, minimumHeight);
69
+ }
70
+
71
+ export function randomPointInRegion(region: NativeRegion, random: () => number, paddingPx = 0): NativePoint {
72
+ const safe = paddedRegion(region, paddingPx);
73
+ if (safe.width <= 0 || safe.height <= 0) {
74
+ return centerOf(region);
75
+ }
76
+
77
+ return {
78
+ x: safe.x + random() * safe.width,
79
+ y: safe.y + random() * safe.height
80
+ };
81
+ }
82
+
83
+ export function roundPoint(point: NativePoint): NativePoint {
84
+ return {
85
+ x: Math.round(point.x),
86
+ y: Math.round(point.y)
87
+ };
88
+ }
@@ -0,0 +1,6 @@
1
+ import path from 'node:path';
2
+
3
+ export function redactPath(filePath: string, reveal = false): string {
4
+ if (reveal) return filePath;
5
+ return path.basename(filePath) === '' ? '<path>' : `<path:${path.basename(filePath)}>`;
6
+ }
@@ -0,0 +1,25 @@
1
+ import { delay } from '../../utils/delay.js';
2
+
3
+ export interface WaitForOptions {
4
+ timeoutMs?: number;
5
+ intervalMs?: number;
6
+ description?: string;
7
+ }
8
+
9
+ export async function waitFor<T>(predicate: () => Promise<T | null | undefined | false>, options: WaitForOptions = {}): Promise<T> {
10
+ const timeoutMs = options.timeoutMs ?? 15_000;
11
+ const intervalMs = options.intervalMs ?? 100;
12
+ const startedAt = Date.now();
13
+ let lastValue: T | null | undefined | false;
14
+
15
+ do {
16
+ lastValue = await predicate();
17
+ if (lastValue !== null && lastValue !== undefined && lastValue !== false) {
18
+ return lastValue;
19
+ }
20
+ await delay(intervalMs);
21
+ } while (Date.now() - startedAt < timeoutMs);
22
+
23
+ const description = options.description ?? 'condition';
24
+ throw new Error(`Timed out after ${timeoutMs}ms waiting for ${description}.`);
25
+ }
@@ -6,6 +6,11 @@ import {parseRate} from "./upwork-com.util.js";
6
6
  import {ScrapeDashboardInput} from "../example/index.js";
7
7
  import {parseUpworkSearchResults, UpworkJobListing} from "./util/scrapeJobListing.js";
8
8
  import {Page} from "puppeteer-core";
9
+ import {
10
+ fillUpworkProposalQuestions,
11
+ parseUpworkProposalQuestions,
12
+ UpworkProposalQuestions
13
+ } from "./util/parseJobApplicationDetails.js";
9
14
 
10
15
  export const upworkComActor = defineActor({
11
16
  id: 'upwork-com',
@@ -57,34 +62,60 @@ export const upworkComActor = defineActor({
57
62
  ]
58
63
  }),
59
64
  tasks: {
65
+ test: async (context, input) => {
66
+ await context.native.mouse.moveTo({x: 100, y: 200})
67
+ },
60
68
  searchJobs: async (context, input: UpworkJobSearchFields = {}): Promise<UpworkJobListing[]> => {
61
69
  const path = '/nx/s/universal-search/jobs?category2_uid=531770282580668420,531770282580668419,531770282580668418&client_hires=1-9,10-&payment_verified=1&q=%27rancher%27%20or%20%27terraform%27%20or%20%27gitops%27%20or%20%27azure%27%20or%20%27microsoft%20azure%27%20or%20%27cloud%20architect%27%20or%20%27ai%20architect%27%20or%20%27forward%20deployed%20engineer%27%20or%20%27aws%27%20or%20%27aks%27%20or%20%27eks%27%20or%20%27gke%27%20or%20%27cloud%20engineer%27%20or%20devops%20or%20kuberentes%20or%20%27platform%20engineer%27%20or%20%27infrastructure%20engineer%27%20or%20"google%20cloud%20platform"%20or%20"GCP"%20or%20"langsmith"%20or%20"langgraph"%20or%20"gemini%20enterprise"&sort=recency&user_location_match=1';
62
70
 
63
- await context.nav.goto(path, {
64
-
65
- });
71
+ await context.nav.goto(path, {});
66
72
 
67
73
  return await parseUpworkSearchResults(context.session.page as Page);
68
74
  },
69
75
  scrapeJobs: async (context, input: ScrapeDashboardInput = {}): Promise<any> => {
70
76
  await parseUpworkSearchResults(context.session.page as Page)
71
77
  },
72
- applyToJob: async (_context, input: UpworkApplyToJobInput): Promise<UpworkApplyToJobResult> => {
73
- const coverLetter = input.coverLetter.trim();
74
- if (coverLetter.length === 0) {
75
- throw new Error('coverLetter must be a non-empty string.');
76
- }
78
+ applyToJobPart1: async (_context, input: UpworkApplyToJobInput): Promise<UpworkProposalQuestions> => {
77
79
 
78
- const rate = parseRate(input.rate);
79
- if (!Number.isFinite(rate) || rate <= 0) {
80
- throw new Error('rate must be a positive number.');
81
- }
80
+ await _context.cursor.scrollIntoView(`[data-ev-job-uid="${input.jobId}"]`);
81
+
82
+ await _context.cursor.move(`[data-ev-job-uid="${input.jobId}"]`);
83
+ await _context.cursor.click(`[data-ev-job-uid="${input.jobId}"]`);
84
+
85
+ await _context.cursor.move(`[data-test="Apply"]>button`);
86
+ // await _context.cursor.click(`[data-test="Apply"]>button`);
87
+
88
+ await _context.nav.goto(`/nx/proposals/job/~${input.jobId}/apply/`);
89
+
90
+ await _context.cursor.move('.up-fe-agency-member-selector > [data-test="dropdown-toggle"]')
91
+ await _context.cursor.click('.up-fe-agency-member-selector > [data-test="dropdown-toggle"]')
92
+
93
+ await _context.cursor.move('li[role="option"][tabindex="0"]')
94
+ await _context.cursor.click('li[role="option"][tabindex="0"]')
95
+
96
+ await _context.cursor.move('.up-fe-contractor-selector > [data-test="dropdown-toggle"]')
97
+ await _context.cursor.click('.up-fe-contractor-selector > [data-test="dropdown-toggle"]')
98
+
99
+ await _context.cursor.move('li[role="option"][tabindex="0"]')
100
+ await _context.cursor.click('li[role="option"][tabindex="0"]')
101
+
102
+ await _context.cursor.scrollIntoView('[data-test="sri-form-card"] > [data-test="dropdown-toggle"] > [aria-label="How often do you want a rate increase?"]')
103
+
104
+ await _context.cursor.move('[data-test="sri-form-card"] > [data-test="dropdown-toggle"] > [aria-label="How often do you want a rate increase?"]')
105
+ await _context.cursor.click('[data-test="sri-form-card"] > [data-test="dropdown-toggle"] > [aria-label="How often do you want a rate increase?"]')
106
+ await _context.cursor.move('li[role="option"][tabindex="0"]')
107
+ await _context.cursor.click('li[role="option"][tabindex="0"]')
108
+
109
+
110
+ return await parseUpworkProposalQuestions(_context.session.page as Page);
111
+ },
112
+ applyToJobPart2: async (_context, input: UpworkApplyToJobInput): Promise<UpworkApplyToJobResult> => {
113
+ await fillUpworkProposalQuestions(_context.session.page as Page, input.answers)
82
114
 
83
115
  return {
84
- coverLetter,
85
- rate
116
+ coverLetter: input.answers.coverLetter ?? '',
117
+ rate: parseRate(input.rate)
86
118
  };
87
119
  }
88
120
  }
89
121
  });
90
- `asd*poq@#aes48d#@jna8sndaskDF#KN$@#a78sdASD1`
@@ -1,3 +1,5 @@
1
+ import {UpworkProposalQuestionAnswers} from "./util/parseJobApplicationDetails.js";
2
+
1
3
  export enum JobSearchSort {
2
4
  BestMatch = 'best_match',
3
5
  }
@@ -92,8 +94,9 @@ export interface UpworkJobSearchResult {
92
94
  }
93
95
 
94
96
  export interface UpworkApplyToJobInput {
95
- coverLetter: string;
97
+ jobId: string;
96
98
  rate: number | string;
99
+ answers: UpworkProposalQuestionAnswers
97
100
  }
98
101
 
99
102
  export interface UpworkApplyToJobResult {