textweb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/docs/index.html +761 -0
- package/mcp/index.js +275 -0
- package/package.json +34 -0
- package/src/apply.js +565 -0
- package/src/browser.js +134 -0
- package/src/cli.js +427 -0
- package/src/renderer.js +452 -0
- package/src/server.js +504 -0
- package/tools/crewai.py +128 -0
- package/tools/langchain.py +165 -0
- package/tools/system_prompt.md +37 -0
- package/tools/tool_definitions.json +154 -0
package/docs/index.html
ADDED
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>TextWeb — See the web without screenshots</title>
|
|
7
|
+
<meta name="description" content="A text-grid web renderer for AI agents. 500x fewer tokens than screenshots. No vision model needed.">
|
|
8
|
+
<meta property="og:title" content="TextWeb — Text-grid browser for AI agents">
|
|
9
|
+
<meta property="og:description" content="Render web pages as structured text grids. 2-5KB instead of 1MB screenshots. No vision model needed.">
|
|
10
|
+
<meta property="og:type" content="website">
|
|
11
|
+
<meta property="og:url" content="https://chrisrobison.github.io/textweb">
|
|
12
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
13
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
14
|
+
<style>
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #0a0e17;
|
|
17
|
+
--surface: #121825;
|
|
18
|
+
--surface2: #1a2235;
|
|
19
|
+
--border: #2a3550;
|
|
20
|
+
--text: #e2e8f0;
|
|
21
|
+
--text-dim: #8892a8;
|
|
22
|
+
--accent: #22d3ee;
|
|
23
|
+
--accent2: #a78bfa;
|
|
24
|
+
--green: #4ade80;
|
|
25
|
+
--red: #f87171;
|
|
26
|
+
--orange: #fb923c;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
30
|
+
|
|
31
|
+
body {
|
|
32
|
+
font-family: 'Inter', -apple-system, sans-serif;
|
|
33
|
+
background: var(--bg);
|
|
34
|
+
color: var(--text);
|
|
35
|
+
line-height: 1.6;
|
|
36
|
+
overflow-x: hidden;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Hero */
|
|
40
|
+
.hero {
|
|
41
|
+
min-height: 90vh;
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
text-align: center;
|
|
47
|
+
padding: 2rem;
|
|
48
|
+
position: relative;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.hero::before {
|
|
52
|
+
content: '';
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
55
|
+
background:
|
|
56
|
+
radial-gradient(ellipse 60% 40% at 50% 0%, rgba(34,211,238,0.08) 0%, transparent 100%),
|
|
57
|
+
radial-gradient(ellipse 40% 30% at 80% 20%, rgba(167,139,250,0.06) 0%, transparent 100%);
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.badge {
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 0.5rem;
|
|
65
|
+
padding: 0.4rem 1rem;
|
|
66
|
+
border-radius: 999px;
|
|
67
|
+
border: 1px solid var(--border);
|
|
68
|
+
background: var(--surface);
|
|
69
|
+
font-size: 0.85rem;
|
|
70
|
+
color: var(--accent);
|
|
71
|
+
margin-bottom: 2rem;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
h1 {
|
|
76
|
+
font-size: clamp(2.5rem, 6vw, 4.5rem);
|
|
77
|
+
font-weight: 800;
|
|
78
|
+
line-height: 1.1;
|
|
79
|
+
margin-bottom: 1.5rem;
|
|
80
|
+
letter-spacing: -0.03em;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
h1 .highlight {
|
|
84
|
+
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
85
|
+
-webkit-background-clip: text;
|
|
86
|
+
-webkit-text-fill-color: transparent;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.subtitle {
|
|
90
|
+
font-size: 1.25rem;
|
|
91
|
+
color: var(--text-dim);
|
|
92
|
+
max-width: 600px;
|
|
93
|
+
margin-bottom: 2.5rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.hero-actions {
|
|
97
|
+
display: flex;
|
|
98
|
+
gap: 1rem;
|
|
99
|
+
flex-wrap: wrap;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.btn {
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 0.5rem;
|
|
107
|
+
padding: 0.75rem 1.5rem;
|
|
108
|
+
border-radius: 8px;
|
|
109
|
+
font-size: 1rem;
|
|
110
|
+
font-weight: 600;
|
|
111
|
+
text-decoration: none;
|
|
112
|
+
transition: all 0.2s;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
border: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.btn-primary {
|
|
118
|
+
background: linear-gradient(135deg, var(--accent), #06b6d4);
|
|
119
|
+
color: #0a0e17;
|
|
120
|
+
}
|
|
121
|
+
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 30px rgba(34,211,238,0.3); }
|
|
122
|
+
|
|
123
|
+
.btn-secondary {
|
|
124
|
+
background: var(--surface2);
|
|
125
|
+
color: var(--text);
|
|
126
|
+
border: 1px solid var(--border);
|
|
127
|
+
}
|
|
128
|
+
.btn-secondary:hover { border-color: var(--accent); }
|
|
129
|
+
|
|
130
|
+
/* Install bar */
|
|
131
|
+
.install-bar {
|
|
132
|
+
margin-top: 2.5rem;
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
gap: 0.75rem;
|
|
136
|
+
background: var(--surface);
|
|
137
|
+
border: 1px solid var(--border);
|
|
138
|
+
border-radius: 10px;
|
|
139
|
+
padding: 0.6rem 1rem;
|
|
140
|
+
font-family: 'JetBrains Mono', monospace;
|
|
141
|
+
font-size: 0.95rem;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.install-bar .prompt { color: var(--green); user-select: none; }
|
|
145
|
+
.install-bar code { color: var(--text); }
|
|
146
|
+
.install-bar .copy-btn {
|
|
147
|
+
background: none;
|
|
148
|
+
border: none;
|
|
149
|
+
color: var(--text-dim);
|
|
150
|
+
cursor: pointer;
|
|
151
|
+
padding: 0.25rem;
|
|
152
|
+
font-size: 1.1rem;
|
|
153
|
+
transition: color 0.2s;
|
|
154
|
+
}
|
|
155
|
+
.install-bar .copy-btn:hover { color: var(--accent); }
|
|
156
|
+
|
|
157
|
+
/* Comparison */
|
|
158
|
+
.comparison {
|
|
159
|
+
padding: 5rem 2rem;
|
|
160
|
+
max-width: 1100px;
|
|
161
|
+
margin: 0 auto;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.comparison h2 {
|
|
165
|
+
text-align: center;
|
|
166
|
+
font-size: 2rem;
|
|
167
|
+
margin-bottom: 3rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.compare-grid {
|
|
171
|
+
display: grid;
|
|
172
|
+
grid-template-columns: 1fr 1fr;
|
|
173
|
+
gap: 2rem;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@media (max-width: 768px) {
|
|
177
|
+
.compare-grid { grid-template-columns: 1fr; }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.compare-card {
|
|
181
|
+
background: var(--surface);
|
|
182
|
+
border: 1px solid var(--border);
|
|
183
|
+
border-radius: 12px;
|
|
184
|
+
overflow: hidden;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.compare-card.bad { border-color: rgba(248,113,113,0.3); }
|
|
188
|
+
.compare-card.good { border-color: rgba(34,211,238,0.3); }
|
|
189
|
+
|
|
190
|
+
.compare-header {
|
|
191
|
+
padding: 1rem 1.5rem;
|
|
192
|
+
font-weight: 700;
|
|
193
|
+
font-size: 0.85rem;
|
|
194
|
+
text-transform: uppercase;
|
|
195
|
+
letter-spacing: 0.05em;
|
|
196
|
+
display: flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
justify-content: space-between;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.compare-card.bad .compare-header { background: rgba(248,113,113,0.08); color: var(--red); }
|
|
202
|
+
.compare-card.good .compare-header { background: rgba(34,211,238,0.08); color: var(--accent); }
|
|
203
|
+
|
|
204
|
+
.compare-body {
|
|
205
|
+
padding: 1.5rem;
|
|
206
|
+
font-family: 'JetBrains Mono', monospace;
|
|
207
|
+
font-size: 0.75rem;
|
|
208
|
+
line-height: 1.5;
|
|
209
|
+
color: var(--text-dim);
|
|
210
|
+
white-space: pre;
|
|
211
|
+
overflow-x: auto;
|
|
212
|
+
min-height: 200px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.compare-card.bad .compare-body {
|
|
216
|
+
display: flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
justify-content: center;
|
|
219
|
+
flex-direction: column;
|
|
220
|
+
gap: 1rem;
|
|
221
|
+
min-height: 200px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.blob {
|
|
225
|
+
width: 120px;
|
|
226
|
+
height: 80px;
|
|
227
|
+
background: linear-gradient(135deg, #1a1a2e, #2a2a4e, #1a1a2e);
|
|
228
|
+
border-radius: 8px;
|
|
229
|
+
opacity: 0.5;
|
|
230
|
+
position: relative;
|
|
231
|
+
}
|
|
232
|
+
.blob::after {
|
|
233
|
+
content: '📸 1.2 MB screenshot';
|
|
234
|
+
position: absolute;
|
|
235
|
+
bottom: -1.8rem;
|
|
236
|
+
left: 50%;
|
|
237
|
+
transform: translateX(-50%);
|
|
238
|
+
white-space: nowrap;
|
|
239
|
+
font-size: 0.75rem;
|
|
240
|
+
color: var(--red);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.size-tag {
|
|
244
|
+
font-size: 0.8rem;
|
|
245
|
+
padding: 0.2rem 0.6rem;
|
|
246
|
+
border-radius: 4px;
|
|
247
|
+
font-weight: 600;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.compare-card.bad .size-tag { background: rgba(248,113,113,0.15); color: var(--red); }
|
|
251
|
+
.compare-card.good .size-tag { background: rgba(34,211,238,0.15); color: var(--accent); }
|
|
252
|
+
|
|
253
|
+
/* Stats */
|
|
254
|
+
.stats {
|
|
255
|
+
display: grid;
|
|
256
|
+
grid-template-columns: repeat(3, 1fr);
|
|
257
|
+
gap: 1.5rem;
|
|
258
|
+
max-width: 800px;
|
|
259
|
+
margin: 4rem auto;
|
|
260
|
+
padding: 0 2rem;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@media (max-width: 600px) {
|
|
264
|
+
.stats { grid-template-columns: 1fr; }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.stat {
|
|
268
|
+
text-align: center;
|
|
269
|
+
padding: 2rem;
|
|
270
|
+
background: var(--surface);
|
|
271
|
+
border: 1px solid var(--border);
|
|
272
|
+
border-radius: 12px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.stat-value {
|
|
276
|
+
font-size: 2.5rem;
|
|
277
|
+
font-weight: 800;
|
|
278
|
+
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
279
|
+
-webkit-background-clip: text;
|
|
280
|
+
-webkit-text-fill-color: transparent;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.stat-label {
|
|
284
|
+
font-size: 0.9rem;
|
|
285
|
+
color: var(--text-dim);
|
|
286
|
+
margin-top: 0.25rem;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* How it works */
|
|
290
|
+
.how-it-works {
|
|
291
|
+
padding: 5rem 2rem;
|
|
292
|
+
max-width: 900px;
|
|
293
|
+
margin: 0 auto;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.how-it-works h2 {
|
|
297
|
+
text-align: center;
|
|
298
|
+
font-size: 2rem;
|
|
299
|
+
margin-bottom: 3rem;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.steps {
|
|
303
|
+
display: flex;
|
|
304
|
+
flex-direction: column;
|
|
305
|
+
gap: 2rem;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.step {
|
|
309
|
+
display: flex;
|
|
310
|
+
gap: 1.5rem;
|
|
311
|
+
align-items: flex-start;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.step-num {
|
|
315
|
+
flex-shrink: 0;
|
|
316
|
+
width: 3rem;
|
|
317
|
+
height: 3rem;
|
|
318
|
+
border-radius: 50%;
|
|
319
|
+
background: var(--surface2);
|
|
320
|
+
border: 2px solid var(--accent);
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
justify-content: center;
|
|
324
|
+
font-weight: 700;
|
|
325
|
+
font-size: 1.1rem;
|
|
326
|
+
color: var(--accent);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.step-content h3 {
|
|
330
|
+
font-size: 1.15rem;
|
|
331
|
+
margin-bottom: 0.3rem;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.step-content p {
|
|
335
|
+
color: var(--text-dim);
|
|
336
|
+
font-size: 0.95rem;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Code examples */
|
|
340
|
+
.examples {
|
|
341
|
+
padding: 5rem 2rem;
|
|
342
|
+
max-width: 900px;
|
|
343
|
+
margin: 0 auto;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.examples h2 {
|
|
347
|
+
text-align: center;
|
|
348
|
+
font-size: 2rem;
|
|
349
|
+
margin-bottom: 0.5rem;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.examples .subtitle {
|
|
353
|
+
text-align: center;
|
|
354
|
+
margin: 0 auto 3rem;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.code-tabs {
|
|
358
|
+
display: flex;
|
|
359
|
+
gap: 0;
|
|
360
|
+
margin-bottom: 0;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.code-tab {
|
|
364
|
+
padding: 0.6rem 1.2rem;
|
|
365
|
+
background: var(--surface);
|
|
366
|
+
border: 1px solid var(--border);
|
|
367
|
+
border-bottom: none;
|
|
368
|
+
cursor: pointer;
|
|
369
|
+
font-size: 0.85rem;
|
|
370
|
+
font-weight: 600;
|
|
371
|
+
color: var(--text-dim);
|
|
372
|
+
border-radius: 8px 8px 0 0;
|
|
373
|
+
transition: all 0.2s;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.code-tab.active {
|
|
377
|
+
background: var(--surface2);
|
|
378
|
+
color: var(--accent);
|
|
379
|
+
border-color: var(--accent);
|
|
380
|
+
border-bottom: 1px solid var(--surface2);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.code-block {
|
|
384
|
+
background: var(--surface2);
|
|
385
|
+
border: 1px solid var(--border);
|
|
386
|
+
border-radius: 0 12px 12px 12px;
|
|
387
|
+
padding: 1.5rem;
|
|
388
|
+
font-family: 'JetBrains Mono', monospace;
|
|
389
|
+
font-size: 0.85rem;
|
|
390
|
+
line-height: 1.7;
|
|
391
|
+
overflow-x: auto;
|
|
392
|
+
display: none;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.code-block.active { display: block; }
|
|
396
|
+
|
|
397
|
+
.code-block .comment { color: #6b7280; }
|
|
398
|
+
.code-block .keyword { color: var(--accent2); }
|
|
399
|
+
.code-block .string { color: var(--green); }
|
|
400
|
+
.code-block .fn { color: var(--accent); }
|
|
401
|
+
.code-block .prompt { color: var(--green); user-select: none; }
|
|
402
|
+
.code-block .output { color: var(--text-dim); }
|
|
403
|
+
|
|
404
|
+
/* Use cases */
|
|
405
|
+
.use-cases {
|
|
406
|
+
padding: 5rem 2rem;
|
|
407
|
+
max-width: 1100px;
|
|
408
|
+
margin: 0 auto;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.use-cases h2 {
|
|
412
|
+
text-align: center;
|
|
413
|
+
font-size: 2rem;
|
|
414
|
+
margin-bottom: 3rem;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.cases-grid {
|
|
418
|
+
display: grid;
|
|
419
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
420
|
+
gap: 1.5rem;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.case-card {
|
|
424
|
+
background: var(--surface);
|
|
425
|
+
border: 1px solid var(--border);
|
|
426
|
+
border-radius: 12px;
|
|
427
|
+
padding: 1.75rem;
|
|
428
|
+
transition: border-color 0.2s;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.case-card:hover { border-color: var(--accent); }
|
|
432
|
+
|
|
433
|
+
.case-icon {
|
|
434
|
+
font-size: 1.8rem;
|
|
435
|
+
margin-bottom: 0.75rem;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.case-card h3 {
|
|
439
|
+
font-size: 1.1rem;
|
|
440
|
+
margin-bottom: 0.5rem;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.case-card p {
|
|
444
|
+
font-size: 0.9rem;
|
|
445
|
+
color: var(--text-dim);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* Grid conventions */
|
|
449
|
+
.conventions {
|
|
450
|
+
padding: 5rem 2rem;
|
|
451
|
+
max-width: 700px;
|
|
452
|
+
margin: 0 auto;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.conventions h2 {
|
|
456
|
+
text-align: center;
|
|
457
|
+
font-size: 2rem;
|
|
458
|
+
margin-bottom: 2rem;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.conv-table {
|
|
462
|
+
width: 100%;
|
|
463
|
+
border-collapse: collapse;
|
|
464
|
+
font-size: 0.9rem;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.conv-table th {
|
|
468
|
+
text-align: left;
|
|
469
|
+
padding: 0.75rem 1rem;
|
|
470
|
+
border-bottom: 2px solid var(--border);
|
|
471
|
+
color: var(--text-dim);
|
|
472
|
+
font-weight: 600;
|
|
473
|
+
text-transform: uppercase;
|
|
474
|
+
font-size: 0.75rem;
|
|
475
|
+
letter-spacing: 0.05em;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.conv-table td {
|
|
479
|
+
padding: 0.75rem 1rem;
|
|
480
|
+
border-bottom: 1px solid var(--border);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.conv-table td:last-child {
|
|
484
|
+
font-family: 'JetBrains Mono', monospace;
|
|
485
|
+
font-size: 0.8rem;
|
|
486
|
+
color: var(--accent);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* Footer */
|
|
490
|
+
.footer {
|
|
491
|
+
text-align: center;
|
|
492
|
+
padding: 4rem 2rem;
|
|
493
|
+
border-top: 1px solid var(--border);
|
|
494
|
+
margin-top: 4rem;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.footer p {
|
|
498
|
+
color: var(--text-dim);
|
|
499
|
+
font-size: 0.9rem;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.footer a {
|
|
503
|
+
color: var(--accent);
|
|
504
|
+
text-decoration: none;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.footer a:hover { text-decoration: underline; }
|
|
508
|
+
</style>
|
|
509
|
+
</head>
|
|
510
|
+
<body>
|
|
511
|
+
|
|
512
|
+
<!-- Hero -->
|
|
513
|
+
<section class="hero">
|
|
514
|
+
<div class="badge">⚡ Open Source · MIT License</div>
|
|
515
|
+
<h1>See the web in <span class="highlight">plain text</span></h1>
|
|
516
|
+
<p class="subtitle">
|
|
517
|
+
TextWeb renders web pages as structured text grids for AI agents.
|
|
518
|
+
2-5KB instead of 1MB screenshots. No vision model needed.
|
|
519
|
+
</p>
|
|
520
|
+
<div class="hero-actions">
|
|
521
|
+
<a href="https://github.com/chrisrobison/textweb" class="btn btn-primary">
|
|
522
|
+
⭐ View on GitHub
|
|
523
|
+
</a>
|
|
524
|
+
<a href="#examples" class="btn btn-secondary">See Examples</a>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="install-bar">
|
|
527
|
+
<span class="prompt">$</span>
|
|
528
|
+
<code>npm install -g textweb</code>
|
|
529
|
+
<button class="copy-btn" onclick="navigator.clipboard.writeText('npm install -g textweb');this.textContent='✓';setTimeout(()=>this.textContent='📋',1500)" title="Copy">📋</button>
|
|
530
|
+
</div>
|
|
531
|
+
</section>
|
|
532
|
+
|
|
533
|
+
<!-- Comparison -->
|
|
534
|
+
<section class="comparison">
|
|
535
|
+
<h2>Screenshot vs TextWeb</h2>
|
|
536
|
+
<div class="compare-grid">
|
|
537
|
+
<div class="compare-card bad">
|
|
538
|
+
<div class="compare-header">
|
|
539
|
+
<span>❌ Traditional: Screenshot + Vision Model</span>
|
|
540
|
+
<span class="size-tag">~1.2 MB</span>
|
|
541
|
+
</div>
|
|
542
|
+
<div class="compare-body">
|
|
543
|
+
<div class="blob"></div>
|
|
544
|
+
<div style="text-align:center; margin-top:1rem; font-size:0.8rem;">
|
|
545
|
+
+ GPT-4V / Claude Vision API call<br>
|
|
546
|
+
+ ~1500ms latency<br>
|
|
547
|
+
+ ~2000 tokens just for the image<br>
|
|
548
|
+
+ Hallucinations on dense UIs
|
|
549
|
+
</div>
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
<div class="compare-card good">
|
|
553
|
+
<div class="compare-header">
|
|
554
|
+
<span>✅ TextWeb: Structured Text Grid</span>
|
|
555
|
+
<span class="size-tag">~1.8 KB</span>
|
|
556
|
+
</div>
|
|
557
|
+
<div class="compare-body">[0]Hacker News [1]new | [2]past | [3]comments | [4]ask | [5]show | [6]jobs | [7]submit [8]login
|
|
558
|
+
|
|
559
|
+
1. [9]Show HN: TextWeb – text-grid browser
|
|
560
|
+
for AI agents (github.com)
|
|
561
|
+
142 pts by chrisrobison 3h ago | [10]89 comments
|
|
562
|
+
2. [11]Why LLMs don't need screenshots
|
|
563
|
+
87 pts by somebody 5h ago | [12]34 comments
|
|
564
|
+
3. [13]The future of agent-computer interfaces
|
|
565
|
+
56 pts by researcher 8h ago | [14]12 comments
|
|
566
|
+
|
|
567
|
+
[15:______________________] [16 Search]</div>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
</section>
|
|
571
|
+
|
|
572
|
+
<!-- Stats -->
|
|
573
|
+
<section class="stats">
|
|
574
|
+
<div class="stat">
|
|
575
|
+
<div class="stat-value">500×</div>
|
|
576
|
+
<div class="stat-label">Smaller than screenshots</div>
|
|
577
|
+
</div>
|
|
578
|
+
<div class="stat">
|
|
579
|
+
<div class="stat-value">$0</div>
|
|
580
|
+
<div class="stat-label">Vision model cost</div>
|
|
581
|
+
</div>
|
|
582
|
+
<div class="stat">
|
|
583
|
+
<div class="stat-value"><100ms</div>
|
|
584
|
+
<div class="stat-label">Render time</div>
|
|
585
|
+
</div>
|
|
586
|
+
</section>
|
|
587
|
+
|
|
588
|
+
<!-- How it works -->
|
|
589
|
+
<section class="how-it-works">
|
|
590
|
+
<h2>How It Works</h2>
|
|
591
|
+
<div class="steps">
|
|
592
|
+
<div class="step">
|
|
593
|
+
<div class="step-num">1</div>
|
|
594
|
+
<div class="step-content">
|
|
595
|
+
<h3>Real browser renders the page</h3>
|
|
596
|
+
<p>Headless Chromium executes all JavaScript, CSS, and dynamic content — exactly like a real user sees it.</p>
|
|
597
|
+
</div>
|
|
598
|
+
</div>
|
|
599
|
+
<div class="step">
|
|
600
|
+
<div class="step-num">2</div>
|
|
601
|
+
<div class="step-content">
|
|
602
|
+
<h3>Extract positions & semantics</h3>
|
|
603
|
+
<p>TextWeb reads every visible element's bounding rect, text content, and interactivity. Links, buttons, inputs — all identified.</p>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
<div class="step">
|
|
607
|
+
<div class="step-num">3</div>
|
|
608
|
+
<div class="step-content">
|
|
609
|
+
<h3>Map to a character grid</h3>
|
|
610
|
+
<p>Pixel positions become row/column positions on a text grid. Spatial layout is preserved — things that are next to each other on screen are next to each other in text.</p>
|
|
611
|
+
</div>
|
|
612
|
+
</div>
|
|
613
|
+
<div class="step">
|
|
614
|
+
<div class="step-num">4</div>
|
|
615
|
+
<div class="step-content">
|
|
616
|
+
<h3>Annotate interactive elements</h3>
|
|
617
|
+
<p>Every clickable link, button, input, and dropdown gets a reference number like <code>[0]</code>, <code>[1]</code>. Agents say "click 3" or "type 7 hello" to interact.</p>
|
|
618
|
+
</div>
|
|
619
|
+
</div>
|
|
620
|
+
</div>
|
|
621
|
+
</section>
|
|
622
|
+
|
|
623
|
+
<!-- Examples -->
|
|
624
|
+
<section class="examples" id="examples">
|
|
625
|
+
<h2>Usage</h2>
|
|
626
|
+
<p class="subtitle">CLI, HTTP API, or programmatic — pick your style.</p>
|
|
627
|
+
|
|
628
|
+
<div class="code-tabs">
|
|
629
|
+
<div class="code-tab active" onclick="showTab('cli')">CLI</div>
|
|
630
|
+
<div class="code-tab" onclick="showTab('api')">HTTP API</div>
|
|
631
|
+
<div class="code-tab" onclick="showTab('node')">Node.js</div>
|
|
632
|
+
</div>
|
|
633
|
+
|
|
634
|
+
<div class="code-block active" id="tab-cli"><span class="comment"># Render any page</span>
|
|
635
|
+
<span class="prompt">$ </span>textweb https://news.ycombinator.com
|
|
636
|
+
|
|
637
|
+
<span class="comment"># Interactive mode — browse with commands</span>
|
|
638
|
+
<span class="prompt">$ </span>textweb --interactive https://github.com
|
|
639
|
+
<span class="output">textweb> click 3
|
|
640
|
+
textweb> type 7 search query
|
|
641
|
+
textweb> scroll down
|
|
642
|
+
textweb> refs</span>
|
|
643
|
+
|
|
644
|
+
<span class="comment"># JSON output for piping to agents</span>
|
|
645
|
+
<span class="prompt">$ </span>textweb --json https://example.com
|
|
646
|
+
|
|
647
|
+
<span class="comment"># Custom grid size</span>
|
|
648
|
+
<span class="prompt">$ </span>textweb --cols 80 --rows 24 https://example.com</div>
|
|
649
|
+
|
|
650
|
+
<div class="code-block" id="tab-api"><span class="comment"># Start the server</span>
|
|
651
|
+
<span class="prompt">$ </span>textweb --serve 3000
|
|
652
|
+
|
|
653
|
+
<span class="comment"># Navigate to a page</span>
|
|
654
|
+
<span class="prompt">$ </span>curl -X POST http://localhost:3000/navigate \
|
|
655
|
+
-H <span class="string">'Content-Type: application/json'</span> \
|
|
656
|
+
-d <span class="string">'{"url": "https://example.com"}'</span>
|
|
657
|
+
|
|
658
|
+
<span class="comment"># Click an element</span>
|
|
659
|
+
<span class="prompt">$ </span>curl -X POST http://localhost:3000/click \
|
|
660
|
+
-d <span class="string">'{"ref": 3}'</span>
|
|
661
|
+
|
|
662
|
+
<span class="comment"># Type into an input</span>
|
|
663
|
+
<span class="prompt">$ </span>curl -X POST http://localhost:3000/type \
|
|
664
|
+
-d <span class="string">'{"ref": 7, "text": "hello world"}'</span></div>
|
|
665
|
+
|
|
666
|
+
<div class="code-block" id="tab-node"><span class="keyword">const</span> { AgentBrowser } = <span class="fn">require</span>(<span class="string">'textweb'</span>);
|
|
667
|
+
|
|
668
|
+
<span class="keyword">const</span> browser = <span class="keyword">new</span> <span class="fn">AgentBrowser</span>({ cols: 120 });
|
|
669
|
+
|
|
670
|
+
<span class="comment">// Navigate and get the text grid</span>
|
|
671
|
+
<span class="keyword">const</span> { view, elements, meta } = <span class="keyword">await</span> browser.<span class="fn">navigate</span>(<span class="string">'https://example.com'</span>);
|
|
672
|
+
|
|
673
|
+
console.<span class="fn">log</span>(view); <span class="comment">// The text grid</span>
|
|
674
|
+
console.<span class="fn">log</span>(elements); <span class="comment">// { 0: { selector, tag, text }, ... }</span>
|
|
675
|
+
|
|
676
|
+
<span class="comment">// Interact</span>
|
|
677
|
+
<span class="keyword">await</span> browser.<span class="fn">click</span>(3); <span class="comment">// Click element [3]</span>
|
|
678
|
+
<span class="keyword">await</span> browser.<span class="fn">type</span>(7, <span class="string">'hello'</span>); <span class="comment">// Type into element [7]</span>
|
|
679
|
+
<span class="keyword">await</span> browser.<span class="fn">scroll</span>(<span class="string">'down'</span>); <span class="comment">// Scroll down</span>
|
|
680
|
+
|
|
681
|
+
<span class="keyword">await</span> browser.<span class="fn">close</span>();</div>
|
|
682
|
+
</section>
|
|
683
|
+
|
|
684
|
+
<!-- Grid conventions -->
|
|
685
|
+
<section class="conventions">
|
|
686
|
+
<h2>Grid Conventions</h2>
|
|
687
|
+
<table class="conv-table">
|
|
688
|
+
<thead>
|
|
689
|
+
<tr><th>Element</th><th>Rendered As</th></tr>
|
|
690
|
+
</thead>
|
|
691
|
+
<tbody>
|
|
692
|
+
<tr><td>Headings</td><td>═══ HEADING TEXT ═══════</td></tr>
|
|
693
|
+
<tr><td>Links</td><td>[ref]link text</td></tr>
|
|
694
|
+
<tr><td>Buttons</td><td>[ref button text]</td></tr>
|
|
695
|
+
<tr><td>Text inputs</td><td>[ref:placeholder____]</td></tr>
|
|
696
|
+
<tr><td>Checkboxes</td><td>[ref:X] Label / [ref: ] Label</td></tr>
|
|
697
|
+
<tr><td>Radio buttons</td><td>[ref:●] Label / [ref:○] Label</td></tr>
|
|
698
|
+
<tr><td>Dropdowns</td><td>[ref:▼ Selected]</td></tr>
|
|
699
|
+
<tr><td>Separators</td><td>────────────────</td></tr>
|
|
700
|
+
<tr><td>List items</td><td>• Item text</td></tr>
|
|
701
|
+
</tbody>
|
|
702
|
+
</table>
|
|
703
|
+
</section>
|
|
704
|
+
|
|
705
|
+
<!-- Use Cases -->
|
|
706
|
+
<section class="use-cases">
|
|
707
|
+
<h2>Built For</h2>
|
|
708
|
+
<div class="cases-grid">
|
|
709
|
+
<div class="case-card">
|
|
710
|
+
<div class="case-icon">🤖</div>
|
|
711
|
+
<h3>AI Agents</h3>
|
|
712
|
+
<p>Give your agent eyes on the web without burning tokens on vision models. Works with any LLM — GPT, Claude, Llama, Gemma.</p>
|
|
713
|
+
</div>
|
|
714
|
+
<div class="case-card">
|
|
715
|
+
<div class="case-icon">📝</div>
|
|
716
|
+
<h3>Form Automation</h3>
|
|
717
|
+
<p>Fill out job applications, surveys, registrations. The agent sees labeled fields and reference numbers — no pixel guessing.</p>
|
|
718
|
+
</div>
|
|
719
|
+
<div class="case-card">
|
|
720
|
+
<div class="case-icon">🔍</div>
|
|
721
|
+
<h3>Web Scraping</h3>
|
|
722
|
+
<p>Extract structured data with full JS execution. See the page as rendered, not raw HTML soup.</p>
|
|
723
|
+
</div>
|
|
724
|
+
<div class="case-card">
|
|
725
|
+
<div class="case-icon">🧪</div>
|
|
726
|
+
<h3>Testing & QA</h3>
|
|
727
|
+
<p>Snapshot-test your UI as text. Diffs are readable, version-controllable, and don't break on font rendering differences.</p>
|
|
728
|
+
</div>
|
|
729
|
+
<div class="case-card">
|
|
730
|
+
<div class="case-icon">♿</div>
|
|
731
|
+
<h3>Accessibility Audit</h3>
|
|
732
|
+
<p>See exactly what an agent (or screen reader) would perceive. Missing labels and broken semantics become obvious.</p>
|
|
733
|
+
</div>
|
|
734
|
+
<div class="case-card">
|
|
735
|
+
<div class="case-icon">📊</div>
|
|
736
|
+
<h3>Monitoring</h3>
|
|
737
|
+
<p>Watch dashboards, track changes, alert on diffs — all in text. Pipe to grep, diff, or your favorite Unix tools.</p>
|
|
738
|
+
</div>
|
|
739
|
+
</div>
|
|
740
|
+
</section>
|
|
741
|
+
|
|
742
|
+
<!-- Footer -->
|
|
743
|
+
<section class="footer">
|
|
744
|
+
<p>
|
|
745
|
+
Built by <a href="https://cdr2.com">Christopher Robison</a> ·
|
|
746
|
+
<a href="https://github.com/chrisrobison/textweb">GitHub</a> ·
|
|
747
|
+
<a href="https://www.npmjs.com/package/textweb">npm</a> ·
|
|
748
|
+
MIT License
|
|
749
|
+
</p>
|
|
750
|
+
</section>
|
|
751
|
+
|
|
752
|
+
<script>
|
|
753
|
+
function showTab(name) {
|
|
754
|
+
document.querySelectorAll('.code-tab').forEach(t => t.classList.remove('active'));
|
|
755
|
+
document.querySelectorAll('.code-block').forEach(b => b.classList.remove('active'));
|
|
756
|
+
document.querySelector(`#tab-${name}`).classList.add('active');
|
|
757
|
+
event.target.classList.add('active');
|
|
758
|
+
}
|
|
759
|
+
</script>
|
|
760
|
+
</body>
|
|
761
|
+
</html>
|