textweb 0.2.0 โ†’ 0.2.3

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.
@@ -6,16 +6,30 @@
6
6
  "type": "function",
7
7
  "function": {
8
8
  "name": "textweb_navigate",
9
- "description": "Navigate to a URL and render the page as a structured text grid. Interactive elements are annotated with [ref] numbers. Returns the text view (~2-5KB) instead of a screenshot (~1MB). No vision model needed.",
9
+ "description": "Navigate to a URL and render the page as a structured text grid.",
10
10
  "parameters": {
11
11
  "type": "object",
12
12
  "properties": {
13
13
  "url": {
14
14
  "type": "string",
15
15
  "description": "The URL to navigate to"
16
+ },
17
+ "session_id": {
18
+ "type": "string",
19
+ "description": "Optional session id (default: default)"
20
+ },
21
+ "retries": {
22
+ "type": "integer",
23
+ "description": "Retry attempts for flaky transitions"
24
+ },
25
+ "retry_delay_ms": {
26
+ "type": "integer",
27
+ "description": "Delay between retries in ms"
16
28
  }
17
29
  },
18
- "required": ["url"]
30
+ "required": [
31
+ "url"
32
+ ]
19
33
  }
20
34
  }
21
35
  },
@@ -23,16 +37,30 @@
23
37
  "type": "function",
24
38
  "function": {
25
39
  "name": "textweb_click",
26
- "description": "Click an interactive element identified by its [ref] number from the text grid.",
40
+ "description": "Click an interactive element identified by its [ref] number.",
27
41
  "parameters": {
28
42
  "type": "object",
29
43
  "properties": {
30
44
  "ref": {
31
45
  "type": "integer",
32
46
  "description": "Element reference number (e.g., 3 for [3])"
47
+ },
48
+ "session_id": {
49
+ "type": "string",
50
+ "description": "Optional session id (default: default)"
51
+ },
52
+ "retries": {
53
+ "type": "integer",
54
+ "description": "Retry attempts for flaky transitions"
55
+ },
56
+ "retry_delay_ms": {
57
+ "type": "integer",
58
+ "description": "Delay between retries in ms"
33
59
  }
34
60
  },
35
- "required": ["ref"]
61
+ "required": [
62
+ "ref"
63
+ ]
36
64
  }
37
65
  }
38
66
  },
@@ -40,7 +68,7 @@
40
68
  "type": "function",
41
69
  "function": {
42
70
  "name": "textweb_type",
43
- "description": "Type text into an input field identified by its [ref] number. Replaces existing content.",
71
+ "description": "Type text into an input field identified by its [ref] number.",
44
72
  "parameters": {
45
73
  "type": "object",
46
74
  "properties": {
@@ -51,9 +79,24 @@
51
79
  "text": {
52
80
  "type": "string",
53
81
  "description": "Text to enter into the field"
82
+ },
83
+ "session_id": {
84
+ "type": "string",
85
+ "description": "Optional session id (default: default)"
86
+ },
87
+ "retries": {
88
+ "type": "integer",
89
+ "description": "Retry attempts for flaky transitions"
90
+ },
91
+ "retry_delay_ms": {
92
+ "type": "integer",
93
+ "description": "Delay between retries in ms"
54
94
  }
55
95
  },
56
- "required": ["ref", "text"]
96
+ "required": [
97
+ "ref",
98
+ "text"
99
+ ]
57
100
  }
58
101
  }
59
102
  },
@@ -72,9 +115,91 @@
72
115
  "value": {
73
116
  "type": "string",
74
117
  "description": "Option value or visible text to select"
118
+ },
119
+ "session_id": {
120
+ "type": "string",
121
+ "description": "Optional session id (default: default)"
122
+ },
123
+ "retries": {
124
+ "type": "integer",
125
+ "description": "Retry attempts for flaky transitions"
126
+ },
127
+ "retry_delay_ms": {
128
+ "type": "integer",
129
+ "description": "Delay between retries in ms"
75
130
  }
76
131
  },
77
- "required": ["ref", "value"]
132
+ "required": [
133
+ "ref",
134
+ "value"
135
+ ]
136
+ }
137
+ }
138
+ },
139
+ {
140
+ "type": "function",
141
+ "function": {
142
+ "name": "textweb_upload",
143
+ "description": "Upload a file to a file input element.",
144
+ "parameters": {
145
+ "type": "object",
146
+ "properties": {
147
+ "ref": {
148
+ "type": "integer",
149
+ "description": "Element reference number of the file input"
150
+ },
151
+ "path": {
152
+ "type": "string",
153
+ "description": "Absolute file path to upload"
154
+ },
155
+ "session_id": {
156
+ "type": "string",
157
+ "description": "Optional session id (default: default)"
158
+ },
159
+ "retries": {
160
+ "type": "integer",
161
+ "description": "Retry attempts for flaky transitions"
162
+ },
163
+ "retry_delay_ms": {
164
+ "type": "integer",
165
+ "description": "Delay between retries in ms"
166
+ }
167
+ },
168
+ "required": [
169
+ "ref",
170
+ "path"
171
+ ]
172
+ }
173
+ }
174
+ },
175
+ {
176
+ "type": "function",
177
+ "function": {
178
+ "name": "textweb_press",
179
+ "description": "Press a keyboard key (Enter, Tab, Escape, ArrowDown, etc.).",
180
+ "parameters": {
181
+ "type": "object",
182
+ "properties": {
183
+ "key": {
184
+ "type": "string",
185
+ "description": "Key name (e.g., Enter, Tab, Escape)"
186
+ },
187
+ "session_id": {
188
+ "type": "string",
189
+ "description": "Optional session id (default: default)"
190
+ },
191
+ "retries": {
192
+ "type": "integer",
193
+ "description": "Retry attempts for flaky transitions"
194
+ },
195
+ "retry_delay_ms": {
196
+ "type": "integer",
197
+ "description": "Delay between retries in ms"
198
+ }
199
+ },
200
+ "required": [
201
+ "key"
202
+ ]
78
203
  }
79
204
  }
80
205
  },
@@ -82,22 +207,32 @@
82
207
  "type": "function",
83
208
  "function": {
84
209
  "name": "textweb_scroll",
85
- "description": "Scroll the current page. Returns the updated text grid.",
210
+ "description": "Scroll the current page.",
86
211
  "parameters": {
87
212
  "type": "object",
88
213
  "properties": {
89
214
  "direction": {
90
215
  "type": "string",
91
- "enum": ["up", "down", "top"],
216
+ "enum": [
217
+ "up",
218
+ "down",
219
+ "top"
220
+ ],
92
221
  "description": "Scroll direction"
93
222
  },
94
223
  "amount": {
95
224
  "type": "integer",
96
225
  "description": "Pages to scroll (default: 1)",
97
226
  "default": 1
227
+ },
228
+ "session_id": {
229
+ "type": "string",
230
+ "description": "Optional session id (default: default)"
98
231
  }
99
232
  },
100
- "required": ["direction"]
233
+ "required": [
234
+ "direction"
235
+ ]
101
236
  }
102
237
  }
103
238
  },
@@ -105,50 +240,190 @@
105
240
  "type": "function",
106
241
  "function": {
107
242
  "name": "textweb_snapshot",
108
- "description": "Re-render the current page without navigating. Use after waiting for dynamic content.",
243
+ "description": "Re-render the current page without navigating.",
109
244
  "parameters": {
110
245
  "type": "object",
111
- "properties": {}
246
+ "properties": {
247
+ "session_id": {
248
+ "type": "string",
249
+ "description": "Optional session id (default: default)"
250
+ }
251
+ }
112
252
  }
113
253
  }
114
254
  },
115
255
  {
116
256
  "type": "function",
117
257
  "function": {
118
- "name": "textweb_press",
119
- "description": "Press a keyboard key (Enter, Tab, Escape, ArrowDown, etc.).",
258
+ "name": "textweb_storage_save",
259
+ "description": "Save cookies/localStorage/sessionStorage to a storageState JSON file.",
120
260
  "parameters": {
121
261
  "type": "object",
122
262
  "properties": {
123
- "key": {
263
+ "path": {
124
264
  "type": "string",
125
- "description": "Key name (e.g., 'Enter', 'Tab', 'Escape')"
265
+ "description": "Absolute output path for storage state JSON"
266
+ },
267
+ "session_id": {
268
+ "type": "string",
269
+ "description": "Optional session id (default: default)"
126
270
  }
127
271
  },
128
- "required": ["key"]
272
+ "required": [
273
+ "path"
274
+ ]
129
275
  }
130
276
  }
131
277
  },
132
278
  {
133
279
  "type": "function",
134
280
  "function": {
135
- "name": "textweb_upload",
136
- "description": "Upload a file to a file input element.",
281
+ "name": "textweb_storage_load",
282
+ "description": "Load cookies/localStorage/sessionStorage from a storageState JSON file.",
283
+ "parameters": {
284
+ "type": "object",
285
+ "properties": {
286
+ "path": {
287
+ "type": "string",
288
+ "description": "Absolute storage state path"
289
+ },
290
+ "session_id": {
291
+ "type": "string",
292
+ "description": "Optional session id (default: default)"
293
+ }
294
+ },
295
+ "required": [
296
+ "path"
297
+ ]
298
+ }
299
+ }
300
+ },
301
+ {
302
+ "type": "function",
303
+ "function": {
304
+ "name": "textweb_wait_for",
305
+ "description": "Wait for selector/text/url state during multi-step flows.",
306
+ "parameters": {
307
+ "type": "object",
308
+ "properties": {
309
+ "selector": {
310
+ "type": "string",
311
+ "description": "CSS selector to wait for"
312
+ },
313
+ "text": {
314
+ "type": "string",
315
+ "description": "Page text to wait for"
316
+ },
317
+ "url_includes": {
318
+ "type": "string",
319
+ "description": "Substring expected in URL"
320
+ },
321
+ "state": {
322
+ "type": "string",
323
+ "enum": [
324
+ "attached",
325
+ "detached",
326
+ "visible",
327
+ "hidden"
328
+ ],
329
+ "description": "Selector state"
330
+ },
331
+ "timeout_ms": {
332
+ "type": "integer",
333
+ "description": "Timeout milliseconds"
334
+ },
335
+ "session_id": {
336
+ "type": "string",
337
+ "description": "Optional session id (default: default)"
338
+ },
339
+ "retries": {
340
+ "type": "integer",
341
+ "description": "Retry attempts for flaky transitions"
342
+ },
343
+ "retry_delay_ms": {
344
+ "type": "integer",
345
+ "description": "Delay between retries in ms"
346
+ },
347
+ "poll_ms": {
348
+ "type": "integer",
349
+ "description": "Polling interval for text/url waits"
350
+ }
351
+ }
352
+ }
353
+ }
354
+ },
355
+ {
356
+ "type": "function",
357
+ "function": {
358
+ "name": "textweb_assert_field",
359
+ "description": "Assert field value/text by element ref.",
137
360
  "parameters": {
138
361
  "type": "object",
139
362
  "properties": {
140
363
  "ref": {
141
364
  "type": "integer",
142
- "description": "Element reference number of the file input"
365
+ "description": "Element reference number"
143
366
  },
144
- "path": {
367
+ "expected": {
145
368
  "type": "string",
146
- "description": "Absolute file path to upload"
369
+ "description": "Expected value or content"
370
+ },
371
+ "comparator": {
372
+ "type": "string",
373
+ "enum": [
374
+ "equals",
375
+ "includes",
376
+ "regex",
377
+ "not_empty"
378
+ ],
379
+ "description": "Comparison mode"
380
+ },
381
+ "attribute": {
382
+ "type": "string",
383
+ "description": "Optional attribute to assert"
384
+ },
385
+ "session_id": {
386
+ "type": "string",
387
+ "description": "Optional session id (default: default)"
147
388
  }
148
389
  },
149
- "required": ["ref", "path"]
390
+ "required": [
391
+ "ref",
392
+ "expected"
393
+ ]
394
+ }
395
+ }
396
+ },
397
+ {
398
+ "type": "function",
399
+ "function": {
400
+ "name": "textweb_session_list",
401
+ "description": "List active textweb sessions and metadata.",
402
+ "parameters": {
403
+ "type": "object",
404
+ "properties": {}
405
+ }
406
+ }
407
+ },
408
+ {
409
+ "type": "function",
410
+ "function": {
411
+ "name": "textweb_session_close",
412
+ "description": "Close one session by session_id, or all sessions when all=true.",
413
+ "parameters": {
414
+ "type": "object",
415
+ "properties": {
416
+ "session_id": {
417
+ "type": "string",
418
+ "description": "Session id to close (default: default)"
419
+ },
420
+ "all": {
421
+ "type": "boolean",
422
+ "description": "Close all active sessions"
423
+ }
424
+ }
150
425
  }
151
426
  }
152
427
  }
153
428
  ]
154
- }
429
+ }
@@ -1,153 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>TextWeb Job Pipeline</title>
7
- <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
- body {
10
- font-family: -apple-system, BlinkMacSystemFont, "SF Pro", system-ui, sans-serif;
11
- background: linear-gradient(135deg, #0a0a1a 0%, #1a1a3e 50%, #0a1628 100%);
12
- color: #e8e8f0; min-height: 100vh; padding: 16px; overflow-y: auto;
13
- }
14
- .hdr { text-align: center; margin-bottom: 14px; }
15
- .hdr h1 {
16
- font-size: 20px; font-weight: 700;
17
- background: linear-gradient(90deg, #7b68ee, #00d4ff);
18
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
19
- }
20
- .sub { font-size: 11px; color: #888; margin-top: 2px; }
21
- .stats { display: flex; gap: 8px; justify-content: center; margin-bottom: 12px; flex-wrap: wrap; }
22
- .st {
23
- background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1);
24
- border-radius: 10px; padding: 8px 12px; text-align: center; flex: 1; max-width: 90px; min-width: 70px;
25
- }
26
- .st .n { font-size: 24px; font-weight: 800; line-height: 1; }
27
- .st .l { font-size: 8px; color: #999; margin-top: 2px; text-transform: uppercase; letter-spacing: 0.5px; }
28
- .st.ok .n { color: #4ade80; }
29
- .st.mb .n { color: #fbbf24; }
30
- .st.fa .n { color: #f87171; }
31
- .st.tt .n { color: #7b68ee; }
32
- .st.ac .n { color: #38bdf8; }
33
- .btns { display: flex; gap: 6px; justify-content: center; margin-bottom: 12px; flex-wrap: wrap; }
34
- .btn {
35
- background: rgba(123,104,238,0.2); border: 1px solid rgba(123,104,238,0.4);
36
- border-radius: 8px; padding: 7px 14px; color: #c8c8ff; font-size: 11px;
37
- cursor: pointer; font-weight: 600; transition: all 0.15s; white-space: nowrap;
38
- }
39
- .btn:hover { background: rgba(123,104,238,0.35); transform: scale(1.02); }
40
- .btn:active { transform: scale(0.98); }
41
- .btn.green { background: rgba(74,222,128,0.15); border-color: rgba(74,222,128,0.3); color: #86efac; }
42
- .btn.amber { background: rgba(251,191,36,0.15); border-color: rgba(251,191,36,0.3); color: #fde68a; }
43
- .jl {
44
- background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08);
45
- border-radius: 10px; overflow: hidden; margin-bottom: 10px;
46
- }
47
- .sl {
48
- font-size: 9px; text-transform: uppercase; letter-spacing: 1px; color: #666;
49
- padding: 6px 12px 3px; background: rgba(255,255,255,0.02);
50
- }
51
- .sl.act { color: #38bdf8; background: rgba(56,189,248,0.05); }
52
- .sl.q { color: #a78bfa; }
53
- .j {
54
- display: flex; align-items: center; padding: 7px 12px;
55
- border-bottom: 1px solid rgba(255,255,255,0.05); gap: 8px; font-size: 12px;
56
- transition: background 0.15s;
57
- }
58
- .j:last-child { border-bottom: none; }
59
- .j:hover { background: rgba(255,255,255,0.03); }
60
- .j.active { background: rgba(56,189,248,0.08); animation: activePulse 2s infinite; }
61
- .j .i { font-size: 14px; flex-shrink: 0; }
62
- .j .c { font-weight: 600; color: #c8c8ff; min-width: 75px; font-size: 11px; }
63
- .j .t { color: #aaa; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }
64
- .j .f { font-size: 10px; color: #666; flex-shrink: 0; font-variant-numeric: tabular-nums; }
65
- .j .tm { font-size: 9px; color: #555; flex-shrink: 0; }
66
- @keyframes activePulse { 0%,100%{background:rgba(56,189,248,0.08)} 50%{background:rgba(56,189,248,0.15)} }
67
- .live { text-align: center; margin-top: 10px; font-size: 10px; color: #666; min-height: 20px; }
68
- .dot {
69
- display: inline-block; width: 6px; height: 6px; border-radius: 50%;
70
- background: #4ade80; margin-right: 4px; animation: jpulse 2s infinite;
71
- }
72
- .dot.active { background: #38bdf8; animation: jpulse 0.8s infinite; }
73
- @keyframes jpulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
74
- .ft { text-align: center; margin-top: 6px; font-size: 9px; color: #444; }
75
- .empty { text-align: center; padding: 30px 20px; color: #666; }
76
- .empty .big { font-size: 40px; margin-bottom: 10px; }
77
- </style>
78
- </head>
79
- <body>
80
- <div class="hdr">
81
- <h1>๐ŸฆŠ TextWeb Job Pipeline</h1>
82
- <div class="sub">Zero screenshots ยท Zero API cost ยท Local LLM</div>
83
- </div>
84
- <div class="stats" id="stats"></div>
85
- <div class="btns">
86
- <div class="btn green" onclick="agentMsg('Find and apply to 5 more engineering leadership jobs via the TextWeb pipeline')">๐Ÿ” Find + Apply</div>
87
- <div class="btn" onclick="agentMsg('Discover new jobs on Greenhouse boards and show them on the canvas dashboard')">๐Ÿ“‹ Discover</div>
88
- <div class="btn amber" onclick="agentMsg('Check my email for any job application confirmations or responses')">๐Ÿ“ฌ Inbox</div>
89
- </div>
90
- <div class="jl" id="jobList"></div>
91
- <div class="live" id="liveStatus"></div>
92
- <div class="ft">TextWeb v0.1.1 ยท Gemma 3 4B ยท Playwright headless</div>
93
- <script>
94
- function agentMsg(msg) {
95
- window.location.href = 'openclaw://agent?message=' + encodeURIComponent(msg);
96
- }
97
- function esc(s) { return (s||'').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
98
- function relTime(iso) {
99
- if (!iso) return '';
100
- const mins = Math.floor((Date.now() - new Date(iso).getTime()) / 60000);
101
- if (mins < 1) return 'now';
102
- if (mins < 60) return mins + 'm';
103
- const hrs = Math.floor(mins / 60);
104
- if (hrs < 24) return hrs + 'h';
105
- return new Date(iso).toLocaleDateString('en-US',{month:'short',day:'numeric'});
106
- }
107
- function render(data) {
108
- const jobs = data.jobs || [];
109
- const icons = {submitted:'โœ…',probable:'๐ŸŸก',active:'โณ',failed:'โŒ',queued:'๐Ÿ“‹',filling:'โœ๏ธ',uploading:'๐Ÿ“Ž',llm:'๐Ÿค–',submitting:'๐Ÿ”˜'};
110
- const activeStatuses = ['active','filling','uploading','llm','submitting'];
111
- const confirmed = jobs.filter(j=>j.status==='submitted').length;
112
- const probable = jobs.filter(j=>j.status==='probable').length;
113
- const active = jobs.filter(j=>activeStatuses.includes(j.status)).length;
114
- const failed = jobs.filter(j=>j.status==='failed').length;
115
- const queued = jobs.filter(j=>j.status==='queued').length;
116
- let sh = `<div class="st ok"><div class="n">${confirmed}</div><div class="l">Confirmed</div></div>
117
- <div class="st mb"><div class="n">${probable}</div><div class="l">Probable</div></div>`;
118
- if(active) sh += `<div class="st ac"><div class="n">${active}</div><div class="l">Active</div></div>`;
119
- if(queued) sh += `<div class="st" style="border-color:rgba(167,139,250,0.3)"><div class="n" style="color:#a78bfa">${queued}</div><div class="l">Queued</div></div>`;
120
- if(failed) sh += `<div class="st fa"><div class="n">${failed}</div><div class="l">Failed</div></div>`;
121
- sh += `<div class="st tt"><div class="n">${jobs.length}</div><div class="l">Total</div></div>`;
122
- document.getElementById('stats').innerHTML = sh;
123
- const groups = [
124
- {label:'๐Ÿ”ด ACTIVE',cls:'act',statuses:activeStatuses},
125
- {label:'QUEUED',cls:'q',statuses:['queued']},
126
- {label:'CONFIRMED',cls:'',statuses:['submitted']},
127
- {label:'PROBABLE',cls:'',statuses:['probable']},
128
- {label:'FAILED',cls:'',statuses:['failed']},
129
- ];
130
- let lh = '';
131
- for (const g of groups) {
132
- const items = jobs.filter(j=>g.statuses.includes(j.status));
133
- if (!items.length) continue;
134
- lh += `<div class="sl ${g.cls}">${g.label}</div>`;
135
- for (const j of items) {
136
- const f = j.autoFields!=null ? `${j.autoFields}+${j.llmFields||0}` : '';
137
- const t = relTime(j.submittedAt||j.startedAt||j.queuedAt);
138
- lh += `<div class="j${activeStatuses.includes(j.status)?' active':''}"><span class="i">${icons[j.status]||'๐Ÿ“‹'}</span><span class="c">${esc(j.company)}</span><span class="t">${esc(j.title)}</span><span class="f">${f}</span><span class="tm">${t}</span></div>`;
139
- }
140
- }
141
- if(!jobs.length) lh = '<div class="empty"><div class="big">๐ŸฆŠ</div>No jobs yet. Click Find + Apply!</div>';
142
- document.getElementById('jobList').innerHTML = lh;
143
- const live = document.getElementById('liveStatus');
144
- if(data.liveStatus) live.innerHTML = `<span class="dot active"></span>${esc(data.liveStatus)}`;
145
- else if(active) live.innerHTML = '<span class="dot active"></span>Applying...';
146
- else live.innerHTML = `<span class="dot"></span>Idle ยท ${new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'})}`;
147
- }
148
- // Use embedded state (injected by dashboard.js) or empty
149
- const state = window.__PIPELINE_STATE__ || {jobs:[]};
150
- render(state);
151
- </script>
152
- </body>
153
- </html>