sv-cli 0.3.0__tar.gz → 0.5.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 (79) hide show
  1. {sv_cli-0.3.0 → sv_cli-0.5.0}/AGENT.md +24 -18
  2. {sv_cli-0.3.0 → sv_cli-0.5.0}/PKG-INFO +13 -7
  3. {sv_cli-0.3.0 → sv_cli-0.5.0}/README.md +11 -5
  4. {sv_cli-0.3.0 → sv_cli-0.5.0}/pyproject.toml +1 -1
  5. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/__init__.py +1 -1
  6. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/adapters.py +24 -4
  7. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/api_client.py +8 -3
  8. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/executor.py +2 -1
  9. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/main.py +189 -30
  10. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/resolver.py +34 -0
  11. sv_cli-0.5.0/tests/fixtures/ranklens_definitions.json +16 -0
  12. sv_cli-0.5.0/tests/fixtures/seogpt2_definitions.json +15 -0
  13. sv_cli-0.5.0/tests/test_executor.py +150 -0
  14. sv_cli-0.5.0/tests/test_help_filtering.py +80 -0
  15. {sv_cli-0.3.0 → sv_cli-0.5.0}/tests/test_resolver.py +48 -0
  16. sv_cli-0.3.0/tests/test_executor.py +0 -76
  17. {sv_cli-0.3.0 → sv_cli-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  18. {sv_cli-0.3.0 → sv_cli-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  19. {sv_cli-0.3.0 → sv_cli-0.5.0}/.github/workflows/publish.yml +0 -0
  20. {sv_cli-0.3.0 → sv_cli-0.5.0}/.github/workflows/release.yml +0 -0
  21. {sv_cli-0.3.0 → sv_cli-0.5.0}/.github/workflows/test.yml +0 -0
  22. {sv_cli-0.3.0 → sv_cli-0.5.0}/.gitignore +0 -0
  23. {sv_cli-0.3.0 → sv_cli-0.5.0}/CHANGELOG.md +0 -0
  24. {sv_cli-0.3.0 → sv_cli-0.5.0}/CODE_OF_CONDUCT.md +0 -0
  25. {sv_cli-0.3.0 → sv_cli-0.5.0}/CONTRIBUTING.md +0 -0
  26. {sv_cli-0.3.0 → sv_cli-0.5.0}/LICENSE +0 -0
  27. {sv_cli-0.3.0 → sv_cli-0.5.0}/PULL_REQUEST_TEMPLATE.md +0 -0
  28. {sv_cli-0.3.0 → sv_cli-0.5.0}/SECURITY.md +0 -0
  29. {sv_cli-0.3.0 → sv_cli-0.5.0}/docs/agent-usage.md +0 -0
  30. {sv_cli-0.3.0 → sv_cli-0.5.0}/docs/api-definition-format.md +0 -0
  31. {sv_cli-0.3.0 → sv_cli-0.5.0}/docs/authentication.md +0 -0
  32. {sv_cli-0.3.0 → sv_cli-0.5.0}/docs/commands.md +0 -0
  33. {sv_cli-0.3.0 → sv_cli-0.5.0}/docs/examples.md +0 -0
  34. {sv_cli-0.3.0 → sv_cli-0.5.0}/docs/installation.md +0 -0
  35. {sv_cli-0.3.0 → sv_cli-0.5.0}/docs/troubleshooting.md +0 -0
  36. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/__init__.py +0 -0
  37. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/auth.py +0 -0
  38. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/better_keywords.py +0 -0
  39. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/call.py +0 -0
  40. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/config.py +0 -0
  41. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/content_quality.py +0 -0
  42. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/content_transformer.py +0 -0
  43. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/core_analysis.py +0 -0
  44. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/definitions.py +0 -0
  45. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/geo_audit.py +0 -0
  46. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/insight_igniter.py +0 -0
  47. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/marketplace_services.py +0 -0
  48. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/options.py +0 -0
  49. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/preliminary_audit.py +0 -0
  50. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/ranklens.py +0 -0
  51. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/seo_image.py +0 -0
  52. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/seo_mapping.py +0 -0
  53. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/seogpt.py +0 -0
  54. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/seogpt2.py +0 -0
  55. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/seogpt_compare.py +0 -0
  56. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/top_competitors.py +0 -0
  57. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/commands/topical_authority.py +0 -0
  58. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/config.py +0 -0
  59. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/definitions.py +0 -0
  60. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/errors.py +0 -0
  61. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/formatter.py +0 -0
  62. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/renderers/__init__.py +0 -0
  63. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/renderers/csv_renderer.py +0 -0
  64. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/renderers/json_renderer.py +0 -0
  65. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/renderers/markdown_renderer.py +0 -0
  66. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/renderers/table_renderer.py +0 -0
  67. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/renderers/text_renderer.py +0 -0
  68. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/schemas/__init__.py +0 -0
  69. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/schemas/api_response.py +0 -0
  70. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/schemas/config.py +0 -0
  71. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/schemas/tool_definition.py +0 -0
  72. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/tasks.py +0 -0
  73. {sv_cli-0.3.0 → sv_cli-0.5.0}/src/sv_cli/utils.py +0 -0
  74. {sv_cli-0.3.0 → sv_cli-0.5.0}/tests/fixtures/api_root.json +0 -0
  75. {sv_cli-0.3.0 → sv_cli-0.5.0}/tests/fixtures/better_keywords_response.json +0 -0
  76. {sv_cli-0.3.0 → sv_cli-0.5.0}/tests/fixtures/seogpt_definitions.json +0 -0
  77. {sv_cli-0.3.0 → sv_cli-0.5.0}/tests/test_config.py +0 -0
  78. {sv_cli-0.3.0 → sv_cli-0.5.0}/tests/test_definitions.py +0 -0
  79. {sv_cli-0.3.0 → sv_cli-0.5.0}/tests/test_formatter.py +0 -0
@@ -55,16 +55,16 @@ All 16 available tools. Use exact command names — aliases are human shortcuts.
55
55
 
56
56
  | Command | Alias | Default Action | API Required Fields | Async |
57
57
  |---|---|---|---|---|
58
- | `better-keywords` | `keywords` | `research` / `filter` | `--keyword` (`filter` also needs `--data`) | No |
58
+ | `better-keywords` | `keywords` | `research` / `filter` | `--keyword` (`filter` needs a JSON `data` array — raw call only) | No |
59
59
  | `content-transformer` | `transform` | `rewrite` | `--text` | No |
60
60
  | `core-analysis` | `core` | `analyze` | *(none required)* | No |
61
61
  | `geo-audit` | `audit` | `create-task` | `--url` `--keyword` | Yes |
62
62
  | `insight-igniter` | `insights` | `entities` | `--url` | No |
63
63
  | `preliminary-audit` | `prelim-audit` | `analyze` | `--url` | No |
64
- | `ranklens` | — | `rank` | `--keyword` `--url` | No |
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` | `--keyword` *(= Topic)* | Yes |
67
+ | `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 |
@@ -87,7 +87,7 @@ seogpt: generate, raw
87
87
  seogpt2: 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
@@ -112,6 +112,7 @@ sv options seogpt type --search meta
112
112
  # Shorthand subcommands (where available)
113
113
  sv seogpt types
114
114
  sv seogpt types --search meta
115
+ sv seogpt lengths
115
116
  sv seogpt languages
116
117
  sv seogpt engines
117
118
  sv image types
@@ -128,6 +129,7 @@ sv seo-mapping types
128
129
  sv better-keywords types
129
130
  sv better-keywords languages
130
131
  sv content-transformer types
132
+ sv content-transformer lengths
131
133
  sv content-transformer languages
132
134
  sv ranklens languages
133
135
  sv ranklens engines
@@ -241,8 +243,8 @@ sv --format json task result TASK_ID --tool geo-audit
241
243
  ### Pattern 3: Direct tool polling
242
244
 
243
245
  ```bash
244
- sv geo-audit get-task-status --task_id TASK_ID --format json
245
- sv geo-audit get-result --task_id TASK_ID --format json
246
+ sv geo-audit get-task-status --task-id TASK_ID --format json
247
+ sv geo-audit get-result --task-id TASK_ID --format json
246
248
  ```
247
249
 
248
250
  Same for `seogpt2`, `seogpt-compare`, `seo-mapping` — replace `geo-audit` with the tool name.
@@ -253,14 +255,17 @@ Same for `seogpt2`, `seogpt-compare`, `seo-mapping` — replace `geo-audit` with
253
255
 
254
256
  These differ from the standard pattern — get them wrong and the call fails.
255
257
 
256
- ### seogpt2 — `--keyword` maps to `Topic`, not `kw`
258
+ ### seogpt2 — `--topic` maps to `Topic` (required), `--keyword`/`--kw` is a separate field
257
259
 
258
260
  ```bash
259
- # CORRECT — --keyword sends value as "Topic" API field
260
- sv seogpt2 create-task --keyword "White Label SEO for Agencies" --type on-page-blog-article --wait --strict --no-fuzzy --non-interactive --format json
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
261
263
 
262
- # WRONG — no other flag maps to Topic
263
- sv seogpt2 create-task --text "..." ... # also maps to Topic, use --keyword
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
266
+
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
264
269
  ```
265
270
 
266
271
  ### better-keywords `filter` — requires `data` array from prior `research` call
@@ -288,11 +293,12 @@ No CLI flag exists for `mgptid`. Must use raw call:
288
293
 
289
294
  ```bash
290
295
  # Step 1 — get MGPTID from rank response
291
- sv ranklens rank --keyword "white label seo" --url https://example.com --format json
296
+ sv ranklens rank --entity "white label seo" --url https://example.com --format json
292
297
  # → parse response["data"]["MGPTID"]
298
+ # response["data"] always uses "entity", never "keyword" — --keyword/--kw still works as an input alias, but the response field name is always "entity"
293
299
 
294
300
  # Step 2 — pass MGPTID via raw call
295
- sv --format json call ranklens --json '{"action":"competitors","mgptid":"MGPTID_VALUE","web":"https://example.com","kw":"white label seo"}'
301
+ sv --format json call ranklens --json '{"action":"competitors","mgptid":"MGPTID_VALUE","web":"https://example.com","entity":"white label seo"}'
296
302
  ```
297
303
 
298
304
  ### `sv task status/result` — `--format` must be global
@@ -313,7 +319,7 @@ sv task status TASK_ID --tool geo-audit --format json
313
319
  |---|---|---|
314
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 |
315
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 |
316
- | `Topic is required` | seogpt2 called without `--keyword` | Add `--keyword "your topic"` |
322
+ | `Topic is required` | seogpt2 called without `--topic` | Add `--topic "your topic"` |
317
323
  | `task_id is invalid` | Task has expired on the server | Create a new task |
318
324
  | `No local tool mapping found for task` | Task not in `~/.sv/tasks.json` | Add `--tool TOOL_NAME` explicitly |
319
325
  | `API authentication failed: HTTP 401` | Bad or missing API key | Check `SV_API_KEY` environment variable |
@@ -331,7 +337,7 @@ When friendly flags are insufficient, send the payload directly. The CLI injects
331
337
  ```bash
332
338
  sv --format json call seogpt --json '{"action":"generate","kw":"white label seo","type":18}'
333
339
  sv --format json call geo-audit --json '{"action":"createTask","kw":"white label seo","URL":"https://example.com"}'
334
- sv --format json call ranklens --json '{"action":"competitors","mgptid":"...","web":"https://example.com","kw":"white label seo"}'
340
+ sv --format json call ranklens --json '{"action":"competitors","mgptid":"...","web":"https://example.com","entity":"white label seo"}'
335
341
  ```
336
342
 
337
343
  From a file:
@@ -370,12 +376,12 @@ sv --format json call better-keywords --json '{"action":"filter","kw":"white lab
370
376
  sv content-transformer rewrite --text "SEO services for agencies" --keyword "white label seo" --strict --no-fuzzy --non-interactive --format json
371
377
  sv core-analysis analyze --url https://example.com --keyword "white label seo" --strict --no-fuzzy --non-interactive --format json
372
378
  sv geo-audit create-task --url https://example.com --keyword "white label seo" --wait --strict --no-fuzzy --non-interactive --format json
373
- sv insight-igniter entities --url https://example.com --keyword "white label seo" --strict --no-fuzzy --non-interactive --format json
379
+ sv insight-igniter entities --url https://example.com --strict --no-fuzzy --non-interactive --format json
374
380
  sv preliminary-audit analyze --url https://example.com --strict --no-fuzzy --non-interactive --format json
375
- sv ranklens rank --keyword "white label seo" --url https://example.com --strict --no-fuzzy --non-interactive --format json
381
+ sv ranklens rank --entity "white label seo" --url https://example.com --strict --no-fuzzy --non-interactive --format json
376
382
  sv seo-image generate --keyword "white label seo" --type 33 --strict --no-fuzzy --non-interactive --format json
377
383
  sv seogpt generate --keyword "white label seo" --type 18 --strict --no-fuzzy --non-interactive --format json
378
- sv seogpt2 create-task --keyword "White Label SEO for Agencies" --type 0 --wait --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
379
385
  sv seogpt-compare create-task --url https://example.com --keyword "white label seo" --wait --strict --no-fuzzy --non-interactive --format json
380
386
  sv seo-mapping create-task --url https://example.com --keyword "white label seo" --wait --strict --no-fuzzy --non-interactive --format json
381
387
  sv topical-authority topics --keyword "white label seo" --strict --no-fuzzy --non-interactive --format json
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: sv-cli
3
- Version: 0.3.0
3
+ Version: 0.5.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
@@ -175,7 +175,7 @@ The canonical API tool keys are discovered from the live API root; local aliases
175
175
 
176
176
  ## Enum resolution
177
177
 
178
- For enum-heavy fields such as content type, language, engine, image type, theme, background, color, and size, the CLI resolves values in this order:
178
+ For enum-heavy fields such as content type, content length, language, engine, image type, theme, background, color, and size, the CLI resolves values in this order:
179
179
 
180
180
  1. Numeric ID exact match
181
181
  2. Exact slug match
@@ -196,6 +196,12 @@ Examples that all resolve to Meta Description (id 18):
196
196
  --type "meta desc"
197
197
  ```
198
198
 
199
+ `--length`/`--contentlength` (content length by ID, slug, or label — run `sv TOOL lengths` for valid values) resolves the same way:
200
+
201
+ ```bash
202
+ sv seogpt generate --keyword "white label seo" --type 18 --length 300
203
+ ```
204
+
199
205
  > **Note:** Fuzzy matching is enabled by default. Use `--strict --no-fuzzy` in scripts to require exact ID or slug and avoid unintended matches.
200
206
 
201
207
  Agent-safe mode:
@@ -246,10 +252,10 @@ sv task status TASK_ID --tool geo-audit
246
252
  sv task result TASK_ID --tool geo-audit
247
253
  ```
248
254
 
249
- `seogpt2` is another async tool. Its required field is `Topic` (a title or subject), mapped via `--keyword`:
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:
250
256
 
251
257
  ```bash
252
- sv seogpt2 create-task --keyword "White Label SEO for Agencies" --type on-page-blog-article --wait
258
+ sv seogpt2 create-task --topic "White Label SEO for Agencies" --type on-page-blog-article --wait
253
259
  ```
254
260
 
255
261
  See available types with `sv seogpt2 types`, lengths with `sv seogpt2 lengths`, engines with `sv seogpt2 engines`.
@@ -258,8 +264,8 @@ Manual 3-step flow (without `--wait`):
258
264
 
259
265
  ```bash
260
266
  sv geo-audit create-task --url https://example.com --keyword "seo agency,white label seo"
261
- sv geo-audit get-task-status --task_id TASK_ID
262
- sv geo-audit get-result --task_id TASK_ID
267
+ sv geo-audit get-task-status --task-id TASK_ID
268
+ sv geo-audit get-result --task-id TASK_ID
263
269
  ```
264
270
 
265
271
  > **Note:** `--format` is not available on `sv task status` or `sv task result` directly. Place it before `task` as a global flag:
@@ -140,7 +140,7 @@ The canonical API tool keys are discovered from the live API root; local aliases
140
140
 
141
141
  ## Enum resolution
142
142
 
143
- For enum-heavy fields such as content type, language, engine, image type, theme, background, color, and size, the CLI resolves values in this order:
143
+ For enum-heavy fields such as content type, content length, language, engine, image type, theme, background, color, and size, the CLI resolves values in this order:
144
144
 
145
145
  1. Numeric ID exact match
146
146
  2. Exact slug match
@@ -161,6 +161,12 @@ Examples that all resolve to Meta Description (id 18):
161
161
  --type "meta desc"
162
162
  ```
163
163
 
164
+ `--length`/`--contentlength` (content length by ID, slug, or label — run `sv TOOL lengths` for valid values) resolves the same way:
165
+
166
+ ```bash
167
+ sv seogpt generate --keyword "white label seo" --type 18 --length 300
168
+ ```
169
+
164
170
  > **Note:** Fuzzy matching is enabled by default. Use `--strict --no-fuzzy` in scripts to require exact ID or slug and avoid unintended matches.
165
171
 
166
172
  Agent-safe mode:
@@ -211,10 +217,10 @@ sv task status TASK_ID --tool geo-audit
211
217
  sv task result TASK_ID --tool geo-audit
212
218
  ```
213
219
 
214
- `seogpt2` is another async tool. Its required field is `Topic` (a title or subject), mapped via `--keyword`:
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:
215
221
 
216
222
  ```bash
217
- sv seogpt2 create-task --keyword "White Label SEO for Agencies" --type on-page-blog-article --wait
223
+ sv seogpt2 create-task --topic "White Label SEO for Agencies" --type on-page-blog-article --wait
218
224
  ```
219
225
 
220
226
  See available types with `sv seogpt2 types`, lengths with `sv seogpt2 lengths`, engines with `sv seogpt2 engines`.
@@ -223,8 +229,8 @@ Manual 3-step flow (without `--wait`):
223
229
 
224
230
  ```bash
225
231
  sv geo-audit create-task --url https://example.com --keyword "seo agency,white label seo"
226
- sv geo-audit get-task-status --task_id TASK_ID
227
- sv geo-audit get-result --task_id TASK_ID
232
+ sv geo-audit get-task-status --task-id TASK_ID
233
+ sv geo-audit get-result --task-id TASK_ID
228
234
  ```
229
235
 
230
236
  > **Note:** `--format` is not available on `sv task status` or `sv task result` directly. Place it before `task` as a global flag:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sv-cli"
7
- version = "0.3.0"
7
+ version = "0.5.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.4.0"
@@ -31,11 +31,19 @@ class ToolAdapter:
31
31
  COMMON_FIELD_ALIASES: dict[str, tuple[str, ...]] = {
32
32
  "keyword": ("keyword", "kw", "query", "seed_keyword"),
33
33
  "keywords": ("keywords", "kws", "keyword_list", "kwlist"),
34
+ "topic": ("topic", "title"),
35
+ # Deliberately narrow: no "kw"/"keyword" fallback here, unlike "keyword" above.
36
+ # Those are common to many unrelated tools (better-keywords, seogpt2, ...) and
37
+ # would make --entity spuriously "relevant" (and functional, just semantically
38
+ # wrong) for all of them. RankLens overrides this below with its own aliases.
39
+ "entity": ("entity", "entities"),
40
+ "task_id": ("task_id", "taskid", "task-id"),
34
41
  "url": ("url", "domain", "page_url", "website"),
35
42
  "url_a": ("url_a", "urla", "url1", "first_url", "competitor_url"),
36
43
  "url_b": ("url_b", "urlb", "url2", "second_url", "comparison_url"),
37
44
  "brand": ("brand", "brand_name", "company", "company_name"),
38
45
  "type": ("contenttype", "content_type", "imagetype", "image_type", "type"),
46
+ "length": ("contentlength", "content_length", "length"),
39
47
  "language": ("language", "lang", "language_id"),
40
48
  "engine": ("engine", "ai_engine", "model"),
41
49
  "country": ("country", "country_code", "location", "gl"),
@@ -127,7 +135,14 @@ TOOL_ADAPTERS: dict[str, ToolAdapter] = {
127
135
  aliases=(),
128
136
  default_action="rank",
129
137
  actions=("rank", "competitors", "raw"),
130
- field_aliases=COMMON_FIELD_ALIASES,
138
+ field_aliases={
139
+ **COMMON_FIELD_ALIASES,
140
+ # "entity" is RankLens's real, documented field. --keyword/--kw remain
141
+ # accepted as aliases (the API still honors kw/keyword input too), but
142
+ # should resolve onto entity now rather than the deprecated field names.
143
+ "keyword": ("entity", "kw", "keyword", "query", "seed_keyword"),
144
+ "entity": ("entity", "entities", "kw", "keyword", "query", "seed_keyword"),
145
+ },
131
146
  option_aliases={
132
147
  "languages": ("lang", "language", "languages"),
133
148
  "engines": ("engine", "model", "engines"),
@@ -176,8 +191,13 @@ TOOL_ADAPTERS: dict[str, ToolAdapter] = {
176
191
  actions=("create-task", "get-task-status", "get-result", "raw"),
177
192
  field_aliases={
178
193
  **COMMON_FIELD_ALIASES,
179
- "keyword": ("Topic", "topic", "kw", "keyword", "query", "seed_keyword"),
180
- "text": ("Topic", "topic", "text", "content", "input", "body"),
194
+ # The real backend (SEOB's seogpt2api.php) treats these as two distinct
195
+ # fields: Topic (required, 12-200 chars, the article subject) and KW
196
+ # (optional, up to 5 keywords). --keyword/--kw/--keywords must all land
197
+ # on KW, never on Topic - --topic/--title are the only way to set Topic.
198
+ "keyword": ("KW", "kw", "keywords", "keyword", "query", "seed_keyword"),
199
+ "keywords": ("KW", "kw", "keywords", "keyword", "keyword_list", "kwlist"),
200
+ "topic": ("Topic", "topic", "title"),
181
201
  },
182
202
  async_likely=True,
183
203
  option_aliases={
@@ -222,7 +242,7 @@ TOOL_ADAPTERS: dict[str, ToolAdapter] = {
222
242
  command="topical-authority",
223
243
  aliases=("topical",),
224
244
  default_action="topics",
225
- actions=("topics", "content", "raw"),
245
+ actions=("topics", "raw"),
226
246
  field_aliases=COMMON_FIELD_ALIASES,
227
247
  option_aliases={
228
248
  "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,
@@ -35,13 +39,14 @@ class APIClient:
35
39
  payload: dict[str, Any],
36
40
  api_key: str | None,
37
41
  method: str = "POST",
38
- timeout: float = 60.0,
42
+ timeout: float = 300.0,
39
43
  ) -> APIResponse:
40
44
  final_payload = dict(payload)
41
45
  if api_key and not any(key in final_payload for key in ("k", "api_key", "apikey", "key")):
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