@isoldex/sentinel 0.1.0 → 2.0.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 (100) hide show
  1. package/README.md +368 -62
  2. package/dist/__tests__/action-engine.test.d.ts +2 -0
  3. package/dist/__tests__/action-engine.test.d.ts.map +1 -0
  4. package/dist/__tests__/action-engine.test.js +144 -0
  5. package/dist/__tests__/action-engine.test.js.map +1 -0
  6. package/dist/__tests__/state-parser.test.d.ts +2 -0
  7. package/dist/__tests__/state-parser.test.d.ts.map +1 -0
  8. package/dist/__tests__/state-parser.test.js +176 -0
  9. package/dist/__tests__/state-parser.test.js.map +1 -0
  10. package/dist/__tests__/verifier.test.d.ts +2 -0
  11. package/dist/__tests__/verifier.test.d.ts.map +1 -0
  12. package/dist/__tests__/verifier.test.js +68 -0
  13. package/dist/__tests__/verifier.test.js.map +1 -0
  14. package/dist/__tests__/workflow-recorder.test.d.ts +2 -0
  15. package/dist/__tests__/workflow-recorder.test.d.ts.map +1 -0
  16. package/dist/__tests__/workflow-recorder.test.js +71 -0
  17. package/dist/__tests__/workflow-recorder.test.js.map +1 -0
  18. package/dist/agent/agent-loop.d.ts +36 -0
  19. package/dist/agent/agent-loop.d.ts.map +1 -0
  20. package/dist/agent/agent-loop.js +127 -0
  21. package/dist/agent/agent-loop.js.map +1 -0
  22. package/dist/agent/memory.d.ts +24 -0
  23. package/dist/agent/memory.d.ts.map +1 -0
  24. package/dist/agent/memory.js +34 -0
  25. package/dist/agent/memory.js.map +1 -0
  26. package/dist/agent/planner.d.ts +18 -0
  27. package/dist/agent/planner.d.ts.map +1 -0
  28. package/dist/agent/planner.js +68 -0
  29. package/dist/agent/planner.js.map +1 -0
  30. package/dist/api/act.d.ts +4 -2
  31. package/dist/api/act.d.ts.map +1 -1
  32. package/dist/api/act.js +174 -30
  33. package/dist/api/act.js.map +1 -1
  34. package/dist/api/extract.d.ts +2 -3
  35. package/dist/api/extract.d.ts.map +1 -1
  36. package/dist/api/extract.js +0 -1
  37. package/dist/api/extract.js.map +1 -1
  38. package/dist/api/observe.d.ts +2 -2
  39. package/dist/api/observe.d.ts.map +1 -1
  40. package/dist/api/observe.js +0 -1
  41. package/dist/api/observe.js.map +1 -1
  42. package/dist/core/driver.d.ts +21 -1
  43. package/dist/core/driver.d.ts.map +1 -1
  44. package/dist/core/driver.js +109 -19
  45. package/dist/core/driver.js.map +1 -1
  46. package/dist/core/state-parser.d.ts +1 -0
  47. package/dist/core/state-parser.d.ts.map +1 -1
  48. package/dist/core/state-parser.js +60 -0
  49. package/dist/core/state-parser.js.map +1 -1
  50. package/dist/core/vision-grounding.d.ts +36 -0
  51. package/dist/core/vision-grounding.d.ts.map +1 -0
  52. package/dist/core/vision-grounding.js +118 -0
  53. package/dist/core/vision-grounding.js.map +1 -0
  54. package/dist/index.d.ts +111 -2
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +168 -4
  57. package/dist/index.js.map +1 -1
  58. package/dist/recorder/workflow-recorder.d.ts +30 -0
  59. package/dist/recorder/workflow-recorder.d.ts.map +1 -0
  60. package/dist/recorder/workflow-recorder.js +83 -0
  61. package/dist/recorder/workflow-recorder.js.map +1 -0
  62. package/dist/reliability/verifier.d.ts +2 -2
  63. package/dist/reliability/verifier.d.ts.map +1 -1
  64. package/dist/reliability/verifier.js +0 -1
  65. package/dist/reliability/verifier.js.map +1 -1
  66. package/dist/types/errors.d.ts +45 -0
  67. package/dist/types/errors.d.ts.map +1 -0
  68. package/dist/types/errors.js +69 -0
  69. package/dist/types/errors.js.map +1 -0
  70. package/dist/utils/gemini.d.ts +3 -6
  71. package/dist/utils/gemini.d.ts.map +1 -1
  72. package/dist/utils/gemini.js +9 -94
  73. package/dist/utils/gemini.js.map +1 -1
  74. package/dist/utils/llm-provider.d.ts +28 -0
  75. package/dist/utils/llm-provider.d.ts.map +1 -0
  76. package/dist/utils/llm-provider.js +2 -0
  77. package/dist/utils/llm-provider.js.map +1 -0
  78. package/dist/utils/providers/claude-provider.d.ts +17 -0
  79. package/dist/utils/providers/claude-provider.d.ts.map +1 -0
  80. package/dist/utils/providers/claude-provider.js +49 -0
  81. package/dist/utils/providers/claude-provider.js.map +1 -0
  82. package/dist/utils/providers/gemini-provider.d.ts +14 -0
  83. package/dist/utils/providers/gemini-provider.d.ts.map +1 -0
  84. package/dist/utils/providers/gemini-provider.js +95 -0
  85. package/dist/utils/providers/gemini-provider.js.map +1 -0
  86. package/dist/utils/providers/ollama-provider.d.ts +18 -0
  87. package/dist/utils/providers/ollama-provider.d.ts.map +1 -0
  88. package/dist/utils/providers/ollama-provider.js +62 -0
  89. package/dist/utils/providers/ollama-provider.js.map +1 -0
  90. package/dist/utils/providers/openai-provider.d.ts +18 -0
  91. package/dist/utils/providers/openai-provider.d.ts.map +1 -0
  92. package/dist/utils/providers/openai-provider.js +51 -0
  93. package/dist/utils/providers/openai-provider.js.map +1 -0
  94. package/dist/utils/token-tracker.d.ts +25 -0
  95. package/dist/utils/token-tracker.d.ts.map +1 -0
  96. package/dist/utils/token-tracker.js +45 -0
  97. package/dist/utils/token-tracker.js.map +1 -0
  98. package/dist/whatsapp-test.js +58 -17
  99. package/dist/whatsapp-test.js.map +1 -1
  100. package/package.json +10 -4
package/README.md CHANGED
@@ -1,17 +1,28 @@
1
1
  # Sentinel 🛡️
2
2
 
3
- Sentinel is a high-performance, AI-driven browser automation framework built on top of **Playwright** and powered by **Google Gemini**. It allows you to automate complex web tasks using natural language, extract structured data with ease, and perform robust interactions with self-healing capabilities.
3
+ **Sentinel** is a high-performance, AI-driven browser automation framework built on **Playwright** and powered by **Google Gemini** (or any LLM of your choice). Automate complex web tasks with natural language, extract structured data with Zod, run autonomous multi-step agents, and record & replay workflows — all with self-healing reliability.
4
4
 
5
- Think of it as a **fast, lightweight, and cost-effective alternative to Stagehand**, specifically optimized for the Gemini ecosystem.
5
+ > Think of it as a **fast, lightweight, and cost-effective alternative to BrowserUse, Stagehand, and AutoGPT** with vision grounding, multi-LLM support, and a full agent loop built in.
6
+
7
+ ---
6
8
 
7
9
  ## ✨ Features
8
10
 
9
- - **🗣️ Natural Language Interactions**: Perform actions like `act('Click the "Add to Cart" button')` without writing fragile CSS selectors.
10
- - **📊 Structured Data Extraction**: Use Zod or JSON Schema to extract precisely formatted data from any page.
11
- - **⚡ High Performance**: Optimized with parallel CDP (Chrome DevTools Protocol) requests and smart state caching.
12
- - **🛡️ Robust & Reliable**: Includes a semantic verification loop that confirms every action and automatically retries with fallbacks on failure.
13
- - **🔍 Deep Observation**: Understand page structure through the Accessibility Object Model (AOM) and raw text content.
14
- - **🔧 Playwright Powered**: Direct access to the underlying Playwright `Page` and `BrowserContext`.
11
+ | Feature | Description |
12
+ |---|---|
13
+ | 🗣️ **Natural Language Actions** | `act('Click the login button')` no CSS selectors needed |
14
+ | 📊 **Structured Extraction** | Zod-typed `extract()` with full TypeScript inference |
15
+ | 🤖 **Autonomous Agent Loop** | `run(goal)` Plan Execute Verify Reflect cycle |
16
+ | 👁️ **Vision Grounding** | Gemini Vision fallback when AOM can't find an element |
17
+ | 🗂️ **Multi-Tab & Multi-Browser** | Chromium, Firefox, WebKit + tab management |
18
+ | 🔄 **Record & Replay** | Record workflows, export as TypeScript or JSON, replay anytime |
19
+ | 🔌 **Multi-LLM Support** | Gemini, OpenAI, Claude, Ollama (local) — plug in any provider |
20
+ | 💾 **Session Persistence** | Save & load cookies/localStorage for authenticated workflows |
21
+ | 🕵️ **Stealth & Proxy** | Human-like delays, User-Agent rotation, proxy support |
22
+ | 📡 **Event System** | `sentinel.on('action', ...)` — full observability |
23
+ | 💰 **Token Tracking** | Monitor LLM usage and estimated cost per session |
24
+ | ⚡ **High Performance** | Parallel CDP requests, smart AOM state caching (TTL 500ms) |
25
+ | 🛡️ **Self-Healing** | Semantic verification loop with automatic retry & fallback |
15
26
 
16
27
  ---
17
28
 
@@ -22,101 +33,396 @@ Think of it as a **fast, lightweight, and cost-effective alternative to Stagehan
22
33
  ```bash
23
34
  npm install @isoldex/sentinel playwright
24
35
  ```
25
- > [!NOTE]
26
- > Playwright is a peer dependency. Make sure to install it alongside Sentinel.
27
36
 
28
- ### 2. Configuration
37
+ > **Note:** Playwright is a peer dependency. Install it alongside Sentinel.
29
38
 
30
- Set your Gemini API key in your environment or via a `.env` file:
39
+ ### 2. Configuration
31
40
 
32
41
  ```env
33
42
  GEMINI_API_KEY=your_api_key_here
34
- GEMINI_VERSION=gemini-3-flash-preview # recommended for speed
43
+ GEMINI_VERSION=gemini-2.0-flash # recommended for speed
35
44
  ```
36
45
 
37
- ### 3. Usage
46
+ ### 3. Basic Usage
38
47
 
39
48
  ```typescript
40
49
  import { Sentinel, z } from '@isoldex/sentinel';
41
50
 
42
- async function run() {
43
- const sentinel = new Sentinel({
44
- apiKey: process.env.GEMINI_API_KEY!,
45
- headless: false,
46
- verbose: 1
47
- });
51
+ const sentinel = new Sentinel({
52
+ apiKey: process.env.GEMINI_API_KEY!,
53
+ headless: false,
54
+ verbose: 1,
55
+ });
56
+
57
+ await sentinel.init();
58
+ await sentinel.goto('https://news.ycombinator.com');
59
+
60
+ // Extract structured data with Zod
61
+ const data = await sentinel.extract('Get the top 3 stories', z.object({
62
+ stories: z.array(z.object({
63
+ title: z.string(),
64
+ points: z.number(),
65
+ }))
66
+ }));
67
+ console.log(data.stories);
68
+
69
+ // Natural language action
70
+ await sentinel.act('Click on the "new" link in the header');
71
+
72
+ await sentinel.close();
73
+ ```
74
+
75
+ ---
76
+
77
+ ## 🤖 Autonomous Agent
78
+
79
+ Run a high-level goal autonomously — Sentinel plans, executes, verifies, and reflects until the goal is reached:
80
+
81
+ ```typescript
82
+ const result = await sentinel.run(
83
+ 'Go to Amazon, search for "mechanical keyboard under 100 euros", and extract the top 5 results',
84
+ {
85
+ maxSteps: 20,
86
+ onStep: (event) => console.log(`Step ${event.step}: ${event.action}`),
87
+ }
88
+ );
89
+
90
+ console.log(result.success, result.message);
91
+ console.log(result.extractedData);
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 🔄 Record & Replay
97
+
98
+ Record any workflow and replay it later — or export it as TypeScript code:
99
+
100
+ ```typescript
101
+ // Record
102
+ sentinel.startRecording('my-workflow');
103
+ await sentinel.goto('https://example.com');
104
+ await sentinel.act('Click the sign in button');
105
+ await sentinel.act('Fill "user@example.com" into the email field');
106
+ const workflow = sentinel.stopRecording();
107
+
108
+ // Export as TypeScript
109
+ const code = sentinel.exportWorkflowAsCode(workflow);
110
+ console.log(code);
111
+
112
+ // Export as JSON
113
+ const json = sentinel.exportWorkflowAsJSON(workflow);
114
+
115
+ // Replay
116
+ await sentinel.replay(workflow);
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 🗂️ Multi-Tab & Multi-Browser
48
122
 
49
- await sentinel.init();
123
+ ```typescript
124
+ // Use Firefox instead of Chromium
125
+ const sentinel = new Sentinel({ apiKey: '...', browser: 'firefox' });
126
+
127
+ // Open a second tab
128
+ const tabIndex = await sentinel.newTab('https://google.com');
129
+
130
+ // Switch between tabs
131
+ await sentinel.switchTab(0);
132
+ await sentinel.switchTab(tabIndex);
133
+
134
+ // Close a tab
135
+ await sentinel.closeTab(tabIndex);
136
+
137
+ console.log(sentinel.tabCount); // number of open tabs
138
+ ```
139
+
140
+ ---
50
141
 
51
- // Navigate
52
- await sentinel.goto('https://news.ycombinator.com');
142
+ ## 💾 Session Persistence
53
143
 
54
- // Extract structured data with Zod
55
- const schema = z.object({
56
- topStory: z.string(),
57
- points: z.number()
58
- });
59
-
60
- const data = await sentinel.extract('Get the title and points of the #1 story', schema);
61
- console.log('Top Story:', data);
144
+ Save and restore authenticated sessions — no need to log in every time:
145
+
146
+ ```typescript
147
+ // First run: log in and save session
148
+ await sentinel.goto('https://github.com/login');
149
+ await sentinel.act('Fill "myuser" into the username field');
150
+ await sentinel.act('Fill "mypassword" into the password field');
151
+ await sentinel.act('Click the sign in button');
152
+ await sentinel.saveSession('./sessions/github.json');
153
+
154
+ // Subsequent runs: load session and skip login
155
+ const sentinel = new Sentinel({
156
+ apiKey: '...',
157
+ sessionPath: './sessions/github.json', // auto-loaded on init()
158
+ });
159
+ await sentinel.init();
160
+ await sentinel.goto('https://github.com'); // already logged in!
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 🔌 Multi-LLM Providers
166
+
167
+ Swap out Gemini for OpenAI, Claude, or a local Ollama model:
168
+
169
+ ```typescript
170
+ import { Sentinel, OpenAIProvider, ClaudeProvider, OllamaProvider } from '@isoldex/sentinel';
171
+
172
+ // OpenAI GPT-4o
173
+ const sentinel = new Sentinel({
174
+ apiKey: 'gemini-key', // still required for fallback
175
+ provider: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o' }),
176
+ });
177
+
178
+ // Anthropic Claude
179
+ const sentinel = new Sentinel({
180
+ apiKey: 'gemini-key',
181
+ provider: new ClaudeProvider({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-3-5-sonnet-20241022' }),
182
+ });
183
+
184
+ // Local Ollama (no API key needed)
185
+ const sentinel = new Sentinel({
186
+ apiKey: 'gemini-key',
187
+ provider: new OllamaProvider({ model: 'llama3.2', baseUrl: 'http://localhost:11434' }),
188
+ });
189
+ ```
190
+
191
+ ---
192
+
193
+ ## 👁️ Vision Grounding
194
+
195
+ Enable Gemini Vision as a fallback when the Accessibility Tree can't locate an element (e.g. canvas, shadow DOM, custom components):
196
+
197
+ ```typescript
198
+ const sentinel = new Sentinel({
199
+ apiKey: process.env.GEMINI_API_KEY!,
200
+ visionFallback: true, // enables screenshot-based fallback
201
+ });
202
+
203
+ // Take a screenshot
204
+ const png = await sentinel.screenshot();
205
+
206
+ // Describe the current screen visually
207
+ const description = await sentinel.describeScreen();
208
+ console.log(description);
209
+ ```
210
+
211
+ ---
212
+
213
+ ## 🕵️ Stealth & Proxy
214
+
215
+ ```typescript
216
+ const sentinel = new Sentinel({
217
+ apiKey: '...',
218
+ humanLike: true, // random delays between actions
219
+ proxy: {
220
+ server: 'http://proxy.example.com:8080',
221
+ username: 'user',
222
+ password: 'pass',
223
+ },
224
+ });
225
+ ```
226
+
227
+ ---
228
+
229
+ ## 📡 Events & Observability
230
+
231
+ ```typescript
232
+ sentinel.on('action', (event) => {
233
+ console.log('Action performed:', event.instruction, event.result);
234
+ });
235
+
236
+ sentinel.on('navigate', (event) => {
237
+ console.log('Navigated to:', event.url);
238
+ });
239
+
240
+ sentinel.on('close', () => {
241
+ console.log('Browser closed');
242
+ });
243
+ ```
62
244
 
63
- // Perform natural language actions
64
- await sentinel.act('Click on the "new" link in the header');
245
+ ---
65
246
 
66
- await sentinel.close();
67
- }
247
+ ## 💰 Token Usage & Cost Tracking
68
248
 
69
- run();
249
+ ```typescript
250
+ const usage = sentinel.getTokenUsage();
251
+ console.log(usage);
252
+ // {
253
+ // totalTokens: 12400,
254
+ // inputTokens: 9800,
255
+ // outputTokens: 2600,
256
+ // estimatedCostUsd: 0.003,
257
+ // calls: 14
258
+ // }
259
+
260
+ // Export full log as JSON
261
+ sentinel.exportLogs('./logs/session.json');
70
262
  ```
71
263
 
72
264
  ---
73
265
 
74
- ## 🛠️ API Reference
266
+ ## 🛠️ Full API Reference
75
267
 
76
268
  ### `new Sentinel(options: SentinelOptions)`
77
- - `apiKey`: Your Google AI API key.
78
- - `headless`: Whether to run the browser in the background (default: `false`).
79
- - `verbose`: Logging level (`0` = silent, `1` = actions, `2` = full debug).
80
- - `enableCaching`: Enable state caching between calls (default: `true`).
81
269
 
82
- ### `sentinel.act(instruction: string, options?: ActOptions)`
83
- Performs an action on the page.
84
- - `instruction`: Natural language string (e.g., `"Click the login button"`).
85
- - `options.variables`: Key-value pairs for string interpolation (e.g., `"%query%"`).
270
+ | Option | Type | Default | Description |
271
+ |---|---|---|---|
272
+ | `apiKey` | `string` | | Google Gemini API key |
273
+ | `headless` | `boolean` | `false` | Run browser headlessly |
274
+ | `browser` | `'chromium' \| 'firefox' \| 'webkit'` | `'chromium'` | Browser engine |
275
+ | `viewport` | `{ width, height }` | `1280×720` | Viewport size |
276
+ | `verbose` | `0 \| 1 \| 2` | `1` | Log level (0=silent, 2=debug) |
277
+ | `enableCaching` | `boolean` | `true` | Cache AOM state between calls |
278
+ | `visionFallback` | `boolean` | `false` | Enable Gemini Vision fallback |
279
+ | `provider` | `LLMProvider` | Gemini | Custom LLM provider |
280
+ | `sessionPath` | `string` | — | Path to session file (auto-loaded) |
281
+ | `proxy` | `ProxyOptions` | — | Proxy server configuration |
282
+ | `humanLike` | `boolean` | `false` | Random delays between actions |
283
+ | `domSettleTimeoutMs` | `number` | `3000` | DOM settle wait time (ms) |
284
+
285
+ ---
286
+
287
+ ### Core Methods
288
+
289
+ #### `sentinel.init(): Promise<void>`
290
+ Initialize the browser and all internal engines. Must be called before any other method.
291
+
292
+ #### `sentinel.goto(url: string): Promise<void>`
293
+ Navigate to a URL and wait for the DOM to settle.
294
+
295
+ #### `sentinel.close(): Promise<void>`
296
+ Close the browser and release all resources.
297
+
298
+ ---
299
+
300
+ ### `sentinel.act(instruction, options?): Promise<ActionResult>`
301
+ Perform a natural language action on the page.
302
+
303
+ ```typescript
304
+ await sentinel.act('Click the "Add to Cart" button');
305
+ await sentinel.act('Fill %email% into the email field', { variables: { email: 'user@example.com' } });
306
+ await sentinel.act('Press Enter');
307
+ await sentinel.act('Scroll down');
308
+ await sentinel.act('Select "Germany" from the country dropdown');
309
+ await sentinel.act('Double-click the image');
310
+ ```
311
+
312
+ **Supported action types:** `click`, `fill`, `hover`, `press`, `select`, `double-click`, `right-click`, `scroll-down`, `scroll-up`, `scroll-to`
313
+
314
+ ---
315
+
316
+ ### `sentinel.extract<T>(instruction, schema): Promise<T>`
317
+ Extract structured data from the current page using a Zod schema or JSON Schema.
86
318
 
87
- ### `sentinel.extract<T>(instruction: string, schema: SchemaInput<T>)`
88
- Returns structured data. Supports **Zod schemas** for full TypeScript type inference.
319
+ ```typescript
320
+ const result = await sentinel.extract('Get all product names and prices', z.object({
321
+ products: z.array(z.object({ name: z.string(), price: z.number() }))
322
+ }));
323
+ ```
89
324
 
90
- ### `sentinel.observe(instruction?: string)`
91
- Returns a list of possible interactive elements and their purposes.
325
+ ---
92
326
 
93
- ### `sentinel.page` / `sentinel.context`
94
- Direct access to the underlying **Playwright** `Page` and `BrowserContext`.
327
+ ### `sentinel.observe(instruction?): Promise<ObserveResult[]>`
328
+ Return a list of interactive elements and their purposes.
329
+
330
+ ```typescript
331
+ const elements = await sentinel.observe('Find all navigation links');
332
+ ```
95
333
 
96
334
  ---
97
335
 
98
- ## 🏗️ Development & Contributing
336
+ ### `sentinel.run(goal, options?): Promise<AgentResult>`
337
+ Run an autonomous multi-step agent to achieve a high-level goal.
99
338
 
100
- To develop Sentinel locally:
339
+ | Option | Type | Default | Description |
340
+ |---|---|---|---|
341
+ | `maxSteps` | `number` | `15` | Maximum number of steps |
342
+ | `onStep` | `(event: AgentStepEvent) => void` | — | Step callback |
343
+
344
+ ---
345
+
346
+ ### Tab Management
347
+
348
+ ```typescript
349
+ const idx = await sentinel.newTab(url?) // open new tab, returns index
350
+ await sentinel.switchTab(index) // switch active tab
351
+ await sentinel.closeTab(index) // close tab by index
352
+ sentinel.tabCount // number of open tabs
353
+ ```
354
+
355
+ ---
356
+
357
+ ### Session Management
358
+
359
+ ```typescript
360
+ await sentinel.saveSession(filePath) // save cookies & storage to JSON
361
+ // auto-load via sessionPath option in constructor
362
+ await sentinel.hasLoginForm() // detect if page has a login form
363
+ ```
364
+
365
+ ---
366
+
367
+ ### Recording & Replay
368
+
369
+ ```typescript
370
+ sentinel.startRecording(name?) // start recording actions
371
+ const workflow = sentinel.stopRecording() // stop and get RecordedWorkflow
372
+ sentinel.exportWorkflowAsCode(workflow) // export as TypeScript string
373
+ sentinel.exportWorkflowAsJSON(workflow) // export as JSON string
374
+ await sentinel.replay(workflow) // replay a recorded workflow
375
+ ```
376
+
377
+ ---
378
+
379
+ ### Vision
380
+
381
+ ```typescript
382
+ const png = await sentinel.screenshot() // take a screenshot (Buffer)
383
+ const desc = await sentinel.describeScreen() // visual description via Gemini Vision
384
+ ```
385
+
386
+ ---
387
+
388
+ ### Observability
389
+
390
+ ```typescript
391
+ sentinel.on('action', handler) // emitted after every act()
392
+ sentinel.on('navigate', handler) // emitted after every goto()
393
+ sentinel.on('close', handler) // emitted on close()
394
+ sentinel.getTokenUsage() // token usage + estimated cost
395
+ sentinel.exportLogs(filePath) // export usage log as JSON
396
+ ```
397
+
398
+ ---
399
+
400
+ ## 📁 Examples
401
+
402
+ See the [`examples/`](./examples) folder for ready-to-run scripts:
403
+
404
+ - **`hacker-news.ts`** — Extract top stories from Hacker News
405
+ - **`google-search.ts`** — Search Google, extract results, and demo Record & Replay
406
+ - **`agent-amazon.ts`** — Autonomous shopping agent on Amazon
407
+
408
+ ---
409
+
410
+ ## 🏗️ Development
101
411
 
102
412
  ```bash
103
413
  git clone https://github.com/ArasHuseyin/sentinel.ai.git
104
414
  cd sentinel.ai
105
415
  npm install
106
- npm run build
107
- ```
108
-
109
- ### Running Tests
110
- A comprehensive WhatsApp Web test is included in the source:
111
- ```bash
112
- npx tsc
113
- node dist/whatsapp-test.js
416
+ npx tsc --noEmit # type-check
417
+ npm run build # compile to dist/
114
418
  ```
115
419
 
116
420
  ---
117
421
 
118
422
  ## 🧠 Why Sentinel?
119
423
 
120
- Unlike standard automation tools that rely on brittle XPaths, Sentinel "sees" the page like a human through accessibility trees and visual text. It understands context, handles dynamic content automatically, and verifies its own success.
424
+ Unlike standard automation tools that rely on brittle XPaths or CSS selectors, Sentinel "sees" the page through the **Accessibility Object Model (AOM)** and falls back to **Gemini Vision** when needed. It understands context, verifies its own actions, and can autonomously plan and execute multi-step goals — making it a true AI agent framework, not just a scripting library.
425
+
426
+ ---
121
427
 
122
428
  Licensed under [ISC](LICENSE).
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=action-engine.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-engine.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/action-engine.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,144 @@
1
+ import { jest, describe, it, expect } from '@jest/globals';
2
+ import { ActionEngine } from '../api/act.js';
3
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
4
+ function makeState(overrides = {}) {
5
+ return {
6
+ url: 'https://example.com',
7
+ title: 'Example',
8
+ elements: [
9
+ { id: 0, role: 'button', name: 'Submit', boundingClientRect: { x: 10, y: 20, width: 80, height: 30 } },
10
+ { id: 1, role: 'textbox', name: 'Email', boundingClientRect: { x: 10, y: 60, width: 200, height: 30 } },
11
+ { id: 2, role: 'link', name: 'Home', boundingClientRect: { x: 10, y: 100, width: 60, height: 20 } },
12
+ ],
13
+ ...overrides,
14
+ };
15
+ }
16
+ function makeMockStateParser(state) {
17
+ return {
18
+ parse: jest.fn(async () => state),
19
+ invalidateCache: jest.fn(),
20
+ };
21
+ }
22
+ function makeMockLocator() {
23
+ const locator = {
24
+ click: jest.fn(async () => { }),
25
+ dblclick: jest.fn(async () => { }),
26
+ hover: jest.fn(async () => { }),
27
+ fill: jest.fn(async () => { }),
28
+ focus: jest.fn(async () => { }),
29
+ press: jest.fn(async () => { }),
30
+ pressSequentially: jest.fn(async () => { }),
31
+ selectOption: jest.fn(async () => []),
32
+ scrollIntoViewIfNeeded: jest.fn(async () => { }),
33
+ evaluate: jest.fn(async () => { }),
34
+ boundingBox: jest.fn(async () => ({ x: 10, y: 20, width: 80, height: 30 })),
35
+ first: jest.fn(() => locator),
36
+ };
37
+ return locator;
38
+ }
39
+ function makeMockPage() {
40
+ return {
41
+ url: () => 'https://example.com',
42
+ waitForLoadState: jest.fn(async () => { }),
43
+ mouse: {
44
+ click: jest.fn(async () => { }),
45
+ dblclick: jest.fn(async () => { }),
46
+ move: jest.fn(async () => { }),
47
+ },
48
+ keyboard: {
49
+ press: jest.fn(async () => { }),
50
+ type: jest.fn(async () => { }),
51
+ },
52
+ evaluate: jest.fn(async () => { }),
53
+ waitForTimeout: jest.fn(async () => { }),
54
+ locator: jest.fn(() => makeMockLocator()),
55
+ };
56
+ }
57
+ function makeMockLLM(decision) {
58
+ return {
59
+ generateStructuredData: jest.fn(async () => decision),
60
+ generateText: jest.fn(async () => ''),
61
+ };
62
+ }
63
+ // ─── Tests ────────────────────────────────────────────────────────────────────
64
+ describe('ActionEngine', () => {
65
+ it('returns success for a click action', async () => {
66
+ const page = makeMockPage();
67
+ const stateParser = makeMockStateParser(makeState());
68
+ const llm = makeMockLLM({ elementId: 0, action: 'click', reasoning: 'Submit button found' });
69
+ const engine = new ActionEngine(page, stateParser, llm);
70
+ const result = await engine.act('Click the submit button');
71
+ expect(result.success).toBe(true);
72
+ expect(result.action).toContain('click');
73
+ });
74
+ it('returns success for a fill action with value', async () => {
75
+ const page = makeMockPage();
76
+ const stateParser = makeMockStateParser(makeState());
77
+ const llm = makeMockLLM({ elementId: 1, action: 'fill', value: 'test@example.com', reasoning: 'Email field' });
78
+ const engine = new ActionEngine(page, stateParser, llm);
79
+ const result = await engine.act('Fill in the email field with test@example.com');
80
+ expect(result.success).toBe(true);
81
+ expect(result.action).toContain('fill');
82
+ });
83
+ it('interpolates %variable% placeholders', async () => {
84
+ const page = makeMockPage();
85
+ const stateParser = makeMockStateParser(makeState());
86
+ const llm = makeMockLLM({ elementId: 1, action: 'fill', value: 'user@test.com', reasoning: 'Email field' });
87
+ const engine = new ActionEngine(page, stateParser, llm);
88
+ await engine.act('Fill %email% into the email field', { variables: { email: 'user@test.com' } });
89
+ // The prompt passed to LLM should contain the resolved value
90
+ const promptArg = llm.generateStructuredData.mock.calls[0][0];
91
+ expect(promptArg).toContain('user@test.com');
92
+ expect(promptArg).not.toContain('%email%');
93
+ });
94
+ it('returns failure when element id not found', async () => {
95
+ const page = makeMockPage();
96
+ const stateParser = makeMockStateParser(makeState());
97
+ // LLM returns an elementId that does not exist in state
98
+ const llm = makeMockLLM({ elementId: 99, action: 'click', reasoning: 'Non-existent element' });
99
+ const engine = new ActionEngine(page, stateParser, llm);
100
+ const result = await engine.act('Click something that does not exist');
101
+ expect(result.success).toBe(false);
102
+ });
103
+ it('handles scroll-down without target element (elementId 0)', async () => {
104
+ const page = makeMockPage();
105
+ const stateParser = makeMockStateParser(makeState());
106
+ const llm = makeMockLLM({ elementId: 0, action: 'scroll-down', reasoning: 'Scroll page down' });
107
+ // Use a state with NO element id=0 so isScrollWithoutTarget logic triggers correctly
108
+ const emptyState = makeState({ elements: [] });
109
+ const emptyParser = makeMockStateParser(emptyState);
110
+ const engine = new ActionEngine(page, emptyParser, llm);
111
+ const result = await engine.act('Scroll down the page');
112
+ expect(result.success).toBe(true);
113
+ expect(result.action).toContain('scroll-down');
114
+ });
115
+ it('handles press action with keyboard shortcut', async () => {
116
+ const page = makeMockPage();
117
+ const stateParser = makeMockStateParser(makeState());
118
+ const llm = makeMockLLM({ elementId: 0, action: 'press', value: 'Enter', reasoning: 'Press Enter' });
119
+ const engine = new ActionEngine(page, stateParser, llm);
120
+ const result = await engine.act('Press Enter');
121
+ expect(result.success).toBe(true);
122
+ expect(result.action).toContain('press');
123
+ });
124
+ it('calls stateParser.parse() to get current state', async () => {
125
+ const page = makeMockPage();
126
+ const stateParser = makeMockStateParser(makeState());
127
+ const llm = makeMockLLM({ elementId: 0, action: 'click', reasoning: 'OK' });
128
+ const engine = new ActionEngine(page, stateParser, llm);
129
+ await engine.act('Click submit');
130
+ expect(stateParser.parse).toHaveBeenCalled();
131
+ });
132
+ it('includes page url and title in LLM prompt', async () => {
133
+ const page = makeMockPage();
134
+ const state = makeState({ url: 'https://shop.example.com', title: 'My Shop' });
135
+ const stateParser = makeMockStateParser(state);
136
+ const llm = makeMockLLM({ elementId: 0, action: 'click', reasoning: 'OK' });
137
+ const engine = new ActionEngine(page, stateParser, llm);
138
+ await engine.act('Click submit');
139
+ const promptArg = llm.generateStructuredData.mock.calls[0][0];
140
+ expect(promptArg).toContain('https://shop.example.com');
141
+ expect(promptArg).toContain('My Shop');
142
+ });
143
+ });
144
+ //# sourceMappingURL=action-engine.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-engine.test.js","sourceRoot":"","sources":["../../src/__tests__/action-engine.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=state-parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-parser.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/state-parser.test.ts"],"names":[],"mappings":""}