web2cli 0.2.0__py3-none-any.whl

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 (44) hide show
  1. web2cli/__init__.py +3 -0
  2. web2cli/__main__.py +5 -0
  3. web2cli/adapter/__init__.py +0 -0
  4. web2cli/adapter/lint.py +667 -0
  5. web2cli/adapter/loader.py +157 -0
  6. web2cli/adapter/validator.py +127 -0
  7. web2cli/adapters/discord.com/web2cli.yaml +476 -0
  8. web2cli/adapters/mail.google.com/parsers/inbox.py +200 -0
  9. web2cli/adapters/mail.google.com/web2cli.yaml +52 -0
  10. web2cli/adapters/news.ycombinator.com/web2cli.yaml +356 -0
  11. web2cli/adapters/reddit.com/web2cli.yaml +233 -0
  12. web2cli/adapters/slack.com/web2cli.yaml +445 -0
  13. web2cli/adapters/stackoverflow.com/web2cli.yaml +257 -0
  14. web2cli/adapters/x.com/providers/x_graphql.py +299 -0
  15. web2cli/adapters/x.com/web2cli.yaml +449 -0
  16. web2cli/auth/__init__.py +0 -0
  17. web2cli/auth/browser_login.py +820 -0
  18. web2cli/auth/manager.py +166 -0
  19. web2cli/auth/store.py +68 -0
  20. web2cli/cli.py +1286 -0
  21. web2cli/executor/__init__.py +0 -0
  22. web2cli/executor/http.py +113 -0
  23. web2cli/output/__init__.py +0 -0
  24. web2cli/output/formatter.py +116 -0
  25. web2cli/parser/__init__.py +0 -0
  26. web2cli/parser/custom.py +21 -0
  27. web2cli/parser/html_parser.py +111 -0
  28. web2cli/parser/transforms.py +127 -0
  29. web2cli/pipe.py +10 -0
  30. web2cli/providers/__init__.py +6 -0
  31. web2cli/providers/base.py +22 -0
  32. web2cli/providers/registry.py +86 -0
  33. web2cli/runtime/__init__.py +1 -0
  34. web2cli/runtime/cache.py +42 -0
  35. web2cli/runtime/engine.py +743 -0
  36. web2cli/runtime/parser.py +398 -0
  37. web2cli/runtime/template.py +52 -0
  38. web2cli/types.py +71 -0
  39. web2cli-0.2.0.dist-info/METADATA +467 -0
  40. web2cli-0.2.0.dist-info/RECORD +44 -0
  41. web2cli-0.2.0.dist-info/WHEEL +5 -0
  42. web2cli-0.2.0.dist-info/entry_points.txt +2 -0
  43. web2cli-0.2.0.dist-info/licenses/LICENSE +202 -0
  44. web2cli-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,445 @@
1
+ meta:
2
+ spec_version: "0.2"
3
+ name: slack
4
+ domain: slack.com
5
+ base_url: https://slack.com/api
6
+ version: 0.2.0
7
+ description: "Slack — channels, messages, DMs"
8
+ author: web2cli-core
9
+ transport: http
10
+ impersonate: chrome
11
+ aliases:
12
+ - slack
13
+ default_headers:
14
+ User-Agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
15
+ Accept: "application/json"
16
+
17
+ auth:
18
+ methods:
19
+ - type: cookies
20
+ keys: [d]
21
+ env_var: WEB2CLI_SLACK_COOKIES
22
+ - type: token
23
+ env_var: WEB2CLI_SLACK_TOKEN
24
+ inject:
25
+ target: form
26
+ key: token
27
+ capture:
28
+ from: request.form
29
+ key: token
30
+ match:
31
+ host: slack.com
32
+ path_regex: "^/api/"
33
+ method: POST
34
+
35
+ resources:
36
+ users:
37
+ cache:
38
+ key: users
39
+ ttl: 600
40
+ request:
41
+ method: POST
42
+ url: /users.list
43
+ body:
44
+ encoding: form
45
+ template:
46
+ limit: 200
47
+ paginate:
48
+ cursor_param: cursor
49
+ cursor_location: body
50
+ cursor_path: "$.response_metadata.next_cursor"
51
+ response:
52
+ format: json
53
+ extract: "$.members[*]"
54
+ fields:
55
+ - name: id
56
+ from: "$.id"
57
+ - name: name
58
+ from:
59
+ coalesce:
60
+ - "$.profile.display_name"
61
+ - "$.profile.real_name"
62
+ - "$.real_name"
63
+ - "$.name"
64
+
65
+ channels:
66
+ cache:
67
+ key: channels
68
+ ttl: 300
69
+ request:
70
+ method: POST
71
+ url: /conversations.list
72
+ body:
73
+ encoding: form
74
+ template:
75
+ types: public_channel,private_channel
76
+ limit: 200
77
+ paginate:
78
+ cursor_param: cursor
79
+ cursor_location: body
80
+ cursor_path: "$.response_metadata.next_cursor"
81
+ response:
82
+ format: json
83
+ extract: "$.channels[*]"
84
+ fields:
85
+ - name: id
86
+ from: "$.id"
87
+ - name: name
88
+ from: "$.name"
89
+ - name: topic
90
+ from: "$.topic.value"
91
+ - name: purpose
92
+ from: "$.purpose.value"
93
+ - name: num_members
94
+ from: "$.num_members"
95
+ - name: is_private
96
+ from: "$.is_private"
97
+
98
+ dm_channels:
99
+ cache:
100
+ key: dm_channels
101
+ ttl: 300
102
+ request:
103
+ method: POST
104
+ url: /conversations.list
105
+ body:
106
+ encoding: form
107
+ template:
108
+ types: im
109
+ limit: 200
110
+ paginate:
111
+ cursor_param: cursor
112
+ cursor_location: body
113
+ cursor_path: "$.response_metadata.next_cursor"
114
+ response:
115
+ format: json
116
+ extract: "$.channels[*]"
117
+ fields:
118
+ - name: id
119
+ from: "$.id"
120
+ - name: user_id
121
+ from: "$.user"
122
+ - name: user_name
123
+ from: "$.user"
124
+ ops:
125
+ - map_lookup:
126
+ from: "steps.users.map_by_id"
127
+ default: unknown
128
+
129
+ commands:
130
+ me:
131
+ description: "Show current user and workspace info"
132
+ pipeline:
133
+ - request:
134
+ name: fetch
135
+ method: POST
136
+ url: /auth.test
137
+ body:
138
+ encoding: form
139
+ template: {}
140
+ - parse:
141
+ name: parsed
142
+ from: fetch
143
+ format: json
144
+ extract: "$"
145
+ fields:
146
+ - name: ok
147
+ from: "$.ok"
148
+ - name: user_id
149
+ from: "$.user_id"
150
+ - name: user
151
+ from: "$.user"
152
+ - name: team_id
153
+ from: "$.team_id"
154
+ - name: team
155
+ from: "$.team"
156
+ - name: url
157
+ from: "$.url"
158
+ - name: error
159
+ from: "$.error"
160
+ output:
161
+ from_step: parsed
162
+ default_fields: [user, team, url]
163
+ default_format: table
164
+
165
+ channels:
166
+ description: "List channels in workspace"
167
+ pipeline:
168
+ - resolve:
169
+ name: channels
170
+ resource: channels
171
+ input: null
172
+ by: name
173
+ value: id
174
+ output:
175
+ from_step: channels
176
+ default_fields: [name, topic, num_members]
177
+ default_format: table
178
+
179
+ messages:
180
+ description: "Get messages from a channel"
181
+ args:
182
+ channel:
183
+ type: string
184
+ required: true
185
+ description: "Channel name"
186
+ limit:
187
+ type: int
188
+ required: false
189
+ default: 20
190
+ max: 200
191
+ description: "Number of messages to fetch"
192
+ pipeline:
193
+ - resolve:
194
+ name: users
195
+ resource: users
196
+ input: null
197
+ by: id
198
+ value: name
199
+ - resolve:
200
+ name: channel
201
+ resource: channels
202
+ input: "{{args.channel}}"
203
+ by: name
204
+ value: id
205
+ match: ci_equals
206
+ - request:
207
+ name: fetch
208
+ method: POST
209
+ url: /conversations.history
210
+ body:
211
+ encoding: form
212
+ template:
213
+ channel: "{{steps.channel.id}}"
214
+ limit: "{{args.limit}}"
215
+ - parse:
216
+ name: parsed
217
+ from: fetch
218
+ format: json
219
+ extract: "$.messages[*]"
220
+ fields:
221
+ - name: author
222
+ from: "$.user"
223
+ ops:
224
+ - map_lookup:
225
+ from: "steps.users.map_by_id"
226
+ default: unknown
227
+ - name: content
228
+ from: "$.text"
229
+ - name: timestamp
230
+ from: "$.ts"
231
+ transform: timestamp
232
+ - name: reactions
233
+ from: "$.reactions[*].name"
234
+ ops:
235
+ - join:
236
+ sep: " "
237
+ - name: type
238
+ from: "$.subtype"
239
+ default: message
240
+ - name: ts
241
+ from: "$.ts"
242
+ post_ops:
243
+ - reverse
244
+ output:
245
+ from_step: parsed
246
+ default_fields: [author, content, timestamp, reactions]
247
+ default_format: table
248
+
249
+ send:
250
+ description: "Send a message to a channel"
251
+ args:
252
+ channel:
253
+ type: string
254
+ required: true
255
+ description: "Channel name"
256
+ message:
257
+ type: string
258
+ required: true
259
+ source: [arg, stdin]
260
+ description: "Message content"
261
+ pipeline:
262
+ - resolve:
263
+ name: channel
264
+ resource: channels
265
+ input: "{{args.channel}}"
266
+ by: name
267
+ value: id
268
+ match: ci_equals
269
+ - request:
270
+ name: send
271
+ method: POST
272
+ url: /chat.postMessage
273
+ body:
274
+ encoding: form
275
+ template:
276
+ channel: "{{steps.channel.id}}"
277
+ text: "{{args.message}}"
278
+ - parse:
279
+ name: parsed
280
+ from: send
281
+ format: json
282
+ extract: "$"
283
+ fields:
284
+ - name: ok
285
+ from: "$.ok"
286
+ - name: channel
287
+ from: "$.channel"
288
+ - name: timestamp
289
+ from: "$.message.ts"
290
+ transform: timestamp
291
+ - name: content
292
+ from: "$.message.text"
293
+ - name: error
294
+ from: "$.error"
295
+ output:
296
+ from_step: parsed
297
+ default_fields: [ok, channel, timestamp]
298
+ default_format: table
299
+
300
+ dm:
301
+ description: "List DM conversations"
302
+ pipeline:
303
+ - resolve:
304
+ name: users
305
+ resource: users
306
+ input: null
307
+ by: id
308
+ value: name
309
+ - resolve:
310
+ name: dm_list
311
+ resource: dm_channels
312
+ input: null
313
+ by: user_name
314
+ value: id
315
+ output:
316
+ from_step: dm_list
317
+ default_fields: [user_name, user_id]
318
+ default_format: table
319
+
320
+ dm-messages:
321
+ description: "Get messages from a DM conversation"
322
+ args:
323
+ user:
324
+ type: string
325
+ required: true
326
+ description: "User display name"
327
+ limit:
328
+ type: int
329
+ required: false
330
+ default: 20
331
+ max: 200
332
+ description: "Number of messages to fetch"
333
+ pipeline:
334
+ - resolve:
335
+ name: users
336
+ resource: users
337
+ input: null
338
+ by: id
339
+ value: name
340
+ - resolve:
341
+ name: dm_channel
342
+ resource: dm_channels
343
+ input: "{{args.user}}"
344
+ by: user_name
345
+ value: id
346
+ match: ci_equals
347
+ - request:
348
+ name: fetch
349
+ method: POST
350
+ url: /conversations.history
351
+ body:
352
+ encoding: form
353
+ template:
354
+ channel: "{{steps.dm_channel.id}}"
355
+ limit: "{{args.limit}}"
356
+ - parse:
357
+ name: parsed
358
+ from: fetch
359
+ format: json
360
+ extract: "$.messages[*]"
361
+ fields:
362
+ - name: author
363
+ from: "$.user"
364
+ ops:
365
+ - map_lookup:
366
+ from: "steps.users.map_by_id"
367
+ default: unknown
368
+ - name: content
369
+ from: "$.text"
370
+ - name: timestamp
371
+ from: "$.ts"
372
+ transform: timestamp
373
+ - name: reactions
374
+ from: "$.reactions[*].name"
375
+ ops:
376
+ - join:
377
+ sep: " "
378
+ - name: type
379
+ from: "$.subtype"
380
+ default: message
381
+ - name: ts
382
+ from: "$.ts"
383
+ post_ops:
384
+ - reverse
385
+ output:
386
+ from_step: parsed
387
+ default_fields: [author, content, timestamp, reactions]
388
+ default_format: table
389
+
390
+ dm-send:
391
+ description: "Send a DM to a user"
392
+ args:
393
+ user:
394
+ type: string
395
+ required: true
396
+ description: "User display name"
397
+ message:
398
+ type: string
399
+ required: true
400
+ source: [arg, stdin]
401
+ description: "Message content"
402
+ pipeline:
403
+ - resolve:
404
+ name: users
405
+ resource: users
406
+ input: null
407
+ by: id
408
+ value: name
409
+ - resolve:
410
+ name: dm_channel
411
+ resource: dm_channels
412
+ input: "{{args.user}}"
413
+ by: user_name
414
+ value: id
415
+ match: ci_equals
416
+ - request:
417
+ name: send
418
+ method: POST
419
+ url: /chat.postMessage
420
+ body:
421
+ encoding: form
422
+ template:
423
+ channel: "{{steps.dm_channel.id}}"
424
+ text: "{{args.message}}"
425
+ - parse:
426
+ name: parsed
427
+ from: send
428
+ format: json
429
+ extract: "$"
430
+ fields:
431
+ - name: ok
432
+ from: "$.ok"
433
+ - name: channel
434
+ from: "$.channel"
435
+ - name: timestamp
436
+ from: "$.message.ts"
437
+ transform: timestamp
438
+ - name: content
439
+ from: "$.message.text"
440
+ - name: error
441
+ from: "$.error"
442
+ output:
443
+ from_step: parsed
444
+ default_fields: [ok, channel, timestamp]
445
+ default_format: table
@@ -0,0 +1,257 @@
1
+ meta:
2
+ spec_version: "0.2"
3
+ name: stackoverflow
4
+ domain: stackoverflow.com
5
+ base_url: https://stackoverflow.com
6
+ version: 0.2.0
7
+ description: "Stack Overflow — search questions, read answers, browse tags"
8
+ author: web2cli-core
9
+ transport: http
10
+ impersonate: chrome
11
+ aliases:
12
+ - so
13
+ default_headers:
14
+ User-Agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
15
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
16
+ Accept-Language: "en-US,en;q=0.9"
17
+ Accept-Encoding: "gzip, deflate, br"
18
+
19
+ auth:
20
+ methods:
21
+ - type: cookies
22
+ keys: [cf_clearance]
23
+ env_var: WEB2CLI_SO_COOKIES
24
+
25
+ commands:
26
+ search:
27
+ description: "Search Stack Overflow questions"
28
+ args:
29
+ query:
30
+ type: string
31
+ required: true
32
+ source: [arg, stdin]
33
+ description: "Search query (same syntax as SO search bar)"
34
+ limit:
35
+ type: int
36
+ required: false
37
+ default: 15
38
+ max: 50
39
+ description: "Results per page"
40
+ sort:
41
+ type: string
42
+ required: false
43
+ default: relevance
44
+ enum: [relevance, newest, votes, active]
45
+ description: "Sort order"
46
+ pipeline:
47
+ - request:
48
+ name: fetch
49
+ method: GET
50
+ url: /search
51
+ params:
52
+ q: "{{args.query}}"
53
+ pagesize: "{{args.limit}}"
54
+ tab: "{{args.sort}}"
55
+ - parse:
56
+ name: parsed
57
+ from: fetch
58
+ format: html
59
+ extract: "div.s-post-summary"
60
+ fields:
61
+ - name: title
62
+ path: "h3 a.s-link"
63
+ attribute: text
64
+ - name: url
65
+ path: "h3 a.s-link"
66
+ attribute: href
67
+ prefix: "https://stackoverflow.com"
68
+ - name: votes
69
+ path: "span.s-post-summary--stats-item-number:nth-of-type(1)"
70
+ attribute: text
71
+ transform: int
72
+ - name: answers
73
+ path: "div.s-post-summary--stats-item:nth-of-type(2) span.s-post-summary--stats-item-number"
74
+ attribute: text
75
+ transform: int
76
+ - name: views
77
+ path: "div.s-post-summary--stats-item:nth-of-type(3) span.s-post-summary--stats-item-number"
78
+ attribute: text
79
+ - name: tags
80
+ path: "ul.ml0 li a.post-tag"
81
+ attribute: text
82
+ collect: true
83
+ join: ", "
84
+ - name: excerpt
85
+ path: "div.s-post-summary--content-excerpt"
86
+ attribute: text
87
+ truncate: 80
88
+ - transform:
89
+ name: sorted
90
+ from: parsed
91
+ ops:
92
+ - sort:
93
+ by: votes
94
+ order: desc
95
+ output:
96
+ from_step: sorted
97
+ default_fields: [title, votes, answers, tags]
98
+ default_format: table
99
+
100
+ question:
101
+ description: "Read a specific question and its top answers"
102
+ args:
103
+ id:
104
+ type: string
105
+ required: true
106
+ source: [arg, stdin]
107
+ description: "Question ID (number from URL)"
108
+ pipeline:
109
+ - request:
110
+ name: fetch
111
+ method: GET
112
+ url: /questions/{{args.id}}
113
+ - parse:
114
+ name: question
115
+ from: fetch
116
+ format: html
117
+ extract: "body"
118
+ fields:
119
+ - name: type
120
+ path: ".__web2cli_missing__"
121
+ attribute: text
122
+ default: question
123
+ - name: title
124
+ path: "#question-header h1 a"
125
+ attribute: text
126
+ - name: votes
127
+ path: "#question .js-vote-count"
128
+ attribute: text
129
+ transform: int
130
+ - name: author
131
+ path: "#question .post-signature:last-child .user-details a"
132
+ attribute: text
133
+ default: Unknown
134
+ - name: tags
135
+ path: "#question .post-tag"
136
+ attribute: text
137
+ collect: true
138
+ join: ", "
139
+ - name: body
140
+ path: "#question .s-prose.js-post-body"
141
+ attribute: text
142
+ truncate: 500
143
+ - parse:
144
+ name: answers
145
+ from: fetch
146
+ format: html
147
+ extract: "#answers .answer"
148
+ fields:
149
+ - name: type
150
+ path: ".__web2cli_missing__"
151
+ attribute: text
152
+ default: answer
153
+ - name: title
154
+ path: ".__web2cli_missing__"
155
+ attribute: text
156
+ default: ""
157
+ - name: votes
158
+ path: ".js-vote-count"
159
+ attribute: text
160
+ transform: int
161
+ - name: author
162
+ path: ".post-signature:last-child .user-details a"
163
+ attribute: text
164
+ default: Unknown
165
+ - name: tags
166
+ path: ".__web2cli_missing__"
167
+ attribute: text
168
+ default: ""
169
+ - name: body
170
+ path: ".s-prose.js-post-body"
171
+ attribute: text
172
+ truncate: 500
173
+ - transform:
174
+ name: answers_sorted
175
+ from: answers
176
+ ops:
177
+ - sort:
178
+ by: votes
179
+ order: desc
180
+ - transform:
181
+ name: parsed
182
+ from: answers_sorted
183
+ ops:
184
+ - concat:
185
+ steps: [question]
186
+ position: before
187
+ output:
188
+ from_step: parsed
189
+ default_fields: [type, votes, author, body]
190
+ default_format: plain
191
+
192
+ tagged:
193
+ description: "Browse questions by tag"
194
+ args:
195
+ tag:
196
+ type: string
197
+ required: true
198
+ description: "Tag name (e.g., python, javascript, rust)"
199
+ sort:
200
+ type: string
201
+ required: false
202
+ default: votes
203
+ enum: [newest, active, bounties, unanswered, frequent, votes]
204
+ limit:
205
+ type: int
206
+ required: false
207
+ default: 15
208
+ max: 50
209
+ pipeline:
210
+ - request:
211
+ name: fetch
212
+ method: GET
213
+ url: /questions/tagged/{{args.tag}}
214
+ params:
215
+ tab: "{{args.sort}}"
216
+ pagesize: "{{args.limit}}"
217
+ - parse:
218
+ name: parsed
219
+ from: fetch
220
+ format: html
221
+ extract: "div.s-post-summary"
222
+ fields:
223
+ - name: id
224
+ path: "h3 a.s-link"
225
+ attribute: href
226
+ transform: int
227
+ - name: title
228
+ path: "h3 a.s-link"
229
+ attribute: text
230
+ - name: url
231
+ path: "h3 a.s-link"
232
+ attribute: href
233
+ prefix: "https://stackoverflow.com"
234
+ - name: votes
235
+ path: "span.s-post-summary--stats-item-number:nth-of-type(1)"
236
+ attribute: text
237
+ transform: int
238
+ - name: answers
239
+ path: "div.s-post-summary--stats-item:nth-of-type(2) span.s-post-summary--stats-item-number"
240
+ attribute: text
241
+ transform: int
242
+ - name: tags
243
+ path: "ul.ml0 li a.post-tag"
244
+ attribute: text
245
+ collect: true
246
+ join: ", "
247
+ - transform:
248
+ name: sorted
249
+ from: parsed
250
+ ops:
251
+ - sort:
252
+ by: votes
253
+ order: desc
254
+ output:
255
+ from_step: sorted
256
+ default_fields: [id, title, votes, answers, tags]
257
+ default_format: table