sv-cli 0.4.0__tar.gz → 0.6.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.
- {sv_cli-0.4.0 → sv_cli-0.6.0}/AGENT.md +17 -17
- {sv_cli-0.4.0 → sv_cli-0.6.0}/PKG-INFO +6 -6
- {sv_cli-0.4.0 → sv_cli-0.6.0}/README.md +4 -4
- {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/commands.md +1 -1
- {sv_cli-0.4.0 → sv_cli-0.6.0}/pyproject.toml +1 -1
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/__init__.py +1 -1
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/adapters.py +9 -3
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/api_client.py +7 -2
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/executor.py +2 -1
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/main.py +14 -2
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/tasks.py +1 -1
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_help_filtering.py +12 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/workflows/publish.yml +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/workflows/release.yml +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/workflows/test.yml +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/.gitignore +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/CHANGELOG.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/CODE_OF_CONDUCT.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/CONTRIBUTING.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/LICENSE +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/PULL_REQUEST_TEMPLATE.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/SECURITY.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/agent-usage.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/api-definition-format.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/authentication.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/examples.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/installation.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/troubleshooting.md +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/__init__.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/auth.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/better_keywords.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/call.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/config.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/content_quality.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/content_transformer.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/core_analysis.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/definitions.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/geo_audit.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/insight_igniter.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/marketplace_services.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/options.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/preliminary_audit.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/ranklens.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seo_image.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seo_mapping.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seogpt.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seogpt2.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seogpt_compare.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/top_competitors.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/topical_authority.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/config.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/definitions.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/errors.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/formatter.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/__init__.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/csv_renderer.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/json_renderer.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/markdown_renderer.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/table_renderer.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/text_renderer.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/resolver.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/schemas/__init__.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/schemas/api_response.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/schemas/config.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/schemas/tool_definition.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/utils.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/api_root.json +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/better_keywords_response.json +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/ranklens_definitions.json +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/seogpt2_definitions.json +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/seogpt_definitions.json +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_config.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_definitions.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_executor.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_formatter.py +0 -0
- {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_resolver.py +0 -0
|
@@ -64,7 +64,7 @@ All 16 available tools. Use exact command names — aliases are human shortcuts.
|
|
|
64
64
|
| `ranklens` | — | `rank` | `--entity` `--url` | No |
|
|
65
65
|
| `seo-image` | `image` | `generate` | `--keyword` | No |
|
|
66
66
|
| `seogpt` | `seo-gpt` | `generate` | `--keyword` `--type` | No |
|
|
67
|
-
| `
|
|
67
|
+
| `prose` | `seogpt2`, `seo-gpt2` | `create-task` | `--topic` *(`--keyword`/`--kw` is a separate, optional field)* | Yes |
|
|
68
68
|
| `seogpt-compare` | `compare` | `create-task` | `--url` `--keyword` | Yes |
|
|
69
69
|
| `seo-mapping` | `mapping` | `create-task` | `--url` `--keyword` | Yes |
|
|
70
70
|
| `topical-authority` | `topical` | `topics` | `--keyword` | No |
|
|
@@ -84,10 +84,10 @@ preliminary-audit: analyze, raw
|
|
|
84
84
|
ranklens: rank, competitors, raw
|
|
85
85
|
seo-image: generate, raw
|
|
86
86
|
seogpt: generate, raw
|
|
87
|
-
|
|
87
|
+
prose: create-task, get-task-status, get-result, raw
|
|
88
88
|
seogpt-compare: create-task, get-task-status, get-result, raw
|
|
89
89
|
seo-mapping: create-task, get-task-status, get-result, raw
|
|
90
|
-
topical-authority: topics,
|
|
90
|
+
topical-authority: topics, raw
|
|
91
91
|
top-competitors: analyze, raw
|
|
92
92
|
marketplace-services: search, raw
|
|
93
93
|
content-quality: analyze, raw
|
|
@@ -118,11 +118,11 @@ sv seogpt engines
|
|
|
118
118
|
sv image types
|
|
119
119
|
sv image themes
|
|
120
120
|
sv image themes --search wild
|
|
121
|
-
sv
|
|
122
|
-
sv
|
|
123
|
-
sv
|
|
124
|
-
sv
|
|
125
|
-
sv
|
|
121
|
+
sv prose types
|
|
122
|
+
sv prose lengths
|
|
123
|
+
sv prose languages
|
|
124
|
+
sv prose tones
|
|
125
|
+
sv prose engines
|
|
126
126
|
sv geo-audit types
|
|
127
127
|
sv geo-audit languages
|
|
128
128
|
sv seo-mapping types
|
|
@@ -190,7 +190,7 @@ Parse: `response["data"]["text"]`
|
|
|
190
190
|
```
|
|
191
191
|
Parse: `response["data"]` — array of objects
|
|
192
192
|
|
|
193
|
-
**Async task created** (`geo-audit create-task`, `
|
|
193
|
+
**Async task created** (`geo-audit create-task`, `prose create-task`, `seogpt-compare create-task`, `seo-mapping create-task`):
|
|
194
194
|
```json
|
|
195
195
|
{ "data": { "task_id": "wFEGe...", "status": "pending", "stage": "queued", "percent_complete": 1 } }
|
|
196
196
|
```
|
|
@@ -247,7 +247,7 @@ sv geo-audit get-task-status --task-id TASK_ID --format json
|
|
|
247
247
|
sv geo-audit get-result --task-id TASK_ID --format json
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
-
Same for `
|
|
250
|
+
Same for `prose`, `seogpt-compare`, `seo-mapping` — replace `geo-audit` with the tool name.
|
|
251
251
|
|
|
252
252
|
---
|
|
253
253
|
|
|
@@ -255,17 +255,17 @@ Same for `seogpt2`, `seogpt-compare`, `seo-mapping` — replace `geo-audit` with
|
|
|
255
255
|
|
|
256
256
|
These differ from the standard pattern — get them wrong and the call fails.
|
|
257
257
|
|
|
258
|
-
###
|
|
258
|
+
### prose — `--topic` maps to `Topic` (required), `--keyword`/`--kw` is a separate field
|
|
259
259
|
|
|
260
260
|
```bash
|
|
261
261
|
# CORRECT — --topic sends value as the required "Topic" API field
|
|
262
|
-
sv
|
|
262
|
+
sv prose create-task --topic "White Label SEO for Agencies" --type on-page-blog-article --wait --strict --no-fuzzy --non-interactive --format json
|
|
263
263
|
|
|
264
264
|
# --title is an alias for --topic (same field)
|
|
265
|
-
sv
|
|
265
|
+
sv prose create-task --title "White Label SEO for Agencies" --type on-page-blog-article --wait --strict --no-fuzzy --non-interactive --format json
|
|
266
266
|
|
|
267
267
|
# --keyword/--kw maps to the separate, optional KW field — it does NOT set Topic
|
|
268
|
-
sv
|
|
268
|
+
sv prose create-task --topic "White Label SEO for Agencies" --keyword "white label seo" --type on-page-blog-article --wait --strict --no-fuzzy --non-interactive --format json
|
|
269
269
|
```
|
|
270
270
|
|
|
271
271
|
### better-keywords `filter` — requires `data` array from prior `research` call
|
|
@@ -319,7 +319,7 @@ sv task status TASK_ID --tool geo-audit --format json
|
|
|
319
319
|
|---|---|---|
|
|
320
320
|
| `Could not resolve --type "X" in strict mode` | Value is not a valid slug or ID | Run `sv options TOOL type` → use `id` or `slug` column |
|
|
321
321
|
| `Could not resolve --type "X"` (no strict) | No match found at any level | Run `sv options TOOL type --search X` to find closest match |
|
|
322
|
-
| `Topic is required` |
|
|
322
|
+
| `Topic is required` | prose called without `--topic` | Add `--topic "your topic"` |
|
|
323
323
|
| `task_id is invalid` | Task has expired on the server | Create a new task |
|
|
324
324
|
| `No local tool mapping found for task` | Task not in `~/.sv/tasks.json` | Add `--tool TOOL_NAME` explicitly |
|
|
325
325
|
| `API authentication failed: HTTP 401` | Bad or missing API key | Check `SV_API_KEY` environment variable |
|
|
@@ -381,7 +381,7 @@ sv preliminary-audit analyze --url https://example.com --strict --no-fuzzy --non
|
|
|
381
381
|
sv ranklens rank --entity "white label seo" --url https://example.com --strict --no-fuzzy --non-interactive --format json
|
|
382
382
|
sv seo-image generate --keyword "white label seo" --type 33 --strict --no-fuzzy --non-interactive --format json
|
|
383
383
|
sv seogpt generate --keyword "white label seo" --type 18 --strict --no-fuzzy --non-interactive --format json
|
|
384
|
-
sv
|
|
384
|
+
sv prose create-task --topic "White Label SEO for Agencies" --type 0 --wait --strict --no-fuzzy --non-interactive --format json
|
|
385
385
|
sv seogpt-compare create-task --url https://example.com --keyword "white label seo" --wait --strict --no-fuzzy --non-interactive --format json
|
|
386
386
|
sv seo-mapping create-task --url https://example.com --keyword "white label seo" --wait --strict --no-fuzzy --non-interactive --format json
|
|
387
387
|
sv topical-authority topics --keyword "white label seo" --strict --no-fuzzy --non-interactive --format json
|
|
@@ -394,4 +394,4 @@ Common enum IDs (verify with `sv options` — these are from live API at time of
|
|
|
394
394
|
- `seogpt --type 18` = Meta Description
|
|
395
395
|
- `seogpt --type 8` = Page Title
|
|
396
396
|
- `seo-image --type 33` = Blog Header Image
|
|
397
|
-
- `
|
|
397
|
+
- `prose --type 0` = On-Page Blog Article
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: sv-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Open-source, definition-driven command-line client for SV AI API tools.
|
|
5
5
|
Project-URL: Homepage, https://github.com/seovendorco/sv-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/seovendorco/sv-cli/issues
|
|
@@ -163,7 +163,7 @@ sv title --keyword "white label seo" --url https://example.com
|
|
|
163
163
|
| `ranklens` | | `ranklens` |
|
|
164
164
|
| `seo-image` | `image` | `seo-image` |
|
|
165
165
|
| `seogpt` | `seo-gpt` | `seogpt` |
|
|
166
|
-
| `
|
|
166
|
+
| `prose` | `seogpt2`, `seo-gpt2` | `seogpt2` |
|
|
167
167
|
| `seogpt-compare` | `compare` | `seogptcompare` |
|
|
168
168
|
| `seo-mapping` | `mapping` | `seogptmapping` |
|
|
169
169
|
| `topical-authority` | `topical` | `topical-authority` |
|
|
@@ -252,13 +252,13 @@ sv task status TASK_ID --tool geo-audit
|
|
|
252
252
|
sv task result TASK_ID --tool geo-audit
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
`seogpt2` is another async tool. Its required field is `Topic` (a title or subject), mapped via `--topic` (`--title` is an alias for the same field). `--keyword`/`--kw` is a separate, optional field for additional keywords — it does not set the topic:
|
|
255
|
+
`prose` (formerly `seogpt2`) is another async tool. Its required field is `Topic` (a title or subject), mapped via `--topic` (`--title` is an alias for the same field). `--keyword`/`--kw` is a separate, optional field for additional keywords — it does not set the topic:
|
|
256
256
|
|
|
257
257
|
```bash
|
|
258
|
-
sv
|
|
258
|
+
sv prose create-task --topic "White Label SEO for Agencies" --type on-page-blog-article --wait
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
See available types with `sv
|
|
261
|
+
See available types with `sv prose types`, lengths with `sv prose lengths`, engines with `sv prose engines`.
|
|
262
262
|
|
|
263
263
|
Manual 3-step flow (without `--wait`):
|
|
264
264
|
|
|
@@ -128,7 +128,7 @@ sv title --keyword "white label seo" --url https://example.com
|
|
|
128
128
|
| `ranklens` | | `ranklens` |
|
|
129
129
|
| `seo-image` | `image` | `seo-image` |
|
|
130
130
|
| `seogpt` | `seo-gpt` | `seogpt` |
|
|
131
|
-
| `
|
|
131
|
+
| `prose` | `seogpt2`, `seo-gpt2` | `seogpt2` |
|
|
132
132
|
| `seogpt-compare` | `compare` | `seogptcompare` |
|
|
133
133
|
| `seo-mapping` | `mapping` | `seogptmapping` |
|
|
134
134
|
| `topical-authority` | `topical` | `topical-authority` |
|
|
@@ -217,13 +217,13 @@ sv task status TASK_ID --tool geo-audit
|
|
|
217
217
|
sv task result TASK_ID --tool geo-audit
|
|
218
218
|
```
|
|
219
219
|
|
|
220
|
-
`seogpt2` is another async tool. Its required field is `Topic` (a title or subject), mapped via `--topic` (`--title` is an alias for the same field). `--keyword`/`--kw` is a separate, optional field for additional keywords — it does not set the topic:
|
|
220
|
+
`prose` (formerly `seogpt2`) is another async tool. Its required field is `Topic` (a title or subject), mapped via `--topic` (`--title` is an alias for the same field). `--keyword`/`--kw` is a separate, optional field for additional keywords — it does not set the topic:
|
|
221
221
|
|
|
222
222
|
```bash
|
|
223
|
-
sv
|
|
223
|
+
sv prose create-task --topic "White Label SEO for Agencies" --type on-page-blog-article --wait
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
See available types with `sv
|
|
226
|
+
See available types with `sv prose types`, lengths with `sv prose lengths`, engines with `sv prose engines`.
|
|
227
227
|
|
|
228
228
|
Manual 3-step flow (without `--wait`):
|
|
229
229
|
|
|
@@ -24,7 +24,7 @@ sv preliminary-audit run --url https://example.com
|
|
|
24
24
|
sv ranklens analyze --brand "SV" --keyword "white label seo"
|
|
25
25
|
sv seo-image generate --keyword "white label seo" --type blog-header
|
|
26
26
|
sv seogpt generate --type meta-description --keyword "white label seo"
|
|
27
|
-
sv
|
|
27
|
+
sv prose article --keyword "white label seo" --wait
|
|
28
28
|
sv seogpt-compare run --url https://example.com --keyword "white label seo"
|
|
29
29
|
sv seo-mapping run --url https://example.com --keywords "seo agency" --wait
|
|
30
30
|
sv topical-authority generate --keyword "local seo"
|
|
@@ -184,9 +184,15 @@ TOOL_ADAPTERS: dict[str, ToolAdapter] = {
|
|
|
184
184
|
},
|
|
185
185
|
),
|
|
186
186
|
"seogpt2": ToolAdapter(
|
|
187
|
+
# "Prose" is the current product name; seogpt2 is the original one, kept
|
|
188
|
+
# working as an alias so existing scripts don't break. canonical stays
|
|
189
|
+
# "seogpt2" deliberately - it's what the live API root still keys this
|
|
190
|
+
# tool under, and keeping it means the CLI works whether or not the
|
|
191
|
+
# /api/prose/ alias folder is deployed yet. Flip it to "prose" only when
|
|
192
|
+
# /api/seogpt2/ is actually retired.
|
|
187
193
|
canonical="seogpt2",
|
|
188
|
-
command="
|
|
189
|
-
aliases=("seo-gpt2"
|
|
194
|
+
command="prose",
|
|
195
|
+
aliases=("seogpt2", "seo-gpt2"),
|
|
190
196
|
default_action="create-task",
|
|
191
197
|
actions=("create-task", "get-task-status", "get-result", "raw"),
|
|
192
198
|
field_aliases={
|
|
@@ -242,7 +248,7 @@ TOOL_ADAPTERS: dict[str, ToolAdapter] = {
|
|
|
242
248
|
command="topical-authority",
|
|
243
249
|
aliases=("topical",),
|
|
244
250
|
default_action="topics",
|
|
245
|
-
actions=("topics", "
|
|
251
|
+
actions=("topics", "raw"),
|
|
246
252
|
field_aliases=COMMON_FIELD_ALIASES,
|
|
247
253
|
option_aliases={
|
|
248
254
|
"modes": ("topicmode", "topic_mode", "mode", "modes"),
|
|
@@ -24,9 +24,13 @@ class APIResponse:
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class APIClient:
|
|
27
|
-
def __init__(self, *, debug: bool = False, console: Console | None = None) -> None:
|
|
27
|
+
def __init__(self, *, debug: bool = False, console: Console | None = None, client_type: str | None = None) -> None:
|
|
28
28
|
self.debug = debug
|
|
29
29
|
self.console = console or Console(stderr=True)
|
|
30
|
+
# Identifies this process to the SV API as "cli" or "mcp" (X-SV-Client header)
|
|
31
|
+
# for usage tracking. None means the caller didn't specify one - the header is
|
|
32
|
+
# simply omitted, and the API buckets the call as generic "api" usage.
|
|
33
|
+
self.client_type = client_type
|
|
30
34
|
|
|
31
35
|
def request_tool(
|
|
32
36
|
self,
|
|
@@ -42,6 +46,7 @@ class APIClient:
|
|
|
42
46
|
final_payload["k"] = api_key
|
|
43
47
|
|
|
44
48
|
method = method.upper()
|
|
49
|
+
headers = {"X-SV-Client": self.client_type} if self.client_type else None
|
|
45
50
|
if self.debug:
|
|
46
51
|
parsed = urlparse(endpoint)
|
|
47
52
|
shown_path = parsed.path or endpoint
|
|
@@ -50,7 +55,7 @@ class APIClient:
|
|
|
50
55
|
|
|
51
56
|
started = time.perf_counter()
|
|
52
57
|
try:
|
|
53
|
-
with httpx.Client(timeout=timeout, follow_redirects=True) as client:
|
|
58
|
+
with httpx.Client(timeout=timeout, follow_redirects=True, headers=headers) as client:
|
|
54
59
|
if method == "GET":
|
|
55
60
|
response = client.get(endpoint, params=final_payload)
|
|
56
61
|
elif method in {"POST", "PUT", "PATCH"}:
|
|
@@ -120,6 +120,7 @@ def execute_tool(
|
|
|
120
120
|
raw_payload: dict[str, Any] | None = None,
|
|
121
121
|
method: str = "POST",
|
|
122
122
|
console: Console | None = None,
|
|
123
|
+
client_type: str | None = None,
|
|
123
124
|
) -> Any:
|
|
124
125
|
console = console or Console()
|
|
125
126
|
definitions = DefinitionsManager(runtime.base_url)
|
|
@@ -149,7 +150,7 @@ def execute_tool(
|
|
|
149
150
|
non_interactive=runtime.non_interactive,
|
|
150
151
|
)
|
|
151
152
|
|
|
152
|
-
client = APIClient(debug=runtime.debug, console=Console(stderr=True))
|
|
153
|
+
client = APIClient(debug=runtime.debug, console=Console(stderr=True), client_type=client_type)
|
|
153
154
|
response = client.request_tool(endpoint=str(endpoint), payload=payload, api_key=api_key, method=method)
|
|
154
155
|
data = response.data
|
|
155
156
|
|
|
@@ -228,6 +228,7 @@ def run_tool_action(
|
|
|
228
228
|
wait_options=wait_options,
|
|
229
229
|
method=method,
|
|
230
230
|
console=console,
|
|
231
|
+
client_type="cli",
|
|
231
232
|
)
|
|
232
233
|
except Exception as exc: # noqa: BLE001
|
|
233
234
|
fail(exc)
|
|
@@ -597,7 +598,16 @@ def raw_call(
|
|
|
597
598
|
runtime = runtime_from_ctx(ctx)
|
|
598
599
|
# Raw calls intentionally bypass fuzzy matching. Users can pass exact API payloads.
|
|
599
600
|
runtime.no_fuzzy = True
|
|
600
|
-
execute_tool(
|
|
601
|
+
execute_tool(
|
|
602
|
+
tool_name=tool,
|
|
603
|
+
action=None,
|
|
604
|
+
params={},
|
|
605
|
+
runtime=runtime,
|
|
606
|
+
raw_payload=payload,
|
|
607
|
+
method=method,
|
|
608
|
+
console=console,
|
|
609
|
+
client_type="cli",
|
|
610
|
+
)
|
|
601
611
|
except Exception as exc: # noqa: BLE001
|
|
602
612
|
fail(exc)
|
|
603
613
|
|
|
@@ -617,7 +627,7 @@ def task_call(ctx: typer.Context, task_id: str, action: str, tool: str | None) -
|
|
|
617
627
|
non_interactive=runtime.non_interactive,
|
|
618
628
|
)
|
|
619
629
|
payload = status_payload(task_id) if action == "status" else result_payload(task_id)
|
|
620
|
-
data = APIClient(debug=runtime.debug, console=err_console).request_tool(
|
|
630
|
+
data = APIClient(debug=runtime.debug, console=err_console, client_type="cli").request_tool(
|
|
621
631
|
endpoint=str(entry.get("endpoint")), payload=payload, api_key=api_key
|
|
622
632
|
).data
|
|
623
633
|
print_output(console, data, runtime.output_format, runtime.output)
|
|
@@ -945,6 +955,7 @@ def make_tool_app(adapter: ToolAdapter) -> typer.Typer:
|
|
|
945
955
|
runtime=runtime,
|
|
946
956
|
wait_options=wait_options,
|
|
947
957
|
console=console,
|
|
958
|
+
client_type="cli",
|
|
948
959
|
)
|
|
949
960
|
except Exception as exc: # noqa: BLE001
|
|
950
961
|
fail(exc)
|
|
@@ -977,6 +988,7 @@ def make_tool_app(adapter: ToolAdapter) -> typer.Typer:
|
|
|
977
988
|
raw_payload=payload,
|
|
978
989
|
method=method,
|
|
979
990
|
console=console,
|
|
991
|
+
client_type="cli",
|
|
980
992
|
)
|
|
981
993
|
except Exception as exc: # noqa: BLE001
|
|
982
994
|
fail(exc)
|
|
@@ -50,7 +50,7 @@ def get_task_tool(task_id: str, tool: str | None = None) -> str:
|
|
|
50
50
|
return str(entry["tool"])
|
|
51
51
|
raise ConfigError(
|
|
52
52
|
f'No local tool mapping found for task "{task_id}". Re-run with --tool, for example: '
|
|
53
|
-
f"sv task status {task_id} --tool
|
|
53
|
+
f"sv task status {task_id} --tool prose"
|
|
54
54
|
)
|
|
55
55
|
|
|
56
56
|
|
|
@@ -78,3 +78,15 @@ def test_tool_help_separates_actions_from_option_lookups(tmp_path):
|
|
|
78
78
|
actions_section = output.split("Option lookups")[0]
|
|
79
79
|
assert "create-task" in actions_section
|
|
80
80
|
assert "languages" not in actions_section
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_prose_is_the_primary_name_and_seogpt2_still_works(tmp_path):
|
|
84
|
+
"""Prose is the current product name; the old names stay as aliases.
|
|
85
|
+
|
|
86
|
+
All three must resolve to the same tool - dropping seogpt2/seo-gpt2 would
|
|
87
|
+
break any existing script that hardcodes them.
|
|
88
|
+
"""
|
|
89
|
+
for name in ("prose", "seogpt2", "seo-gpt2"):
|
|
90
|
+
output = _run_cli(tmp_path, name, "create-task", "--help")
|
|
91
|
+
assert "--topic" in output, f"{name} did not resolve to the Prose tool"
|
|
92
|
+
assert "--keyword" in output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|