webextrator-cli 2026.7.1.0__tar.gz

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.
@@ -0,0 +1,3 @@
1
+ ACEDATACLOUD_API_TOKEN=your_api_token_here
2
+ ACEDATACLOUD_API_BASE_URL=https://api.acedata.cloud
3
+ WEBEXTRATOR_REQUEST_TIMEOUT=60
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2025-05-04
4
+
5
+ ### Added
6
+ - Initial release of WebExtrator CLI
7
+ - `extract` command for extracting structured content from web pages (`/webextrator/extract`)
8
+ - `render` command for rendering web pages and returning HTML (`/webextrator/render`)
9
+ - `tasks retrieve` command to query a single render/extract task
10
+ - `tasks batch` command to query multiple render/extract tasks
11
+ - `config` command to show current configuration
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 AceDataCloud
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.1
2
+ Name: webextrator-cli
3
+ Version: 2026.7.1.0
4
+ Summary: CLI tool for WebExtrator Web Render & Extract via AceDataCloud
5
+ Project-URL: Homepage, https://github.com/AceDataCloud/Clis
6
+ Project-URL: Repository, https://github.com/AceDataCloud/Clis
7
+ Project-URL: Issues, https://github.com/AceDataCloud/Clis/issues
8
+ Project-URL: Changelog, https://github.com/AceDataCloud/Clis/blob/main/webextrator/CHANGELOG.md
9
+ Author-email: AceDataCloud <support@acedata.cloud>
10
+ Maintainer-email: AceDataCloud <support@acedata.cloud>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: acedata,cli,command-line,web-rendering,web-scraping,webextrator
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Internet :: WWW/HTTP
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: click>=8.1.0
27
+ Requires-Dist: httpx>=0.27.0
28
+ Requires-Dist: pydantic>=2.0.0
29
+ Requires-Dist: python-dotenv>=1.0.0
30
+ Requires-Dist: rich>=13.0.0
31
+ Provides-Extra: all
32
+ Requires-Dist: webextrator-cli[dev,release,test]; extra == 'all'
33
+ Provides-Extra: dev
34
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
35
+ Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
37
+ Provides-Extra: release
38
+ Requires-Dist: build>=1.2.0; extra == 'release'
39
+ Requires-Dist: twine>=6.1.0; extra == 'release'
40
+ Provides-Extra: test
41
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
42
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
43
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
44
+ Requires-Dist: respx>=0.21.0; extra == 'test'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # WebExtrator CLI
48
+
49
+ A command-line tool for web rendering and extraction via [AceDataCloud](https://platform.acedata.cloud).
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install webextrator-cli
55
+ ```
56
+
57
+ ## Quick Start
58
+
59
+ ```bash
60
+ export ACEDATACLOUD_API_TOKEN=your_token
61
+
62
+ webextrator extract https://www.amazon.com/dp/B0C1234567
63
+ webextrator render https://example.com
64
+ webextrator tasks retrieve --id <task-id>
65
+ ```
66
+
67
+ ## Commands
68
+
69
+ | Command | Description |
70
+ |---------|-------------|
71
+ | `webextrator extract <url>` | Extract structured content from a web page |
72
+ | `webextrator render <url>` | Render a web page and return the HTML |
73
+ | `webextrator tasks retrieve` | Retrieve a single task by ID or trace ID |
74
+ | `webextrator tasks batch` | Retrieve multiple tasks at once |
75
+ | `webextrator config` | Show current configuration |
76
+
77
+ ## Global Options
78
+
79
+ ```
80
+ --token TEXT API token (or set ACEDATACLOUD_API_TOKEN env var)
81
+ --version Show version
82
+ --help Show help message
83
+ ```
84
+
85
+ ## Configuration
86
+
87
+ Set environment variables or use a `.env` file:
88
+
89
+ ```bash
90
+ ACEDATACLOUD_API_TOKEN=your_token
91
+ ACEDATACLOUD_API_BASE_URL=https://api.acedata.cloud
92
+ WEBEXTRATOR_REQUEST_TIMEOUT=60
93
+ ```
94
+
95
+ ## License
96
+
97
+ MIT
@@ -0,0 +1,51 @@
1
+ # WebExtrator CLI
2
+
3
+ A command-line tool for web rendering and extraction via [AceDataCloud](https://platform.acedata.cloud).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install webextrator-cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ export ACEDATACLOUD_API_TOKEN=your_token
15
+
16
+ webextrator extract https://www.amazon.com/dp/B0C1234567
17
+ webextrator render https://example.com
18
+ webextrator tasks retrieve --id <task-id>
19
+ ```
20
+
21
+ ## Commands
22
+
23
+ | Command | Description |
24
+ |---------|-------------|
25
+ | `webextrator extract <url>` | Extract structured content from a web page |
26
+ | `webextrator render <url>` | Render a web page and return the HTML |
27
+ | `webextrator tasks retrieve` | Retrieve a single task by ID or trace ID |
28
+ | `webextrator tasks batch` | Retrieve multiple tasks at once |
29
+ | `webextrator config` | Show current configuration |
30
+
31
+ ## Global Options
32
+
33
+ ```
34
+ --token TEXT API token (or set ACEDATACLOUD_API_TOKEN env var)
35
+ --version Show version
36
+ --help Show help message
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ Set environment variables or use a `.env` file:
42
+
43
+ ```bash
44
+ ACEDATACLOUD_API_TOKEN=your_token
45
+ ACEDATACLOUD_API_BASE_URL=https://api.acedata.cloud
46
+ WEBEXTRATOR_REQUEST_TIMEOUT=60
47
+ ```
48
+
49
+ ## License
50
+
51
+ MIT
@@ -0,0 +1,117 @@
1
+ [project]
2
+ name = "webextrator-cli"
3
+ version = "2026.7.1.0"
4
+ description = "CLI tool for WebExtrator Web Render & Extract via AceDataCloud"
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ requires-python = ">=3.10"
8
+ authors = [
9
+ { name = "AceDataCloud", email = "support@acedata.cloud" }
10
+ ]
11
+ maintainers = [
12
+ { name = "AceDataCloud", email = "support@acedata.cloud" }
13
+ ]
14
+ keywords = [
15
+ "cli",
16
+ "webextrator",
17
+ "web-scraping",
18
+ "web-rendering",
19
+ "acedata",
20
+ "command-line",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Intended Audience :: Developers",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.10",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Topic :: Internet :: WWW/HTTP",
32
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
33
+ "Environment :: Console",
34
+ ]
35
+ dependencies = [
36
+ "click>=8.1.0",
37
+ "httpx>=0.27.0",
38
+ "python-dotenv>=1.0.0",
39
+ "rich>=13.0.0",
40
+ "pydantic>=2.0.0",
41
+ ]
42
+
43
+ [project.optional-dependencies]
44
+ dev = [
45
+ "ruff>=0.4.0",
46
+ "mypy>=1.10.0",
47
+ "pre-commit>=3.7.0",
48
+ ]
49
+ test = [
50
+ "pytest>=8.0.0",
51
+ "pytest-asyncio>=0.23.0",
52
+ "pytest-cov>=5.0.0",
53
+ "respx>=0.21.0",
54
+ ]
55
+ release = [
56
+ "build>=1.2.0",
57
+ "twine>=6.1.0",
58
+ ]
59
+ all = [
60
+ "webextrator-cli[dev,test,release]",
61
+ ]
62
+
63
+ [project.scripts]
64
+ webextrator-cli = "webextrator_cli.main:cli"
65
+ webextrator = "webextrator_cli.main:cli"
66
+
67
+ [project.urls]
68
+ Homepage = "https://github.com/AceDataCloud/Clis"
69
+ Repository = "https://github.com/AceDataCloud/Clis"
70
+ Issues = "https://github.com/AceDataCloud/Clis/issues"
71
+ Changelog = "https://github.com/AceDataCloud/Clis/blob/main/webextrator/CHANGELOG.md"
72
+
73
+ [build-system]
74
+ requires = ["hatchling>=1.21.0,<1.22.0"]
75
+ build-backend = "hatchling.build"
76
+
77
+ [tool.hatch.build.targets.wheel]
78
+ packages = ["webextrator_cli"]
79
+
80
+ [tool.hatch.build.targets.sdist]
81
+ include = [
82
+ "webextrator_cli/",
83
+ "tests/",
84
+ "README.md",
85
+ "LICENSE",
86
+ "CHANGELOG.md",
87
+ ".env.example",
88
+ ]
89
+
90
+ # Mypy Configuration
91
+ [tool.mypy]
92
+ python_version = "3.10"
93
+ warn_return_any = true
94
+ warn_unused_configs = true
95
+ disallow_untyped_defs = true
96
+ check_untyped_defs = true
97
+
98
+ # Pytest Configuration
99
+ [tool.pytest.ini_options]
100
+ testpaths = ["tests"]
101
+ asyncio_mode = "auto"
102
+ markers = [
103
+ "integration: marks tests that require real API access",
104
+ "slow: marks slow tests",
105
+ ]
106
+
107
+ # Ruff Configuration
108
+ [tool.ruff]
109
+ line-length = 100
110
+ target-version = "py310"
111
+
112
+ [tool.ruff.lint]
113
+ select = ["E", "W", "F", "I", "B", "C4", "UP", "ARG", "SIM"]
114
+ ignore = ["E501"]
115
+
116
+ [tool.ruff.format]
117
+ quote-style = "double"
@@ -0,0 +1 @@
1
+ """Tests init."""
@@ -0,0 +1,55 @@
1
+ """Test configuration and fixtures for WebExtrator CLI."""
2
+
3
+ import pytest
4
+
5
+
6
+ @pytest.fixture
7
+ def mock_extract_response() -> dict:
8
+ return {
9
+ "success": True,
10
+ "task_id": "550e8400-e29b-41d4-a716-446655440000",
11
+ "trace_id": "550e8400-e29b-41d4-a716-446655440001",
12
+ "started_at": "2025-05-02T10:30:00.123Z",
13
+ "finished_at": "2025-05-02T10:30:08.789Z",
14
+ "elapsed": 8.666,
15
+ "data": {
16
+ "kind": "extract",
17
+ "url": "https://www.amazon.com/dp/B0C1234567",
18
+ "contentType": "product",
19
+ "title": "Acme Widget",
20
+ "description": "A widget that does things.",
21
+ },
22
+ }
23
+
24
+
25
+ @pytest.fixture
26
+ def mock_render_response() -> dict:
27
+ return {
28
+ "success": True,
29
+ "task_id": "550e8400-e29b-41d4-a716-446655440002",
30
+ "trace_id": "550e8400-e29b-41d4-a716-446655440003",
31
+ "started_at": "2025-05-02T10:30:00.123Z",
32
+ "finished_at": "2025-05-02T10:30:05.456Z",
33
+ "elapsed": 5.333,
34
+ "data": {
35
+ "kind": "render",
36
+ "url": "https://example.com",
37
+ "finalUrl": "https://example.com/",
38
+ "title": "Example Domain",
39
+ "status": 200,
40
+ "html": "<!DOCTYPE html><html>...</html>",
41
+ },
42
+ }
43
+
44
+
45
+ @pytest.fixture
46
+ def mock_task_response() -> dict:
47
+ return {
48
+ "id": "550e8400-e29b-41d4-a716-446655440000",
49
+ "task_id": "550e8400-e29b-41d4-a716-446655440000",
50
+ "trace_id": "550e8400-e29b-41d4-a716-446655440001",
51
+ "type": "extract",
52
+ "started_at": "2025-05-02T10:30:00.123Z",
53
+ "finished_at": "2025-05-02T10:30:08.789Z",
54
+ "elapsed": 8.666,
55
+ }
@@ -0,0 +1,315 @@
1
+ """Tests for WebExtrator CLI commands."""
2
+
3
+ import json
4
+
5
+ import pytest
6
+ import respx
7
+ from click.testing import CliRunner
8
+ from httpx import Response
9
+
10
+ from webextrator_cli.main import cli
11
+
12
+
13
+ @pytest.fixture
14
+ def runner() -> CliRunner:
15
+ return CliRunner()
16
+
17
+
18
+ class TestGlobalCommands:
19
+ """Tests for global CLI options."""
20
+
21
+ def test_version(self, runner):
22
+ result = runner.invoke(cli, ["--version"])
23
+ assert result.exit_code == 0
24
+ assert "webextrator-cli" in result.output
25
+
26
+ def test_help(self, runner):
27
+ result = runner.invoke(cli, ["--help"])
28
+ assert result.exit_code == 0
29
+ assert "extract" in result.output
30
+ assert "render" in result.output
31
+
32
+ def test_help_extract(self, runner):
33
+ result = runner.invoke(cli, ["extract", "--help"])
34
+ assert result.exit_code == 0
35
+ assert "URL" in result.output
36
+
37
+ def test_help_render(self, runner):
38
+ result = runner.invoke(cli, ["render", "--help"])
39
+ assert result.exit_code == 0
40
+ assert "URL" in result.output
41
+
42
+
43
+ class TestExtractCommand:
44
+ """Tests for the extract command."""
45
+
46
+ @respx.mock
47
+ def test_extract_json(self, runner, mock_extract_response):
48
+ respx.post("https://api.acedata.cloud/webextrator/extract").mock(
49
+ return_value=Response(200, json=mock_extract_response)
50
+ )
51
+ result = runner.invoke(
52
+ cli,
53
+ [
54
+ "--token",
55
+ "test-token",
56
+ "extract",
57
+ "https://www.amazon.com/dp/B0C1234567",
58
+ "--json",
59
+ ],
60
+ )
61
+ assert result.exit_code == 0
62
+ data = json.loads(result.output)
63
+ assert data["success"] is True
64
+ assert data["task_id"] == "550e8400-e29b-41d4-a716-446655440000"
65
+
66
+ @respx.mock
67
+ def test_extract_rich_output(self, runner, mock_extract_response):
68
+ respx.post("https://api.acedata.cloud/webextrator/extract").mock(
69
+ return_value=Response(200, json=mock_extract_response)
70
+ )
71
+ result = runner.invoke(
72
+ cli,
73
+ ["--token", "test-token", "extract", "https://www.amazon.com/dp/B0C1234567"],
74
+ )
75
+ assert result.exit_code == 0
76
+ assert "550e8400-e29b-41d4-a716-446655440000" in result.output
77
+
78
+ @respx.mock
79
+ def test_extract_with_expected_type(self, runner, mock_extract_response):
80
+ respx.post("https://api.acedata.cloud/webextrator/extract").mock(
81
+ return_value=Response(200, json=mock_extract_response)
82
+ )
83
+ result = runner.invoke(
84
+ cli,
85
+ [
86
+ "--token",
87
+ "test-token",
88
+ "extract",
89
+ "https://example.com",
90
+ "--expected-type",
91
+ "product",
92
+ "--json",
93
+ ],
94
+ )
95
+ assert result.exit_code == 0
96
+
97
+ @respx.mock
98
+ def test_extract_with_enable_llm(self, runner, mock_extract_response):
99
+ route = respx.post("https://api.acedata.cloud/webextrator/extract").mock(
100
+ return_value=Response(200, json=mock_extract_response)
101
+ )
102
+ result = runner.invoke(
103
+ cli,
104
+ [
105
+ "--token",
106
+ "test-token",
107
+ "extract",
108
+ "https://example.com",
109
+ "--enable-llm",
110
+ "--json",
111
+ ],
112
+ )
113
+ assert result.exit_code == 0
114
+ sent = json.loads(route.calls[0].request.content)
115
+ assert sent["enable_llm"] is True
116
+
117
+ @respx.mock
118
+ def test_extract_with_block_resources_and_headers(self, runner, mock_extract_response):
119
+ route = respx.post("https://api.acedata.cloud/webextrator/extract").mock(
120
+ return_value=Response(200, json=mock_extract_response)
121
+ )
122
+ result = runner.invoke(
123
+ cli,
124
+ [
125
+ "--token",
126
+ "test-token",
127
+ "extract",
128
+ "https://example.com",
129
+ "--block-resources",
130
+ "--headers",
131
+ '{"Accept-Language":"en-US"}',
132
+ "--json",
133
+ ],
134
+ )
135
+ assert result.exit_code == 0
136
+ sent = json.loads(route.calls[0].request.content)
137
+ assert sent["block_resources"] is True
138
+ assert sent["headers"] == {"Accept-Language": "en-US"}
139
+
140
+ def test_extract_no_token(self, runner):
141
+ result = runner.invoke(cli, ["--token", "", "extract", "https://example.com"])
142
+ assert result.exit_code != 0
143
+
144
+
145
+ class TestRenderCommand:
146
+ """Tests for the render command."""
147
+
148
+ @respx.mock
149
+ def test_render_json(self, runner, mock_render_response):
150
+ respx.post("https://api.acedata.cloud/webextrator/render").mock(
151
+ return_value=Response(200, json=mock_render_response)
152
+ )
153
+ result = runner.invoke(
154
+ cli,
155
+ ["--token", "test-token", "render", "https://example.com", "--json"],
156
+ )
157
+ assert result.exit_code == 0
158
+ data = json.loads(result.output)
159
+ assert data["success"] is True
160
+ assert data["task_id"] == "550e8400-e29b-41d4-a716-446655440002"
161
+
162
+ @respx.mock
163
+ def test_render_rich_output(self, runner, mock_render_response):
164
+ respx.post("https://api.acedata.cloud/webextrator/render").mock(
165
+ return_value=Response(200, json=mock_render_response)
166
+ )
167
+ result = runner.invoke(cli, ["--token", "test-token", "render", "https://example.com"])
168
+ assert result.exit_code == 0
169
+ assert "550e8400-e29b-41d4-a716-446655440002" in result.output
170
+
171
+ @respx.mock
172
+ def test_render_with_wait_until(self, runner, mock_render_response):
173
+ respx.post("https://api.acedata.cloud/webextrator/render").mock(
174
+ return_value=Response(200, json=mock_render_response)
175
+ )
176
+ result = runner.invoke(
177
+ cli,
178
+ [
179
+ "--token",
180
+ "test-token",
181
+ "render",
182
+ "https://example.com",
183
+ "--wait-until",
184
+ "load",
185
+ "--json",
186
+ ],
187
+ )
188
+ assert result.exit_code == 0
189
+
190
+ @respx.mock
191
+ def test_render_with_callback(self, runner, mock_render_response):
192
+ route = respx.post("https://api.acedata.cloud/webextrator/render").mock(
193
+ return_value=Response(200, json=mock_render_response)
194
+ )
195
+ result = runner.invoke(
196
+ cli,
197
+ [
198
+ "--token",
199
+ "test-token",
200
+ "render",
201
+ "https://example.com",
202
+ "--callback-url",
203
+ "https://my.server.com/webhook",
204
+ "--json",
205
+ ],
206
+ )
207
+ assert result.exit_code == 0
208
+ sent = json.loads(route.calls[0].request.content)
209
+ assert sent["callback_url"] == "https://my.server.com/webhook"
210
+
211
+ @respx.mock
212
+ def test_render_with_block_resources_and_headers(self, runner, mock_render_response):
213
+ route = respx.post("https://api.acedata.cloud/webextrator/render").mock(
214
+ return_value=Response(200, json=mock_render_response)
215
+ )
216
+ result = runner.invoke(
217
+ cli,
218
+ [
219
+ "--token",
220
+ "test-token",
221
+ "render",
222
+ "https://example.com",
223
+ "--block-resources",
224
+ "--headers",
225
+ '{"X-Test":"1"}',
226
+ "--json",
227
+ ],
228
+ )
229
+ assert result.exit_code == 0
230
+ sent = json.loads(route.calls[0].request.content)
231
+ assert sent["block_resources"] is True
232
+ assert sent["headers"] == {"X-Test": "1"}
233
+
234
+ def test_render_no_token(self, runner):
235
+ result = runner.invoke(cli, ["--token", "", "render", "https://example.com"])
236
+ assert result.exit_code != 0
237
+
238
+
239
+ class TestTasksCommands:
240
+ """Tests for tasks management commands."""
241
+
242
+ @respx.mock
243
+ def test_tasks_retrieve_json(self, runner, mock_task_response):
244
+ respx.post("https://api.acedata.cloud/webextrator/tasks").mock(
245
+ return_value=Response(200, json=mock_task_response)
246
+ )
247
+ result = runner.invoke(
248
+ cli,
249
+ [
250
+ "--token",
251
+ "test-token",
252
+ "tasks",
253
+ "retrieve",
254
+ "--id",
255
+ "550e8400-e29b-41d4-a716-446655440000",
256
+ "--json",
257
+ ],
258
+ )
259
+ assert result.exit_code == 0
260
+ data = json.loads(result.output)
261
+ assert data["task_id"] == "550e8400-e29b-41d4-a716-446655440000"
262
+
263
+ @respx.mock
264
+ def test_tasks_retrieve_by_trace_id(self, runner, mock_task_response):
265
+ respx.post("https://api.acedata.cloud/webextrator/tasks").mock(
266
+ return_value=Response(200, json=mock_task_response)
267
+ )
268
+ result = runner.invoke(
269
+ cli,
270
+ [
271
+ "--token",
272
+ "test-token",
273
+ "tasks",
274
+ "retrieve",
275
+ "--trace-id",
276
+ "550e8400-e29b-41d4-a716-446655440001",
277
+ "--json",
278
+ ],
279
+ )
280
+ assert result.exit_code == 0
281
+
282
+ def test_tasks_retrieve_requires_id_or_trace_id(self, runner):
283
+ result = runner.invoke(cli, ["--token", "test-token", "tasks", "retrieve"])
284
+ assert result.exit_code != 0
285
+
286
+ @respx.mock
287
+ def test_tasks_batch_json(self, runner, mock_task_response):
288
+ batch_response = {"items": [mock_task_response], "count": 1}
289
+ respx.post("https://api.acedata.cloud/webextrator/tasks").mock(
290
+ return_value=Response(200, json=batch_response)
291
+ )
292
+ result = runner.invoke(
293
+ cli,
294
+ [
295
+ "--token",
296
+ "test-token",
297
+ "tasks",
298
+ "batch",
299
+ "--ids",
300
+ "id1",
301
+ "--ids",
302
+ "id2",
303
+ "--json",
304
+ ],
305
+ )
306
+ assert result.exit_code == 0
307
+
308
+
309
+ class TestInfoCommands:
310
+ """Tests for info and utility commands."""
311
+
312
+ def test_config(self, runner):
313
+ result = runner.invoke(cli, ["config"])
314
+ assert result.exit_code == 0
315
+ assert "api.acedata.cloud" in result.output
@@ -0,0 +1,3 @@
1
+ """WebExtrator CLI package."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for python -m webextrator_cli."""
2
+
3
+ from webextrator_cli.main import cli
4
+
5
+ if __name__ == "__main__":
6
+ cli()
@@ -0,0 +1 @@
1
+ """WebExtrator CLI commands package."""