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.
Files changed (78) hide show
  1. {sv_cli-0.4.0 → sv_cli-0.6.0}/AGENT.md +17 -17
  2. {sv_cli-0.4.0 → sv_cli-0.6.0}/PKG-INFO +6 -6
  3. {sv_cli-0.4.0 → sv_cli-0.6.0}/README.md +4 -4
  4. {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/commands.md +1 -1
  5. {sv_cli-0.4.0 → sv_cli-0.6.0}/pyproject.toml +1 -1
  6. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/__init__.py +1 -1
  7. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/adapters.py +9 -3
  8. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/api_client.py +7 -2
  9. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/executor.py +2 -1
  10. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/main.py +14 -2
  11. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/tasks.py +1 -1
  12. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_help_filtering.py +12 -0
  13. {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/workflows/publish.yml +0 -0
  16. {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/workflows/release.yml +0 -0
  17. {sv_cli-0.4.0 → sv_cli-0.6.0}/.github/workflows/test.yml +0 -0
  18. {sv_cli-0.4.0 → sv_cli-0.6.0}/.gitignore +0 -0
  19. {sv_cli-0.4.0 → sv_cli-0.6.0}/CHANGELOG.md +0 -0
  20. {sv_cli-0.4.0 → sv_cli-0.6.0}/CODE_OF_CONDUCT.md +0 -0
  21. {sv_cli-0.4.0 → sv_cli-0.6.0}/CONTRIBUTING.md +0 -0
  22. {sv_cli-0.4.0 → sv_cli-0.6.0}/LICENSE +0 -0
  23. {sv_cli-0.4.0 → sv_cli-0.6.0}/PULL_REQUEST_TEMPLATE.md +0 -0
  24. {sv_cli-0.4.0 → sv_cli-0.6.0}/SECURITY.md +0 -0
  25. {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/agent-usage.md +0 -0
  26. {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/api-definition-format.md +0 -0
  27. {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/authentication.md +0 -0
  28. {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/examples.md +0 -0
  29. {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/installation.md +0 -0
  30. {sv_cli-0.4.0 → sv_cli-0.6.0}/docs/troubleshooting.md +0 -0
  31. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/__init__.py +0 -0
  32. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/auth.py +0 -0
  33. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/better_keywords.py +0 -0
  34. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/call.py +0 -0
  35. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/config.py +0 -0
  36. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/content_quality.py +0 -0
  37. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/content_transformer.py +0 -0
  38. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/core_analysis.py +0 -0
  39. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/definitions.py +0 -0
  40. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/geo_audit.py +0 -0
  41. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/insight_igniter.py +0 -0
  42. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/marketplace_services.py +0 -0
  43. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/options.py +0 -0
  44. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/preliminary_audit.py +0 -0
  45. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/ranklens.py +0 -0
  46. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seo_image.py +0 -0
  47. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seo_mapping.py +0 -0
  48. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seogpt.py +0 -0
  49. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seogpt2.py +0 -0
  50. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/seogpt_compare.py +0 -0
  51. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/top_competitors.py +0 -0
  52. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/commands/topical_authority.py +0 -0
  53. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/config.py +0 -0
  54. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/definitions.py +0 -0
  55. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/errors.py +0 -0
  56. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/formatter.py +0 -0
  57. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/__init__.py +0 -0
  58. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/csv_renderer.py +0 -0
  59. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/json_renderer.py +0 -0
  60. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/markdown_renderer.py +0 -0
  61. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/table_renderer.py +0 -0
  62. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/renderers/text_renderer.py +0 -0
  63. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/resolver.py +0 -0
  64. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/schemas/__init__.py +0 -0
  65. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/schemas/api_response.py +0 -0
  66. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/schemas/config.py +0 -0
  67. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/schemas/tool_definition.py +0 -0
  68. {sv_cli-0.4.0 → sv_cli-0.6.0}/src/sv_cli/utils.py +0 -0
  69. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/api_root.json +0 -0
  70. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/better_keywords_response.json +0 -0
  71. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/ranklens_definitions.json +0 -0
  72. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/seogpt2_definitions.json +0 -0
  73. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/fixtures/seogpt_definitions.json +0 -0
  74. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_config.py +0 -0
  75. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_definitions.py +0 -0
  76. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_executor.py +0 -0
  77. {sv_cli-0.4.0 → sv_cli-0.6.0}/tests/test_formatter.py +0 -0
  78. {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
- | `seogpt2` | `seo-gpt2` | `create-task` | `--topic` *(`--keyword`/`--kw` is a separate, optional field)* | Yes |
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
- seogpt2: create-task, get-task-status, get-result, raw
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, content, raw
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 seogpt2 types
122
- sv seogpt2 lengths
123
- sv seogpt2 languages
124
- sv seogpt2 tones
125
- sv seogpt2 engines
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`, `seogpt2 create-task`, `seogpt-compare create-task`, `seo-mapping 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 `seogpt2`, `seogpt-compare`, `seo-mapping` — replace `geo-audit` with the tool name.
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
- ### seogpt2 — `--topic` maps to `Topic` (required), `--keyword`/`--kw` is a separate field
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 seogpt2 create-task --topic "White Label SEO for Agencies" --type on-page-blog-article --wait --strict --no-fuzzy --non-interactive --format json
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 seogpt2 create-task --title "White Label SEO for Agencies" --type on-page-blog-article --wait --strict --no-fuzzy --non-interactive --format json
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 seogpt2 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
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` | seogpt2 called without `--topic` | Add `--topic "your topic"` |
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 seogpt2 create-task --topic "White Label SEO for Agencies" --type 0 --wait --strict --no-fuzzy --non-interactive --format json
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
- - `seogpt2 --type 0` = On-Page Blog Article
397
+ - `prose --type 0` = On-Page Blog Article
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: sv-cli
3
- Version: 0.4.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
- | `seogpt2` | `seo-gpt2` | `seogpt2` |
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 seogpt2 create-task --topic "White Label SEO for Agencies" --type on-page-blog-article --wait
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 seogpt2 types`, lengths with `sv seogpt2 lengths`, engines with `sv seogpt2 engines`.
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
- | `seogpt2` | `seo-gpt2` | `seogpt2` |
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 seogpt2 create-task --topic "White Label SEO for Agencies" --type on-page-blog-article --wait
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 seogpt2 types`, lengths with `sv seogpt2 lengths`, engines with `sv seogpt2 engines`.
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 seogpt2 article --keyword "white label seo" --wait
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"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sv-cli"
7
- version = "0.4.0"
7
+ version = "0.6.0"
8
8
  description = "Open-source, definition-driven command-line client for SV AI API tools."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """SV CLI package."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.6.0"
@@ -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="seogpt2",
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", "content", "raw"),
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(tool_name=tool, action=None, params={}, runtime=runtime, raw_payload=payload, method=method, console=console)
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 seogpt2"
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